@Configuration 使用与原理

@Configuration 使用与原理

@Configuration 基本使用

@Configuration 源码的注解非常长,十分详细地阐述了 @Configuration 的使用方法并给出了示例代码,我这里就贴出一小部分:

  • 搭配 @Bean 注入到 Spring 容器 Indicates that a class declares one or more @Bean methods and may be processed by the Spring container to generate bean definitions and service requests for those beans at runtime.
  • 搭配 @ComponentScan 扫描 Bean @Configuration classes may not only be bootstrapped using component scanning, but may also themselves configure component scanning using the @ComponentScan annotation.
  • ...

而 @Configuration 有一个属性 proxyBeanMethods 往往被人忽视,我们重点来看一下:

@Configuration 的两种模式

java 复制代码
 // @Configuration 本身是个复合注解,包含了一个 @Component
 @Component
 public @interface Configuration {
     @AliasFor(annotation = Component.class)
     String value() default "";
     boolean proxyBeanMethods() default true;
 }

为什么配置类也会有代理?其实这个属性值对应了两种模式:Full 与 Lite,对应 Spring 容器在处理 @Bean 时的两种不同行为。

java 复制代码
 // 默认true即为Full模式;false为Lite模式,可以指定配置类为Lite模式
 @Configuration(proxyBeanMethods = false)

1、Full模式:代理配置类

Full 模式会使用 CGLib 代理这个配置类。

Full 模式下,一个方法调用另外一个 @Bean 方法,动态代理方法会先去容器中检查是否存在该 Bean,如果存在,则直接使用容器中的 Bean,否则才会去创建新的对象

java 复制代码
 @Configuration
 public class xxxConfig {
     // 一个方法getUser1调用另外一个 @Bean方法 getUser2
     @Bean
     public User getUser1(){
         return getUser2();
     }
 ​
     @Bean
     public User getUser2(){
         return new User();
     }
 }

此时的输出结果:

java 复制代码
 applicationContext.getBean(User.class)
 // expected single matching bean but found 2: getUser1,getUser2    

发现只有一个名为 getUser2 的 Bean。

2、Lite模式

显示指定 proxyBeanMethods = false,此时为 Lite 模式。

仍然是上面的代码,只修改了proxyBeanMethods的值。

此时直接编译报错,提示为:

java 复制代码
 // Method annotated with @Bean is called directly in a @Configuration where proxyBeanMethods set to false. Set proxyBeanMethods to true or use dependency injection. 

即:Spring不允许在Lite模式下的一个类内方法调用另外一个 @Bean 方法,因为这可能导致对象被重复创建,而Full模式通过动态代理杜绝了这个可能

直接把 @Configuration 替换为 @Component,同样报错,提示为:

java 复制代码
 // Method annotated with @Bean is called directly. Use dependency injection instead. 

总之,Spring建议我们通过依赖注入或者Full模式解决这个问题。

Full vs Lite

可能大多数情况下都采用了默认的 Full 模式,但 Full 模式也并非万金油。

因为 Full 用到了动态代理,因此速度上比 Lite 慢,并且继承了CGLIB的局限性:final修饰的方法无法被代理,因此可能报错。

但Lite的缺点是:一个@Bean 方法无法调用另外一个 @Bean 方法。

@Configuration 源码分析

源码的注释中提到了一个关键类:ConfigurationClassPostProcessor。

xml 复制代码
 <beans>
    <context:annotation-config/>
    <bean class="com.acme.AppConfig"/>
 </beans>

In the example above, is required in order to enable ConfigurationClassPostProcessor and other annotation-related post processors that facilitate handling @Configuration classes.

但是,该从哪下手呢?我尝试对 @ComponentScan 使用 ALT+F7 并打上断点,发现调用栈进入的是 refresh 方法中的 invokeBeanFactoryPostProcessors(beanFactory)

java 复制代码
     // refresh 方法部分源码 ...
     StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
     // Invoke factory processors registered as beans in the context.
     invokeBeanFactoryPostProcessors(beanFactory);
 ​
     // Register bean processors that intercept bean creation.
     registerBeanPostProcessors(beanFactory);

