SpringBoot源码学习二——Bean的装配

一、装配方式

装配就是找到Bean的定义,然后加载到容器的过程,这一过程会产生BeanDefinition,还没有实例化、初始化,也没有依赖注入。SpringBoot出现之前,Spring对于Bean的定义分为XML和注解,扫描这些XML和指定目录的注解就可以将这些Bean加载到容器。
SpringBoot推荐使用注解配置而非XML配置,但是SpringBoot中也可以使用XML配置,通过 @ImportResource 注解可以引入一个XML配置。我们这里只讨论基于Java的注解配置。SpringBoot对于Bean的装配模式有两种,分为自定义装配和自动装配,区别就是配置类的来源不同
自定义装配
自定义装配就是我们自己编写的类加上一些注解之后,SpringBoot可以识别并解析,这些注解分为:

  • 模式注解 @Component、@Controller、@Service、@Repository ,后面三个注解都是基于@Component,只是在代码分层之后的不同层的标记
  • 配置类 @Configuration与@Bean,@Configuration也是基于@Component,@Configuration注解的类也会被加载到容器
  • 模块装配 @EnableXXX与@Import
  • 条件装配 @Conditional与@ConditionalOnXXX

自动装配
在微服务时代,一些基础功能,如缓存、文件、数据库、异常处理等,是每个微服务都需要的。可以把他们抽取处理形成公共包给各个微服务使用。那么如何加载这些公共包中的Bean,就是自动装配要解决的问题。
SpringBoot规定,被加载的类需要在META-INF/spring.factories文件中声明。自动装配就是从SpringBot提供或者用户自定义的公共包读取spring.factories文件,获取到需要进行装配的类,这些类一般使用@Configuration声明,读取到这些类之后,它们的解析和加载过程与手动装配的配置类是一样的。

二、自定义装配

环境在前文SpringBoot的启动流程的基础上修改。前文已经讲到启动类最先被加载到容器,所有配置类都是从启动类衍生的,它本身是第一个配置类。在前文讲到过启动类在run里面的prepareContext()方法被加载到了容器,并且在后置增强器ConfigurationClassPostProcessor里面进行解析。来看下是怎么解析这个配置类的。
启动类上一般使用注解@SpringBootApplication
在这里插入图片描述
它上面又有三个注解

@ComponetScan扫描所有@Component注解的类,包括模式类和配置类
模式类:使用@Component、@Controller、@Service、@Repository注解的类
配置类:使用@Configuration注解的类
@EnableAutoConfiguration用于开启自动装配,后面讲。
@SpringBootConfiguration加了@Configuration注解,只是Spring标准@Configuration批注的替代,包含一个属性proxyBeanMethods,默认是true,意思是默认代理被@Bean修饰的方法,保证该方法产生的是单例对象。
先提一下SpringBoot启动过程先处理@ComponetScan,再处理@EnableAutoConfiguration

2.1 模式注解

使用模式注解@Component、@Controller、@Service、@Repository的Bean将被扫描。进入ConfigurationClassPostProcessor看代码
在这里插入图片描述
目前为止只有启动类和一些默认增强器,监听器被加载,进入processConfigBeanDefinitions
在这里插入图片描述
checkConfigurationClassCandidate将检查给定的bean定义是否是配置类的候选项,被以下注解修饰的类都将被视为候选项:@Configuration,@Component、@ComponentScan、@Import、@ImportResource。继续往下
在这里插入图片描述
只有主启动类满足条件,进入parse

parser.parse(candidates);–>parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());–>processConfigurationClass(new ConfigurationClass(metadata, beanName), DEFAULT_EXCLUSION_FILTER);

在这里插入图片描述
这个方法比较重要,他会在doProcessConfigurationClass()里面被递归调用,由启动类衍生的所有配置类都将放入configurationClasses,它使用了LinkedHashMap保证有序。进入doProcessConfigurationClass()
在这里插入图片描述
@PropertySources用于读取环境变量的值
在这里插入图片描述
此时,模式注解修饰的类都已经加载到容器

2.2 配置类@Configuration与@Bean

@Configuration注解本身也是被@Componet注解的,所以AppConfig也被加载到容器,看看它的解析过程

