本文基于Spring 5.2.7
Spring配置类不仅仅是@Configuration注解修饰的类,只是@Configuration修饰的类更加正式,Spring对此也是进行了区分处理,Spring将配置类划分为了full模式和lite模式。
一、配置类的full和lite模式
lite模式包含:
- 没有被@Configuration修饰,被@Component修饰
- 没有被@Configuration修饰,被@ComponentScan修饰
- 没有被@Configuration修饰,被@Import修饰
- 没有被@Configuration修饰,被@ImportResource修饰
- 没有任何Spring相关注解,类里面有@Bean修饰的方法
- 被@Configuration修饰,但是属性proxyBeanMethods = false
full模式包含:
- 被@Configuration修饰,且属性proxyBeanMethods = true(proxyBeanMethods 默认为true)
full模式使用特性:
- full模式下的配置类会被CGLIB代理生成代理类取代原始类型(在容器中)
- full模式下的@Bean方法不能是private和final
- 单例scope下不同@Bean方法可以互相引用,达到单实例的语义
lite模式使用特性:
- lite模式下的配置类不生成代理,原始类型进入容器
- lite模式下的@Bean方法可以是private和final
- 单例scope下不同@Bean方法引用时无法做到单例
lite测试用例
@Component
public class LiteConfig {
@Bean
User u1() {
return new User("小明", 13);
}
@Bean
User u2() {
User localUser = u1();
System.out.println(localUser.hashCode());
User localUser2 = u1();
System.out.println(localUser2.hashCode());
return new User("小明2", 14);
}
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(LiteConfig.class);
Object liteConfig = ctx.getBean("liteConfig");
System.out.println(liteConfig);
}
}
368342628
1192923170
com.peace.test.configuration.test2.LiteConfig@59e2d8e3
Process finished with exit code 0
full测试用例
@Configuration
public class FullConfig {
@Bean
User u1() {
return new User("小明", 13);
}
@Bean
User u2() {
User localUser = u1();
System.out.println(System.identityHashCode(localUser));
User localUser2 = u1();
System.out.println(System.identityHashCode(localUser2));
return new User("小明2", 14);
}
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(com.peace.test.configuration.test2.FullConfig.class);
Object fullConfig = ctx.getBean("fullConfig");
System.out.println(fullConfig);
}
}
1200906406
1200906406
com.peace.test.configuration.test2.FullConfig$$EnhancerBySpringCGLIB$$d004b363@4f0100a7
二、为何要区分full和lite模式
因为被Spring管理的类功能比较多,有一些是业务类,这些类解析起来比较简单和有规律,可归纳为lite模式一类,有一些类需要额外一些逻辑,需要生成CGLIB代理,所以这一类就被划分到full模式一类。
二、@Bean官方文档
@Bean注解始自Spring 3.0
Overview
表明一个方法,这个方法可以生产一个被Spring容器管理的bean。
这个注解的属性值和对应的语义都被特意的贴近Spring XML模式中的<bean>元素。
使用例子:
@Bean
public MyBean myBean() {
// instantiate and configure MyBean obj
return obj;
}
Bean Names
当name属性可用时(难道还有不可用的时候?早期不可用,4.3.3才支持),确定bean name的默认策略是使用@Bean修饰的方法名。这是方便有易懂的,但如果需要显示命名,可以使用name(或者其属性别名)属性。同时注意,name属性接受一个Strings数组,允许一个单例bean有多个名字(也即一个主名字加一个或多个别名)。
@Bean({"b1", "b2"}) // bean available as 'b1' and 'b2', but not 'myBean'
public MyBean myBean() {
// instantiate and configure MyBean obj
return obj;
}
Profile, Scope, Lazy, DependsOn, Primary, Order
注意,@Bean注解不提供profile,scope, lazy, depends-on和primary这些属性。相反,应该使用@Scope,@Lazy,@DependsOn,@Primary注解来表明这些语义。例如:
@Bean
@Profile("production")
@Scope("prototype")
public MyBean myBean() {
// instantiate and configure MyBean obj
return obj;
}
上面提到的注解的语义和在类级别上使用时是一样的。
@Profile允许有环境选择性的注入某个bean
@Scope可以将bean的scope从singleton改到其他指定的scope
@Lazy只在默认的singleton scope下才起到实际效果
@DependsOn,在创建当前bean之前,除了创建当前bean直接引用的bean,@DependsOn会强制在创建当前bean之前创建其指定的bean,这通常在单例启动时有用。
@Primary是一种解决单个bean注入时有多个类型匹配的bean的歧义的机制。
此外,@Bean方法在注入时还可以使用qualifier注解和@Order注解,就像在bean上使用对应的注解一样,但是这种情况可能非常个性化(一个类有多个定义的场景)。Qualifiers在初始类型匹配后缩小了候选beans的范围。order值确定了多个collection注入场景时已解析元素的顺序(有多个目标bean类型匹配限定符也匹配场景)。
注意:@Order值可能影响注入点的优先级,但请知悉,他不会影响单例启动顺序,因为单例启动是顺序由依赖关系和@DependsOn的交叉关系决定的。另外,@Priority不能用在这里因为他不能在方法上声明,@Priority的语义可以通过@Order值结合@Primary实现。
@Bean Methods in @Configuration Classes
通常,@Bean方法被声明在@Configuration类里面。这种情况下,同一个类的里@Bean方法可能直接调用另一个@Bean方法,这确保了beans之间的引用强类型和导向的。这种所谓的inter-bean references是遵守scoping和AOP语义的保证,就像getBean()方法会查找引用一样。这些就是从最初的Spring JavaConfig中已知的语义,他需要在运行时为每个@Configuration类生成CGLIB子类,结果就是,这种模式下,@Configuration类和他的factory methods不能是final和private的,例如:
@Configuration
public class AppConfig {
@Bean
public FooService fooService() {
return new FooService(fooRepository());
}
@Bean
public FooRepository fooRepository() {
return new JdbcFooRepository(dataSource());
}
// ...
}
@Bean Lite Mode
@Bean方法也可以不在@Configuration类里面使用,例如,@Bean方法可以在@Component类里面甚至一个pojo类里面使用。这种场景下,@Bean方法会得到一个所谓的lite mode的处理。lite mode下的@Bean方法会被容器当作简单factory methods处理(类似于XML中factory-method的声明),并能正确被应用scoping和lifecycle回调。这种情形下,containing class保持不变,没有其他特殊的限制针对containing class和factory methods。
与@Configuration里@Bean方法语义不同,lite mode下inter-bean references不受支持。相反,lite mode下,当一个@Bean方法调用另一个@Bean方法,是标准的Java方法调用,Spring不会通过CGLIB拦截这个调用。这和Spring不拦截代理模式下内部@Transactional方法调用类似——Spring只在ASpectJ模式下拦截。使用例子:
@Component
public class Calculator {
public int sum(int a, int b) {
return a+b;
}
@Bean
public MyBean myBean() {
return new MyBean();
}
}
Bootstrapping
查阅@Configuration文档获取更多使用AnnotationConfigApplicationContext等相关类启动容器的细节。
BeanFactoryPostProcessor returning @Bean methods
必须特别注意返回BeanFactoryPostProcessor(BFPP)类型的@Bean方法。因为BFPP对象必须在容器生命周期的很早阶段被实例化,在@Configuration里面他们能介入处理@Autowired,@Value,@PostConstruct注解,为了避免这种生命周期导致的问题,要将@Bean方法声明为static,使用例子:
@Bean
public static PropertySourcesPlaceholderConfigurer pspc() {
// instantiate, configure and return pspc ...
}
通过标记这个方法为static,他能在其外部的@Configuration类还没实例化之前被调用,就能避免上面提到的生命周期冲突的问题。但是请注意,static @Bean方法不会得到scoping和AOP语义的增强。这在BFPP情形下是可行的,因为他们通常不被其他@Bean方法调用。作为提醒,任何非static @Bean方法返回BFPP类型时会有一条警告日志给出。