一次搞懂SpringBoot核心原理:自動(dòng)配置伦吠、事件驅(qū)動(dòng)、Condition

前言

SpringBoot是Spring的包裝,通過自動(dòng)配置使得SpringBoot可以做到開箱即用讨勤,上手成本非常低箭跳,但是學(xué)習(xí)其實(shí)現(xiàn)原理的成本大大增加晨另,需要先了解熟悉Spring原理潭千。如果還不清楚Spring原理的,可以先查看博主之前的文章借尿,本篇主要分析SpringBoot的啟動(dòng)刨晴、自動(dòng)配置、Condition路翻、事件驅(qū)動(dòng)原理狈癞。

啟動(dòng)原理

SpringBoot啟動(dòng)非常簡(jiǎn)單,因其內(nèi)置了Tomcat茂契,所以只需要通過下面幾種方式啟動(dòng)即可:

@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();? ? }}

可以看到第一種是最簡(jiǎn)單的蝶桶,也是最常用的方式,需要注意類上面需要標(biāo)注@SpringBootApplication注解掉冶,這是自動(dòng)配置的核心實(shí)現(xiàn)真竖,稍后分析,先來看看SpringBoot啟動(dòng)做了些什么厌小?

在往下之前恢共,不妨先猜測(cè)一下,run方法中需要做什么璧亚?對(duì)比Spring源碼讨韭,我們知道,Spring的啟動(dòng)都會(huì)創(chuàng)建一個(gè)ApplicationContext的應(yīng)用上下文對(duì)象癣蟋,并調(diào)用其refresh方法啟動(dòng)容器透硝,SpringBoot只是Spring的一層殼,肯定也避免不了這樣的操作疯搅。另一方面濒生,以前通過Spring搭建的項(xiàng)目,都需要打成War包發(fā)布到Tomcat才行秉撇,而現(xiàn)在SpringBoot已經(jīng)內(nèi)置了Tomcat甜攀,只需要打成Jar包啟動(dòng)即可,所以在run方法中肯定也會(huì)創(chuàng)建對(duì)應(yīng)的Tomcat對(duì)象并啟動(dòng)琐馆。以上只是我們的猜想规阀,下面就來驗(yàn)證,進(jìn)入run方法:

publicConfigurableApplicationContext run(String... args) {// 統(tǒng)計(jì)時(shí)間用的工具類StopWatch stopWatch =newStopWatch();stopWatch.start();ConfigurableApplicationContext context =null;Collection exceptionReporters =newArrayList<>();configureHeadlessProperty();// 獲取實(shí)現(xiàn)了SpringApplicationRunListener接口的實(shí)現(xiàn)類瘦麸,通過SPI機(jī)制加載// META-INF/spring.factories文件下的類SpringApplicationRunListeners listeners = getRunListeners(args);// 首先調(diào)用SpringApplicationRunListener的starting方法listeners.starting();try{ApplicationArguments applicationArguments =newDefaultApplicationArguments(args);// 處理配置數(shù)據(jù)ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);configureIgnoreBeanInfo(environment);// 啟動(dòng)時(shí)打印bannerBanner printedBanner = printBanner(environment);// 創(chuàng)建上下文對(duì)象context = createApplicationContext();// 獲取SpringBootExceptionReporter接口的類谁撼,異常報(bào)告exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,newClass[] { ConfigurableApplicationContext.class }, context);prepareContext(context, environment, listeners, applicationArguments, printedBanner);// 核心方法,啟動(dòng)spring容器refreshContext(context);afterRefresh(context, applicationArguments);// 統(tǒng)計(jì)結(jié)束stopWatch.stop();if(this.logStartupInfo) {newStartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);}// 調(diào)用startedlisteners.started(context);// ApplicationRunner// CommandLineRunner// 獲取這兩個(gè)接口的實(shí)現(xiàn)類,并調(diào)用其run方法callRunners(context, applicationArguments);}catch(Throwable ex) {handleRunFailure(context, ex, exceptionReporters, listeners);thrownewIllegalStateException(ex);}try{// 最后調(diào)用running方法listeners.running(context);}catch(Throwable ex) {handleRunFailure(context, ex, exceptionReporters,null);thrownewIllegalStateException(ex);}returncontext;}

SpringBoot的啟動(dòng)流程就是這個(gè)方法厉碟,先看getRunListeners方法喊巍,這個(gè)方法就是去拿到所有的

SpringApplicationRunListener實(shí)現(xiàn)類,這些類是用于SpringBoot事件發(fā)布的箍鼓,關(guān)于事件驅(qū)動(dòng)稍后分析崭参,這里主要看這個(gè)方法的實(shí)現(xiàn)原理:

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));// 加載上來后反射實(shí)例化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機(jī)制根據(jù)接口類型從META-INF/spring.factories文件中加載對(duì)應(yīng)的實(shí)現(xiàn)類并實(shí)例化,SpringBoot的自動(dòng)配置也是這樣實(shí)現(xiàn)的款咖。為什么要這樣實(shí)現(xiàn)呢何暮?通過注解掃描不可以么?當(dāng)然不行铐殃,這些類都在第三方j(luò)ar包中海洼,注解掃描實(shí)現(xiàn)是很麻煩的,當(dāng)然你也可以通過@Import注解導(dǎo)入富腊,但是這種方式不適合擴(kuò)展類特別多的情況坏逢,所以這里采用SPI的優(yōu)點(diǎn)就顯而易見了。

