装配就是找到Bean的定义,然后加载到容器的过程,这一过程会产生BeanDefinition,还没有实例化、初始化,也没有依赖注入。SpringBoot出现之前,Spring对于Bean的定义分为XML和注解,扫描这些XML和指定目录的注解就可以将这些Bean加载到容器。
SpringBoot推荐使用注解配置而非XML配置,但是SpringBoot中也可以使用XML配置,通过 @ImportResource 注解可以引入一个XML配置。我们这里只讨论基于Java的注解配置。SpringBoot对于Bean的装配模式有两种,分为自定义装配和自动装配,区别就是配置类的来源不同。
自定义装配
自定义装配就是我们自己编写的类加上一些注解之后,SpringBoot可以识别并解析,这些注解分为:
自动装配
在微服务时代,一些基础功能,如缓存、文件、数据库、异常处理等,是每个微服务都需要的。可以把他们抽取处理形成公共包给各个微服务使用。那么如何加载这些公共包中的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
使用模式注解@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用于读取环境变量的值
此时,模式注解修饰的类都已经加载到容器
@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
下一个处理的配置类候选是YexAspect,它只使用了@Component,SpringBoot将所有@Component注解的Bean都当做配置类解析一遍。因此,在它里面也可以使用@Import或@Bean来注解一个方法生成bean。每个类都是先处理@Import再处理@Bean,@Import引入的配置类将递归处理,衍生的配置类解析完之后,会解析启动类上的@Import。
模块装配使用注解@EnableXXX 和 @Import配合,在启动类加上@EnableHello(isLinux = true)注解,下面看看@EnableHello的处理,这是自定义的注解,使用了@Import。先上代码
@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();
}
}
再跟代码看doProcessConfigurationClass的第四步会对@Import进行处理
getImports(sourceClass)–>collectImports(sourceClass, imports, visited);
解析到3个类
从String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());进去就执行到了HelloConfigImportSelector.selectImports
得到类名,递归进行处理
得到的是配置类HelloConfig,再次进入processImports方法
在最终的else处理,看注释就知道是什么意思了。processConfigurationClass又进入了递归,递归里面套递归,这块逻辑有点复杂,需要多研究一下。
处理完HelloConfigImportSelector,再看自动装配@EnableAutoConfiguration的@Import(AutoConfigurationImportSelector.class)
AutoConfigurationImportSelector实现了DeferredImportSelector,进入this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
将importSelector包装进DeferredImportSelectorHolder,再存放入DeferredImportSelectorHandler的deferredImportSelectors,返回到doProcessConfigurationClass,此时前解析到3个类都处理完了,DeferredImportSelector只是做了封装还未执行selectImports。
继续返回到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。
前面讲到条件的使用在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类加载器加载。
总结:
自定义的配置类先解析,SpringBoot提供的默认配置类很多都使用了条件注解,在自定义配置类中声明的Bean,默认配置类里面就不生效。SpringBoot先将这些配置类全部解析成ConfigurationClass(LinkedHashSet存储有序不可重复),再使用ConfigurationClassBeanDefinitionReader,将所有配置类里面的Bean加载到容器