最棒 Spring Boot 干貨總結(jié) !

來源:33h.co/eLt2
說明:前面有4個小節(jié)關(guān)于Spring的基礎(chǔ)知識
分別是:IOC容器、JavaConfig、事件監(jiān)聽、SpringFactoriesLoader詳解
它們占據(jù)了本文的大部分內(nèi)容:
雖然它們之間可能沒有太多的聯(lián)系,但這些知識對于理解Spring Boot的核心原理至關(guān)重要,如果你對Spring框架爛熟于心,完全可以跳過這4個小節(jié)。正是因為這個系列的文章是由這些看似不相關(guān)的知識點組成,因此取名知識清單。
在過去兩三年的Spring生態(tài)圈,最讓人興奮的莫過于Spring Boot框架。或許從命名上就能看出這個框架的設(shè)計初衷:快速的啟動Spring應(yīng)用。因而Spring Boot應(yīng)用本質(zhì)上就是一個基于Spring框架的應(yīng)用,它是Spring對“約定優(yōu)先于配置”理念的最佳實踐產(chǎn)物,它能夠幫助開發(fā)者更快速高效地構(gòu)建基于Spring生態(tài)圈的應(yīng)用。
那Spring Boot有何魔法?
自動配置、起步依賴、Actuator、命令行界面(CLI) 是Spring Boot最重要的4大核心特性,其中CLI是Spring Boot的可選特性,雖然它功能強大,但也引入了一套不太常規(guī)的開發(fā)模型,因而這個系列的文章僅關(guān)注其它3種特性。如文章標(biāo)題,本文是這個系列的第一部分,將為你打開Spring Boot的大門,重點為你剖析其啟動流程以及自動配置實現(xiàn)原理。要掌握這部分核心內(nèi)容,理解一些Spring框架的基礎(chǔ)知識,將會讓你事半功倍。
一、拋磚引玉:探索Spring IoC容器
如果有看過SpringApplication.run()方法的源碼,Spring Boot冗長無比的啟動流程一定
會讓你抓狂,透過現(xiàn)象看本質(zhì).
SpringApplication只是將一個典型的Spring應(yīng)用的啟動流程進行了擴展,因此,透徹理解,?Spring容器是打開Spring Boot大門的一把鑰匙。
1.1、Spring IoC容器
可以把Spring IoC容器比作一間餐館,當(dāng)你來到餐館,通常會直接招呼服務(wù)員:點菜!至于菜的原料是什么?如何用原料把菜做出來?可能你根本就不關(guān)心。
IoC容器也是一樣,你只需要告訴它需要某個bean,它就把對應(yīng)的實例(instance)扔給你,至于這個bean是否依賴其他組件,怎樣完成它的初始化,根本就不需要你關(guān)心。
作為餐館,想要做出菜肴,得知道菜的原料和菜譜,同樣地,IoC容器想要管理各個業(yè)務(wù)對象以及它們之間的依賴關(guān)系,需要通過某種途徑來記錄和管理這些信息。
BeanDefinition對象就承擔(dān)了這個責(zé)任:容器中的每一個bean都會有一個對應(yīng)的BeanDefinition實例,該實例負(fù)責(zé)保存bean對象的所有必要信息,包括bean對象的class類型、是否是抽象類、構(gòu)造方法和參數(shù)、其它屬性等等。
當(dāng)客戶端向容器請求相應(yīng)對象時,容器就會通過這些信息為客戶端返回一個完整可用的bean實例。
原材料已經(jīng)準(zhǔn)備好(把BeanDefinition看著原料),開始做菜吧,等等,你還需要一份菜譜,BeanDefinitionRegistry和BeanFactory就是這份菜譜,BeanDefinitionRegistry抽象出bean的注冊邏輯,而BeanFactory則抽象出了bean的管理邏輯,而各個BeanFactory的實現(xiàn)類就具體承擔(dān)了bean的注冊以及管理工作。
它們之間的關(guān)系就如下圖:

BeanFactory、BeanDefinitionRegistry關(guān)系圖(來自:Spring揭秘)
DefaultListableBeanFactory作為一個比較通用的BeanFactory實現(xiàn),它同時也實現(xiàn)了BeanDefinitionRegistry接口,因此它就承擔(dān)了Bean的注冊管理工作。從圖中也可以看出,BeanFactory接口中主要包含getBean、containBean、getType、getAliases等管理bean的方法,而BeanDefinitionRegistry接口則包含registerBeanDefinition、removeBeanDefinition、getBeanDefinition等注冊管理BeanDefinition的方法。
下面通過一段簡單的代碼來模擬BeanFactory底層是如何工作的:
// 默認(rèn)容器實現(xiàn)
?DefaultListableBeanFactory beanRegistry = new?DefaultListableBeanFactory();
?// 根據(jù)業(yè)務(wù)對象構(gòu)造相應(yīng)的BeanDefinition
?AbstractBeanDefinition definition = new?RootBeanDefinition(Business.class,true);
?// 將bean定義注冊到容器中
?beanRegistry.registerBeanDefinition("beanName",definition);
?// 如果有多個bean,還可以指定各個bean之間的依賴關(guān)系
?// ........
??// 然后可以從容器中獲取這個bean的實例
?// 注意:這里的beanRegistry其實實現(xiàn)了BeanFactory接口,所以可以強轉(zhuǎn),
?// 單純的BeanDefinitionRegistry是無法強制轉(zhuǎn)換到BeanFactory類型的
?BeanFactory container = (BeanFactory)beanRegistry;
?Business business = (Business)container.getBean("beanName");// 通常為BeanDefinitionRegistry的實現(xiàn)類,這里以DeFaultListabeBeanFactory為例
?BeanDefinitionRegistry beanRegistry = new?DefaultListableBeanFactory();
??// XmlBeanDefinitionReader實現(xiàn)了BeanDefinitionReader接口,用于解析XML文件
?XmlBeanDefinitionReader beanDefinitionReader = new?XmlBeanDefinitionReaderImpl(beanRegistry);
?// 加載配置文件 beanDefinitionReader.loadBeanDefinitions("classpath:spring-bean.xml");
??// 從容器中獲取bean實例
?BeanFactory container = (BeanFactory)beanRegistry;
?Business business = (Business)container.getBean("beanName");// 通常為BeanDefinitionRegistry的實現(xiàn)類,這里以DeFaultListabeBeanFactory為例
?BeanDefinitionRegistry beanRegistry = new?DefaultListableBeanFactory();
??// XmlBeanDefinitionReader實現(xiàn)了BeanDefinitionReader接口,用于解析XML文件
?XmlBeanDefinitionReader beanDefinitionReader = new?XmlBeanDefinitionReaderImpl(beanRegistry);
?// 加載配置文件 beanDefinitionReader.loadBeanDefinitions("classpath:spring-bean.xml");
??// 從容器中獲取bean實例
?BeanFactory container = (BeanFactory)beanRegistry;
?Business business = (Business)container.getBean("beanName");
// 代碼來自:org.springframework.context.support.ApplicationContextAwareProcessor
?// 其postProcessBeforeInitialization方法調(diào)用了invokeAwareInterfaces方法
?private?void?invokeAwareInterfaces(Object?bean){
?????if?(bean instanceof?EnvironmentAware){
?????????((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());
?????}
?????if?(bean instanceof?ApplicationContextAware){
?????????((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);
?????}
?????// ......
}"bookService" ?class="cn.moondev.service.BookServiceImpl">bean>@Configuration
?public?class
?MoonBookConfiguration
?{
??????// 任何標(biāo)志了@Bean的方法,其返回值將作為一個bean注冊到Spring的IOC容器中
?????// 方法名默認(rèn)成為該bean定義的id
?????@Bean
?????public?BookService bookService()?{
?????????return?new?BookServiceImpl();
?????}
?}"bookService" ?class="cn.moondev.service.BookServiceImpl">
?????<property?name="dependencyService"?ref="dependencyService"/>
?bean>
??"otherService"?class="cn.moondev.service.OtherServiceImpl">
?????<property?name="dependencyService"?ref="dependencyService"/>
?bean>
??"dependencyService"?class="DependencyServiceImpl"/> @Configuration
?public?class?MoonBookConfiguration?{
??????// 如果一個bean依賴另一個bean,則直接調(diào)用對應(yīng)JavaConfig類中依賴bean的創(chuàng)建方法即可
?????// 這里直接調(diào)用dependencyService()
?????@Bean
?????public?BookService bookService()?{
?????????return?new?BookServiceImpl(dependencyService());
?????}
??????@Bean
?????public?OtherService otherService()?{
?????????return?new?OtherServiceImpl(dependencyService());
?????}
??????@Bean
?????public?DependencyService dependencyService()?{
?????????return?new?DependencyServiceImpl();
?????}
?}@Configuration
?public?class?MoonBookConfiguration{
?????@Bean
?????public?BookService bookService()?{
?????????return?new?BookServiceImpl();
?????}
?}@Configuration
?// 可以同時導(dǎo)入多個配置類,比如:@Import({A.class,B.class})
?@Import(MoonBookConfiguration.class)
?public class MoonUserConfiguration
?{
?????@Bean
?????public UserService userService(BookService bookService) {
?????????return?new?BookServiceImpl(bookService);
?????}
?}
public?class?JdbcTemplateCondition?implements?Condition?{
??????@Override
?????public?boolean?matches(ConditionContext
?conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata)?{
?????????try?{
?????????conditionContext.getClassLoader().loadClass("org.springframework.jdbc.core.JdbcTemplate");
?????????????return?true;
?????????}
?catch?(ClassNotFoundException e) {
?????????????e.printStackTrace();
?????????}
?????????return?false;
?????}
?}@Conditional(JdbcTemplateCondition.class) @Service?public MyService service() { ...... }
// jdbc config
?jdbc.mysql.url=jdbc:mysql://localhost:3306/sampledb
?jdbc.mysql.username=root
?jdbc.mysql.password=123456
?......
??// 配置數(shù)據(jù)源
?@Configuration
?public?class?HikariDataSourceConfiguration {
??????@Value("jdbc.mysql.url")
?????public?String?url;
?????@Value("jdbc.mysql.username")
?????public?String?user;
?????@Value("jdbc.mysql.password")
?????public?String?password;
??????????@Bean
?????public?HikariDataSource dataSource() {
?????????HikariConfig hikariConfig = new?HikariConfig();
?????????hikariConfig.setJdbcUrl(url);
?????????hikariConfig.setUsername(user);
?????????hikariConfig.setPassword(password);
?????????// 省略部分代碼
?????????return?new?HikariDataSource(hikariConfig);
?????}
?}@Component
?// 還可以通過@PropertySource("classpath:jdbc.properties")來指定配置文件
?@ConfigurationProperties("jdbc.mysql")
?// 前綴=jdbc.mysql,會在配置文件中尋找jdbc.mysql.*的配置項
?pulic class?JdbcConfig {
?????public?String?url;
?????public?String?username;
?????public?String?password;
?}
??@Configuration
?public?class?HikariDataSourceConfiguration {
??????@AutoWired
?????public?JdbcConfig config;
??????????@Bean
?????public?HikariDataSource dataSource() {
?????????HikariConfig hikariConfig = new?HikariConfig();
?????????hikariConfig.setJdbcUrl(config.url);
?????????hikariConfig.setUsername(config.username);
?????????hikariConfig.setPassword(config.password);
?????????// 省略部分代碼
?????????return?new?HikariDataSource(hikariConfig);
???}
?}#App
?app.menus[0].title=Home
?app.menus[0].name=Home
?app.menus[0].path=/
?app.menus[1].title=Login
?app.menus[1].name=Login
?app.menus[1].path=/login
??app.compiler.timeout=5
?app.compiler.output-folder=/temp/
??app.error=/error/@Component
?@ConfigurationProperties("app")
?public?class?AppProperties {
??????public?String?error;
?????public?Listprotected?Class> loadClass(String?name, boolean?resolve) {
?????synchronized (getClassLoadingLock(name)) {
?????// 首先,檢查該類是否已經(jīng)被加載,如果從JVM緩存中找到該類,則直接返回
?????Class> c = findLoadedClass(name);
?????if?(c == null) {
?????????try?{
?????????????// 遵循雙親委派的模型,首先會通過遞歸從父加載器開始找,
?????????????// 直到父類加載器是BootstrapClassLoader為止
?????????????if?(parent != null) {
?????????????????c = parent.loadClass(name, false);
?????????????} else?{
?????????????????c = findBootstrapClassOrNull(name);
?????????????}
????????} catch?(ClassNotFoundException e) {}
?????????if?(c == null) {
?????????????// 如果還找不到,嘗試通過findClass方法去尋找
?????????????// findClass是留給開發(fā)者自己實現(xiàn)的,也就是說
?????????????// 自定義類加載器時,重寫此方法即可
????????????c = findClass(name);
?????????}
?????}
?????if?(resolve) {
????????resolveClass(c);
?????}
?????return?c;
?????}
?}
for?(String?aDriver : driversList) {
?????try?{
?????????// 直接使用AppClassLoader
?????????Class.forName(aDriver, true, ClassLoader.getSystemClassLoader());
?????} catch?(Exception ex) {
?????????println("DriverManager.Initialize: load failed: "?+ ex);
??????}
???}Thread.currentThread().getClassLoader()Thread.currentThread().getContextClassLoader()public Enumeration<URL>?getResources(String name) throws IOException {
?????Enumeration<URL>[] tmp = (Enumeration<URL>[]) new Enumeration>[2];
?????if?(parent?!= null) {
?????????tmp[0] = parent.getResources(name);
?????} else?{
?????????tmp[0] = getBootstrapResources(name);
?????}
?????tmp[1] = findResources(name);
?????return?new?CompoundEnumeration<>(tmp);
?}// 尋找Array.class文件
?public?static?void?main(String[] args) throws Exception{
?????// Array.class的完整路徑
?????String name = "java/sql/Array.class";
?????Enumeration urls = Thread.currentThread().getContextClassLoader().getResources(name);
?????while?(urls.hasMoreElements()) {
?????????URL url = urls.nextElement();
?????????System.out.println(url.toString());
?????}
?} $JAVA_HOME/jre/lib/rt.jar!/java/sql/Array.classpublic?static?final String?FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
?// spring.factories文件的格式為:key=value1,value2,value3
?// 從所有的jar包中找到META-INF/spring.factories文件
?// 然后從文件中解析出key=factoryClass類名稱的所有value值
?public?static?List<String> loadFactoryNames(Class> factoryClass, ClassLoader classLoader) {
?????String?factoryClassName = factoryClass.getName();
?????// 取得資源文件的URL
?????Enumeration urls = (classLoader != null?? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
?????List<String> result = new?ArrayList<String>();
?????// 遍歷所有的URL
?????while?(urls.hasMoreElements()) {
?????????URL url = urls.nextElement();
?????????// 根據(jù)資源文件URL解析properties文件
?????????Properties properties = PropertiesLoaderUtils.loadProperties(new?UrlResource(url));
?????????String?factoryClassNames = properties.getProperty(factoryClassName);
?????????// 組裝數(shù)據(jù),并返回
?????????result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
?????}
?????return?result;
?} // 來自 org.springframework.boot.autoconfigure下的META-INF/spring.factories
?// EnableAutoConfiguration后文會講到,它用于開啟Spring Boot自動配置功能
?org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
?org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
?org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
?org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration\public?class?MethodMonitorEvent?extends?EventObject?{
?????// 時間戳,用于記錄方法開始執(zhí)行的時間
?????public?long?timestamp;
??????public?MethodMonitorEvent(Object source)?{
?????????super(source);
?????}
?}// 1、定義事件監(jiān)聽接口
?public?interface?MethodMonitorEventListener?extends?EventListener?{
?????// 處理方法執(zhí)行之前發(fā)布的事件
?????public?void?onMethodBegin(MethodMonitorEvent event);
?????// 處理方法結(jié)束時發(fā)布的事件
?????public?void?onMethodEnd(MethodMonitorEvent event);
?}
?// 2、事件監(jiān)聽接口的實現(xiàn):如何處理
?public?class?AbstractMethodMonitorEventListener?implements?MethodMonitorEventListener?{
??????@Override
?????public?void?onMethodBegin(MethodMonitorEvent event) {
?????????// 記錄方法開始執(zhí)行時的時間
?????????event.timestamp = System.currentTimeMillis();
?????}
??????@Override
?????public?void?onMethodEnd(MethodMonitorEvent event) {
?????????// 計算方法耗時
?????????long?duration = System.currentTimeMillis() - event.timestamp;
?????????System.out.println("耗時:"?+ duration);
?????}
?}public?class?MethodMonitorEventPublisher?{
??????private?List listeners = new?ArrayList();
??????public?void?methodMonitor() {
?????????MethodMonitorEvent eventObject = new?MethodMonitorEvent(this);
?????????publishEvent("begin",eventObject);
?????????// 模擬方法執(zhí)行:休眠5秒鐘
?????????TimeUnit.SECONDS.sleep(5);
?????????publishEvent("end",eventObject);
??????}
??????private?void?publishEvent(String status,MethodMonitorEvent event) {
?????????// 避免在事件處理期間,監(jiān)聽器被移除,這里為了安全做一個復(fù)制操作
?????????List copyListeners = ? new?ArrayList(listeners);
?????????for?(MethodMonitorEventListener listener : copyListeners) {
?????????????if?("begin".equals(status)) {
?????????????????listener.onMethodBegin(event);
?????????????} else?{
?????????????????listener.onMethodEnd(event);
?????????????}
?????????}
?????}
?????????public?static?void?main(String[] args) {
?????????MethodMonitorEventPublisher publisher = new?MethodMonitorEventPublisher();
?????????publisher.addEventListener(new?AbstractMethodMonitorEventListener());
?????????publisher.methodMonitor();
?????}
?????// 省略實現(xiàn)
?????public?void?addEventListener(MethodMonitorEventListener listener) {}
?????public?void?removeEventListener(MethodMonitorEventListener listener) {}
?????public?void?removeAllListeners() {} 2. 事件監(jiān)聽器的管理。publisher類中提供了事件監(jiān)聽器的注冊與移除方法,這樣客戶端可以根據(jù)實際情況決定是否需要注冊新的監(jiān)聽器或者移除某個監(jiān)聽器。
@SpringBootApplication
?public?class?MoonApplication?{
??????public?static?void?main(String[] args)?{
?????????SpringApplication.run(MoonApplication.class, args);
?????}
?}@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 @interface?SpringBootApplication {
?????// ......
?}@Target(ElementType.TYPE)
?@Retention(RetentionPolicy.RUNTIME)
?@Documented
?@Inherited
?@AutoConfigurationPackage
?@Import(EnableAutoConfigurationImportSelector.class)
?public @interface?EnableAutoConfiguration {
?????// ......
?}public?String[] selectImports(AnnotationMetadata annotationMetadata) {
?????// 省略了大部分代碼,保留一句核心代碼
?????// 注意:SpringBoot最近版本中,這句代碼被封裝在一個單獨的方法中
?????// SpringFactoriesLoader相關(guān)知識請參考前文
?????List<String> factories = new?ArrayList<String>(new?LinkedHashSet<String>(
???????????SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class, this.beanClassLoader)));
?}// 來自 org.springframework.boot.autoconfigure下的META-INF/spring.factories
?// 配置的key = EnableAutoConfiguration,與代碼中一致
?org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
?org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
?org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
?org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration\
?.....@Configuration
?@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
?@EnableConfigurationProperties(DataSourceProperties.class)
?@Import({ Registrar.class, DataSourcePoolMetadataProvidersConfiguration.class })
?public class DataSourceAutoConfiguration {
?}// 提供對datasource配置信息的支持,所有的配置前綴為:spring.datasource
?@ConfigurationProperties(prefix = "spring.datasource")
?public?class?DataSourceProperties {
?????private?ClassLoader classLoader;
?????private?Environment environment;
?????private?String?name = "testdb";
?????......
?}@Configuration
?public class DataSourcePoolMetadataProvidersConfiguration {
??????@Configuration
?????@ConditionalOnClass(org.apache.tomcat.jdbc.pool.DataSource.class)
?????static class TomcatDataSourcePoolMetadataProviderConfiguration {
?????????@Bean
?????????public DataSourcePoolMetadataProvider tomcatPoolDataSourceMetadataProvider() {
?????????????.....
?????????}
?????}
???......
?}
private?void?initialize(Object[] sources) {
??????if?(sources != null?&& sources.length > 0) {
??????????this.sources.addAll(Arrays.asList(sources));
??????}
??????// 判斷是否是Web項目
??????this.webEnvironment = deduceWebEnvironment();
??????setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
??????setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
??????// 找到入口類
??????this.mainApplicationClass = deduceMainApplicationClass();
?}org.springframework.context.ApplicationListener=\ cn.moondev.listeners.xxxxListener\關(guān)于SpringApplication的初始化,我們就說這么多。public?ConfigurableApplicationContext run(String... args) {
?????????StopWatch stopWatch = new?StopWatch();
?????????stopWatch.start();
?????????ConfigurableApplicationContext context = null;
?????????FailureAnalyzers analyzers = null;
?????????configureHeadlessProperty();
?????????// ①
?????????SpringApplicationRunListeners listeners = getRunListeners(args);
?????????listeners.starting();
?????????try?{
?????????????// ②
ApplicationArguments applicationArguments = new?DefaultApplicationArguments(args);
?????????????ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);
?????????????// ③
?????????????Banner printedBanner = printBanner(environment);
?????????????// ④
?????????????context = createApplicationContext();
?????????????// ⑤
?????????????analyzers = new?FailureAnalyzers(context);
?????????????// ⑥
?????????????prepareContext(context, environment, listeners, applicationArguments,printedBanner);
?????????????// ⑦
??????????????refreshContext(context);
?????????????// ⑧
?????????????afterRefresh(context, applicationArguments);
?????????????// ⑨
?????????????listeners.finished(context, null);
?????????????stopWatch.stop();
?????????????return?context;
????????}
?????????catch?(Throwable ex) {
?????????????handleRunFailure(context, listeners, analyzers, ex);
?????????????throw?new?IllegalStateException(ex);
?????????}
?????}public?interface?SpringApplicationRunListener?{
??????// 運行run方法時立即調(diào)用此方法,可以用戶非常早期的初始化工作
?????void?starting();
??????????// Environment準(zhǔn)備好后,并且ApplicationContext創(chuàng)建之前調(diào)用
?????void?environmentPrepared(ConfigurableEnvironment environment);
??????// ApplicationContext創(chuàng)建好后立即調(diào)用
?????void?contextPrepared(ConfigurableApplicationContext context);
??????// ApplicationContext加載完成,在refresh之前調(diào)用
?????void?contextLoaded(ConfigurableApplicationContext context);
??????// 當(dāng)run方法結(jié)束之前調(diào)用
?????void?finished(ConfigurableApplicationContext
?context, Throwable exception);
??}public?void?starting()?{
?????// 發(fā)布一個ApplicationStartedEvent
?????this.initialMulticaster.multicastEvent(new?ApplicationStartedEvent(this.application, this.args));
?}listeners.environmentPrepared(environment);. ____??????????_????????????__?_?_
??/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
?( ( )\___ | '_?| '_|?| '_ \/ _` |?\ \ \ \
??\\/ ___)| |_)| |?| |?| || (_|?| ) ) ) )
???' |____| .__|_| |_|_|?|_\__, |?/ / /?/
??=========|_|==============|___/=/_/_/_/
?:: Spring Boot :: (v1.5.6.RELEASE)// 摘自refresh()方法中一句代碼
?invokeBeanFactoryPostProcessors(beanFactory);protected?void?invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory)?{
?????PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
?????......
?}ConfigurationWarningsApplicationContextInitializer$ConfigurationWarningsPostProcessor
?SharedMetadataReaderFactoryContextInitializer$CachingMetadataReaderFactoryPostProcessor
?ConfigFileApplicationListener$PropertySourceOrderingPostProcessorpublic?void?initialize(ConfigurableApplicationContext context)?{
?????context.addBeanFactoryPostProcessor(new?ConfigurationWarningsPostProcessor(getChecks()));
?}
好文章,我在看