回到run方法中赘被,可以看到調(diào)用了createApplicationContext方法是整,見名知意,這個(gè)就是去創(chuàng)建應(yīng)用上下文對(duì)象:

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);}

注意這里通過反射實(shí)例化了一個(gè)新的沒見過的上下文對(duì)象

AnnotationConfigServletWebServerApplicationContext帘腹,這個(gè)是SpringBoot擴(kuò)展的贰盗,看看其構(gòu)造方法:

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

如果你有看過Spring注解驅(qū)動(dòng)的實(shí)現(xiàn)原理,這兩個(gè)對(duì)象肯定不會(huì)陌生阳欲,一個(gè)實(shí)支持注解解析的舵盈,另外一個(gè)是掃描包用的。

上下文創(chuàng)建好了球化,下一步自然就是調(diào)用

refresh方法啟動(dòng)容器:

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();}

這里首先會(huì)調(diào)用到其父類中

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();}}}

這個(gè)方法不會(huì)陌生吧秽晚,之前已經(jīng)分析過了,這里不再贅述筒愚,至此SpringBoot的容器就啟動(dòng)了赴蝇,但是Tomcat啟動(dòng)是在哪里呢?run方法中也沒有看到巢掺。實(shí)際上Tomcat的啟動(dòng)也是在refresh流程中句伶,這個(gè)方法其中一步是調(diào)用了onRefresh方法,在Spring中這是一個(gè)沒有實(shí)現(xiàn)的模板方法陆淀,而SpringBoot就通過這個(gè)方法完成了Tomcat的啟動(dòng):

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();// 主要看這個(gè)方法this.webServer = factory.getWebServer(getSelfInitializer());}elseif(servletContext !=null) {try{getSelfInitializer().onStartup(servletContext);}catch(ServletException ex) {thrownewApplicationContextException("Cannot initialize servlet context", ex);}}initPropertySources();}

這里首先拿到

TomcatServletWebServerFactory對(duì)象考余,通過該對(duì)象再去創(chuàng)建和啟動(dòng)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);}

上面的每一步都可以對(duì)比Tomcat的配置文件,需要注意默認(rèn)只支持了http協(xié)議:

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

如果想要擴(kuò)展的話則可以對(duì)

additionalTomcatConnectors屬性設(shè)置值轧苫,需要注意這個(gè)屬性沒有對(duì)應(yīng)的setter方法楚堤,只有addAdditionalTomcatConnectors方法,也就是說我們只能通過實(shí)現(xiàn)BeanFactoryPostProcessor接口的postProcessBeanFactory方法,而不能通過BeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry方法身冬,因?yàn)榍罢呖梢酝ㄟ^傳入的BeanFactory對(duì)象提前獲取到TomcatServletWebServerFactory對(duì)象調(diào)用addAdditionalTomcatConnectors即可衅胀;而后者只能拿到BeanDefinition對(duì)象,該對(duì)象只能通過setter方法設(shè)置值酥筝。

