此係列是針對springboot的啓動,旨在於和你們一塊兒來看看springboot啓動的過程當中到底作了一些什麼事。若是你們對springboot的源碼有所研究,能夠挑些本身感興趣或者對本身有幫助的看;可是若是你們沒有研究過springboot的源碼,不知道springboot在啓動過程當中作了些什麼,那麼我建議你們從頭開始一篇一篇按順序讀該系列,不至於從中途插入,看的有些懵懂。固然,文中講的不對的地方也歡迎你們指出,有待改善的地方也但願你們不吝賜教。老規矩:一週至少一更,中途會不按期的更新一些其餘的博客,多是springboot的源碼,也多是其餘的源碼解析,也有多是其餘的。html
路漫漫其修遠兮,吾將上下而求索!java
github:https://github.com/youzhibinggit
碼雲(gitee):https://gitee.com/youzhibinggithub
你們還記得上篇博文講了什麼嗎,或者說你們知道上篇博文講了什麼嗎。這裏幫你們作個簡單回顧,主要作了兩件事web
一、加載外部化配置的資源到environmentspring
包括命令行參數、servletConfigInitParams、servletContextInitParams、systemProperties、sytemEnvironment、random、application.yml(.yaml/.xml/.properties),以下所示編程
二、廣播ApplicationEnvironmentPreparedEvent事件,觸發相應的監聽器緩存
ConfigFileApplicationListenerspringboot
添加名叫random的RandomValuePropertySource到environmentapp
添加名叫applicationConfig:[classpath:/application.yml]的OriginTrackedMapPropertySource到environment
LoggingApplicationListener
初始化日誌系統
先欣賞下咱們的戰績,看看咱們對run方法完成了多少的源碼解讀
/** * Run the Spring application, creating and refreshing a new * {@link ApplicationContext}. * @param args the application arguments (usually passed from a Java main method) * @return a running {@link ApplicationContext} */ public ConfigurableApplicationContext run(String... args) { // 秒錶,用於記錄啓動時間;記錄每一個任務的時間,最後會輸出每一個任務的總費時 StopWatch stopWatch = new StopWatch(); stopWatch.start(); // spring應用上下文,也就是咱們所說的spring根容器 ConfigurableApplicationContext context = null; // 自定義SpringApplication啓動錯誤的回調接口 Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); // 設置jdk系統屬性java.awt.headless,默認狀況爲true即開啓 configureHeadlessProperty(); // 獲取啓動時監聽器(EventPublishingRunListener實例) SpringApplicationRunListeners listeners = getRunListeners(args) // 觸發ApplicationStartingEvent事件,啓動監聽器會被調用,一共5個監聽器被調用,但只有兩個監聽器在此時作了事 listeners.starting(); try { // 參數封裝,也就是在命令行下啓動應用帶的參數,如--server.port=9000 ApplicationArguments applicationArguments = new DefaultApplicationArguments( args); // 準備環境:一、加載外部化配置的資源到environment;二、觸發ApplicationEnvironmentPreparedEvent事件 ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); // 配置spring.beaninfo.ignore,並添加到名叫systemProperties的PropertySource中;默認爲true即開啓 configureIgnoreBeanInfo(environment); // 打印banner圖 Banner printedBanner = printBanner(environment); // 建立應用上下文,這是本文重點 context = createApplicationContext(); exceptionReporters = getSpringFactoriesInstances( SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context); prepareContext(context, environment, listeners, applicationArguments, printedBanner); refreshContext(context); afterRefresh(context, applicationArguments); stopWatch.stop(); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass) .logStarted(getApplicationLog(), stopWatch); } listeners.started(context); callRunners(context, applicationArguments); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, listeners); throw new IllegalStateException(ex); } try { listeners.running(context); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, null); throw new IllegalStateException(ex); } return context; }
configureIgnoreBeanInfo(environment);
private void configureIgnoreBeanInfo(ConfigurableEnvironment environment) { if (System.getProperty( CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME) == null) { Boolean ignore = environment.getProperty("spring.beaninfo.ignore", Boolean.class, Boolean.TRUE); System.setProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME, ignore.toString()); } }
配置spring.beaninfo.ignore,並添加到名叫systemProperties的PropertySource中,默認爲true即開啓,如上圖所示。至於spring.beaninfo.ignore配置這個有什麼用,何時用,暫時還沒體現,後續應該會有所體現,咱們暫時先將其當作一個疑問放着。
printBanner(environment);
private Banner printBanner(ConfigurableEnvironment environment) { if (this.bannerMode == Banner.Mode.OFF) { return null; } ResourceLoader resourceLoader = (this.resourceLoader != null ? this.resourceLoader : new DefaultResourceLoader(getClassLoader())); SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter( resourceLoader, this.banner); if (this.bannerMode == Mode.LOG) { return bannerPrinter.print(environment, this.mainApplicationClass, logger); } return bannerPrinter.print(environment, this.mainApplicationClass, System.out); }
打印banner圖,就是下面這個圖
並返回Banner對象,後續還會用到。
經過前面兩道前菜,我相信咱們已經胃口大開了,那麼請開始咱們的正餐 - createApplicationContext
源代碼
/** * Strategy method used to create the {@link ApplicationContext}. By default this * method will respect any explicitly set application context or application context * class before falling back to a suitable default. * @return the application context (not yet refreshed) * @see #setApplicationContextClass(Class) */ protected ConfigurableApplicationContext createApplicationContext() { Class<?> contextClass = this.applicationContextClass; if (contextClass == null) { try { switch (this.webApplicationType) { case SERVLET: contextClass = Class.forName(DEFAULT_WEB_CONTEXT_CLASS); break; case REACTIVE: contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS); break; default: contextClass = Class.forName(DEFAULT_CONTEXT_CLASS); } } catch (ClassNotFoundException ex) { throw new IllegalStateException( "Unable create a default ApplicationContext, " + "please specify an ApplicationContextClass", ex); } } return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass); }
根據SpringApplication的webApplicationType來實例化對應的上下文;若是webApplicationType的值是SERVLET,那麼實例化AnnotationConfigServletWebServerApplicationContext,若是是REACTIVE則實例化AnnotationConfigReactiveWebServerApplicationContext(響應式編程,後續再看),若是既不是SERVLET、也不是REACTIVE,那麼則是默認狀況(也就是咱們所說的非web引用),實例化AnnotationConfigApplicationContext。還記得webApplicationType的值是怎麼獲取的嗎,請點這裏。很顯然咱們目前的應用類型是SERVLET,那麼實例化AnnotationConfigServletWebServerApplicationContext。
利用反射調用AnnotationConfigServletWebServerApplicationContext的構造方法進行實例化,期間有對構造方法進行可訪問性設置,同時還進行了Kotlin的校驗。咱們目前應用中不涉及Kotlin,先放着不用看。
AnnotationConfigServletWebServerApplicationContext類圖
AnnotationConfigServletWebServerApplicationContext父級類
從類圖中咱們可知,類結構比較深,咱們從上往下來看各個父類的構造方法(實現的接口先不看),看看構造方法體裏面所作的事。
DefaultResourceLoader,默認資源加載器
獲取默認的類加載器,獲取的是當前線程的上下文類加載器。
AbstractApplicationContext,抽象應用上下文
初始化屬性resourcePatternResolver,也就是資源模式解析器;實際類型是PathMatchingResourcePatternResolver,它是基於模式匹配的,默認使用AntPathMatcher進行路徑匹配,它除了支持ResourceLoader支持的前綴外,還額外支持「classpath*:」用於加載全部匹配的類路徑Resource。
另外beanFactoryPostProcessors屬性此時已經初始化了,後續確定會用到,你們注意下。
GenericApplicationContext,通用應用上下文
初始化屬性beanFactory,其類型是DefaultListableBeanFactory,DefaultListableBeanFactory類圖以下
DefaultListableBeanFactory的父級類
咱們根據上圖,從上往下讀
SimpleAliasRegistry,簡單別名註冊器
沒有明確的定義構造方法,也就是隻有默認的無參構造方法,咱們可認爲只是實例化了本身。
DefaultSingletonBeanRegistry,默認單例bean註冊器,用於註冊共享的單例bean
沒有明確的定義構造方法,也就是隻有默認的無參構造方法,咱們可認爲只是實例化了本身。
FactoryBeanRegistrySupport,工廠bean註冊器支持,用於註冊工廠bean單例
沒有明確的定義構造方法,也就是隻有默認的無參構造方法,咱們可認爲只是實例化了本身。
AbstractBeanFactory,抽象bean工廠
無參構造方法體內爲空,咱們可認爲只是實例化了本身。
AbstractAutowireCapableBeanFactory,抽象的有自動裝配裝配能力的bean工廠,賦予了自動裝配功能
該類提供bean建立(具備構造函數解析),屬性填充,接線(包括自動裝配)和初始化。 處理運行時bean引用,解析託管集合,調用初始化方法等。支持自動裝配構造函數,按名稱的屬性和按類型的屬性。
無參構造方法中,添加了三個非自動裝配的接口:BeanNameAware、BeanFactoryAware和BeanClassLoaderAware。
DefaultListableBeanFactory,ListableBeanFactory的默認實現
該類用於註冊全部bean定義、也可用做獨立的bean工廠,固然也能夠用做咱們自定義bean工廠的父類。
無參構造方法中也只是調用了super(),咱們可認爲只是實例化了本身。
GenericWebApplicationContext,通用web應用上下文,在GenericApplicationContext基礎上增長web支持
無參構造方法中,只是調用了super(),咱們可認爲只是實例化了本身。
ServletWebServerApplicationContext,servlet web服務應用上下文,可以從自身引導,建立,初始化和運行WebServer
無參構造方法中是空內容,咱們可認爲只是實例化了本身。
DefaultListableBeanFactory類圖中,有不少類的屬性值得咱們留意,好比SimpleAliasRegistry的aliasMap、DefaultSingletonBeanRegistry的singletonObjects、singletonFactories和earlySingletonObjects、FactoryBeanRegistrySupport的factoryBeanObjectCache、AbstractBeanFactory的beanPostProcessors、AbstractAutowireCapableBeanFactory的ignoredDependencyInterfaces、DefaultListableBeanFactory中的屬性beanDefinitionMap和beanDefinitionNames。
AnnotationConfigServletWebServerApplicationContext類圖中,也有不少類的屬性值得咱們留意,好比AbstractApplicationContext的beanFactoryPostProcessors、GenericApplicationContext的beanFactory(就是DefaultListableBeanFactory)、GenericWebApplicationContext的servletContext、ServletWebServerApplicationContext的webServer和servletConfig。
AnnotationConfigServletWebServerApplicationContext構造方法
/** * Create a new {@link AnnotationConfigServletWebServerApplicationContext} that needs * to be populated through {@link #register} calls and then manually * {@linkplain #refresh refreshed}. */ public AnnotationConfigServletWebServerApplicationContext() { this.reader = new AnnotatedBeanDefinitionReader(this); // 實例化註解bean定義讀取器 this.scanner = new ClassPathBeanDefinitionScanner(this); // 實例化類路徑bean定義掃描器 }
構造方法中的內容也比較簡單,就是實例化兩個bean,並賦值給本身的屬性。咱們接着往下看,AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner究竟是什麼,構造方法中到底作了什麼?
AnnotatedBeanDefinitionReader
從類註釋上來看,做用就是用於編程式註解bean的註冊,例如咱們平時用到的@Component,還有@Configuration類下的@Bean等。
構造方法中調用getOrCreateEnvironment(registry)來獲取environment;你們調試跟進的話會發現,此處新實例化了StandardServletEnvironment,你們還記得SpringApplication中的environment嗎,它也是StandardServletEnvironment實例,那麼此處爲何還要新new一個StandardServletEnvironment呢,總結中給你們答案。
屬性ConditionEvaluator conditionEvaluator,你們留意下屬性類型ConditionEvaluator ,一般用來評估@Conditional。
另外還註冊了註解配置處理器:AnnotationAwareOrderComparator、ContextAnnotationAutowireCandidateResolver、ConfigurationClassPostProcessor、AutowiredAnnotationBeanPostProcessor、RequiredAnnotationBeanPostProcessor、CommonAnnotationBeanPostProcessor、EventListenerMethodProcessor、DefaultEventListenerFactory。
此時beanFactory屬性以下
ClassPathBeanDefinitionScanner
從類註釋上來看,就是一個bean定義掃描器,用來掃描類路徑下的bean侯選者。
其中registerDefaultFilters,註冊了兩個AnnotationTypeFilter:一個針對@Component,一個針對@ManagedBean
此時beanFactory屬性以下
此時雖然已經建立了應用上下文,但還只是具備一個骨架(填充了少部份內容),後續會往這個骨架上填充器官和肉體,來構成一個完整的應用。那往哪填充呢?就是咱們上面提的到個各個類中的屬性。
上面講了那麼多,相信你們此時有點蒙,看似一個簡單的createApplicationContext,卻引起了一系列類的實例化;你們主要關注上述兩個類圖中父級類,對每一個類進行一遍通讀,大體瞭解下每一個類中有些什麼屬性。後續確定會對這些屬性進行填充,並利用這些屬性完成咱們的應用。
有時候,不是對手有多強大,只是咱們不敢去嘗試;勇敢踏出第一步,你會發現本身比想象中更優秀!誠如海因斯第一次跑進人類10s大關時所說:上帝啊,原來那扇門是虛掩着的!
一、文中疑問
AnnotatedBeanDefinitionReader中爲何還要實例化一個StandardServletEnvironment?
咱們能夠把這個問題變一下,爲何不把SpringApplication中的environment直接注入到AnnotatedBeanDefinitionReader,而是要在AnnotatedBeanDefinitionReader中實例化新的StandardServletEnvironment?
咱們看下類所在的包可知,SpringApplication是Spring boot的特有的類,而AnnotatedBeanDefinitionReader是spring中的類,咱們知道spring boot依賴spring,但spring不依賴spring boot,那麼咱們在spring中能用spring boot特有的內容嗎?咱們可能又有另外的疑問了,那爲何不先實例化spring中的StandardServletEnvironment,而後將它賦值給SpringApplication,就目前而言,我也不知道,不過在後續應該能找到答案,咱們暫且先當一個疑問留着。
二、AnnotatedBeanDefinitionReader與ClassPathBeanDefinitionScanner
前者是註解bean定義讀取器,用於編程式註解bean的註冊;後者是類路徑bean定義掃描器,用於檢測類路徑上的bean候選者。
AnnotatedBeanDefinitionReade用來加載class類型的配置,在它初始化的時候,會預先註冊一些BeanPostProcessor和BeanFactoryPostProcessor,這些處理器會在接下來的spring初始化流程中被調用。ClassPathBeanDefinitionScanner是一個掃描指定類路徑中註解Bean定義的掃描器,在它初始化的時候,會初始化一些須要被掃描的註解。
三、BeanDefinition
Spring容器裏經過BeanDefinition對象來表示Bean,BeanDefinition描述了Bean的配置信息;根據BeanDefinition實例化bean,並放到bean緩存中。
四、spring bean配置方式
有三種:基於XML的配置方式 、基於註解的配置方式和基於Java類的配置方式。
基於XML,這個咱們都很熟,相似:<bean id="xx" class="xxx" />
基於註解,這個咱們也用的比較多,入@Component、@Service、@Controller等
基於java類,spring的推薦配置方式,@Configuration配合@Bean
五、createApplicationContext到底作了什麼
說的簡單點:建立web應用上下文,對其部分屬性:reader、scanner、beanFactory進行了實例化;reader中實例化了屬性conditionEvaluator;scanner中添加了兩個AnnotationTypeFilter:一個針對@Component,一個針對@ManagedBean;beanFactory中註冊了8個註解配置處理器。這些就目前而言,可能沒體現其做用,後續確定會用到的。
Spring boot 源碼