聊透spring @Configuration配置类

 本章节我们来探索Spring中一个常用的注解@Configuration。我们先来了解一下该注解的作用是:用来定义当前类为配置类

 那啥是配置类啊,有啥用啊。这个我们得结合实际使用场景来说,通常情况下。加了@Configuration的配置类内部,都会包含一个或多个@Bean注解的方法

为了简化定义,在后续我们称@Bean注解的方法为工厂方法。

 配置类的奥秘就在这里,Spring会保证多次调用@Bean标注的工厂方法,不会重复产生新的对象,始终是同一个,这也贯彻了Spring的单例哲学。

 多次调用创建方法,产生的竟然是同一个对象,这貌似违背了编程的基础原理。怎么可能😱,一定是Spring做了什么,那就跟随着贰师兄的脚步一起解开@Configuration的神秘面纱吧。

这里大家很容易产生一个误解,认为只有在加了@Configuration的配置类中,使用@Bean,才能将自定义创建的对象放入Spring容器中。其实不然,在Spring中:万物皆为配置类在任何能够被Spring管理的类(比如加了@Component的类)中,定义@Bean方法,都能将自定义对象放入到Spring容器,@Bean本身的能力和@Configuration无关哦

1. @Configuration的作用

 我们先通过一个简单的案例,看一下@Configuration的作用。

需要特别说明的是:@Configuration继承了@Component,这意味着@Configuration拥有@Component的全部功能,这也正是只加@Configuration,也能被Spring扫描并处理的原因。

  • 情况一:被@Component标识
@Component
public class MarkedByComponent {
    @Bean
    public A a(){
        return new A();
    }

    @Bean
    public B b(){
        a();
        return new B();
    }
}

// 输出信息:
// A created...
// A created...
// B created...
复制代码
  • 情况二:被@Configuration标识
@Configuration
public class MarkedByConfiguration {
    @Bean
    public A a(){
        return new A();
    }

    @Bean
    public B b(){
        a();
        return new B();
    }
}

// 输出信息:
// A created...
// B created...
复制代码
  • 结论
     通过上述输出我们发现:在多次调用a()的情况下,被@Component标识的类,A会被创建多次。而被@Configuration标识的配置类,A只会被创建一次。此时我们可以大胆猜测:在@Configuration标识的配置类中,重复调用@Bean标识的工厂方法,Spring会对创建的对象进行缓存。仅在缓存中不存在时,才会通过工厂方法创建对象。后续重复调用工厂方法创建对象,先去缓存中找,不直接创建对象

这里小伙伴可能有个疑惑,a()只在b()被调用一次,为什么说多次呢,其实除了在b()中声明式调用,由于a()被@Bean标识,Spring也会主动调用一次的哦。

2. @Configuration的原理分析

 上一章节,我们通过一个简单的案例,了解@Configuration的作用:使@Bean标识的工厂方法,不会重复创建bean对象。同时也大胆猜测了,Spring对@Configuration标识的配置类,做了缓存处理,从而让@Bean工厂方法,有了"幂等的能力"。本章节我们一起探索一下,这个增强的缓存逻辑,是怎么做到的?

  要对一个类的功能进行增强。代理 这个词是不是已经在脑海中呼之欲出了,是的,就是代理。Spring对@Configuration标识的类做了代理,从而进行功能的增强。

 当然,现在我们离直接分析代理功能还有点遥远,毕竟步子不能迈的太大,不然容易扯着蛋。一步步来,首先我们先看看Spring是如何解析@Configuration注解的,毕竟需要先找出来,才能代理增强嘛。

2.1 @Configuration解析

  对@Configuration的解析过程是在spring扫描类的时候进行的。这里需要介绍一下,Spring把加了@Configuration注解的称为全配置类,其他的称为半配置类,两者判别标准是:是否加了@Configuration注解。

// ConfigurationClassParser.java
@Nullable
protected final SourceClass doProcessConfigurationClass(
      ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter) throws IOException {
    //... 省略部分非相关代码

    // 解析是否加了@ComponentScan, 并进行扫描
    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) {

          // 1: 解析ComponentScan配置信息,完成扫描(扫描出来的类在beanDefinitionMap中)
          Set<BeanDefinitionHolder> scannedBeanDefinitions =
                this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());

          // 2: 遍历扫描出来的类,检查是不是配置类(配置类需要继续递归解析)
          for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
             BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
             if (bdCand == null) {
                bdCand = holder.getBeanDefinition();
             }
             // 重点:判断类是不是配置类, 并标注类属性信息(是否为全配置类、@Order值等)
             if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
                // 3 如果扫描出来的类,是配置类,还需要递归处理扫描出来的配置类
                parse(bdCand.getBeanClassName(), holder.getBeanName());
             }
          }
       }
    }
    //... 省略部分非相关代码

}

//ConfigurationClassUtils.java
// 全配置类标记值
public static final String CONFIGURATION_CLASS_FULL = "full";
// 半配置类标记值
public static final String CONFIGURATION_CLASS_LITE = "lite";

public static boolean checkConfigurationClassCandidate(
      BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {
     //... 省略部分非相关代码
      
    // 2.1 从注解信息中, 获取@Configuration的注解信息(如存在,标记为为全配置类)
    Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());
    if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {
       beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
    }
    
    // 有@Component,@ComponentScan,@Import,@ImportResource注解,被标记成半配置类
    else if (config != null || isConfigurationCandidate(metadata)) {
       beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
    }
    else
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值