一次搞懂SpringBoot核心原理:自动配置、事件驱动、Condition

前言

SpringBoot是Spring的包装,通过自动配置使得SpringBoot可以做到开箱即用,上手成本非常低,但是学习其实现原理的成本大大增加,需要先了解熟悉Spring原理。如果还不清楚Spring原理的,可以先查看博主之前的文章,本篇主要分析SpringBoot的启动、自动配置、Condition、事件驱动原理。

启动原理

SpringBoot启动非常简单,因其内置了Tomcat,所以只需要通过下面几种方式启动即可:

@SpringBootApplication(scanBasePackages = {"cn.dark"})publicclassSpringbootDemo{publicstaticvoidmain(String[] args){// 第一种SpringApplication.run(SpringbootDemo.class,args);// 第二种newSpringApplicationBuilder(SpringbootDemo.class)).run(args);// 第三种SpringApplication springApplication =newSpringApplication(SpringbootDemo.class);        springApplication.run();    }}

可以看到第一种是最简单的,也是最常用的方式,需要注意类上面需要标注@SpringBootApplication注解,这是自动配置的核心实现,稍后分析,先来看看SpringBoot启动做了些什么?

在往下之前,不妨先猜测一下,run方法中需要做什么?对比Spring源码,我们知道,Spring的启动都会创建一个ApplicationContext的应用上下文对象,并调用其refresh方法启动容器,SpringBoot只是Spring的一层壳,肯定也避免不了这样的操作。另一方面,以前通过Spring搭建的项目,都需要打成War包发布到Tomcat才行,而现在SpringBoot已经内置了Tomcat,只需要打成Jar包启动即可,所以在run方法中肯定也会创建对应的Tomcat对象并启动。以上只是我们的猜想,下面就来验证,进入run方法:

publicConfigurableApplicationContext run(String... args) {// 统计时间用的工具类StopWatch stopWatch =newStopWatch();stopWatch.start();ConfigurableApplicationContext context =null;Collection exceptionReporters =newArrayList<>();configureHeadlessProperty();// 获取实现了SpringApplicationRunListener接口的实现类,通过SPI机制加载// META-INF/spring.factories文件下的类SpringApplicationRunListeners listeners = getRunListeners(args);// 首先调用SpringApplicationRunListener的starting方法listeners.starting();try{ApplicationArguments applicationArguments =newDefaultApplicationArguments(args);// 处理配置数据ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);configureIgnoreBeanInfo(environment);// 启动时打印bannerBanner printedBanner = printBanner(environment);// 创建上下文对象context = createApplicationContext();// 获取SpringBootExceptionReporter接口的类,异常报告exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,newClass[] { ConfigurableApplicationContext.class }, context);prepareContext(context, environment, listeners, applicationArguments, printedBanner);// 核心方法,启动spring容器refreshContext(context);afterRefresh(context, applicationArguments);// 统计结束stopWatch.stop();if(this.logStartupInfo) {newStartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);}// 调用startedlisteners.started(context);// ApplicationRunner// CommandLineRunner// 获取这两个接口的实现类,并调用其run方法callRunners(context, applicationArguments);}catch(Throwable ex) {handleRunFailure(context, ex, exceptionReporters, listeners);thrownewIllegalStateException(ex);}try{// 最后调用running方法listeners.running(context);}catch(Throwable ex) {handleRunFailure(context, ex, exceptionReporters,null);thrownewIllegalStateException(ex);}returncontext;}

SpringBoot的启动流程就是这个方法,先看getRunListeners方法,这个方法就是去拿到所有的

SpringApplicationRunListener实现类,这些类是用于SpringBoot事件发布的,关于事件驱动稍后分析,这里主要看这个方法的实现原理:

privateSpringApplicationRunListeners getRunListeners(String[] args) {Class[] types =newClass[] { SpringApplication.class,String[].class };returnnewSpringApplicationRunListeners(logger,getSpringFactoriesInstances(SpringApplicationRunListener.class, types,this, args));}private Collection getSpringFactoriesInstances(Classtype, Class[] parameterTypes,Object... args) {ClassLoader classLoader = getClassLoader();// Use names and ensure unique to protect against duplicatesSet names =newLinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));// 加载上来后反射实例化List instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);AnnotationAwareOrderComparator.sort(instances);returninstances;}publicstaticList loadFactoryNames(Class factoryType,@NullableClassLoader classLoader) {StringfactoryTypeName = factoryType.getName();returnloadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());}publicstaticfinalStringFACTORIES_RESOURCE_LOCATION ="META-INF/spring.factories";privatestaticMap> loadSpringFactories(@NullableClassLoader classLoader) {MultiValueMap result = cache.get(classLoader);if(result !=null) {returnresult;}try{Enumeration urls = (classLoader !=null?classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));result =newLinkedMultiValueMap<>();while(urls.hasMoreElements()) {URL url = urls.nextElement();UrlResource resource =newUrlResource(url);Properties properties = PropertiesLoaderUtils.loadProperties(resource);for(Map.Entry entry : properties.entrySet()) {StringfactoryTypeName = ((String) entry.getKey()).trim();for(StringfactoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {result.add(factoryTypeName, factoryImplementationName.trim());}}}cache.put(classLoader, result);returnresult;}}

一步步追踪下去可以看到最终就是通过SPI机制根据接口类型从META-INF/spring.factories文件中加载对应的实现类并实例化,SpringBoot的自动配置也是这样实现的。为什么要这样实现呢?通过注解扫描不可以么?当然不行,这些类都在第三方jar包中,注解扫描实现是很麻烦的,当然你也可以通过@Import注解导入,但是这种方式不适合扩展类特别多的情况,所以这里采用SPI的优点就显而易见了。

回到run方法中,可以看到调用了createApplicationContext方法,见名知意,这个就是去创建应用上下文对象:

publicstaticfinalString DEFAULT_SERVLET_WEB_CONTEXT_CLASS ="org.springframework.boot."+"web.servlet.context.AnnotationConfigServletWebServerApplicationContext";protectedConfigurableApplicationContextcreateApplicationContext(){Class contextClass =this.applicationContextClass;if(contextClass ==null) {try{switch(this.webApplicationType) {caseSERVLET:contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);break;caseREACTIVE:contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);break;default:contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);}}catch(ClassNotFoundException ex) {thrownewIllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);}}return(ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);}

