目錄
前言
正文啟動原理事件驅動自動配置原理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方法:
publicConfigurableApplicationContextrun(String... args) {// 統計時間用的工具類StopWatchstopWatch =newStopWatch();stopWatch.start();ConfigurableApplicationContextcontext=null;CollectionexceptionReporters=newArrayList<>();configureHeadlessProperty();// 獲取實現了SpringApplicationRunListener接口的實現類,通過SPI機制加載// META-INF/spring.factories文件下的類SpringApplicationRunListenerslisteners=getRunListeners(args);// 首先調用SpringApplicationRunListener的starting方法listeners.starting();try{ApplicationArgumentsapplicationArguments=newDefaultApplicationArguments(args);// 處理配置數據ConfigurableEnvironmentenvironment=prepareEnvironment(listeners, applicationArguments);configureIgnoreBeanInfo(environment);// 啟動時打印bannerBannerprintedBanner=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(Throwableex) {handleRunFailure(context, ex, exceptionReporters, listeners);thrownewIllegalStateException(ex);}try{// 最后調用running方法listeners.running(context);}catch(Throwableex) {handleRunFailure(context, ex, exceptionReporters, null);thrownewIllegalStateException(ex);}returncontext;}
SpringBoot的啟動流程就是這個方法,先看getRunListeners方法,這個方法就是去拿到所有的SpringApplicationRunListener實現類,這些類是用于SpringBoot事件發布的,關于事件驅動稍后分析,這里主要看這個方法的實現原理:
private SpringApplicationRunListeners getRunListeners(String[] args) {Class[] types =newClass[] { SpringApplication.class,String[].class};returnnewSpringApplicationRunListeners(logger,getSpringFactoriesInstances(SpringApplicationRunListener.class, types,this, args));}private Collection getSpringFactoriesInstances(Class type, 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";protectedConfigurableApplicationContext createApplicationContext() {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:
public WebServer getWebServer(ServletContextInitializer... initializers) {if (this.disableMBeanRegistry) {Registry.disableRegistry();}Tomcat tomcat = new Tomcat();FilebaseDir = (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 (ConnectoradditionalConnector : this.additionalTomcatConnectors) {tomcat.getService().addConnector(additionalConnector);}prepareContext(tomcat.getHost(), initializers);return getTomcatWebServer(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的所有實現類Listconfigurations= getCandidateConfigurations(annotationMetadata, attributes);configurations= removeDuplicates(configurations);Set exclusions = getExclusions(annotationMetadata, attributes);checkExcludedClasses(configurations, exclusions);configurations.removeAll(exclusions);// 把某些自動配置類過濾掉configurations= filter(configurations, autoConfigurationMetadata);fireAutoConfigurationImportEvents(configurations, exclusions);// 包裝成自動配置實體類returnnewAutoConfigurationEntry(configurations, exclusions);}protectedList getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {// SPI獲取EnableAutoConfiguration為key的所有實現類Listconfigurations= 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方法:
publicvoidprocessConfigBeanDefinitions(BeanDefinitionRegistry registry) {List configCandidates =newArrayList<>();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(newBeanDefinitionHolder(beanDef, beanName));}}// Return immediately if no @Configuration classes were found
if (configCandidates.isEmpty()) {
return;
}
//Sortbypreviously determined @Order value,ifapplicableconfigCandidates.sort((bd1, bd2) -> {int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());returnInteger.compare(i1, i2);});//DetectanycustombeannamegenerationstrategysuppliedthroughtheenclosingapplicationcontextSingletonBeanRegistrysbr=null;if(registryinstanceofSingletonBeanRegistry){sbr=(SingletonBeanRegistry)registry;if(!this.localBeanNameGeneratorSet){BeanNameGeneratorgenerator=(BeanNameGenerator)sbr.getSingleton(
AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);if(generator !=null){this.componentScanBeanNameGenerator=generator;this.importBeanNameGenerator=generator;}}}if(this.environment ==null){this.environment=newStandardEnvironment();}//Parseeach@ConfigurationclassConfigurationClassParserparser=newConfigurationClassParser(this.metadataReaderFactory,this.problemReporter,this.environment,this.resourceLoader,this.componentScanBeanNameGenerator, registry);Setcandidates=newLinkedHashSet<>(configCandidates);SetalreadyParsed=newHashSet<>(configCandidates.size());do{parser.parse(candidates);parser.validate();SetconfigClasses=newLinkedHashSet<>(parser.getConfigurationClasses());configClasses.removeAll(alreadyParsed);//Readthemodelandcreatebeandefinitionsbasedonitscontentif(this.reader ==null){this.reader=newConfigurationClassBeanDefinitionReader(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) {DeferredImportSelectorGroupingHandlerhandler=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) {DeferredImportSelectorGroupingHandlerhandler=newDeferredImportSelectorGroupingHandler();deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);deferredImports.forEach(handler::register);handler.processGroupImports();}}finally{this.deferredImportSelectors =newArrayList<>();}}
反之則是直接執行,首先通過register拿到AutoConfigurationGroup對象:
public void register(DeferredImportSelectorHolder deferredImport) {Class group = deferredImport.getImportSelector().getImportGroup();DeferredImportSelectorGrouping grouping = this.groupings.computeIfAbsent((group !=null? group : deferredImport),key -> new DeferredImportSelectorGrouping(createGroup(group)));grouping.add(deferredImport);this.configurationClasses.put(deferredImport.getConfigurationClass().getMetadata(),deferredImport.getConfigurationClass());}public Class getImportGroup() {return AutoConfigurationGroup.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方法中就完成了對process和selectImports方法的調用,拿到自動配置類后再遞歸調用調用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類中:
publicbooleanshouldSkip(@NullableAnnotatedTypeMetadata metadata,@NullableConfigurationPhase phase) {if(metadata ==null|| !metadata.isAnnotated(Conditional.class.getName())) {returnfalse;}if(phase ==null) {if(metadatainstanceofAnnotationMetadata &&ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {returnshouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);}returnshouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);}List conditions =newArrayList<>();for(String[]conditionClasses :getConditionClasses(metadata)) {for(StringconditionClass :conditionClasses) {Condition condition = getCondition(conditionClass,this.context.getClassLoader());conditions.add(condition);}}AnnotationAwareOrderComparator.sort(conditions);for(Conditioncondition :conditions) {ConfigurationPhase requiredPhase =null;if(conditioninstanceofConfigurationCondition) {requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();}if((requiredPhase ==null|| requiredPhase == phase) && !condition.matches(this.context, metadata)) {returntrue;}}returnfalse;}
再來看看matches的實現,但OnBeanCondition類中沒有實現該方法,而是在其父類SpringBootCondition中:
public finalboolean matches(ConditionContextcontext, AnnotatedTypeMetadata metadata) {String classOrMethodName = getClassOrMethodName(metadata);try {ConditionOutcome outcome = getMatchOutcome(context, metadata);logOutcome(classOrMethodName, outcome);recordEvaluation(context, classOrMethodName, outcome);return outcome.isMatch();}
getMatchOutcome方法也是一個模板方法,具體的匹配邏輯就在這個方法中實現,該方法返回的ConditionOutcome對象就包含了是否匹配和日志消息兩個字段。進入到OnBeanCondition類中:
publicConditionOutcomegetMatchOutcome(ConditionContextcontext,AnnotatedTypeMetadatametadata) {ConditionMessagematchMessage =ConditionMessage.empty();MergedAnnotationsannotations = metadata.getAnnotations();if(annotations.isPresent(ConditionalOnBean.class)) {Spec spec =newSpec<>(context, metadata, annotations,ConditionalOnBean.class);MatchResultmatchResult=getMatchingBeans(context, spec);if(!matchResult.isAllMatched()) {Stringreason=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())) {Specspec=newSingleCandidateSpec(context, metadata, annotations);MatchResultmatchResult=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())) {Specspec=newSpec<>(context, metadata, annotations,ConditionalOnMissingBean.class);MatchResultmatchResult=getMatchingBeans(context, spec);if(matchResult.isAnyMatched()) {Stringreason=createOnMissingBeanNoMatchReason(matchResult);returnConditionOutcome.noMatch(spec.message().because(reason));}matchMessage=spec.message(matchMessage).didNotFind("any beans").atAll();}returnConditionOutcome.match(matchMessage);}
可以看到該類支持了@ConditionalOnBean、@ConditionalOnSingleCandidate、@ConditionalOnMissingBean注解,主要的匹配邏輯在getMatchingBeans方法中:
protected final MatchResult getMatchingBeans(ConditionContextcontext, Spec spec) {ClassLoader classLoader =context.getClassLoader();ConfigurableListableBeanFactorybeanFactory =context.getBeanFactory();boolean considerHierarchy = spec.getStrategy() != SearchStrategy.CURRENT;Set> parameterizedContainers = spec.getParameterizedContainers();if (spec.getStrategy() == SearchStrategy.ANCESTORS) {BeanFactory parent =beanFactory.getParentBeanFactory();Assert.isInstanceOf(ConfigurableListableBeanFactory.class, parent,"Unable to use SearchStrategy.ANCESTORS");beanFactory = (ConfigurableListableBeanFactory) parent;}MatchResult result = new MatchResult();SetbeansIgnoredByType = getNamesOfBeansIgnoredByType(classLoader,beanFactory, considerHierarchy,spec.getIgnoredTypes(), parameterizedContainers);for (String type : spec.getTypes()) {Collection 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 annotationMatches = getBeanNamesForAnnotation(classLoader,beanFactory, annotation,considerHierarchy);annotationMatches.removeAll(beansIgnoredByType);if (annotationMatches.isEmpty()) {result.recordUnmatchedAnnotation(annotation);}else {result.recordMatchedAnnotation(annotation, annotationMatches);}}for (StringbeanName : spec.getNames()) {if (!beansIgnoredByType.contains(beanName) && containsBean(beanFactory, beanName, considerHierarchy)) {result.recordMatchedName(beanName);}else {result.recordUnmatchedName(beanName);}}return result;}
這里邏輯看起來比較復雜,但實際上就做了兩件事,首先通過getNamesOfBeansIgnoredByType方法調用beanFactory.getBeanNamesForType拿到容器中對應的Bean實例,然后根據返回的結果判斷哪些Bean存在,哪些Bean不存在(Condition注解中是可以配置多個值的)并返回MatchResult對象,而MatchResult中只要有一個Bean沒有匹配上就返回false,也就決定了當前Bean是否需要實例化。
總結
本篇分析了SpringBoot核心原理的實現,通過本篇相信讀者也將能更加熟練地使用和擴展SpringBoot。另外還有一些常用的組件我沒有展開分析,如事務、MVC、監聽器的自動配置,這些我們有了Spring源碼基礎的話下來看一下就明白了,這里就不贅述了。最后讀者可以思考一下我們應該如何自定義starter啟動器,相信看完本篇應該難不倒你。
如果,如果啊,你還是沒有看懂,幸好我還留有后手,哈哈,添加v:bjmsb07? 免費送給你springboot從入門到精通的教學視頻!不免費,來舉報!