1、上一篇文章对springboot进行了一个简单的使用,接下来我们分析一下springboot的核心原理,需要储备的知识点是对spring framework的扩展点比较属性才能看懂。
2、springboot的核心是从启动类开始的
@SpringBootApplication
public class SpringBootBaseusedApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootBaseusedApplication.class, args);
}
}
启动类中核心的地方是SpringApplication类 + 注解@SpringBootApplication
我们先来分析注解@SpringBootApplication,这个注解上面标注了核心的三个注解分别如下:
1、@SpringBootConfiguration
2、@EnableAutoConfiguration
3、@ComponentScan
先说@SpringBootConfiguration这个注解吧,这个注解又被@Configuration注解标注了,那就说明一个问题,我们当前的启动类其实就是一个配置类,既然是一个配置类,那么我们就能联想起spring framework中一个很重要的类叫做ConfigurationClassPostProcessor,这个类专业负责通过配置类的定义来解析和注册BenaDefintion到beanFactory实例中,解析的范畴包括解析注解@ComponentScan、@Import、@ImportResource、@Component、@PropertySources几个注解,那么问题来了,我们知道ConfigurationClassPostProcessor会处理配置类,处理的时候也是先获取配置类的BeanDefinition实例然后才解析,那么我们的启动类的BeanDefinition实例是什么时候构建以及注册到beanFactory实例中的呢????带着这个问题我们开始从SpringApplication的run(...)方法进行源码调试。
入口:传入了当前的启动类的Class对象以及启动参数
SpringApplication.run(SpringBootBaseusedApplication.class, args);
SpringApplication.run(...)方法:
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] { primarySource }, args);
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
先传入当前的启动类的Class实例去new一个SpringApplication实例,然后在执行其run方法。
return new SpringApplication(primarySources).run(args);
}
最终会执行到SpringApplication的构造函数
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
1、此时传入的resourceLoader是null,primarySources数组里面存有我们启动的Class对象。
this.resourceLoader = resourceLoader;
2、如果主资源(我们的启动类就是主资源的范畴)是空的将会抛出异常。
Assert.notNull(primarySources, "PrimarySources must not be null");
3、设置当前创建的SpringApplication的private Set<Class<?>> primarySources属性等于传入的主资源转化的集合。
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
4、进行应用的类型推断,就是判断当前的应用是什么类型的应用,有SERVLET、REACTIVE、NONE三种
类型,默认是NONE类型,推断的方式就是查看当前类路径下有没有相关的依赖来判定的,比如当我们引入spring-
boot-starter-web的依赖就会推断出当前的永用类型是SERVLET类型。
this.webApplicationType = WebApplicationType.deduceFromClasspath();
5、使用SPI的方式获取应用上下文的初始者列表,以便于后续去初始化应用上下文。
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
6、同样使用SPI的方式获取应用监听器列表,以便于在发布事件的时候能够被监听器处理。
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
7、推断应用的主类,方式是遍历当前线程的栈帧,如果栈帧中对应执行的方法名称是“main”方法,
那么main方法所在的类就是主类。
this.mainApplicationClass = deduceMainApplicationClass();
}
SpringApplication实例创建好后接下来就会调用其非静态的run(...)方法:此处是核心中的核心:
public ConfigurableApplicationContext run(String... args) {
1、创建一个计时表并开始计时。
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
2、使用SPI的方式获取SpringApplicationRunListener,默认会获取到一个事件发布监听器
EventPublishingRunListener实例。
SpringApplicationRunListeners listeners = getRunListeners(args);
3、调用每一个spring 应用运行监听器的启动方法,EventPublishingRunListener的starting方法会广播一个应用开始事件ApplicationStartingEvent。
listeners.starting();
try {
4、使用run(...)的入参args来构建一个应用参数集实例ApplicationArguments,注意我们通过-D 或--设置的参数在args的数组中是能够获取到的。
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
5、使用Spring应用运行监听列表SpringApplicationRunListeners + 应用参数集来准备spring 上下文的环境,得到一个可配置的环境实例。
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
6、如果配置了需要忽略的bean的话就在此设置到系统变量中。
configureIgnoreBeanInfo(environment);
7、打印banner
Banner printedBanner = printBanner(environment);
8、创建应用上下文,核心中的核心就在这里!!!!!
context = createApplicationContext();
9、使用SPI的方式获取到异常报告器列表。
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
10、准备应用上下文实例,核心的作用就是进行必要的初始化应用上下文。
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
11、开始刷新应用上下文,这里会走到核心的AbstractApplicationContext的refresh()方法。
refreshContext(context);
12、刷新应用上下文后进行处理。
afterRefresh(context, applicationArguments);
13、停止时钟,这里可以计算出应用上下文的刷新的时长。
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
14、使用应用上下文生命监听器执行stared表示启动完成。
listeners.started(context);
15、执行一些运行器,ApplicationRunner、CommandLineRunner两种类型的运行器,按顺序执行。
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
16、调用应用上下文生命监听器的runing方法,表示应用已经在运行阶段了。
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
重要的调用1:准备环境prepareEnvironment(...):配置源列表是放在Environment中类型是MutablePropertySources实例里面的,MutablePropertySources里面维护的propertySourceList就是配置源列表。此处是重点!!!!!
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
1、获取或者创建一个ConfigurableEnvironment实例。
ConfigurableEnvironment environment = getOrCreateEnvironment();
2、配置环境实例,使用应用参数,这个应用参数里面包含了命令行设置的参数。
configureEnvironment(environment, applicationArguments.getSourceArgs());
3、再添加一个名称是"configurationProperties"的配置源到环境中,而且是添加到配资源列表的
第一个位置,表示优先级最高,名称是"configurationProperties"的配置源是一个组合配资源,它是由其环
境中所有的配置源组合而来的配资源,名称是"configurationProperties"的配置源的类型是
SpringConfigurationPropertySources,这个里的集合类型sources属性就存放了其他的所有配资源。
ConfigurationPropertySources.attach(environment);
4、如果是应用是SERVLET类型的话,到此步的时候,environment中的配置源应该有6个了,然后告
诉SpringApplicationRunListeners中所有的spring应用监听器当前上下文的环境实例已经准备好了,在这
里使用的应用监听器EventPublishingRunListener在处理自己业务的时候又会广播一个环境准备就绪的事件
ApplicationEnvironmentPreparedEvent出去,这个事件又会被一个很重要的监听器
ConfigFileApplicationListener收到,而且是同步执行处理事件,ConfigFileApplicationListener会根
据激活的profile,然后加载配置资源,如application.properties、application-dev.properties等配
置资源到配资源中,有几个资源文件就会添加几个配资源,这个后面我们会详细分析。
listeners.environmentPrepared(environment);
5、将当前准备好配置源的环境实例environment与SpringApplication实例绑定起来。
bindToSpringApplication(environment);
6、进行环境类型的推断,如果不一致进行类型切换。
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
deduceEnvironmentClass());
}
7、环境类型切换完成后再次将名称是"configurationProperties"的配置源添加到配资源列表的第一位。
ConfigurationPropertySources.attach(environment);
return environment;
}
获取或创建一个环境:
private ConfigurableEnvironment getOrCreateEnvironment() {
1、如果当前SpringApplication实例的环境不为空那就直接返回。
if (this.environment != null) {
return this.environment;
}
2、根据类型之前推断的应用类型来创建不同实现的环境类实例。
switch (this.webApplicationType) {
case SERVLET:
2.1、如果是SERVLET类型的应用就创建一个StandardServletEnvironment实例,创建此实例
的时候在构造方法中会添加默认的4个配置源,名称分别如下:顺序也是如此使用的是addLast的方式
servletConfigInitParams
servletContextInitParams
systemProperties
systemEnvironment
return new StandardServletEnvironment();
case REACTIVE:
2.2、如果应用类型是REACTIVE的话,创建的是StandardReactiveWebEnvironment实例,创
建此实例的的时候在构造器函数中会添加2个默认的配置源,名称分别如下:顺序也是如此
systemProperties
systemEnvironment
return new StandardReactiveWebEnvironment();
default:
2.3、如果是其他类型创建一个StandardEnvironment实例,也默认会在其构造函数中添加2个
默认的配置源,名称分别如下:顺序也是如此
systemProperties
systemEnvironment
return new StandardEnvironment();
}
}
配置环境实例:
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
1、先设置环境的类型转换服务为新创建的一个ConversionService实例。
if (this.addConversionService) {
ConversionService conversionService = ApplicationConversionService.getSharedInstance();
environment.setConversionService((ConfigurableConversionService) conversionService);
}
2、使用应用参数(命令行参数)来配置一个配资源,配置源的名称叫“commandLineArgs”,如果
已经包含了名称是"commandLineArgs"的配置源,将命令行参数添加到名称
叫"springApplicationCommandLineArgs"的配置源中,如果没有包含的话,就添加名称
为"commandLineArgs"命令行的参数的配置源添加到配置源列表的第一个位置,表示优先级最高。
configurePropertySources(environment, args);
3、配置激活的环境,此时已经将配资源都配置好了,那就可以来设置激活的环境profile信息。
configureProfiles(environment, args);
}
配置profile:
protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
Set<String> profiles = new LinkedHashSet<>(this.additionalProfiles);
1、先使用environment去获取到激活的profile列表,就在这里会去之前添加好的配置源中获取到
spring.profiles.active这个key的值,从而得到激活的profile列表。
profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
2、获取到所需激活的profile列表后设置到当前的环境实例中。
environment.setActiveProfiles(StringUtils.toStringArray(profiles));
}
重要调用2:创建应用上下文createApplicationContext():
protected ConfigurableApplicationContext createApplicationContext() {
1、默认的应用上下文的Class是空。
Class<?> contextClass = this.applicationContextClass;
2、使用应用的类型来判断,到底应用上下文的类型应该是啥。
if (contextClass == null) {
try {
switch (this.webApplicationType) {
case SERVLET:
servlet类型的话就是AnnotationConfigServletWebServerApplicationContext
contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
break;
case REACTIVE:
web-flux类型的话就是AnnotationConfigReactiveWebServerApplicationContext
contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
break;
default:
否则就是AnnotationConfigApplicationContext
contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
}
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);
}
}
3、反射创建应用上下文实例,既然没有入参,那么默认就是应用上下文类型的无参构造函数咯,我们
以AnnotationConfigServletWebServerApplicationContext类型的应用上下文为例,其他类型的大同异。
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}
!!!重点AnnotationConfigServletWebServerApplicationContext的无参数构造函数:
public AnnotationConfigServletWebServerApplicationContext() {
1、创建一个AnnotatedBeanDefinitionReader实例,就是注解beandDefinition读取器。
this.reader = new AnnotatedBeanDefinitionReader(this);
2、创建一个类路径beandDefinition扫码器。
this.scanner = new ClassPathBeanDefinitionScanner(this);
}
1、AnnotatedBeanDefinitionReader的构造函数:在其父类GenericApplicationContext的无参数
构造函数中会创建一个DefaultListableBeanFactory实例赋值到当前的应用上下文的beanFactory属性。
public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry) {
此处的getOrCreateEnvironment会重新构建一个环境实例,获取不到之前准备好的环境实例。
入参registry就是当前跑创建的AnnotationConfigServletWebServerApplicationContext实例。
this(registry, getOrCreateEnvironment(registry));
}
public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {
Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
Assert.notNull(environment, "Environment must not be null");
this.registry = registry;
this.conditionEvaluator = new ConditionEvaluator(registry, environment, null);
核心在这里:这里会注册注解配置相关的处理器到前面创建的beanFactory中,注册的组件有如下:
ConfigurationClassPostProcessor:配置类后置处理器
AutowiredAnnotationBeanPostProcessor:bean的自动装配后置处理器。
CommonAnnotationBeanPostProcessor:JSR-250 自动装配后置处理器。
PersistenceAnnotationBeanPostProcessor:如果映入了JPA的相关依赖就添加JPA的注解bean后置处理器。
EventListenerMethodProcessor:时间监听器方法处理器,解析@EventListener注解
生成一个beanName + 方法实例method,然后使用此处注册的另外一个组件DefaultEventListenerFactory
去生成一个ApplicationListener监听器,然后将监听器添加到应用上下文中。
DefaultEventListenerFactory:跟EventListenerMethodProcessor配合解析跟生
成监听器ApplicationListener实例添加到应用上下文中。
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
2、ClassPathBeanDefinitionScanner的构造函数:
public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry) {
true 表示使用默认的Filters,组件过滤,组件过滤的意思是,比如包下面很多类,那些类需要被注
册成spring容器的组件,这个需要一个过滤条件,就是用过滤器来表示。
this(registry, true);
}
最终会执行到这里!!!!!
public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,
Environment environment, @Nullable ResourceLoader resourceLoader) {
Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
this.registry = registry;
if (useDefaultFilters) {
注册默认的组件过滤器,默认是标注了@Component以及@Component派生的如@Service、
@Repository、@Controller。 还有就是@ManagedBean、注解@javax.inject.Named 标注的类就是组件。
registerDefaultFilters();
}
设置环境实例,这个环境实例是构建AnnotatedBeanDefinitionReader实例的时候构建的哪一个,
并不是最开始准备好的那个环境实例!!!!!
setEnvironment(environment);
设置资源载入器,就是读取包下面的Class文件等一下组件。
setResourceLoader(resourceLoader);
}
应用上下文实例创建好了,接着主流程走,解析来就是准备应用上下文,主要的作用就是对应用上下文实例进行一下必要的初始化工作:
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
1、设置应用上下文的环境实例是之前已经准备好的环境实例。
context.setEnvironment(environment);
2、对应用上下文进行后置处理,主要设置应用上下中的beanFactory实例设置一个类型转换服务
ApplicationConversionService实例。
postProcessApplicationContext(context);
3、使用之前SPI获取到的应用上下文初始器来初始化应用上下文。
applyInitializers(context);
4、执行SpringApplicationRunListeners 监听器列表的应用上下文准备好策略,在这里
EventPublishingRunListener会广播一个应用上下文已经初始化完成的事件出去
ApplicationContextInitializedEvent。
listeners.contextPrepared(context);
5、打印一下日志,比如当前环境中的profile信息。
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
6、注册一个单例bena到应用上下文的beanFactory中,name=springApplicationArguments
value=之前构建好的应用参数实例applicationArguments。
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
7、注册printedBanner实例到到应用上下文的beanFactory中,name=springBootBanner
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
8、设置当前的应用上下文的beanFactory不允许覆盖beanDefinition。
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
9、如果需要延迟初始化,那就添加一个实现了BeanFactoryPostProcessor的延迟初始化的工厂后
置处理器LazyInitializationBeanFactoryPostProcessor到应用上下文中。
if (this.lazyInitialization) {
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
10、载入资源,这里很核心,载入什么资源,会获取所有的资源,所有的资源就是
SpringApplication实例中的primarySources(主要资源包含了我们的启动类)、sources。
// Load the sources
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
然后加载资源,获取的资源一般情况下就只包含我们的启动类,在这里就会将我们的启动类的
BeanDefinition注册到应用上下文的beanFactory中。
load(context, sources.toArray(new Object[0]));
使用监听器列表执行上下文以及载入完成,在EventPublishingRunListener中会发布一个应用已经
准备完成事件ApplicationPreparedEvent出去。
listeners.contextLoaded(context);
}
载入资源是实现:
protected void load(ApplicationContext context, Object[] sources) {
入参是应用上下文实例 + 包含有启动类Class的资源苏数组。
if (logger.isDebugEnabled()) {
logger.debug("Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
}
1、先构建一个只加载当前资源的bean定义加载器。
BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);
if (this.beanNameGenerator != null) {
loader.setBeanNameGenerator(this.beanNameGenerator);
}
if (this.resourceLoader != null) {
loader.setResourceLoader(this.resourceLoader);
}
if (this.environment != null) {
loader.setEnvironment(this.environment);
}
2、使用这个资源加载器进行加载。
loader.load();
}
加载器的加载实现:
int load() {
int count = 0;
循环遍历含有启动类Class的资源列表进行加载
for (Object source : this.sources) {
count += load(source);
}
return count;
}
加载资源的具体实现:
private int load(Object source) {
Assert.notNull(source, "Source must not be null");
我们的启动类传入的是Class实例,肯定会进这个if进行加载
if (source instanceof Class<?>) {
return load((Class<?>) source);
}
if (source instanceof Resource) {
return load((Resource) source);
}
if (source instanceof Package) {
return load((Package) source);
}
if (source instanceof CharSequence) {
return load((CharSequence) source);
}
throw new IllegalArgumentException("Invalid source type " + source.getClass());
}
载入类型是Class的资源实现:
private int load(Class<?> source) {
1、如果是Groovy类型的就重新构建一个GroovyBeanDefinitionSource资源加载器进行加载。
if (isGroovyPresent() && GroovyBeanDefinitionSource.class.isAssignableFrom(source)) {
// Any GroovyLoaders added in beans{} DSL can contribute beans here
GroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source, GroovyBeanDefinitionSource.class);
load(loader);
}
2、如果当前的Class不满足 的simpleName 不为空 或者Class的名称不匹配Groovy名称的特性 或者
没有构建函数,也就是正常java类,比如我们的启动类,那就使用资源载入器的注解bean定义读取器
annotatedReader来注册当前的Class类型到容器中。
if (isEligible(source)) {
这里就会将我们的启动类构建一个BeanDefinition然后注册到应用上下文的beanFacotry中。
this.annotatedReader.register(source);
return 1;
}
return 0;
}
讲到这里我们终于回答了之前提出的问题:我们的启动类的BeanDefinition实例是什么时候构建以及注册到beanFactory实例中的呢? 答案就是在对应用上下文实例进行准备的时候会将启动类的BeanDefinition进行构建与注册到容器应用上下文中。
3、花了很大的功夫将启动类是如何作为spring容器的组件的原理搞明白了,那么接下来就开始剖析自动装配的原理:
既然启动类是spring容器中的一个组件,且是一个配置类,那么在配置类解析的组件ConfigurationClassPostProcessor在执行的时候,肯定会解析我们的启动类吧,这个是肯定的操作。我们知道启动上标注了注解@SpringBootApplication,这和个注解呢又等价于3个注解:
1、@SpringBootConfiguration
2、@EnableAutoConfiguration
3、@ComponentScan
第1个我们以及剖析过了它的作用就是让启动类成为一个配置类。
接下来剖析第2个注解@EnableAutoConfiguration的作用,@EnableAutoConfiguration注解的源码如下:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
排除那些Class
Class<?>[] exclude() default {};
排除那些名称
String[] excludeName() default {};
}
我们可以发现@EnableAutoConfiguration标注了两个很重要的注解:
1、@AutoConfigurationPackage 这个注解有标注了如下注解:
1.1、@Import({Registrar.class})
2、@Import({AutoConfigurationImportSelector.class})
我们会发现最终都是标注了@Import注解,这个注解又刚好是配置类解析ConfigurationClassPostProcessor能够解析的,巧了。。。。。哈哈 这不就是核心思想吗? 没错很重要的地方,没有这个,自动装配还玩个鸡毛。
既然是两个@Import注解,那我们来分析两个@Import注解导入的类:
1、AutoConfigurationImportSelector
2、Registrar
AutoConfigurationImportSelector:自动配置导入选择器,在ConfigurationClassPostProcessor处理@Import的时候会调用这个类的一些方法,什么方法呢?有什么作用,我们先来查看AutoConfigurationImportSelector的类图关系:
AutoConfigurationImportSelector这个类实现了DeferredImportSelector接口,这个接口继承了ImportSelector接口,先扩展一下知识点,在ConfigurationClassPostProcessor处理器@Import注解的时候,会对导入的类进行判断,判断被导入的类的Class是否是ImportSelector以及ImportBeanDefinitionRegistrar,如果被导入的类是这两种类型其中之一,那就调用其接口定义的方法。而如果是DeferredImportSelector延迟导入选择器的话,会等待所有的ImportBeanDefinitionRegistrar、ImportSelector类型的实例执行完对应的接口方法后,才会执行延迟导入的实现类的对应接口方法。
有了上面的知识点我们来到AutoConfigurationImportSelector的selectImports(...)方法,这个方法的作用就是返回需要注册到spring上下文中的Class集合,我们来剖析实现:
在ConfigurationClassParser中处理延迟导入的代码片段:spring framework的版本是5.2.8
this.deferredImportSelectorHandler.process();
public void process() {
1、拿到在解析@Import的时候发现的延迟导入处理器,我们的
AutoConfigurationImportSelector就在里面,当然这里拿到的是DeferredImportSelectorHolder,
这个实例里面包装了配置类、延迟选择处理器实例。
List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
this.deferredImportSelectors = null;
try {
if (deferredImports != null) {
2、构建延迟导入选择器分组处理器。
DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
3、将所有的延迟导入选择器进行排序。
deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);
4、将每一个延迟导入选择器注册到分组处理器中,在这里会调用延迟选择处理器的
getImportGroup()方法,返回一个分组Group,我们的AutoConfigurationImportSelector返回的Group是
AutoConfigurationImportSelector.AutoConfigurationGroup.class这个。
deferredImports.forEach(handler::register);
5、使用分组处理器进行分组导入。
handler.processGroupImports();
}
}
finally {
this.deferredImportSelectors = new ArrayList<>();
}
}
分组处理器分组导入实现:
public void processGroupImports() {
1、循环在将延迟选择器注册到分组处理阶段获取到的Group,我们的AutoConfigurationGroup
就在这里面。
for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
2、获取导入的时候需要排除的规则组件候选过滤器。
Predicate<String> exclusionFilter = grouping.getCandidateFilter();
3、先使用当前延迟导入分组grouping.getImports()获取到当前延迟导入选择器所有需要
导入的所有的Clas。然后再循环进行解析,在解析的含义在于,如果导入的类还是配置
类,那就需要再做析。我们的AutoConfigurationGroup就是在这里进行导入选者的。
grouping.getImports().forEach(entry -> {
ConfigurationClass configurationClass = this.configurationClasses.get(entry.getMetadata());
try {
processImports(configurationClass, asSourceClass(configurationClass, exclusionFilter),
Collections.singleton(asSourceClass(entry.getImportClassName(), exclusionFilter)),
exclusionFilter, false);
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to process import candidates for configuration class [" +
configurationClass.getMetadata().getClassName() + "]", ex);
}
});
}
}
grouping.getImports(): DeferredImportSelectorGrouping中实现如下:我们的Group会本封装成
DeferredImportSelectorGrouping这样的一个实例,这个实例里面包含了分组实例Group,还有我们的延迟导入
选择器列表,为什么是列表??? 因为一个导入分组当然可以有多个延迟导入选者器来导入了,这样的目的就是
灵活,体现了分组Group的概念。
public Iterable<Group.Entry> getImports() {
for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
循环处理每一个延迟选择处理器,这里就会调用当前分组中的每一个延迟导入选择器的获取
自动装配实例方法getAutoConfigurationEntry(...),并将返回值添加到当前分组实例的自动装配实例集合
autoConfigurationEntries中。
this.group.process(deferredImport.getConfigurationClass().getMetadata(),
deferredImport.getImportSelector());
}
汇总后进行自动装配过滤处理。
return this.group.selectImports();
}
自动配置分组的process(...)实现:
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector, () -> {
return String.format("Only %s implementations are supported, got %s", AutoConfigurationImportSelector.class.getSimpleName(), deferredImportSelector.getClass().getName());
});
1、调用延迟导入选择器的获取自动装配实例的方法,返回值是
AutoConfigurationImportSelector.AutoConfigurationEntry类型,这个类里面封装了当前的延迟导入选
择器需要导入的配置类集合(存放的是SPI文件中获取的类路径) + 需要排除配置(存放的也是配置类的全路
径)。
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector)deferredImportSelector).getAutoConfigurationEntry(annotationMetadata);
2、将当前的延迟选择器的导入信息存到当前分组实例的autoConfigurationEntries集合中。
this.autoConfigurationEntries.add(autoConfigurationEntry);
Iterator var4 = autoConfigurationEntry.getConfigurations().iterator();
3、将需要导入的类信息添加到当前分组实例的entries Map中key=需要导入的类的全路径(SPI
文件中定义的类全路径),value=启动类相关的封装实例。
while(var4.hasNext()) {
String importClassName = (String)var4.next();
this.entries.putIfAbsent(importClassName, annotationMetadata);
}
}
有了上面的流程,我们以AutoConfigurationImportSelector来剖析其获取自动装配实例的实现:
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
入参annotationMetadata是一个标准的注解元数据,描述的启动类。
1、先判断自动装配是否开启,默认是开启,可以在环境中配spring.boot.enableautoconfiguration
来灵活控制自动配置的开启与关闭。
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
2、获取注解的成员属性,这里就是获取@SpringBootApplication注解的成员属性。
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
3、获取配置类候选者列表,这里就会使用spring boot 的SPI机制进行获取。
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
4、去重。
configurations = this.removeDuplicates(configurations);
5、获取在注解@SpringBootApplication中定义的需要排除的配置类,如果有需要排除的就移除掉。
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
6、获取导入自动装配的过滤器,也是使用SPI来进行获取,然后进行过滤。
configurations = this.getConfigurationClassFilter().filter(configurations);
7、获取自动配置的导入监听器,然后发布一个AutoConfigurationImportEvent自动配置导入事件出去。
this.fireAutoConfigurationImportEvents(configurations, exclusions);
8、使用导入的自动配置项 + 排除掉的配置项构建一个自动装配信息AutoConfigurationEntry实例返回。
return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
}
}
我们来看看获取配置类候选者列表,这里就会使用spring boot 的SPI机制进行获取的实现原理:
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
springboot spi实现获取自动配置项,默认从类路径下的"META-INF/spring.factories"加载SPI文
件,此处载入的SPI的类型是this.getSpringFactoriesLoaderFactoryClass()这里获取到的,获取到的是
EnableAutoConfiguration.class这个开启自动装配的类型。
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
return configurations;
}
EnableAutoConfiguration.class 在spring boot 的SPI文件的样例:
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
经过上面步骤,就会将所有的配置类的BeanDefinition 以及配置类中解析到的BeanDefintion(例如@Bean注解)实例都注册到Spring应用上下文中。
Registrar:这个导入器其实没做什么重要的动,它只是注册了一个描述当前扫描包的定义类到spring应用上下文中。
4、前面讲解了@SpringBootConfiguration、@EnableAutoConfiguration两个注解了,3个剩下一个@ComponentScan注解
这个注解是定义组件扫描的,我们知道不是所有类都是组件的,组件扫描可以根据包路径 + 组件过滤器来进行组件筛选,@ComponentScan就是干这个事情的,@ComponentScan注解也是在ConfigurationClassPostProcessor类中进行解析处理的,处理的大概实现就是,解析到@ComponentScan注解上的组件扫描定义,然后构建扫描解析器ComponentScanAnnotationParser进行组件扫描。
我们知道在Spring Boot中,我们不配置组件扫描的包路径的时候,默认只会扫描启动类所在的包 + 启动类所在的包的子孙包下的组件,其实这个不是在Spring Boot时期有的东西,在spring4 版本就有这个实现了。实现的方式是,如果当前配置类上面标注了@ComponentScan注解,并且没有定义basePackages属性,那么默认会使用当前配置类所在的包来作为组件扫描的范围,源码实现如下:
在ComponentScanAnnotationParser中:
Set<String> basePackages = new LinkedHashSet<>();
获取注解@componentScan的成员属性basePackages
String[] basePackagesArray = componentScan.getStringArray("basePackages");
for (String pkg : basePackagesArray) {
String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
Collections.addAll(basePackages, tokenized);
}
for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
basePackages.add(ClassUtils.getPackageName(clazz));
}
如果没有发现@componentScan定义了扫描包范围,那就使用当前配置类的包来作为组件扫描的包范围。
if (basePackages.isEmpty()) {
basePackages.add(ClassUtils.getPackageName(declaringClass));
}
5、以上就是spring boot 自动配置的原理,其实你会发现,自动装配不算复杂,当然前提是你对spring framework比较熟悉,从上面可以看出来,spring boot就是在使用spring framework 的扩展点来实现了自动配置的功能,这就是spring framework 与 spring boot的最大是区别。