Spring的@Configuration配置类-Full和Lite模式
为什么没有@Configuration注解的类其中的@Bean⽅法也能被扫描到?
  这就要从Full和lite模式来说。最初的Spring只⽀持xml⽅式配置Bean,从Spring 3.0起⽀持了⼀种更优的⽅式:基于Java类的配置⽅式,这⼀下⼦让我们Javaer可以从标签语法⾥解放了出来。毕竟作为Java程序员,我们擅长的是写Java类,⽽⾮⽤标签语⾔去写xml⽂件。我对Spring配置的Full/Lite模式的关注和记忆深刻,源⾃于⼀个⼩⼩故事:某⼀年我在看公司的项⽬时发现,数据源配置类⾥有如下⼀段配置代码:
@Configuration
public class DataSourceConfig {
...
@Bean
public DataSource dataSource() {
...
return dataSource;
}
@Bean(name = "transactionManager")
public DataSourceTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
...
}
Java萌新的我,⾮常的费解。⾃然的对此段代码产⽣了较⼤的好奇(其实是质疑):在准
  作为当时还是Java萌新
备DataSourceTransactionManager这个Bean时调⽤了dataSource()⽅法,根据我“⾮常扎实”的JavaSE基础知识,它肯定会重新⾛⼀
遍dataSource()⽅法,从⽽产⽣⼀个新的数据源实例,那么你的事务管理器管理的不就是⼀个“全新数据源”麽?谈何事务呢?
此⽅法并没有执⾏两次。这在当时是震惊了我为了验证我的猜想,我把断点打到dataSource()⽅法内部开始调试,但让我“失望”的是:此⽅法并没有执⾏两次
的,甚⾄⼀度怀疑⾃⼰引以为豪的Java基础了。所以我四处询问,希望得到⼀个“解释”,但奈何,问了好⼏圈,那会没有⼀⼈能给我⼀个合理的说法,只知道那么⽤是没有问题的。
很明显,现在再回头来看当时的这个质疑是显得有些“⽆知”的,这个“难题”困扰了我很久,直到我前2年开始深度研究Spring源码才让此难题迎刃⽽解,当时那种豁然开朗的感觉真好呀。
关于配置类的核⼼概念,在这⾥先予以解释。@Configuration和@Bean
Spring新的配置体系中最为重要的构件是:@Configuration标注的类,@Bean标注的⽅法。
// @since 3.0
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
@AliasFor(annotation = Component.class)
String value() default "";
// @since 5.2
boolean proxyBeanMethods() default true;
}
源。此外,@Configuration类允许通过调⽤同⼀类中的其
⽤@Configuration注解标注的类表明其主要⽬的是作为bean定义的源
他@Bean method⽅法来定义bean之间的依赖关系(下有详解)。
/
/ @since 3.0
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Bean {
@AliasFor("name")
spring ioc注解String[] value() default {};
@AliasFor("value")
String[] name() default {};
@Deprecated
Autowire autowire() default Autowire.NO;
/
/ @since 5.1
boolean autowireCandidate() default true;
String initMethod() default "";
String destroyMethod() default AbstractBeanDefinition.INFER_METHOD;
}
@Bean注解标注在⽅法上,⽤于指⽰⽅法实例化、配置和初始化要由Spring IoC容器管理的新对象。对于熟悉Spring的<beans/>XML 配置的⼈来说,@Bean注解的作⽤与<bean/>元素相同。您可以对任何Spring的@Component组件使⽤@Bean注释的⽅法代替(注意:这是理论上,实际上⽐如使⽤@Controller标注的组件就不能直接使⽤它代替)。
需要注意的是,通常来说,我们均会把@Bean标注的⽅法写在@Configuration标注的类⾥⾯来配合使⽤。
简单粗暴理解:@Configuration标注的类等同于⼀个xml⽂件,@Bean标注的⽅法等同于xml⽂件⾥的⼀个<bean/>标签
@Configuration
public class AppConfig {
@Bean
public User user(){
User user = new User();
user.setName("A哥");
user.setAge(18);
return user;
}
}
public class Application {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
User user = Bean(User.class);
System.out.Class());
System.out.println(user);
}
}
输出:
urbatman.fullliteconfig.User
User{name='A哥', age=18}
Full模式和Lite模式
Full模式和Lite模式均是针对于Spring配置类⽽⾔的,和xml配置⽂件⽆关。值得注意的是:判断是Full模式 or Lite模式的前提是,⾸先你得是个容器组件。⾄于⼀个实例是如何“晋升”成为容器组件的,可以⽤注解也可以没有注解,本⽂就不展开讨论了,这属于Spring的基础知识。
Lite模式
在Lite模式下处理。它包括:在@Component中声明
当@Bean⽅法在没有使⽤@Configuration注释的类中声明时,它们被称为在Lite模式下处理
的@Bean⽅法,甚⾄只是在⼀个⾮常普通的类中声明的Bean⽅法,都被认为是Lite版的配置类。@Bean⽅法是⼀种通⽤的⼯⼚⽅法(factory-method)机制。
和Full模式的@Configuration不同,Lite模式的@Bean⽅法不能声明Bean之间的依赖关系。因此,这样的@Bean⽅法不应该调⽤其他
只是⼀个特定Bean引⽤的⼯⼚⽅法(factory-method),没有任何特殊的运⾏时语义。
@Bean⽅法。每个这样的⽅法实际上只是⼀个特定Bean引⽤的⼯⼚⽅法(factory-method)
何时为Lite模式
官⽅定义为:在没有标注@Configuration的类⾥⾯有@Bean⽅法就称为Lite模式的配置。透过源码再看这个定义是不完全正确的,⽽应该是有如下case均认为是Lite模式的配置类:
1. 类上标注有@Component注解
2. 类上标注有@ComponentScan注解
3. 类上标注有@Import注解
4. 类上标注有@ImportResource注解
5. 若类上没有任何注解
若类上没有任何注解,但类内存在@Bean⽅法
6. 总结⼀句话,只要不标识@Configuration(proxyBeanMethods=true)其他都是lite模式
以上case的前提均是类上没有被标注@Configuration,在Spring 5.2之后
Spring 5.2之后新增了⼀种case也算作Lite模式:
1. 标注有@Configuration(proxyBeanMethods = false),注意:此值默认是true哦,需要显⽰改为false才算是Lite模式细⼼的你会发现,⾃Spring5.2(对应Spring Boot
2.2.0)开始,内置的⼏乎所有的@Configuration配置类都被修改为
了@Configuration(proxyBeanMethods = false),⽬的何为?答:以此来降低启动时间,为Cloud Native继续做准备。
源码:
abstract class ConfigurationClassUtils {
...........
public static boolean checkConfigurationClassCandidate(
BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {
String className = BeanClassName();
if (className == null || FactoryMethodName() != null) {
return false;
}
AnnotationMetadata metadata;
if (beanDef instanceof AnnotatedBeanDefinition &&
className.equals(((AnnotatedBeanDefinition) beanDef).getMetadata().getClassName())) {
// Can reuse the pre-parsed metadata from the
metadata = ((AnnotatedBeanDefinition) beanDef).getMetadata();
}
else if (beanDef instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) beanDef).hasBeanClass()) {
// Check already loaded Class
// since we possibly can't even load the class file for this Class.
Class<?> beanClass = ((AbstractBeanDefinition) beanDef).getBeanClass();
if (BeanFactoryPostProcessor.class.isAssignableFrom(beanClass) ||
BeanPostProcessor.class.isAssignableFrom(beanClass) ||
AopInfrastructureBean.class.isAssignableFrom(beanClass) ||
EventListenerFactory.class.isAssignableFrom(beanClass)) {
return false;
}
metadata = AnnotationMetadata.introspect(beanClass);
}
else {
try {
MetadataReader metadataReader = MetadataReader(className);
metadata = AnnotationMetadata();
}
catch (IOException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Could not find class file for introspecting configuration annotations: " +
className, ex);
}
return false;
}
}
Map<String, Object> config = AnnotationAttributes(Name());
if (config != null && !Boolean.FALSE.("proxyBeanMethods"))) {
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
}
else if (config != null || isConfigurationCandidate(metadata)) {
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
}
else {
return false;
}
// It's a full or lite Let's determine the order value, if any.
Integer order = getOrder(metadata);
if (order != null) {
beanDef.setAttribute(ORDER_ATTRIBUTE, order);
}
return true;
}
}
优缺点
优点:
优点
运⾏时不再需要给对应类⽣成CGLIB⼦类,提⾼了运⾏性能,降低了启动时间
可以是private、可以是final 可以该配置类当作⼀个普通类使⽤喽:也就是说@Bean⽅法 可以是private、可以是final
缺点:
缺点
不能声明@Bean之间的依赖,也就是说不能通过⽅法调⽤来依赖其它Bean
(其实这个缺点还好,很容易⽤其它⽅式“弥补”,⽐如:把依赖Bean放进⽅法⼊参⾥即可)代码⽰例
主配置类:
@ComponentScan("urbatman.fullliteconfig.liteconfig")
@Configuration
public class AppConfig {
}
@Component
// @Configuration(proxyBeanMethods = false) // 这样也是Lite模式
public class LiteConfig {
@Bean
public User user() {
User user = new User();
user.setName("A哥-lite");
user.setAge(18);
return user;
}
@Bean
private final User user2() {
User user = new User();
user.setName("A哥-lite2");
user.setAge(18);
// 模拟依赖于user实例看看是否是同⼀实例
System.out.println(System.identityHashCode(user()));
System.out.println(System.identityHashCode(user()));
return user;
}
public static class InnerConfig {
@Bean
// private final User userInner() { // 只在lite模式下才好使
public User userInner() {
User user = new User();
user.setName("A哥-lite-inner");
user.setAge(18);
return user;
}
}
}
测试⽤例:
public class Application {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
// 配置类情况
System.out.Bean(LiteConfig.class).getClass());
System.out.Bean(LiteConfig.InnerConfig.class).getClass());
String[] beanNames = BeanNamesForType(User.class);
for (String beanName : beanNames) {
User user = Bean(beanName, User.class);
System.out.println("beanName:" + beanName);
System.out.Class());
System.out.println(user);
System.out.println("------------------------");
}
}
}
结果输出:
1100767002
313540687
urbatman.fullliteconfig.liteconfig.LiteConfig
urbatman.fullliteconfig.liteconfig.LiteConfig$InnerConfig
beanName:userInner
urbatman.fullliteconfig.User
User{name='A哥-lite-inner', age=18}
-
-----------------------
beanName:user
urbatman.fullliteconfig.User
User{name='A哥-lite', age=18}
------------------------
beanName:user2
urbatman.fullliteconfig.User
User{name='A哥-lite2', age=18}
------------------------
⼩总结
该模式下,配置类本⾝不会被CGLIB增强,放进IoC容器内的就是本尊
该模式下,对于内部类是没有限制的:可以是Full模式或者Lite模式
不能通过⽅法调⽤来处理依赖,否则每次⽣成的都是⼀个新实例⽽并⾮IoC容器内的单例该模式下,配置类内部不能通过⽅法调⽤
该模式下,配置类就是⼀普通类嘛,所以@Bean⽅法可以使⽤private/final等进⾏修饰(static⾃然也是阔仪的)
Full模式
何时为Full模式
的类中声明,以确保总是使⽤“Full模式”,这么⼀来,交叉⽅法引⽤会被在常见的场景中,@Bean⽅法都会在标注有@Configuration的类中声明
重定向到容器的⽣命周期管理,所以就可以更⽅便的管理Bean依赖。
标注有@Configuration注解的类被称为full模式的配置类。⾃Spring5.2后这句话改为下⾯这样我觉得更为精确些:
标注有@Configuration或者@Configuration(proxyBeanMethods = true)的类被称为Full模式的配置类(当然喽,proxyBeanMethods属性的默认值是true,所以⼀般需要Full模式我们只需要标个注解即可)
优缺点
优点:
优点
可以⽀持通过常规Java调⽤相同类的@Bean⽅法⽽保证是容器内的Bean,这有效规避了在“Lite模式”下操作时难以跟踪的细微错误。
特别对于萌新程序员,这个特点很有意义
缺点:
缺点
运⾏时会给该类⽣成⼀个CGLIB⼦类放进容器,有⼀定的性能、时间开销(这个开销在Spring Boot这
种拥有⼤量配置类的情况下是不容忽视的,这也是为何Spring 5.2新增了proxyBeanMethods属性的最直接原因)
正因为被代理了,所以@Bean⽅法不可以是private、不可以是final
代码⽰例
主配置:
@ComponentScan("urbatman.fullliteconfig.fullconfig")
@Configuration
public class AppConfig {
}
@Configuration
public class FullConfig {
@Bean
public User user() {
User user = new User();
user.setName("A哥-lite");
user.setAge(18);
return user;
}

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。