于是又将断点打在 invokeBeanFactoryPostProcessors(beanFactory) 方法上,此时已经有若干 BeanDefinition:

其中这个 internalConfigurationAnnotationProcessor 就是 ConfigurationClassPostProcessor 类。

进到这个方法里简单看一下:

java 复制代码
 /**
  * Invoke the given BeanDefinitionRegistryPostProcessor beans.
  */
 private static void invokeBeanDefinitionRegistryPostProcessors(
       Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry, ApplicationStartup applicationStartup) {
 ​
    for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {
       StartupStep postProcessBeanDefRegistry = applicationStartup.start("spring.context.beandef-registry.post-process")
             .tag("postProcessor", postProcessor::toString);
        // 执行这个方法时 BeanDefinationMap 还只有 7 个
       postProcessor.postProcessBeanDefinitionRegistry(registry);
        // 执行 end 方法时 BeanDefinationMap 已经加载了很多 Bean 了
       postProcessBeanDefRegistry.end();
    }
 }

我们发现当断点停在postProcessor.postProcessBeanDefinitionRegistry(registry); 时,BeanDefinationMap 还只有 7 个,下一处就有几百个了。因此 postProcessBeanDefinitionRegistry 这个方法就是核心,而它又调用了 processConfigBeanDefinitions

源码上的注解是这样的,又印证了我们的想法:

Build and validate a configuration model based on the registry of Configurationclasses.

这个方法比较长,我只贴出部分核心代码,省略了如去重的部分:

java 复制代码
 // 创建 ConfigurationClassParser 并解析 @Configuration class
 // Parse each @Configuration class
 ​
         ConfigurationClassParser parser = new ConfigurationClassParser(...);
         // candidates 是待解析的 @Configuration 集合
         Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
         do {
             StartupStep processConfig = this.applicationStartup.start("...");
             // 核心方法 parse 解析 @Configuration
             parser.parse(candidates);
             parser.validate();
             Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
 ​
             this.reader.loadBeanDefinitions(configClasses);
             candidates.clear();
             // 加载了新的 BeanDefinition
             if (registry.getBeanDefinitionCount() > candidateNames.length) {
                 // 新加载的(可能是扫描到了新的) Configuration 加入 candidates 循环处理
                 for (String candidateName : newCandidateNames) {
                     if (!oldCandidateNames.contains(candidateName)) {
                         BeanDefinition bd = registry.getBeanDefinition(candidateName);
                         if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) )) {
                             candidates.add(new BeanDefinitionHolder(bd, candidateName));
                         }
                     }
                 }
                 candidateNames = newCandidateNames;
             }
         }
         while (!candidates.isEmpty());

关键问题是,parser.parse(candidates); 具体是如何解析的?

我们发现 parse 方法,会调用到 processConfigurationClass 方法

java 复制代码
 // 递归解析
 // Recursively process the configuration class and its superclass hierarchy.
 SourceClass sourceClass = asSourceClass(configClass, filter);
 do {
    sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
 }
 while (sourceClass != null);
 this.configurationClasses.put(configClass, configClass);

真正处理 @Configuration:doProcessConfigurationClass

而这个 doProcessConfigurationClass 就是最核心的方法:

java 复制代码
 protected final SourceClass doProcessConfigurationClass(
  ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter){
 ​
    if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
       // Recursively process any member (nested) classes first
       processMemberClasses(configClass, sourceClass, filter);
    }
 ​
     // 1. Process any @PropertySource annotations
     // ... 省略判断
     processPropertySource(propertySource);
 ​
 ​
    // 2. Process any @ComponentScan annotations
     // 用 componentScanParser 解析 @ComponentScan 的路径
     this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName())
     // 可能扫描到新的 @Configuration ,parse
     for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
         BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
         if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
         parse(bdCand.getBeanClassName(), holder.getBeanName());
             }
         }
 ​
    // 3. Process any @Import annotations
    processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
 ​
    // 4. Process any @ImportResource annotations
    AnnotationAttributes importResource =
          AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
    if (importResource != null) {
       String[] resources = importResource.getStringArray("locations");
       Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
       for (String resource : resources) {
          String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
          configClass.addImportedResource(resolvedResource, readerClass);
       }
    }
 ​
    // 5. Process individual @Bean methods
    Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
    for (MethodMetadata methodMetadata : beanMethods) {
       configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
    }
 ​
    // 6. Process default methods on interfaces
    processInterfaces(configClass, sourceClass);
 ​
    // 7. Process superclass, if any
    if (sourceClass.getMetadata().hasSuperClass()) {
       String superclass = sourceClass.getMetadata().getSuperClassName();
       if (superclass != null && !superclass.startsWith("java") &&
             !this.knownSuperclasses.containsKey(superclass)) {
          this.knownSuperclasses.put(superclass, configClass);
          // Superclass found, return its annotation metadata and recurse
          return sourceClass.getSuperClass();
       }
    }
 ​
    // No superclass -> processing is complete
    return null;
 }

这里也对不同的注解做了不同的处理,因为比较多,就看一下是如何处理 @Bean 的,其它方法就不一一分析了。

@Bean 的处理方式

java 复制代码
 // Process individual @Bean methods
 // 首先 Retrieve the metadata for all @Bean methods.
 Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
 for (MethodMetadata methodMetadata : beanMethods) {
    configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
 }

ConfigurationClass 有这么一个属性:

java 复制代码
 private final Set<BeanMethod> beanMethods = new LinkedHashSet<>();

也就是把 beanMethod 放入到一个 LinkedHashSet,仅此而已。

而 BeanMethod 的注释很明了:

Represents a @Configuration class method annotated with @Bean.

那么解析 @Bean 又在何时呢?在 beanMethods 属性的 get 方法上打上断点,发现进入了 processConfigBeanDefinitions 方法的 this.reader.loadBeanDefinitions(configClasses);中。

java 复制代码
 // loadBeanDefinitionsForConfigurationClass 方法
 for (BeanMethod beanMethod : configClass.getBeanMethods()) {
    loadBeanDefinitionsForBeanMethod(beanMethod);
 }

这里就为 @Bean 方法加载 BeanDefinitions,有我们熟悉的别名,默认 bean 名称,initMethod,autowire 等