parse(bdCand.getBeanClassName(), holder.getBeanName());–>processConfigurationClass(new ConfigurationClass(reader, beanName), DEFAULT_EXCLUSION_FILTER);–>sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);

这个调用过程会再次进入doProcessConfigurationClass,前面三步不变,由于没有前三步的注解,会跳过前三步。
在这里插入图片描述
第四步处理@Import注解,@Import注解是模块装配和自动装配的入口,后面讲
在这里插入图片描述
处理@Bean注解的方法,得到了两个,就是在AppConfig里面定义的。后面的跳过,返回processConfigurationClass
在这里插入图片描述
处理完AppConfig之后,ConfigurationClass多了两个BeanMethod,然后将ConfigurationClass存到ConfigurationClassParser.configurationClasses

2.3 模块装配@EnableXXX与@Import

下一个处理的配置类候选是YexAspect,它只使用了@Component,SpringBoot将所有@Component注解的Bean都当做配置类解析一遍。因此,在它里面也可以使用@Import或@Bean来注解一个方法生成bean。每个类都是先处理@Import再处理@Bean,@Import引入的配置类将递归处理,衍生的配置类解析完之后,会解析启动类上的@Import。
模块装配使用注解@EnableXXX 和 @Import配合,在启动类加上@EnableHello(isLinux = true)注解,下面看看@EnableHello的处理,这是自定义的注解,使用了@Import。先上代码

2.3.1 代码

@EnableHello其实名称不重要,可以随便定义,按照习惯以Enable开头,代表开启某模块的意思。关键在于它上的@Import,SpringBoot会对它进行解析
EnableHello

import org.springframework.context.annotation.Import;

import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(HelloConfigImportSelector.class)
public @interface EnableHello {
    boolean isLinux();
}

HelloConfigImportSelector

import com.spring.config.HelloConfig;
import com.spring.config.Person;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;

import java.util.Map;

public class HelloConfigImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        Map<String, Object> metaData = importingClassMetadata.getAnnotationAttributes(EnableHello.class.getName());
        Boolean isLinux = (Boolean) metaData.get("isLinux");
        return new String[]{isLinux ? HelloConfig.class.getName() : Person.class.getName()};
    }
}

HelloConfig

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class HelloConfig {
    @Bean
    Person person() {
        return new Person();
    }
}

2.3.2 @Import原理

再跟代码看doProcessConfigurationClass的第四步会对@Import进行处理
在这里插入图片描述

getImports(sourceClass)–>collectImports(sourceClass, imports, visited);

在这里插入图片描述
递归处理所有@import注解,得到所有的import的类

2.3.2.1 processImports()

在这里插入图片描述
解析到3个类
在这里插入图片描述
从String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());进去就执行到了HelloConfigImportSelector.selectImports
在这里插入图片描述
得到类名,递归进行处理
在这里插入图片描述
得到的是配置类HelloConfig,再次进入processImports方法
在这里插入图片描述
在最终的else处理,看注释就知道是什么意思了。processConfigurationClass又进入了递归,递归里面套递归,这块逻辑有点复杂,需要多研究一下。

三、自动装配

3.1 AutoConfigurationImportSelector

处理完HelloConfigImportSelector,再看自动装配@EnableAutoConfiguration的@Import(AutoConfigurationImportSelector.class)
在这里插入图片描述
AutoConfigurationImportSelector实现了DeferredImportSelector,进入this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
在这里插入图片描述
将importSelector包装进DeferredImportSelectorHolder,再存放入DeferredImportSelectorHandler的deferredImportSelectors,返回到doProcessConfigurationClass,此时前解析到3个类都处理完了,DeferredImportSelector只是做了封装还未执行selectImports

3.2 ConfigurationClassParser

继续返回到parse,ConfigurationClassParser持有DeferredImportSelectorHandler的引用deferredImportSelectorHandler
在这里插入图片描述
跟进去process()

this.deferredImportSelectorHandler.process();–>handler.processGroupImports();–>grouping.getImports()–>this.group.process(deferredImport.getConfigurationClass().getMetadata(),deferredImport.getImportSelector());–>AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector).getAutoConfigurationEntry(annotationMetadata);

在这里插入图片描述
getCandidateConfigurations将读取配置类(这里取到了126个)。进去getCandidateConfigurations()看看