注意这里通过反射实例化了一个新的没见过的上下文对象

AnnotationConfigServletWebServerApplicationContext,这个是SpringBoot扩展的,看看其构造方法:

publicAnnotationConfigServletWebServerApplicationContext() {this.reader = new AnnotatedBeanDefinitionReader(this);this.scanner = new ClassPathBeanDefinitionScanner(this);}

如果你有看过Spring注解驱动的实现原理,这两个对象肯定不会陌生,一个实支持注解解析的,另外一个是扫描包用的。

上下文创建好了,下一步自然就是调用

refresh方法启动容器:

privatevoidrefreshContext(ConfigurableApplicationContext context){refresh(context);if(this.registerShutdownHook) {try{context.registerShutdownHook();}catch(AccessControlException ex) {// Not allowed in some environments.}}}protectedvoidrefresh(ApplicationContext applicationContext){Assert.isInstanceOf(AbstractApplicationContext.class,applicationContext);((AbstractApplicationContext) applicationContext).refresh();}

这里首先会调用到其父类中

ServletWebServerApplicationContext:

publicfinalvoidrefresh()throwsBeansException, IllegalStateException{try{super.refresh();}catch(RuntimeException ex) {stopAndReleaseWebServer();throwex;}}

可以看到是直接委托给了父类:

publicvoidrefresh()throwsBeansException, IllegalStateException{synchronized(this.startupShutdownMonitor) {// Prepare this context for refreshing.prepareRefresh();// Tell the subclass to refresh the internal bean factory.ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();// Prepare the bean factory for use in this context.prepareBeanFactory(beanFactory);try{// Allows post-processing of the bean factory in context subclasses.postProcessBeanFactory(beanFactory);// Invoke factory processors registered as beans in the context.invokeBeanFactoryPostProcessors(beanFactory);// Register bean processors that intercept bean creation.registerBeanPostProcessors(beanFactory);// Initialize message source for this context.initMessageSource();// Initialize event multicaster for this context.initApplicationEventMulticaster();// Initialize other special beans in specific context subclasses.onRefresh();// Check for listener beans and register them.registerListeners();// Instantiate all remaining (non-lazy-init) singletons.finishBeanFactoryInitialization(beanFactory);// Last step: publish corresponding event.finishRefresh();}catch(BeansException ex) {if(logger.isWarnEnabled()) {logger.warn("Exception encountered during context initialization - "+"cancelling refresh attempt: "+ ex);}// Destroy already created singletons to avoid dangling resources.destroyBeans();// Reset 'active' flag.cancelRefresh(ex);// Propagate exception to caller.throwex;}finally{// Reset common introspection caches in Spring's core, since we// might not ever need metadata for singleton beans anymore...resetCommonCaches();}}}

这个方法不会陌生吧,之前已经分析过了,这里不再赘述,至此SpringBoot的容器就启动了,但是Tomcat启动是在哪里呢?run方法中也没有看到。实际上Tomcat的启动也是在refresh流程中,这个方法其中一步是调用了onRefresh方法,在Spring中这是一个没有实现的模板方法,而SpringBoot就通过这个方法完成了Tomcat的启动:

protectedvoidonRefresh(){super.onRefresh();try{createWebServer();}catch(Throwable ex) {thrownewApplicationContextException("Unable to start web server", ex);}}privatevoidcreateWebServer(){WebServer webServer =this.webServer;ServletContext servletContext = getServletContext();if(webServer ==null&& servletContext ==null) {ServletWebServerFactory factory = getWebServerFactory();// 主要看这个方法this.webServer = factory.getWebServer(getSelfInitializer());}elseif(servletContext !=null) {try{getSelfInitializer().onStartup(servletContext);}catch(ServletException ex) {thrownewApplicationContextException("Cannot initialize servlet context", ex);}}initPropertySources();}

这里首先拿到

TomcatServletWebServerFactory对象,通过该对象再去创建和启动Tomcat:

publicWebServer getWebServer(ServletContextInitializer... initializers) {if(this.disableMBeanRegistry) {Registry.disableRegistry();}Tomcat tomcat = new Tomcat();File baseDir = (this.baseDirectory !=null) ?this.baseDirectory : createTempDir("tomcat");tomcat.setBaseDir(baseDir.getAbsolutePath());Connector connector = new Connector(this.protocol);connector.setThrowOnFailure(true);tomcat.getService().addConnector(connector);customizeConnector(connector);tomcat.setConnector(connector);tomcat.getHost().setAutoDeploy(false);configureEngine(tomcat.getEngine());for(Connector additionalConnector :this.additionalTomcatConnectors) {tomcat.getService().addConnector(additionalConnector);}prepareContext(tomcat.getHost(), initializers);returngetTomcatWebServer(tomcat);}

上面的每一步都可以对比Tomcat的配置文件,需要注意默认只支持了http协议:

Connector connector =newConnector(this.protocol);privateStringprotocol = DEFAULT_PROTOCOL;publicstaticfinalStringDEFAULT_PROTOCOL ="org.apache.coyote.http11.Http11NioProtocol";

如果想要扩展的话则可以对

additionalTomcatConnectors属性设置值,需要注意这个属性没有对应的setter方法,只有addAdditionalTomcatConnectors方法,也就是说我们只能通过实现BeanFactoryPostProcessor接口的postProcessBeanFactory方法,而不能通过BeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry方法,因为前者可以通过传入的BeanFactory对象提前获取到TomcatServletWebServerFactory对象调用addAdditionalTomcatConnectors即可;而后者只能拿到BeanDefinition对象,该对象只能通过setter方法设置值。

事件驱动

Spring原本就提供了事件机制,而在SpringBoot中又对其进行扩展,通过发布订阅事件在容器的整个生命周期的不同阶段进行不同的操作。我们先来看看SpringBoot启动关闭的过程中默认会发布哪些事件,使用下面的代码即可:

@SpringBootApplicationpublicclassSpringEventDemo{publicstaticvoidmain(String[] args){newSpringApplicationBuilder(SpringEventDemo.class)                .listeners(event->{                    System.err.println("接收到事件:"+ event.getClass().getSimpleName());                })                .run()                .close();    }}

这段代码会在控制台打印所有的事件名称,按照顺序如下:

ApplicationStartingEvent:容器启动

ApplicationEnvironmentPreparedEvent:环境准备好

ApplicationContextInitializedEvent:上下文初始化完成

ApplicationPreparedEvent:上下文准备好

ContextRefreshedEvent:上下文刷新完

ServletWebServerInitializedEvent:webServer初始化完成

ApplicationStartedEvent:容器启动完成

ApplicationReadyEvent:容器就绪

ContextClosedEvent:容器关闭

以上是正常启动关闭,如果发生异常还有发布ApplicationFailedEvent事件。事件的发布遍布在整个容器的启动关闭周期中,事件发布对象刚刚我们也看到了是通过SPI加载的

SpringApplicationRunListener实现类EventPublishingRunListener,同样事件监听器也是在spring.factories文件中配置的,默认实现了以下监听器:

org.springframework.context.ApplicationListener=\

org.springframework.boot.ClearCachesApplicationListener,\

org.springframework.boot.builder.ParentContextCloserApplicationListener,\

org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\

org.springframework.boot.context.FileEncodingApplicationListener,\

org.springframework.boot.context.config.AnsiOutputApplicationListener,\

org.springframework.boot.context.config.ConfigFileApplicationListener,\

org.springframework.boot.context.config.DelegatingApplicationListener,\

org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\

org.springframework.boot.context.logging.LoggingApplicationListener,\

org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener

可以看到有用于文件编码的(

FileEncodingApplicationListener),有加载日志框架的(LoggingApplicationListener),还有加载配置的(ConfigFileApplicationListener)等等一系列监听器,SpringBoot也就是通过这系列监听器将必要的配置和组件加载到容器中来,这里不再详细分析,感兴趣的读者可以通过其实现的onApplicationEvent方法看到每个监听器究竟是监听的哪一个事件,当然事件发布和监听我们自己也是可以扩展的。

自动配置原理

SpringBoot最核心的还是自动配置,为什么它能做到开箱即用,不再需要我们手动使用@EnableXXX等注解来开启?这一切的答案就在@SpringBootApplication注解中:

@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan(excludeFilters = {@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })public@interfaceSpringBootApplication {}

这里重要的注解有三个:@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan。@ComponentScan就不用再说了,@SpringBootConfiguration等同于@Configuration,而@EnableAutoConfiguration就是开启自动配置:

@AutoConfigurationPackage@Import(AutoConfigurationImportSelector.class)public@interfaceEnableAutoConfiguration {}@Import(AutoConfigurationPackages.Registrar.class)public@interfaceAutoConfigurationPackage {}

@AutoConfigurationPackage注解的作用就是将该注解所标记类所在的包作为自动配置的包,简单看看就行,主要看

AutoConfigurationImportSelector,这个就是实现自动配置的核心类,注意这个类是实现的DeferredImportSelector接口。

在这个类中有一个selectImports方法。这个方法在我之前的文章这一次搞懂Spring事务注解的解析也有分析过,只是实现类不同,它同样会被

ConfigurationClassPostProcessor类调用,先来看这个方法做了些什么:

publicString[] selectImports(AnnotationMetadata annotationMetadata) {if(!isEnabled(annotationMetadata)) {returnNO_IMPORTS;}AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);// 获取所有的自动配置类AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,annotationMetadata);returnStringUtils.toStringArray(autoConfigurationEntry.getConfigurations());}protectedAutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,AnnotationMetadata annotationMetadata) {if(!isEnabled(annotationMetadata)) {returnEMPTY_ENTRY;}AnnotationAttributes attributes = getAttributes(annotationMetadata);// SPI获取EnableAutoConfiguration为key的所有实现类List configurations = getCandidateConfigurations(annotationMetadata, attributes);configurations = removeDuplicates(configurations);Set exclusions = getExclusions(annotationMetadata, attributes);checkExcludedClasses(configurations, exclusions);configurations.removeAll(exclusions);// 把某些自动配置类过滤掉configurations = filter(configurations, autoConfigurationMetadata);fireAutoConfigurationImportEvents(configurations, exclusions);// 包装成自动配置实体类returnnew AutoConfigurationEntry(configurations, exclusions);}protectedList getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {// SPI获取EnableAutoConfiguration为key的所有实现类List configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),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.");returnconfigurations;}

追踪源码最终可以看到也是从META-INF/spring.factories文件中拿到所有EnableAutoConfiguration对应的值(在spring-boot-autoconfigure中)并通过反射实例化,过滤后包装成AutoConfigurationEntry对象返回。

看到这里你应该会觉得自动配置的实现就是通过这个selectImports方法,但实际上这个方法通常并不会被调用到,而是会调用该类的内部类AutoConfigurationGroup的process和selectImports方法,前者同样是通过getAutoConfigurationEntry拿到所有的自动配置类,而后者这是过滤排序并包装后返回。

下面就来分析

ConfigurationClassPostProcessor是怎么调用到这里的,直接进入processConfigBeanDefinitions方法:

publicvoid processConfigBeanDefinitions(BeanDefinitionRegistry registry) {List configCandidates = new ArrayList<>();String[] candidateNames = registry.getBeanDefinitionNames();for(String beanName : candidateNames) {BeanDefinition beanDef = registry.getBeanDefinition(beanName);if(beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) !=null) {if(logger.isDebugEnabled()) {logger.debug("Bean definition has already been processed as a configuration class: "+ beanDef);}}elseif(ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef,this.metadataReaderFactory)) {configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));}}// Return immediately if no @Configuration classes were foundif(configCandidates.isEmpty()) {return;}// Sort by previously determined @Order value, if applicableconfigCandidates.sort((bd1, bd2) -> {int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());returnInteger.compare(i1, i2);});// Detect any custom bean name generation strategy supplied through the enclosing application contextSingletonBeanRegistry sbr =null;if(registry instanceof SingletonBeanRegistry) {sbr = (SingletonBeanRegistry) registry;if(!this.localBeanNameGeneratorSet) {BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);if(generator !=null) {this.componentScanBeanNameGenerator = generator;this.importBeanNameGenerator = generator;}}}if(this.environment ==null) {this.environment = new StandardEnvironment();}// Parse each @Configuration classConfigurationClassParser parser = new ConfigurationClassParser(this.metadataReaderFactory,this.problemReporter,this.environment,this.resourceLoader,this.componentScanBeanNameGenerator, registry);Set candidates = new LinkedHashSet<>(configCandidates);Set alreadyParsed = new HashSet<>(configCandidates.size());do{parser.parse(candidates);parser.validate();Set configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());configClasses.removeAll(alreadyParsed);// Read the model and create bean definitions based on its contentif(this.reader ==null) {this.reader = new ConfigurationClassBeanDefinitionReader(registry,this.sourceExtractor,this.resourceLoader,this.environment,this.importBeanNameGenerator, parser.getImportRegistry());}this.reader.loadBeanDefinitions(configClasses);alreadyParsed.addAll(configClasses);// 省略。。。。}

前面一大段主要是拿到合格的Configuration配置类,主要逻辑是在

ConfigurationClassParser.parse方法中,该方法完成了对@Component、@Bean、@Import、@ComponentScans等注解的解析,这里主要看对@Import的解析,其它的读者可自行分析。一步步追踪,最终会进入到processConfigurationClass方法:

protectedvoid processConfigurationClass(ConfigurationClass configClass) throws IOException {if(this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {return;}ConfigurationClass existingClass =this.configurationClasses.get(configClass);if(existingClass !=null) {if(configClass.isImported()) {if(existingClass.isImported()) {existingClass.mergeImportedBy(configClass);}// Otherwise ignore new imported config class; existing non-imported class overrides it.return;}else{// Explicit bean definition found, probably replacing an import.// Let's remove the old one and go with the new one.this.configurationClasses.remove(configClass);this.knownSuperclasses.values().removeIf(configClass::equals);}}// Recursively process the configuration class and its superclass hierarchy.SourceClass sourceClass = asSourceClass(configClass);do{sourceClass = doProcessConfigurationClass(configClass, sourceClass);}while(sourceClass !=null);this.configurationClasses.put(configClass, configClass);}

这里需要注意

this.conditionEvaluator.shouldSkip方法的调用,这个方法就是进行Bean加载过滤的,即根据@Condition注解的匹配值判断是否加载该Bean,具体实现稍后分析,继续跟踪主流程

doProcessConfigurationClass:

protectedfinalSourceClassdoProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)throwsIOException{省略....// Process any @Import annotationsprocessImports(configClass, sourceClass, getImports(sourceClass),true);省略....returnnull;}

这里就是完成对一系列注解的支撑,我省略掉了,主要看processImports方法,这个方法就是处理@Import注解的:

privatevoid processImports(ConfigurationClass configClass, SourceClass currentSourceClass,Collection importCandidates, boolean checkForCircularImports) {if(importCandidates.isEmpty()) {return;}if(checkForCircularImports && isChainedImportOnStack(configClass)) {this.problemReporter.error(new CircularImportProblem(configClass,this.importStack));}else{this.importStack.push(configClass);try{for(SourceClass candidate : importCandidates) {if(candidate.isAssignable(ImportSelector.class)){// Candidate class is an ImportSelector -> delegate to it to determine importsClass candidateClass = candidate.loadClass();ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,this.environment,this.resourceLoader,this.registry);if(selector instanceof DeferredImportSelector) {this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);}else{String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());Collection importSourceClasses = asSourceClasses(importClassNames);processImports(configClass, currentSourceClass, importSourceClasses,false);}}elseif(candidate.isAssignable(ImportBeanDefinitionRegistrar.class)){Class candidateClass = candidate.loadClass();ImportBeanDefinitionRegistrar registrar =ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,this.environment,this.resourceLoader,this.registry);configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());}else{this.importStack.registerImport(currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());processConfigurationClass(candidate.asConfigClass(configClass));}}}}}

刚刚我提醒过

AutoConfigurationImportSelector是实现DeferredImportSelector接口的,如果不是该接口的实现类则是直接调用selectImports方法,反之则是调用DeferredImportSelectorHandler.handle方法:

privateList deferredImportSelectors =newArrayList<>();publicvoidhandle(ConfigurationClass configClass, DeferredImportSelector importSelector){DeferredImportSelectorHolder holder =newDeferredImportSelectorHolder(configClass, importSelector);if(this.deferredImportSelectors ==null) {DeferredImportSelectorGroupingHandler handler =newDeferredImportSelectorGroupingHandler();handler.register(holder);handler.processGroupImports();}else{this.deferredImportSelectors.add(holder);}}

首先创建了一个

DeferredImportSelectorHolder对象,如果是第一次执行则是添加到deferredImportSelectors属性中,等到

ConfigurationClassParser.parse的最后调用process方法:

publicvoidparse(Set<BeanDefinitionHolder> configCandidates){省略.....this.deferredImportSelectorHandler.process();}publicvoidprocess(){List deferredImports =this.deferredImportSelectors;this.deferredImportSelectors =null;try{if(deferredImports !=null) {DeferredImportSelectorGroupingHandler handler =newDeferredImportSelectorGroupingHandler();deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);deferredImports.forEach(handler::register);handler.processGroupImports();}}finally{this.deferredImportSelectors =newArrayList<>();}}

反之则是直接执行,首先通过register拿到AutoConfigurationGroup对象:

publicvoidregister(DeferredImportSelectorHolder deferredImport){Classgroup= deferredImport.getImportSelector().getImportGroup();DeferredImportSelectorGrouping grouping =this.groupings.computeIfAbsent((group!=null?group: deferredImport),key ->newDeferredImportSelectorGrouping(createGroup(group)));grouping.add(deferredImport);this.configurationClasses.put(deferredImport.getConfigurationClass().getMetadata(),deferredImport.getConfigurationClass());}publicClass getImportGroup() {returnAutoConfigurationGroup.class;}

然后在processGroupImports方法中进行真正的处理:

publicvoid processGroupImports() {for(DeferredImportSelectorGrouping grouping :this.groupings.values()) {grouping.getImports().forEach(entry -> {ConfigurationClass configurationClass =this.configurationClasses.get(entry.getMetadata());try{processImports(configurationClass, asSourceClass(configurationClass),asSourceClasses(entry.getImportClassName()),false);}catch(BeanDefinitionStoreException ex) {throwex;}catch(Throwable ex) {thrownew BeanDefinitionStoreException("Failed to process import candidates for configuration class ["+configurationClass.getMetadata().getClassName() +"]", ex);}});}}publicIterable getImports() {for(DeferredImportSelectorHolder deferredImport :this.deferredImports) {this.group.process(deferredImport.getConfigurationClass().getMetadata(),deferredImport.getImportSelector());}returnthis.group.selectImports();}

getImports方法中就完成了对processselectImports方法的调用,拿到自动配置类后再递归调用调用processImports方法完成对自动配置类的加载。至此,自动配置的加载过程就分析完了,下面是时序图:

Condition注解原理

在自动配置类中有很多Condition相关的注解,以AOP为例:

Configuration(proxyBeanMethods =false)@ConditionalOnProperty(prefix ="spring.aop", name ="auto", havingValue ="true", matchIfMissing =true)publicclassAopAutoConfiguration{@Configuration(proxyBeanMethods =false)@ConditionalOnClass(Advice.class)staticclassAspectJAutoProxyingConfiguration{@Configuration(proxyBeanMethods =false)@EnableAspectJAutoProxy(proxyTargetClass =false)@ConditionalOnProperty(prefix ="spring.aop", name ="proxy-target-class", havingValue ="false",matchIfMissing =false)staticclassJdkDynamicAutoProxyConfiguration{}@Configuration(proxyBeanMethods =false)@EnableAspectJAutoProxy(proxyTargetClass =true)@ConditionalOnProperty(prefix ="spring.aop", name ="proxy-target-class", havingValue ="true",matchIfMissing =true)staticclassCglibAutoProxyConfiguration{}}@Configuration(proxyBeanMethods =false)@ConditionalOnMissingClass("org.aspectj.weaver.Advice")@ConditionalOnProperty(prefix ="spring.aop", name ="proxy-target-class", havingValue ="true",matchIfMissing =true)staticclassClassProxyingConfiguration{ClassProxyingConfiguration(BeanFactory beanFactory) {if(beanFactoryinstanceofBeanDefinitionRegistry) {BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);}}}}

这里就能看到@ConditionalOnProperty、@ConditionalOnClass、@ConditionalOnMissingClass,另外还有@ConditionalOnBean、@ConditionalOnMissingBean等等很多条件匹配注解。这些注解表示条件匹配才会加载该Bean,以@ConditionalOnProperty为例,表明配置文件中符合条件才会加载对应的Bean,prefix表示在配置文件中的前缀,name表示配置的名称,havingValue表示配置为该值时才匹配,matchIfMissing则是表示没有该配置是否默认加载对应的Bean。其它注解可类比理解记忆,下面主要来分析该注解的实现原理。

这里注解点进去看会发现每个注解上都标注了@Conditional注解,并且value值都对应一个类,比如OnBeanCondition,而这些类都实现了Condition接口,看看其继承体系:

上面只展示了几个实现类,但实际上Condition的实现类是非常多的,我们还可以自己实现该接口来扩展@Condition注解。

Condition接口中有一个matches方法,这个方法返回true则表示匹配。该方法在ConfigurationClassParser中多处都有调用,也就是刚刚我提醒过的shouldSkip方法,具体实现是在ConditionEvaluator类中:

publicboolean shouldSkip(@NullableAnnotatedTypeMetadata metadata,@NullableConfigurationPhase phase) {if(metadata ==null|| !metadata.isAnnotated(Conditional.class.getName())) {returnfalse;}if(phase ==null) {if(metadata instanceof AnnotationMetadata &&ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {returnshouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);}returnshouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);}List conditions = new ArrayList<>();for(String[] conditionClasses : getConditionClasses(metadata)) {for(String conditionClass : conditionClasses) {Condition condition = getCondition(conditionClass,this.context.getClassLoader());conditions.add(condition);}}AnnotationAwareOrderComparator.sort(conditions);for(Condition condition : conditions) {ConfigurationPhase requiredPhase =null;if(condition instanceof ConfigurationCondition) {requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();}if((requiredPhase ==null|| requiredPhase == phase) && !condition.matches(this.context, metadata)) {returntrue;}}returnfalse;}

再来看看matches的实现,但OnBeanCondition类中没有实现该方法,而是在其父类SpringBootCondition中:

publicfinalbooleanmatches(ConditionContext context, AnnotatedTypeMetadata metadata){String classOrMethodName = getClassOrMethodName(metadata);try{ConditionOutcome outcome = getMatchOutcome(context, metadata);logOutcome(classOrMethodName, outcome);recordEvaluation(context, classOrMethodName, outcome);returnoutcome.isMatch();}

getMatchOutcome方法也是一个模板方法,具体的匹配逻辑就在这个方法中实现,该方法返回的ConditionOutcome对象就包含了是否匹配日志消息两个字段。进入到OnBeanCondition类中:

publicConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {ConditionMessage matchMessage = ConditionMessage.empty();MergedAnnotations annotations = metadata.getAnnotations();if(annotations.isPresent(ConditionalOnBean.class)){Spec spec = new Spec<>(context, metadata, annotations, ConditionalOnBean.class);MatchResult matchResult = getMatchingBeans(context, spec);if(!matchResult.isAllMatched()) {String reason = createOnBeanNoMatchReason(matchResult);returnConditionOutcome.noMatch(spec.message().because(reason));}matchMessage = spec.message(matchMessage).found("bean","beans").items(Style.QUOTE,matchResult.getNamesOfAllMatches());}if(metadata.isAnnotated(ConditionalOnSingleCandidate.class.getName())) {Spec spec = new SingleCandidateSpec(context, metadata, annotations);MatchResult matchResult = getMatchingBeans(context, spec);if(!matchResult.isAllMatched()) {returnConditionOutcome.noMatch(spec.message().didNotFind("any beans").atAll());}elseif(!hasSingleAutowireCandidate(context.getBeanFactory(), matchResult.getNamesOfAllMatches(),spec.getStrategy() == SearchStrategy.ALL)) {returnConditionOutcome.noMatch(spec.message().didNotFind("a primary bean from beans").items(Style.QUOTE, matchResult.getNamesOfAllMatches()));}matchMessage = spec.message(matchMessage).found("a primary bean from beans").items(Style.QUOTE,matchResult.getNamesOfAllMatches());}if(metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) {Spec spec = new Spec<>(context, metadata, annotations,ConditionalOnMissingBean.class);MatchResult matchResult = getMatchingBeans(context, spec);if(matchResult.isAnyMatched()) {String reason = createOnMissingBeanNoMatchReason(matchResult);returnConditionOutcome.noMatch(spec.message().because(reason));}matchMessage = spec.message(matchMessage).didNotFind("any beans").atAll();}returnConditionOutcome.match(matchMessage);}

可以看到该类支持了@ConditionalOnBean、@

ConditionalOnSingleCandidate、@ConditionalOnMissingBean注解,主要的匹配逻辑在getMatchingBeans方法中:

protectedfinal MatchResult getMatchingBeans(ConditionContext context, Spec<?> spec) {ClassLoaderclassLoader = context.getClassLoader();ConfigurableListableBeanFactorybeanFactory = context.getBeanFactory();booleanconsiderHierarchy = spec.getStrategy() != SearchStrategy.CURRENT;Set<Class<?>>parameterizedContainers = spec.getParameterizedContainers();if(spec.getStrategy() == SearchStrategy.ANCESTORS) {BeanFactoryparent = beanFactory.getParentBeanFactory();Assert.isInstanceOf(ConfigurableListableBeanFactory.class,parent,"Unableto use SearchStrategy.ANCESTORS");beanFactory=(ConfigurableListableBeanFactory) parent;}MatchResultresult = new MatchResult();Set<String>beansIgnoredByType = getNamesOfBeansIgnoredByType(classLoader, beanFactory, considerHierarchy,spec.getIgnoredTypes(),parameterizedContainers);for(String type : spec.getTypes()) {Collection<String>typeMatches = getBeanNamesForType(classLoader, considerHierarchy, beanFactory, type,parameterizedContainers);typeMatches.removeAll(beansIgnoredByType);if(typeMatches.isEmpty()) {result.recordUnmatchedType(type);}else{result.recordMatchedType(type,typeMatches);}}for(String annotation : spec.getAnnotations()) {Set<String>annotationMatches = getBeanNamesForAnnotation(classLoader, beanFactory, annotation,considerHierarchy);annotationMatches.removeAll(beansIgnoredByType);if(annotationMatches.isEmpty()) {result.recordUnmatchedAnnotation(annotation);}else{result.recordMatchedAnnotation(annotation,annotationMatches);}}for(String beanName : spec.getNames()) {if(!beansIgnoredByType.contains(beanName) && containsBean(beanFactory, beanName, considerHierarchy)) {result.recordMatchedName(beanName);}else{result.recordUnmatchedName(beanName);}}returnresult;}

这里逻辑看起来比较复杂,但实际上就做了两件事,首先通过

getNamesOfBeansIgnoredByType方法调用beanFactory.getBeanNamesForType拿到容器中对应的Bean实例,然后根据返回的结果判断哪些Bean存在,哪些Bean不存在(Condition注解中是可以配置多个值的)并返回MatchResult对象,而MatchResult中只要有一个Bean没有匹配上就返回false,也就决定了当前Bean是否需要实例化。

总结

本篇分析了SpringBoot核心原理的实现,通过本篇相信读者也将能更加熟练地使用和扩展SpringBoot。另外还有一些常用的组件我没有展开分析,如事务、MVC、监听器的自动配置,这些我们有了Spring源码基础的话下来看一下就明白了,这里就不赘述了。最后读者可以思考一下我们应该如何自定义starter启动器,相信看完本篇应该难不倒你。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 207,248评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,681评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,443评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,475评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,458评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,185评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,451评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,112评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,609评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,083评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,163评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,803评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,357评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,357评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,590评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,636评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,925评论 2 344

推荐阅读更多精彩内容