事件驅(qū)動(dòng)

Spring原本就提供了事件機(jī)制滚躯,而在SpringBoot中又對(duì)其進(jìn)行擴(kuò)展,通過發(fā)布訂閱事件在容器的整個(gè)生命周期的不同階段進(jìn)行不同的操作樱哼。我們先來看看SpringBoot啟動(dòng)關(guān)閉的過程中默認(rèn)會(huì)發(fā)布哪些事件哀九,使用下面的代碼即可:

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

這段代碼會(huì)在控制臺(tái)打印所有的事件名稱剿配,按照順序如下:

ApplicationStartingEvent:容器啟動(dòng)

ApplicationEnvironmentPreparedEvent:環(huán)境準(zhǔn)備好

ApplicationContextInitializedEvent:上下文初始化完成

ApplicationPreparedEvent:上下文準(zhǔn)備好

ContextRefreshedEvent:上下文刷新完

ServletWebServerInitializedEvent:webServer初始化完成

ApplicationStartedEvent:容器啟動(dòng)完成

ApplicationReadyEvent:容器就緒

ContextClosedEvent:容器關(guān)閉

以上是正常啟動(dòng)關(guān)閉搅幅,如果發(fā)生異常還有發(fā)布ApplicationFailedEvent事件。事件的發(fā)布遍布在整個(gè)容器的啟動(dòng)關(guān)閉周期中呼胚,事件發(fā)布對(duì)象剛剛我們也看到了是通過SPI加載的

SpringApplicationRunListener實(shí)現(xiàn)類EventPublishingRunListener茄唐,同樣事件監(jiān)聽器也是在spring.factories文件中配置的,默認(rèn)實(shí)現(xiàn)了以下監(jiān)聽器:

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)等等一系列監(jiān)聽器,SpringBoot也就是通過這系列監(jiān)聽器將必要的配置和組件加載到容器中來年扩,這里不再詳細(xì)分析蚁廓,感興趣的讀者可以通過其實(shí)現(xiàn)的onApplicationEvent方法看到每個(gè)監(jiān)聽器究竟是監(jiān)聽的哪一個(gè)事件,當(dāng)然事件發(fā)布和監(jiān)聽我們自己也是可以擴(kuò)展的厨幻。

自動(dòng)配置原理

SpringBoot最核心的還是自動(dòng)配置相嵌,為什么它能做到開箱即用,不再需要我們手動(dòng)使用@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 {}

這里重要的注解有三個(gè):@SpringBootConfiguration饭宾、@EnableAutoConfiguration、@ComponentScan格了。@ComponentScan就不用再說了看铆,@SpringBootConfiguration等同于@Configuration,而@EnableAutoConfiguration就是開啟自動(dòng)配置:

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

@AutoConfigurationPackage注解的作用就是將該注解所標(biāo)記類所在的包作為自動(dòng)配置的包盛末,簡(jiǎn)單看看就行弹惦,主要看

AutoConfigurationImportSelector,這個(gè)就是實(shí)現(xiàn)自動(dòng)配置的核心類悄但,注意這個(gè)類是實(shí)現(xiàn)的DeferredImportSelector接口棠隐。

在這個(gè)類中有一個(gè)selectImports方法。這個(gè)方法在我之前的文章這一次搞懂Spring事務(wù)注解的解析也有分析過算墨,只是實(shí)現(xiàn)類不同宵荒,它同樣會(huì)被

ConfigurationClassPostProcessor類調(diào)用,先來看這個(gè)方法做了些什么:

publicString[] selectImports(AnnotationMetadata annotationMetadata) {if(!isEnabled(annotationMetadata)) {returnNO_IMPORTS;}AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);// 獲取所有的自動(dòng)配置類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的所有實(shí)現(xiàn)類List configurations = getCandidateConfigurations(annotationMetadata, attributes);configurations = removeDuplicates(configurations);Set exclusions = getExclusions(annotationMetadata, attributes);checkExcludedClasses(configurations, exclusions);configurations.removeAll(exclusions);// 把某些自動(dòng)配置類過濾掉configurations = filter(configurations, autoConfigurationMetadata);fireAutoConfigurationImportEvents(configurations, exclusions);// 包裝成自動(dòng)配置實(shí)體類returnnew AutoConfigurationEntry(configurations, exclusions);}protectedList getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {// SPI獲取EnableAutoConfiguration為key的所有實(shí)現(xiàn)類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對(duì)應(yīng)的值(在spring-boot-autoconfigure中)并通過反射實(shí)例化,過濾后包裝成AutoConfigurationEntry對(duì)象返回报咳。

看到這里你應(yīng)該會(huì)覺得自動(dòng)配置的實(shí)現(xiàn)就是通過這個(gè)selectImports方法侠讯,但實(shí)際上這個(gè)方法通常并不會(huì)被調(diào)用到,而是會(huì)調(diào)用該類的內(nèi)部類AutoConfigurationGroup的process和selectImports方法暑刃,前者同樣是通過getAutoConfigurationEntry拿到所有的自動(dòng)配置類厢漩,而后者這是過濾排序并包裝后返回。

下面就來分析

ConfigurationClassPostProcessor是怎么調(diào)用到這里的岩臣,直接進(jìn)入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方法中谷扣,該方法完成了對(duì)@Component土全、@Bean、@Import会涎、@ComponentScans等注解的解析裹匙,這里主要看對(duì)@Import的解析,其它的讀者可自行分析末秃。一步步追蹤概页,最終會(huì)進(jìn)入到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方法的調(diào)用,這個(gè)方法就是進(jìn)行Bean加載過濾的练慕,即根據(jù)@Condition注解的匹配值判斷是否加載該Bean惰匙,具體實(shí)現(xiàn)稍后分析,繼續(xù)跟蹤主流程

doProcessConfigurationClass:

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

這里就是完成對(duì)一系列注解的支撐贺待,我省略掉了徽曲,主要看processImports方法,這個(gè)方法就是處理@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是實(shí)現(xiàn)DeferredImportSelector接口的麸塞,如果不是該接口的實(shí)現(xiàn)類則是直接調(diào)用selectImports方法秃臣,反之則是調(diào)用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);}}

首先創(chuàng)建了一個(gè)

DeferredImportSelectorHolder對(duì)象,如果是第一次執(zhí)行則是添加到deferredImportSelectors屬性中哪工,等到

ConfigurationClassParser.parse的最后調(diào)用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<>();}}

反之則是直接執(zhí)行奥此,首先通過register拿到AutoConfigurationGroup對(duì)象:

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方法中進(jìn)行真正的處理:

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方法中就完成了對(duì)processselectImports方法的調(diào)用,拿到自動(dòng)配置類后再遞歸調(diào)用調(diào)用processImports方法完成對(duì)自動(dòng)配置類的加載雁比。至此稚虎,自動(dòng)配置的加載過程就分析完了,下面是時(shí)序圖:

Condition注解原理

在自動(dòng)配置類中有很多Condition相關(guān)的注解偎捎,以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等等很多條件匹配注解程奠。這些注解表示條件匹配才會(huì)加載該Bean,以@ConditionalOnProperty為例祭钉,表明配置文件中符合條件才會(huì)加載對(duì)應(yīng)的Bean瞄沙,prefix表示在配置文件中的前綴,name表示配置的名稱慌核,havingValue表示配置為該值時(shí)才匹配距境,matchIfMissing則是表示沒有該配置是否默認(rèn)加載對(duì)應(yīng)的Bean。其它注解可類比理解記憶垮卓,下面主要來分析該注解的實(shí)現(xiàn)原理垫桂。

這里注解點(diǎn)進(jìn)去看會(huì)發(fā)現(xiàn)每個(gè)注解上都標(biāo)注了@Conditional注解,并且value值都對(duì)應(yīng)一個(gè)類扒接,比如OnBeanCondition伪货,而這些類都實(shí)現(xiàn)了Condition接口,看看其繼承體系:

