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

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: 【Spring注解必知必会】深度解析@Configuration注解

概述


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

1671173773034.jpg


注解介绍


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

注解源码:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
  @AliasFor(annotation = Component.class)
  String value() default "";
  boolean proxyBeanMethods() default true;
}

属性说明:

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

元注解说明:

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

注解场景使用场景:

  1. 和@Bean搭配使用,创建管理Bean
@Configuration
   public class AppConfig {
       @Bean
       public MyBean myBean() {
           // instantiate, configure and return bean ...
       }
   }
  1. @PropertySource 注解搭配使用,获取外部配置
@Configuration
   @PropertySource("classpath:/com/acme/app.properties")
   public class AppConfig {
       @Inject Environment env;
       @Value("${bean.name}") String beanName;
       @Bean
       public MyBean myBean() {
           return new MyBean(env.getProperty("bean.name"));
       }
   }
  1. @Import注解搭配使用,导入其他配置类
@Configuration
   public class DatabaseConfig {
       @Bean
       public DataSource dataSource() {
           // instantiate, configure and return DataSource
       }
   }
   @Configuration
   @Import(DatabaseConfig.class)
   public class AppConfig {
       private final DatabaseConfig dataConfig;
       public AppConfig(DatabaseConfig dataConfig) {
           this.dataConfig = dataConfig;
       }
       @Bean
       public MyBean myBean() {
           // reference the dataSource() bean method
           return new MyBean(dataConfig.dataSource());
       }
   }
  1. @Profile搭配使用,指定当前配置使用的profile
@Profile("development")
   @Configuration
   public class EmbeddedDatabaseConfig {
       @Bean
       public DataSource dataSource() {
           // instantiate, configure and return embedded DataSource
       }
   }
   @Profile("production")
   @Configuration
   public class ProductionDatabaseConfig {
       @Bean
       public DataSource dataSource() {
           // instantiate, configure and return production DataSource
       }
   }
@Configuration
   public class ProfileDatabaseConfig {
       @Bean("dataSource")
       @Profile("development")
       public DataSource embeddedDatabase() { ... }
       @Bean("dataSource")
       @Profile("production")
       public DataSource productionDatabase() { ... }
   }
  1. @ImportResource搭配使用,导入xml的spring配置
@Configuration
   @ImportResource("classpath:/com/acme/database-config.xml")
   public class AppConfig {
       @Inject DataSource dataSource; // from XML
       @Bean
       public MyBean myBean() {
           // inject the XML-defined dataSource bean
           return new MyBean(this.dataSource);
       }
   }


注解使用案例


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

@Configuration
public class TestConfig {
    /**
     * 定义猫
     * @return
     */
    @Bean(name = "cat")
    public Cat cat() {
        return new Cat("小猫" + new Random().nextInt(100));
    }
    /**
     * 爸爸
     * @return
     */
    @Bean(name = "dad")
    public Person dadPerson() {
        return new Person("dad", cat());
    }
    /**
     * 爸爸
     * @return
     */
    @Bean(name = "mom")
    public Person momPerson() {
        return new Person("mom",  cat());
    }
}
Cat cat = context.getBean("cat", Cat.class);
        Person dadPerson = context.getBean("dad", Person.class);
        Person momPerson = context.getBean("mom", Person.class);
        System.out.println(cat);
        System.out.println(dadPerson.getCat());
        System.out.println(momPerson.getCat());
        TestConfig testConfig = context.getBean(TestConfig.class);
        System.out.println(testConfig);

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

1671173970608.jpg

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

1671173979473.jpg

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

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

1671173987208.jpg

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

1671173993682.jpg

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

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


原理解析


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

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


BeanDefinition的解析


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

1671174008638.jpg

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

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


Configuration注解的Bean创建


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

1671174029599.jpg

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


动态代理增强逻辑


增强逻辑主要在ConfigurationClassEnhancer类中。

1671174049203.jpg

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


总结


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

目录
相关文章
|
2天前
|
Java Spring
【Spring】方法注解@Bean,配置类扫描路径
@Bean方法注解,如何在同一个类下面定义多个Bean对象,配置扫描路径
109 73
|
3天前
|
设计模式 XML Java
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
本文详细介绍了Spring框架的核心功能,并通过手写自定义Spring框架的方式,深入理解了Spring的IOC(控制反转)和DI(依赖注入)功能,并且学会实际运用设计模式到真实开发中。
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
|
2天前
|
存储 Java Spring
【Spring】获取Bean对象需要哪些注解
@Conntroller,@Service,@Repository,@Component,@Configuration,关于Bean对象的五个常用注解
|
2天前
|
Java Spring
【Spring配置】idea编码格式导致注解汉字无法保存
问题一:对于同一个项目,我们在使用idea的过程中,使用汉字注解完后,再打开该项目,汉字变成乱码问题二:本来a项目中,汉字注解调试好了,没有乱码了,但是创建出来的新的项目,写的注解又成乱码了。
|
29天前
|
前端开发 Java 开发者
Spring MVC中的请求映射:@RequestMapping注解深度解析
在Spring MVC框架中,`@RequestMapping`注解是实现请求映射的关键,它将HTTP请求映射到相应的处理器方法上。本文将深入探讨`@RequestMapping`注解的工作原理、使用方法以及最佳实践,为开发者提供一份详尽的技术干货。
109 2
|
29天前
|
前端开发 Java Spring
探索Spring MVC:@Controller注解的全面解析
在Spring MVC框架中,`@Controller`注解是构建Web应用程序的基石之一。它不仅简化了控制器的定义,还提供了一种优雅的方式来处理HTTP请求。本文将全面解析`@Controller`注解,包括其定义、用法、以及在Spring MVC中的作用。
47 2
|
1月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
77 2
|
2月前
|
缓存 Java 程序员
Map - LinkedHashSet&Map源码解析
Map - LinkedHashSet&Map源码解析
81 0
|
3天前
|
存储 设计模式 算法
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。 行为型模式分为: • 模板方法模式 • 策略模式 • 命令模式 • 职责链模式 • 状态模式 • 观察者模式 • 中介者模式 • 迭代器模式 • 访问者模式 • 备忘录模式 • 解释器模式
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
|
3天前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。 结构型模式分为以下 7 种: • 代理模式 • 适配器模式 • 装饰者模式 • 桥接模式 • 外观模式 • 组合模式 • 享元模式
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析

推荐镜像

更多