Spring 其它注解和杂项
1. @Lazy 注解
使用 @Lazy 注解的典型场景就是解决循环依赖问题。特别是构造注入,@Lazy 是弥补构造注入的『缺点』的关键。
当你对注入的 JavaBean 使用 @Lazy 注解时,Spring 注入的并非是这个单例对象,而是它的一个代理。当你(在未来)第一次使用这个 Bean 时,这个代理对象才会去 IoC 容器中找这个真正的 Bean 。
2. Spring 的 @Import 注解
在使用 maven 多模块的概念去构建项目时,我们的各个 @Bean 会分散在各个子模块中。
当然,我们可以仍在入口模块(web)中通过配置去配置各个模块必须创建的单例 Bean ,不过更好的方式是:将各个模块的配置也分散在各个模块中,由各个模块自己负责,最后让入口模块引入各个模块的配置即可。这样的话,责任更加分明。
- 一个独立的配置类:ConfigA.java
java">@ComponentScan("com.example.commandpattern.config.a")
public class ConfigA {
@Bean
public String demo() {
return "hello world";
}
}
- 另一个独立的配置类:ConfigB.java
java">@ComponentScan("com.example.commandpattern.config.b")
public class ConfigB {
@Bean
public LocalDate localDate() {
return LocalDate.now();
}
}
- 主配置类:MainConfig.java
java">// 主配置类引入各个独立配置类
@Import({ConfigA.class, ConfigB.class})
public class MainConfig {
}
- 引入,使用:
java">// 只需要将主配之类交给 Spring IoC 容器即可。
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);
System.out.println(context.getBean(String.class));
System.out.println(context.getBean(LocalDate.class));
System.out.println(context.getBean(StudentDao.class));
System.out.println(context.getBean(StudentService.class));
}
3. 特殊情况下的 Bean 注入
非托管对象中获取托管对象
有时你需要在非托管对象中获取 Spring 的 ApplicationContext
方案一:通用方案
java">@Slf4j
@Component
public class ApplicationContextHolder {
private static ApplicationContext APPLICATION_CONTEXT;
public ApplicationContextHolder(ApplicationContext applicationContext) {
APPLICATION_CONTEXT = applicationContext;
}
public static ApplicationContext getApplicationContext() {
return APPLICATION_CONTEXT;
}
}
使用时调用:
java">ApplicationContextRegister.getApplicationContext().getBean(Xxx.class);
方案二:Spring 可用,Spring Boot 不可用
在 Spring 项目中可用,在 Spring Boot 项目中不可用。
还有一种方案可以实现同样效果,直接调用 Spring 提供的工具类:
java">ContextLoader.getCurrentWebApplicationContext().getBean(Xxx.class);
经测试发现,在 Spring Boot 项目中该方案无效。有人跟踪源码分析,因为 Spring Boot 的内嵌 Tomcat 和真实 Tomcat 还是有一定的区别,从而导致 Spring Boot 中该方案无法起到一起效果。
单例 Bean 中注入多例 Bean
保证每次获取都是新的多例 Bean 。
在 Spring 中 如果需要一个对象为多例,需要使用 @Scope 注解,指明作用于为 SCOPE_PROTOTYPE 即可。
当我们在一个单例的 Bean A 中注入多例 Bean B 时,由于 Spring 在初始化过程中加载 A 的时候已经将 B 注入到 A 中,所以直接当做成员变量时,只会获取一个实例。
我们可以通过以下两种优雅的方法解决:
-
使用 Spring 的 ObjectFactory
-
使用 @Looup 注解
方案一:Spring 的 ObjectFactory
为你的单例对象注入一个 Spring 提供的 ObjectFactory ,毫无疑问,ObjectFacotry 也是一个单例对象。
java">@Component
public class SingleBean {
@Autowired
ObjectFactory<PrototypeBean> factory;
public void print(String name) {
System.out.println("single service is " + this);
factory.getObject().test(name);
}
}
但是,在单例对象中,你只要通过 ObjectFactory(的封装)的 ObjectFactory.getObject() 方法去获得多例对象,每次它返回给你的都是一个『新』的对象。
方案二:@Lookup 注解
我们可以使用 Spring 的 @Lookup 注解。该注解主要为单例 Bean 实现一个 cglib 代理类,并通过 BeanFacoty.getBean() 来获取对象。
@Lookup 注解是一个作用在方法上的注解,被其标注的方法会被 Spring 通过 cglib 实现的代理类重写,然后根据其返回值的类型,容器调用 BeanFactory 的 getBean() 方法来返回一个 bean 。
java">@Component
public class SingleBean {
public void printClass() {
System.out.println("This is SingleBean: " + this);
getPrototypeBean().xxx();
}
/**
* 方法的存在,以及方法的返回值是关键。
* 该方法会被 Spring 重写:Spring 会来保证在『别处』你调用这个方法时,每次都返回一个新的 PrototypeBean 对象给你。
*/
@Lookup
public PrototypeBean getPrototypeBean() {
return null;
}
}