上面只展示了幾個(gè)實(shí)現(xiàn)類钾怔,但實(shí)際上Condition的實(shí)現(xiàn)類是非常多的,我們還可以自己實(shí)現(xiàn)該接口來擴(kuò)展@Condition注解蒙挑。

Condition接口中有一個(gè)matches方法宗侦,這個(gè)方法返回true則表示匹配。該方法在ConfigurationClassParser中多處都有調(diào)用忆蚀,也就是剛剛我提醒過的shouldSkip方法矾利,具體實(shí)現(xiàn)是在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的實(shí)現(xiàn),但OnBeanCondition類中沒有實(shí)現(xiàn)該方法馋袜,而是在其父類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方法也是一個(gè)模板方法男旗,具體的匹配邏輯就在這個(gè)方法中實(shí)現(xiàn),該方法返回的ConditionOutcome對(duì)象就包含了是否匹配日志消息兩個(gè)字段欣鳖。進(jìn)入到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;}

這里邏輯看起來比較復(fù)雜什荣,但實(shí)際上就做了兩件事,首先通過

getNamesOfBeansIgnoredByType方法調(diào)用beanFactory.getBeanNamesForType拿到容器中對(duì)應(yīng)的Bean實(shí)例怀酷,然后根據(jù)返回的結(jié)果判斷哪些Bean存在稻爬,哪些Bean不存在(Condition注解中是可以配置多個(gè)值的)并返回MatchResult對(duì)象,而MatchResult中只要有一個(gè)Bean沒有匹配上就返回false蜕依,也就決定了當(dāng)前Bean是否需要實(shí)例化桅锄。

總結(jié)

本篇分析了SpringBoot核心原理的實(shí)現(xiàn)琉雳,通過本篇相信讀者也將能更加熟練地使用和擴(kuò)展SpringBoot。另外還有一些常用的組件我沒有展開分析友瘤,如事務(wù)咐吼、MVC、監(jiān)聽器的自動(dòng)配置商佑,這些我們有了Spring源碼基礎(chǔ)的話下來看一下就明白了锯茄,這里就不贅述了。最后讀者可以思考一下我們應(yīng)該如何自定義starter啟動(dòng)器茶没,相信看完本篇應(yīng)該難不倒你肌幽。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市抓半,隨后出現(xiàn)的幾起案子喂急,更是在濱河造成了極大的恐慌,老刑警劉巖笛求,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件廊移,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡探入,警方通過查閱死者的電腦和手機(jī)狡孔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蜂嗽,“玉大人苗膝,你說我怎么就攤上這事≈簿桑” “怎么了辱揭?”我有些...
    開封第一講書人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)病附。 經(jīng)常有香客問我问窃,道長(zhǎng),這世上最難降的妖魔是什么完沪? 我笑而不...
    開封第一講書人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任域庇,我火速辦了婚禮,結(jié)果婚禮上丽焊,老公的妹妹穿的比我還像新娘较剃。我一直安慰自己,他們只是感情好技健,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開白布写穴。 她就那樣靜靜地躺著,像睡著了一般雌贱。 火紅的嫁衣襯著肌膚如雪啊送。 梳的紋絲不亂的頭發(fā)上偿短,一...
    開封第一講書人閱讀 49,111評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音馋没,去河邊找鬼昔逗。 笑死,一個(gè)胖子當(dāng)著我的面吹牛篷朵,可吹牛的內(nèi)容都是我干的勾怒。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼声旺,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼笔链!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起腮猖,我...
    開封第一講書人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤鉴扫,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后澈缺,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體坪创,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年姐赡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了莱预。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡雏吭,死狀恐怖锁施,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情杖们,我是刑警寧澤,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布肩狂,位于F島的核電站摘完,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏傻谁。R本人自食惡果不足惜孝治,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望审磁。 院中可真熱鬧谈飒,春花似錦、人聲如沸态蒂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)钾恢。三九已至手素,卻和暖如春鸳址,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背泉懦。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工稿黍, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人崩哩。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓巡球,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親邓嘹。 傳聞我的和親對(duì)象是個(gè)殘疾皇子酣栈,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容