java 复制代码
 private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) {
    ConfigurationClass configClass = beanMethod.getConfigurationClass();
    MethodMetadata metadata = beanMethod.getMetadata();
    String methodName = metadata.getMethodName();
 ​
 ​
    AnnotationAttributes bean = AnnotationConfigUtils.attributesFor(metadata, Bean.class);
 ​
    // Consider name and any aliases
    List<String> names = new ArrayList<>(Arrays.asList(bean.getStringArray("name")));
     // bean 名称 默认方法名
    String beanName = (!names.isEmpty() ? names.remove(0) : methodName);
 ​
    // 别名 Register aliases even when overridden
    for (String alias : names) {
       this.registry.registerAlias(beanName, alias);
    }
 ​
    ConfigurationClassBeanDefinition beanDef = new ConfigurationClassBeanDefinition(configClass, metadata, beanName);
    beanDef.setSource(this.sourceExtractor.extractSource(metadata, configClass.getResource()));
 ​
    if (metadata.isStatic()) {
       // static @Bean method
       if (configClass.getMetadata() instanceof StandardAnnotationMetadata) {
          beanDef.setBeanClass(((StandardAnnotationMetadata) configClass.getMetadata()).getIntrospectedClass());
       }
       else {
          beanDef.setBeanClassName(configClass.getMetadata().getClassName());
       }
       beanDef.setUniqueFactoryMethodName(methodName);
    }
    else {
       // instance @Bean method
       beanDef.setFactoryBeanName(configClass.getBeanName());
       beanDef.setUniqueFactoryMethodName(methodName);
    }
 ​
    if (metadata instanceof StandardMethodMetadata) {
       beanDef.setResolvedFactoryMethod(((StandardMethodMetadata) metadata).getIntrospectedMethod());
    }
 ​
    beanDef.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);
    beanDef.setAttribute(org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor.
          SKIP_REQUIRED_CHECK_ATTRIBUTE, Boolean.TRUE);
 ​
    AnnotationConfigUtils.processCommonDefinitionAnnotations(beanDef, metadata);
 ​
    Autowire autowire = bean.getEnum("autowire");
    if (autowire.isAutowire()) {
       beanDef.setAutowireMode(autowire.value());
    }
 ​
    boolean autowireCandidate = bean.getBoolean("autowireCandidate");
    if (!autowireCandidate) {
       beanDef.setAutowireCandidate(false);
    }
 ​
    String initMethodName = bean.getString("initMethod");
    if (StringUtils.hasText(initMethodName)) {
       beanDef.setInitMethodName(initMethodName);
    }
 ​
    String destroyMethodName = bean.getString("destroyMethod");
    beanDef.setDestroyMethodName(destroyMethodName);
 ​
    // Consider scoping
    ScopedProxyMode proxyMode = ScopedProxyMode.NO;
    AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(metadata, Scope.class);
    if (attributes != null) {
       beanDef.setScope(attributes.getString("value"));
       proxyMode = attributes.getEnum("proxyMode");
       if (proxyMode == ScopedProxyMode.DEFAULT) {
          proxyMode = ScopedProxyMode.NO;
       }
    }
 ​
    // Replace the original bean definition with the target one, if necessary
    BeanDefinition beanDefToRegister = beanDef;
    if (proxyMode != ScopedProxyMode.NO) {
       BeanDefinitionHolder proxyDef = ScopedProxyCreator.createScopedProxy(
             new BeanDefinitionHolder(beanDef, beanName), this.registry,
             proxyMode == ScopedProxyMode.TARGET_CLASS);
       beanDefToRegister = new ConfigurationClassBeanDefinition(
             (RootBeanDefinition) proxyDef.getBeanDefinition(), configClass, metadata, beanName);
    }
 ​
    this.registry.registerBeanDefinition(beanName, beanDefToRegister);
 }

小结

本文主要介绍了 @Configuration 的两种模式:Full 与 Lite。Full 模式会采取 CGLIB 动态代理实现,保证在调用一个 @Bean 方时法,会先去容器中检查是否存在该 Bean,如果存在,则直接使用容器中的 Bean,否则才会去创建新的对象。而 Lite 模式为了避免对象被重复创建,禁止类内方法调用另外一个 @Bean 方法。

然后对 @Configuration 注解进行源码分析。在 refresh 方法中调用invokeBeanFactoryPostProcessors(beanFactory) 时,ConfigurationClassPostProcessor 会负责解析 @Configuration 类,依次处理 @PropertySource、@ComponentScan,最后是@Bean。并且 @Bean 仅仅是将方法封装并放入 LinkedHashSet 中,在后续 loadBeanDefinitionsForConfigurationClass 时再解析。

参考文档

@Configuration 注解的 Full 模式和 Lite 模式!

相关推荐
Viktor_Ye11 分钟前
高效集成易快报与金蝶应付单的方案
java·前端·数据库
hummhumm13 分钟前
第 25 章 - Golang 项目结构
java·开发语言·前端·后端·python·elasticsearch·golang
一二小选手17 分钟前
【Maven】IDEA创建Maven项目 Maven配置
java·maven
J老熊23 分钟前
JavaFX:简介、使用场景、常见问题及对比其他框架分析
java·开发语言·后端·面试·系统架构·软件工程
猿java28 分钟前
什么是 Hystrix?它的工作原理是什么?
java·微服务·面试
AuroraI'ncoding29 分钟前
时间请求参数、响应
java·后端·spring
好奇的菜鸟41 分钟前
Go语言中的引用类型:指针与传递机制
开发语言·后端·golang
所待.3831 小时前
JavaEE之线程初阶(上)
java·java-ee
Winston Wood1 小时前
Java线程池详解
java·线程池·多线程·性能
Alive~o.01 小时前
Go语言进阶&依赖管理
开发语言·后端·golang