List configurations = getCandidateConfigurations(annotationMetadata, attributes);–>SpringFactoriesLoader.loadFactoryNames–>loadSpringFactories(classLoaderToUse)

在这里插入图片描述
读取到所有META-INF/spring.factories里声明的配置类,返回到getAutoConfigurationEntry
在这里插入图片描述
经过去重、排除启动类配置的exclude(例如:启动类配置了@SpringBootApplication(exclude = DataSourceTransactionManagerAutoConfiguration.class),exclude 属性的值对应的配置类将被去掉),最后进行条件配置判断@ConditionalOnXXX,筛选完之后只剩下30个配置类,返回ConfigurationClassParser.getImports()
在这里插入图片描述
配置类被封装到this.group里面,调用的是它的selectImports,作用是排序。AutoConfigurationImportSelector自己的selectImport没有被调用,再返回
在这里插入图片描述
在processImports中处理这些配置类,最终放入ConfigurationClassParser.configurationClasses。递归完之后回到processConfigBeanDefinitions来处理configurationClasses
在这里插入图片描述
根据配置类ConfigurationClass,加载BeanDefinition。

四、条件的使用@ConditionalOnXXX

前面讲到条件的使用在AutoConfigurationImportSelector.getAutoConfigurationEntry里面,从configurations = filter(configurations, autoConfigurationMetadata);进入
在这里插入图片描述
进入getAutoConfigurationImportFilters()
在这里插入图片描述
加载AutoConfigurationImportFilter的实现类
在这里插入图片描述FilteringSpringBootCondition是抽象类,不需要加载。回到filter方法getAutoConfigurationImportFilters()返回了三种类型的过滤条件OnBeanCondition、OnClassCondition、OnWebApplicationCondition。
以第一个OnClassCondition为例,看下它是如何使用的。它被如下三个条件注解使用,@ConditionalOnClass、@ConditionalOnMissingClass看名称就能猜到作用。
在这里插入图片描述

进入boolean[] match = filter.match(candidates, autoConfigurationMetadata);就是进到了父类FilteringSpringBootCondition的match
在这里插入图片描述
所有配置类都传进来进行一一匹配,并将结果用布尔数据保存返回,匹配失败的打印日志。执行完getOutcomes(autoConfigurationClasses, autoConfigurationMetadata);结果就封装在outcomes里面了。进去看就是OnClassCondition的getOutcomes
在这里插入图片描述
系统CPU线程数大于1,就使用多线程处理
在这里插入图片描述
firstHalfResolver是ThreadedOutcomesResolver线程解析器,另起一个线程处理0至61。secondHalfResolver是StandardOutcomesResolver标准解析器,使用当前线程处理62至末尾,这块代码值得研究借鉴。
子线程的处理就不看了,看看当前线程的处理secondHalfResolver.resolveOutcomes();进入
在这里插入图片描述
再进入
在这里插入图片描述
终于找到了使用ConditionalOnClass的地方。这里拿到每个类的条件注解ConditionalOnClass,如果不为空,就判断对应的Class是否已被JVM类加载器加载。

五、配置类装配顺序

  1. 启动类作为入口也是第一个配置类,首先进行解析
  2. 启动类上的@ComponentScan注解扫描到所有@Component注解的类加载到容器
  3. SpringBoot将所有@Component注解的类都当做配置类进行处理,每个类都是先处理@Import再处理@Bean,@Import引入的配置类将递归处理。
  4. 处理启动类上所有的@Import,@Import可以引入普通类、配置类、实现了ImportSelector的类。普通类直接加载到容器,配置类进行递归处理,实现了ImportSelector的类就调用其selectImports,继续处理返回的类(也可以是普通类、配置类)
  5. 处理启动类上的@EnableAutoConfiguration的@Import(AutoConfigurationImportSelector.class),它将从项目所有依赖包的META-INF/spring.factories文件中读取普通类和配置类(包括SpringBoot提供的和自定义的),通过selectImports方法返回,从而得到处理

总结:
自定义的配置类先解析,SpringBoot提供的默认配置类很多都使用了条件注解,在自定义配置类中声明的Bean,默认配置类里面就不生效。SpringBoot先将这些配置类全部解析成ConfigurationClass(LinkedHashSet存储有序不可重复),再使用ConfigurationClassBeanDefinitionReader,将所有配置类里面的Bean加载到容器

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值