• 【Spring注解必知必会】深度解析@Configuration注解


    概述

    最近在看源码的时候,发现很多源码中写着@Configuration(proxyBeanMethods = false),引起了我的好奇,为啥这么写, 这个proxyBeanMethods属性是干嘛的?@Configuration@Component注解有什么区别呢?

    注解介绍

    @Configuration注解可以加在类上,让这个类的功能等同于一个bean xml配置文件,可以在这个类中管理创建Bean。

    注解源码:

    1. @Target(ElementType.TYPE)
    2. @Retention(RetentionPolicy.RUNTIME)
    3. @Documented
    4. @Component
    5. public @interface Configuration {
    6. @AliasFor(annotation = Component.class)
    7. String value() default "";
    8. boolean proxyBeanMethods() default true;
    9. }
    10. 复制代码

    属性说明:

    • value: 自定义当前组件或者说bean的名称,实际就是@Component的value属性。
    • proxyBeanMethods: 判断是否bean的方法应该被代理,默认是true,后面原理解析中重点分析。

    元注解说明:

    • 该注解只能使用在类,接口、枚举、其他注解上
    • 该注解的生命周期是运行时JVM
    • @Component元注解,相当于拥有了@Component注解的能力,可以被ComponentScan扫描到,变成spring中的Bean。

    注解场景使用场景:

    1. 和@Bean搭配使用,创建管理Bean
    1. @Configuration
    2. public class AppConfig {
    3. @Bean
    4. public MyBean myBean() {
    5. // instantiate, configure and return bean ...
    6. }
    7. }
    8. 复制代码
    1. @PropertySource 注解搭配使用,获取外部配置
    1. @Configuration
    2. @PropertySource("classpath:/com/acme/app.properties")
    3. public class AppConfig {
    4. @Inject Environment env;
    5. @Value("${bean.name}") String beanName;
    6. @Bean
    7. public MyBean myBean() {
    8. return new MyBean(env.getProperty("bean.name"));
    9. }
    10. }
    11. 复制代码
    1. @Import注解搭配使用,导入其他配置类
    1. @Configuration
    2. public class DatabaseConfig {
    3. @Bean
    4. public DataSource dataSource() {
    5. // instantiate, configure and return DataSource
    6. }
    7. }
    8. @Configuration
    9. @Import(DatabaseConfig.class)
    10. public class AppConfig {
    11. private final DatabaseConfig dataConfig;
    12. public AppConfig(DatabaseConfig dataConfig) {
    13. this.dataConfig = dataConfig;
    14. }
    15. @Bean
    16. public MyBean myBean() {
    17. // reference the dataSource() bean method
    18. return new MyBean(dataConfig.dataSource());
    19. }
    20. }
    21. 复制代码
    1. @Profile搭配使用,指定当前配置使用的profile
    1. @Profile("development")
    2. @Configuration
    3. public class EmbeddedDatabaseConfig {
    4. @Bean
    5. public DataSource dataSource() {
    6. // instantiate, configure and return embedded DataSource
    7. }
    8. }
    9. @Profile("production")
    10. @Configuration
    11. public class ProductionDatabaseConfig {
    12. @Bean
    13. public DataSource dataSource() {
    14. // instantiate, configure and return production DataSource
    15. }
    16. }
    17. @Configuration
    18. public class ProfileDatabaseConfig {
    19. @Bean("dataSource")
    20. @Profile("development")
    21. public DataSource embeddedDatabase() { ... }
    22. @Bean("dataSource")
    23. @Profile("production")
    24. public DataSource productionDatabase() { ... }
    25. }
    26. 复制代码
    1. @ImportResource搭配使用,导入xml的spring配置
    1. @Configuration
    2. @ImportResource("classpath:/com/acme/database-config.xml")
    3. public class AppConfig {
    4. @Inject DataSource dataSource; // from XML
    5. @Bean
    6. public MyBean myBean() {
    7. // inject the XML-defined dataSource bean
    8. return new MyBean(this.dataSource);
    9. }
    10. }
    11. 复制代码

    注解使用案例

    这里我们通过一个案例来了解下注解的proxyBeanMethods方法。

    1. @Configuration
    2. public class TestConfig {
    3. /**
    4. * 定义猫
    5. * @return
    6. */
    7. @Bean(name = "cat")
    8. public Cat cat() {
    9. return new Cat("小猫" + new Random().nextInt(100));
    10. }
    11. /**
    12. * 爸爸
    13. * @return
    14. */
    15. @Bean(name = "dad")
    16. public Person dadPerson() {
    17. return new Person("dad", cat());
    18. }
    19. /**
    20. * 爸爸
    21. * @return
    22. */
    23. @Bean(name = "mom")
    24. public Person momPerson() {
    25. return new Person("mom", cat());
    26. }
    27. }
    28. 复制代码
    1. Cat cat = context.getBean("cat", Cat.class);
    2. Person dadPerson = context.getBean("dad", Person.class);
    3. Person momPerson = context.getBean("mom", Person.class);
    4. System.out.println(cat);
    5. System.out.println(dadPerson.getCat());
    6. System.out.println(momPerson.getCat());
    7. TestConfig testConfig = context.getBean(TestConfig.class);
    8. System.out.println(testConfig);
    9. 复制代码

    大家觉得打印的是同一个cat吗?

    他们同一个对象,同一只小猫。

    看到这个config类的类型是CGLIB,说明被代理了。

    现在我们把@Configuration注解的proxyBeanMethods改为false,会有什么不一样呢?

    再次执行上面的输出,如下:

    发现每只小猫都不一样了,这个配置类也不是代理类。

    这是怎么一回事,我们就要分析下源码了。

    原理解析

    因为@Configuration注解是被@Component注解修饰,说明他有@Compnent注解的功能,可以被Spring扫描到成为Bean, 具体原理可以看www.yuque.com/alvinscript…

    整一个实现的源码都是在ConfigurationClassPostProcessor中。

    BeanDefinition的解析

    这里主要是将@Configuration注解的Class解析出BeanDefinition,注册到Spring的BeanDefinition Registry中。

    上图是BeanDefinition解析的调用堆栈。

    1. 解析所有@Component注解的Class为BeanDefinition,注册到Spring的BeanDefinition Registry。
    1. public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
    2. ......
    3. // Parse each @Configuration class
    4. ConfigurationClassParser parser = new ConfigurationClassParser(
    5. this.metadataReaderFactory, this.problemReporter, this.environment,
    6. this.resourceLoader, this.componentScanBeanNameGenerator, registry);
    7. Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
    8. Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
    9. do {
    10. StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse");
    11. // 根据路径扫描解析出BeanDefinition并且注册到registry
    12. parser.parse(candidates);
    13. parser.validate();
    14. ......
    15. }
    16. 复制代码
    1. 调用ConfigurationClassUtils#checkConfigurationClassCandidate设置Configuraion类的BeanDefinition代理信息。
    1. public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
    2. ......
    3. // Parse each @Configuration class
    4. for (String candidateName : newCandidateNames) {
    5. if (!oldCandidateNames.contains(candidateName)) {
    6. BeanDefinition bd = registry.getBeanDefinition(candidateName);
    7. // 校验类
    8. if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
    9. !alreadyParsedClasses.contains(bd.getBeanClassName())) {
    10. candidates.add(new BeanDefinitionHolder(bd, candidateName));
    11. }
    12. }
    13. }
    14. ......
    15. }
    16. 复制代码
    1. public static boolean checkConfigurationClassCandidate(
    2. BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {
    3. .....
    4. // 获取Configuration 注解的信息
    5. Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());
    6. // 如果不为空,并且proxyBeanMethods不是false
    7. if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {
    8. // 设置bean definition为 full
    9. beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
    10. }
    11. else if (config != null || isConfigurationCandidate(metadata)) {
    12. // 设置bean definition为 lite
    13. beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
    14. }
    15. else {
    16. return false;
    17. }
    18. // It's a full or lite configuration candidate... Let's determine the order value, if any.
    19. Integer order = getOrder(metadata);
    20. if (order != null) {
    21. beanDef.setAttribute(ORDER_ATTRIBUTE, order);
    22. }
    23. return true;
    24. }
    25. 复制代码

    Configuration注解的Bean创建

    bean在创建的时候会调用BeanFactoryPostProcessorpostProcessBeanFactory方法来处理Bean。

    1. ConfigurationClassPostProcessor实现了BeanFactoryPostProcessor接口,在Bean创建的时候回调。
    1. @Override
    2. public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
    3. int factoryId = System.identityHashCode(beanFactory);
    4. if (this.factoriesPostProcessed.contains(factoryId)) {
    5. throw new IllegalStateException(
    6. "postProcessBeanFactory already called on this post-processor against " + beanFactory);
    7. }
    8. this.factoriesPostProcessed.add(factoryId);
    9. if (!this.registriesPostProcessed.contains(factoryId)) {
    10. // BeanDefinitionRegistryPostProcessor hook apparently not supported...
    11. // Simply call processConfigurationClasses lazily at this point then.
    12. processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);
    13. }
    14. // 关键在这里增强配置类
    15. enhanceConfigurationClasses(beanFactory);
    16. beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
    17. }
    18. 复制代码
    1. enhanceConfigurationClasses增强处理,主要判断属性是否式full,如果是的话,需要用cglib动态代理增强。
    1. public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
    2. .....
    3. // 如果指定属性是full, 则进行下面处理,该属性在前面阶段设置
    4. if (ConfigurationClassUtils.CONFIGURATION_CLASS_FULL.equals(configClassAttr)) {
    5. if (!(beanDef instanceof AbstractBeanDefinition)) {
    6. throw new BeanDefinitionStoreException("Cannot enhance @Configuration bean definition '" +
    7. beanName + "' since it is not stored in an AbstractBeanDefinition subclass");
    8. }
    9. else if (logger.isInfoEnabled() && beanFactory.containsSingleton(beanName)) {
    10. logger.info("Cannot enhance @Configuration bean definition '" + beanName +
    11. "' since its singleton instance has been created too early. The typical cause " +
    12. "is a non-static @Bean method with a BeanDefinitionRegistryPostProcessor " +
    13. "return type: Consider declaring such methods as 'static'.");
    14. }
    15. // 加入到增强集合中
    16. configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef);
    17. }
    18. }
    19. if (configBeanDefs.isEmpty() || NativeDetector.inNativeImage()) {
    20. // nothing to enhance -> return immediately
    21. enhanceConfigClasses.end();
    22. return;
    23. }
    24. // 新建增强器
    25. ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();
    26. // 遍历待增强的容器列表
    27. for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) {
    28. AbstractBeanDefinition beanDef = entry.getValue();
    29. // If a @Configuration class gets proxied, always proxy the target class
    30. beanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
    31. // Set enhanced subclass of the user-specified bean class
    32. Class<?> configClass = beanDef.getBeanClass();
    33. // 进行增强处理
    34. Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
    35. if (configClass != enhancedClass) {
    36. if (logger.isTraceEnabled()) {
    37. logger.trace(String.format("Replacing bean definition '%s' existing class '%s' with " +
    38. "enhanced class '%s'", entry.getKey(), configClass.getName(), enhancedClass.getName()));
    39. }
    40. beanDef.setBeanClass(enhancedClass);
    41. }
    42. }
    43. enhanceConfigClasses.tag("classCount", () -> String.valueOf(configBeanDefs.keySet().size())).end();
    44. }
    45. 复制代码

    动态代理增强逻辑

    增强逻辑主要在ConfigurationClassEnhancer类中。

    这里面的逻辑它会在从Spring Bean中先获取cat在这个Bean,所以会是同一个对象。

    总结

    回到一开始,为什么很多源码中写着@Configuration(proxyBeanMethods = false),这主要是出去性能的考虑,proxyBeanMethods设置为flase的情况下,将不会进行代理处理,并且每次调用@Bean注解标注的方法时都会创建一个新的Bean实例这种做法可能会稍微缩短启动时间,因为它不需要为配置类创建代理。但是还是要谨慎使用,因为它会改变方法的行为(并且可能最终会创建一个bean的多个实例,而不是单个实例!尤其是当与@Lazy每个调用结合使用时,会创建一个新实例,从而导致每次都是获取类似@Scope注解标注的效果)。

  • 相关阅读:
    c++中的常用知识点总结
    【Vue域名动态配置】
    Python学习记录 类相关
    js常用方法
    SAP UI5 框架的 manifest.json
    Django 4.0.6源码分析:自动重启机制
    MATLAB绘图合集:fcontour绘制隐函数等高线图
    Element树形控件使用过程中遇到的问题及解决方法
    使用react 写前端代码,使用javascript 写后端代码,前端向后端patch数据,如果后端要求integer型,前端要传什么型的数据才能成功修改后端的数据并返回给前端?
    samba实现
  • 原文地址:https://blog.csdn.net/m0_71777195/article/details/126437989