@Configuration的作用和原理解析

1、@Configuration你干啥的

关于@Configuration spring官方文档是这样说的:@Configuration是一个类级别的注释,表明一个对象是 bean 定义的来源。@Configuration类通过带@Bean注释的方法声明 bean 。@Bean对@Configuration类方法的调用也可用于定义 bean 间的依赖关系
@Configuration在spring的注解开发中占有很重要的地位,你当你想要定义一个配置类并交给spring管理的时候你就可以在相关类上面加这个注解,并配合@Bean注解把对象交个spring去管理

@Slf4j /*@Slf4j 是lombok的注解 */
@Configuration
public class CustomConfiguration {

    @Bean
    public CustomBean customBean(){
        log.info("call customBean"); 
        return new CustomBean();
    }

    public static class CustomBean {
    }
}

这样在spring容器启动起来之后在customBean()方法中我们自己new出来的对象会被放到容器中,下面我们写一个测试

@Autowired
private CustomConfiguration.CustomBean customBean;

@Test
void testBean() {
  log.info("customBean exist ? {}", customBean != null);
}

测试日志
从日志中可以看到首先customBean()方法的调用是在spring容器初始化完成之前,而且我们也能通过@Autowired把它从spring的容器中拿出来。
除此之外我们还可以调整他的生命周期比如我在customBean()上面加@Scope(“prototype”)那么这个就成了原型的了,不多比比上日志
原型测试日志
从日志中可以看出方法的调用时在容器初始化完成之后。

2、@Configuration你凭啥能这么干

现在我们从spring的源码中看看凭什么你@Configuration可以这么干,首先我们要知道加了这个注解的类是怎么被spring知道的呢?
第一种方式,你直接告诉他例如

AnnotationConfigApplicationContext context1 = new AnnotationConfigApplicationContext(AppConfig.class);
//AppConfig.class 类上面加了@Configuration注解

spring接受到这个参数之后会把这个类(例子中的AppConfig)转换成BeanDefinition放到beanFactory中( Map<String, BeanDefinition> beanDefinitionMap),然后有一些类去统一处理这些BeanDefinition,这个时候就要提一嘴spring的后置处理器 (一种实现了BeanFactoryPostProcessor接口的类,spring中有好多的这种类而且程序员也可以实现这个接口,他们之间有个执行顺序如果想知道可以留言这个知识点可肝) 处理这个后置处理器是ConfigurationClassPostProcessor类。
在众多的BeanDefinition中你加了@Configuration的是怎么被ConfigurationClassPostProcessor一眼发现的呢,BeanDefinition通俗的来说可以是说对bean的描述这里面放着这个bean是不是需要懒加载作用域是什么类上面的注解等等信息,ConfigurationClassPostProcessor就是在Metadata里面拿到的注解信息(getAnnotationAttributes 方法),然后去判断是否加了@Configuration 不多比比上代码

/*ConfigurationClassUtils.java checkConfigurationClassCandidate 方法*/
AnnotationMetadata metadata;
if (beanDef instanceof AnnotatedBeanDefinition &&
    className.equals(((AnnotatedBeanDefinition) beanDef).getMetadata().getClassName())) {
  // Can reuse the pre-parsed metadata from the given BeanDefinition...
  metadata = ((AnnotatedBeanDefinition) beanDef).getMetadata();
}
/*....中间省略若干*/
Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());
if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {
  //加了@Configuration标签的会被标记为full
  beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
}else if (config != null || isConfigurationCandidate(metadata)) {
  /*@Component、@ComponentScan、@Import、@ImportResource*/
  //加了以上标签的会被标记为lite
  beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
}else {
  return false;
}
return true;

从上面可以看出在ConfigurationClassPostProcessor筛选的时候不只是加了@Configuration会被筛选出来,加了@Component、@ComponentScan、@Import、@ImportResource也会被筛选出来只是打的标签是不一样的,在筛选出来并打上标签以后是怎么解析的我们接着往下看,筛选出来的BeanDefinition被转换成了BeanDefinitionHolder(equals和hashCode都重写了)放在了set中(为了去重),然后先依次解析@PropertySource、@ComponentScan、@Import、@ImportResource、@Bean然后解析过程就结束了。
小看一下@Bean的解析 其他的留到以后再讲

// Process individual @Bean methods
//拿到sourceClass内所有加了@Bean的方法
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
  //添加到configClass里的一个Set<BeanMethod> 中 这里只是把方法扫描出来
  configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}
//ConfigurationClassBeanDefinitionReader.java  loadBeanDefinitionsForConfigurationClass 方法 
//这段代码负责解析上面生成的那个set
for (BeanMethod beanMethod : configClass.getBeanMethods()) {
  //loadBeanDefinitionsForBeanMethod负责用方法生成一个BeanDefinition放到beanFactory中
  loadBeanDefinitionsForBeanMethod(beanMethod);
}
//代码有删减 原方法详见 ConfigurationClassBeanDefinitionReader.java loadBeanDefinitionsForBeanMethod
MethodMetadata metadata = beanMethod.getMetadata();
ConfigurationClassBeanDefinition beanDef = new ConfigurationClassBeanDefinition(configClass, metadata, beanName);
beanDef.setSource(this.sourceExtractor.extractSource(metadata, configClass.getResource()));
beanDef.setFactoryBeanName(configClass.getBeanName());
beanDef.setUniqueFactoryMethodName(methodName);
AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(metadata, Scope.class); //拿到了类上的@Scope注解
if (attributes != null) {
  beanDef.setScope(attributes.getString("value")); //用注解中的内容修改bd内容
  proxyMode = attributes.getEnum("proxyMode");
  if (proxyMode == ScopedProxyMode.DEFAULT) {
    proxyMode = ScopedProxyMode.NO;
  }
}
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);
}
//把这个生成的bd交给bean工厂,后续再bean工厂中就能按照bd生成bean了
this.registry.registerBeanDefinition(beanName, beanDefToRegister); 

上debug图
scope测试图

让spring知道你在类上加了@Configuration的第二种方式,这个和第一种方式是紧密相连的,在上面处理第一种方式的时候最后类上面的@ComponentScan注解被解析了 他会根据你给的参数去扫描类如果发现你这个类加了@Configuration 也会去解析一遍,至此所有的要交给spring管理的bean都已经扫描成BeanDefinition 也是不多比比上代码

// ConfigurationClassParser.java doProcessConfigurationClass方法
// Process any @ComponentScan annotations
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
    sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
if (!componentScans.isEmpty() &&
    !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
  for (AnnotationAttributes componentScan : componentScans) {
    // 根据注解上配置的路径 扫描出来需要交给spring管理的类
    Set<BeanDefinitionHolder> scannedBeanDefinitions =
        this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
    for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
      BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
      if (bdCand == null) {
        bdCand = holder.getBeanDefinition();
      }
      //这个checkConfigurationClassCandidate就是上面那个方法
      if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
        //调用parse方法来解析类上面的注解,解析的路数和你传给spring是相同的
        parse(bdCand.getBeanClassName(), holder.getBeanName());
      }
    }
  }
}

至此所有的要交给spring管理的bean都已经扫描成BeanDefinition存放在beanFactory里面了,后面在用BeanDefinition生成bean的时候会用到full和lite属性,具体这两种属性的区别是什么我们下次再说。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