久久精品精选,精品九九视频,www久久只有这里有精品,亚洲熟女乱色综合一区
    分享

    SpringBoot啟動流程總結

     米老鼠的世界 2021-06-08

    一直很好奇SpringBoot這么一個大怪物,啟動的時候做了哪些事情,然后看了很多老師講的教學視頻,然后自己想好好整理一下,做下學習筆記下次也方便自己閱讀

    1.執行main方法

    1. public static void main(String[] args) {
    2. //代碼很簡單SpringApplication.run();
    3. SpringApplication.run(ConsumerApp.class, args);
    4. }
    1. public static ConfigurableApplicationContext run(Class<?> primarySource,
    2. String... args) {
    3. //這個里面調用了run() 方法,我們轉到定義
    4. return run(new Class<?>[] { primarySource }, args);
    5. }
    6. //這個run方法代碼也很簡單,就做了兩件事情
    7. //1、new了一個SpringApplication() 這么一個對象
    8. //2、執行new出來的SpringApplication()對象的run()方法
    9. public static ConfigurableApplicationContext run(Class<?>[] primarySources,
    10. String[] args) {
    11. return new SpringApplication(primarySources).run(args);
    12. }

     上面代碼主要做了兩件事情。第一步new了一個SpringApplication對象 ,第二部調用了run()方法。接下來我們一起看下new SpringApplication() 主要做了什么事情。

    1. public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    2. this.resourceLoader = resourceLoader;
    3. Assert.notNull(primarySources, "PrimarySources must not be null");
    4. //1、先把主類保存起來
    5. this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    6. //2、判斷運行項目的類型
    7. this.webApplicationType = WebApplicationType.deduceFromClasspath();
    8. //3、掃描當前路徑下META-INF/spring.factories文件的,加載ApplicationContextInitializer接口實例
    9. setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    10. //4、掃描當前路徑下META-INF/spring.factories文件的,加載ApplicationListener接口實例
    11. setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    12. this.mainApplicationClass = deduceMainApplicationClass();
    13. }

    利用SPI機制掃描 META-INF/spring.factories 這個文件,并且加載 ApplicationContextInitializerApplicationListener 接口實例。

    1、ApplicationContextInitializer 這個類當springboot上下文Context初始化完成后會調用

    2、ApplicationListener 當springboot啟動時事件change后都會觸發

    下面我們來自定義ApplicationContextInitializer、ApplicationListener 接口實現類,然后Debug來看下效果。

    1. /**
    2. * Context初始化后調用類
    3. * @author ShiMinChen
    4. *
    5. */
    6. public class StarterApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    7. @Override
    8. public void initialize(ConfigurableApplicationContext applicationContext) {
    9. System.out.println("applicationContext 初始化完成 ... ");
    10. }
    11. }
    1. public class StarterApplicationListener implements ApplicationListener {
    2. @Override
    3. public void onApplicationEvent(ApplicationEvent event) {
    4. System.out.println(event.toString());
    5. System.out.println("ApplicationListener .... " + System.currentTimeMillis());
    6. }
    7. }

    我們需要把這兩個類集成到springboot里面去,其實操作也挺簡單的

    然后在META-INF/spring.factories 文件配置那兩個類

    1. org.springframework.context.ApplicationContextInitializer=\
    2. org.admin.starter.test.listener.StarterApplicationContextInitializer
    3. org.springframework.context.ApplicationListener=\
    4. org.admin.starter.test.listener.StarterApplicationListener

    4、我們代碼DEBUG一下,在loadSpringFactories() 方法打一個斷點

    1. private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    2. MultiValueMap<String, String> result = cache.get(classLoader);
    3. if (result != null)
    4. return result;
    5. try {
    6. Enumeration<URL> urls = (classLoader != null ?
    7. classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
    8. ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
    9. result = new LinkedMultiValueMap<>();
    10. while (urls.hasMoreElements()) {
    11. URL url = urls.nextElement();
    12. UrlResource resource = new UrlResource(url);
    13. Properties properties = PropertiesLoaderUtils.loadProperties(resource);
    14. for (Map.Entry<?, ?> entry : properties.entrySet()) {
    15. List<String> factoryClassNames = Arrays.asList(
    16. StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
    17. result.addAll((String) entry.getKey(), factoryClassNames);
    18. }
    19. }
    20. cache.put(classLoader, result);
    21. // 端點打在這里就行了
    22. return result;
    23. }
    24. catch (IOException ex) {
    25. throw new IllegalArgumentException("Unable to load factories from location [" +
    26. FACTORIES_RESOURCE_LOCATION + "]", ex);
    27. }
    28. }

    總結:上面就是SpringApplication初始化的代碼,new SpringApplication()沒做啥事情 ,利用SPI機制主要加載了META-INF/spring.factories 下面定義的事件監聽器接口實現類

    2.執行run() 方法

    1. public ConfigurableApplicationContext run(String... args) {
    2. <!--1、這個是一個計時器,沒什么好說的-->
    3. StopWatch stopWatch = new StopWatch();
    4. stopWatch.start();
    5. ConfigurableApplicationContext context = null;
    6. Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    7. <!--2、這個也不是重點,就是設置了一些環境變量-->
    8. configureHeadlessProperty();
    9. <!--3、獲取事件監聽器SpringApplicationRunListener類型,并且執行starting()方法-->
    10. SpringApplicationRunListeners listeners = getRunListeners(args);
    11. listeners.starting();
    12. try {
    13. <!--4、把參數args封裝成DefaultApplicationArguments,這個了解一下就知道-->
    14. ApplicationArguments applicationArguments = new DefaultApplicationArguments(
    15. args);
    16. <!--5、這個很重要準備環境了,并且把環境跟spring上下文綁定好,并且執行environmentPrepared()方法-->
    17. ConfigurableEnvironment environment = prepareEnvironment(listeners,
    18. applicationArguments);
    19. <!--6、判斷一些環境的值,并設置一些環境的值-->
    20. configureIgnoreBeanInfo(environment);
    21. <!--7、打印banner-->
    22. Banner printedBanner = printBanner(environment);
    23. <!--8、創建上下文,根據項目類型創建上下文-->
    24. context = createApplicationContext();
    25. <!--9、獲取異常報告事件監聽-->
    26. exceptionReporters = getSpringFactoriesInstances(
    27. SpringBootExceptionReporter.class,
    28. new Class[] { ConfigurableApplicationContext.class }, context);
    29. <!--10、準備上下文,執行完成后調用contextPrepared()方法,contextLoaded()方法-->
    30. prepareContext(context, environment, listeners, applicationArguments,
    31. printedBanner);
    32. <!--11、這個是spring啟動的代碼了,這里就回去里面就回去掃描并且初始化單實列bean了-->
    33. //這個refreshContext()加載了bean,還啟動了內置web容器,需要細細的去看看
    34. refreshContext(context);
    35. <!--12、啥事情都沒有做-->
    36. afterRefresh(context, applicationArguments);
    37. stopWatch.stop();
    38. if (this.logStartupInfo) {
    39. new StartupInfoLogger(this.mainApplicationClass)
    40. .logStarted(getApplicationLog(), stopWatch);
    41. }
    42. <!--13、執行ApplicationRunListeners中的started()方法-->
    43. listeners.started(context);
    44. <!--執行Runner(ApplicationRunner和CommandLineRunner)-->
    45. callRunners(context, applicationArguments);
    46. }
    47. catch (Throwable ex) {
    48. handleRunFailure(context, listeners, exceptionReporters, ex);
    49. throw new IllegalStateException(ex);
    50. }
    51. listeners.running(context);
    52. return context;
    53. }

    2.1  createApplicationContext()

    一起來看下context = createApplicationContext(); 這段代碼,這段代碼主要是根據項目類型創建上下文,并且會注入幾個核心組件類。

    1. protected ConfigurableApplicationContext createApplicationContext() {
    2. Class<?> contextClass = this.applicationContextClass;
    3. if (contextClass == null) {
    4. try {
    5. switch (this.webApplicationType) {
    6. case SERVLET:
    7. contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
    8. break;
    9. case REACTIVE:
    10. contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
    11. break;
    12. default:
    13. contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
    14. }
    15. }
    16. catch (ClassNotFoundException ex) {
    17. throw new IllegalStateException(
    18. "Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);
    19. }
    20. }
    21. return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
    22. }
    1. public AnnotationConfigServletWebServerApplicationContext(DefaultListableBeanFactory beanFactory) {
    2. super(beanFactory);
    3. //1:會去注入一些spring核心組件
    4. this.reader = new AnnotatedBeanDefinitionReader(this);
    5. this.scanner = new ClassPathBeanDefinitionScanner(this);
    6. }

    Web類型項目創建上下文對象 AnnotationConfigServletWebServerApplicationContext 。這里會把 ConfigurationClassPostProcessor 、AutowiredAnnotationBeanPostProcessor 等一些核心組件加入到Spring容器(扯淡有點遠,這是spring容器啟動的一些知識點,去了解一下對理解springboot有很大的幫助

    2.2  refreshContext()

    下面一起來看下refreshContext(context) 這個方法,這個方法啟動spring的代碼加載了bean,還啟動了內置web容器

    1. private void refreshContext(ConfigurableApplicationContext context) {
    2. // 轉到定義看看
    3. refresh(context);
    4. if (this.registerShutdownHook) {
    5. try {
    6. context.registerShutdownHook();
    7. }
    8. catch (AccessControlException ex) {
    9. // Not allowed in some environments.
    10. }
    11. }
    12. }
    1. protected void refresh(ApplicationContext applicationContext) {
    2. Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
    3. //看看refresh()方法去
    4. ((AbstractApplicationContext) applicationContext).refresh();
    5. }

     轉到AbstractApplicationContext - >refresh()方法里面發現這是spring容器啟動代碼

    1. @Override
    2. public void refresh() throws BeansException, IllegalStateException {
    3. synchronized (this.startupShutdownMonitor) {
    4. // Prepare this context for refreshing.
    5. prepareRefresh();
    6. // Tell the subclass to refresh the internal bean factory.
    7. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
    8. // Prepare the bean factory for use in this context.
    9. prepareBeanFactory(beanFactory);
    10. try {
    11. // Allows post-processing of the bean factory in context subclasses.
    12. postProcessBeanFactory(beanFactory);
    13. // Invoke factory processors registered as beans in the context.
    14. invokeBeanFactoryPostProcessors(beanFactory);
    15. // Register bean processors that intercept bean creation.
    16. registerBeanPostProcessors(beanFactory);
    17. // Initialize message source for this context.
    18. initMessageSource();
    19. // Initialize event multicaster for this context.
    20. initApplicationEventMulticaster();
    21. // Initialize other special beans in specific context subclasses.
    22. onRefresh();
    23. // Check for listener beans and register them.
    24. registerListeners();
    25. // Instantiate all remaining (non-lazy-init) singletons.
    26. finishBeanFactoryInitialization(beanFactory);
    27. // Last step: publish corresponding event.
    28. finishRefresh();
    29. }
    30. catch (BeansException ex) {
    31. if (logger.isWarnEnabled()) {
    32. logger.warn("Exception encountered during context initialization - " +
    33. "cancelling refresh attempt: " + ex);
    34. }
    35. // Destroy already created singletons to avoid dangling resources.
    36. destroyBeans();
    37. // Reset 'active' flag.
    38. cancelRefresh(ex);
    39. // Propagate exception to caller.
    40. throw ex;
    41. }
    42. finally {
    43. // Reset common introspection caches in Spring's core, since we
    44. // might not ever need metadata for singleton beans anymore...
    45. resetCommonCaches();
    46. }
    47. }
    48. }

     spring容器啟動代碼就不說了,這里主要看一下onRefresh() 這個方法。轉到定義發現這個方法里面啥都沒有,這明顯是一個鉤子方法,它會鉤到它子類重寫onRefresh()方法。所以去看子類里面的onRefresh()

    1. protected void onRefresh() throws BeansException {
    2. //這是一個空方法,AbstractApplicationContext 這個類是一個抽象類,
    3. //所以我們要找到集成AbstractApplicationContext的子類,去看子類里面的onRefresh()
    4. // For subclasses: do nothing by default.
    5. }

     我們這里是一個Web項目,所以我們就去看 ServletWebServerApplicationContext 這個類 ,我還是把類的關系圖貼一下

    我們就去看 ServletWebServerApplicationContext 這個類下面的 onRefresh() 方法

    1. protected void onRefresh() {
    2. super.onRefresh();
    3. try {
    4. //看到內置容器的影子了,進去看看
    5. createWebServer();
    6. }
    7. catch (Throwable ex) {
    8. throw new ApplicationContextException("Unable to start web server", ex);
    9. }
    10. }
    1. private void createWebServer() {
    2. WebServer webServer = this.webServer;
    3. ServletContext servletContext = getServletContext();
    4. if (webServer == null && servletContext == null) {
    5. //1、這個獲取webServerFactory還是要進去看看
    6. ServletWebServerFactory factory = getWebServerFactory();
    7. this.webServer = factory.getWebServer(getSelfInitializer());
    8. }
    9. else if (servletContext != null) {
    10. try {
    11. getSelfInitializer().onStartup(servletContext);
    12. }
    13. catch (ServletException ex) {
    14. throw new ApplicationContextException("Cannot initialize servlet context",
    15. ex);
    16. }
    17. }
    18. initPropertySources();
    19. }

     我們繼續看下getWebServletFactory() 這個方法,這個里面其實就是選擇出哪種類型的web容器了

    1. protected ServletWebServerFactory getWebServerFactory() {
    2. // Use bean names so that we don't consider the hierarchy
    3. String[] beanNames = getBeanFactory()
    4. .getBeanNamesForType(ServletWebServerFactory.class);
    5. if (beanNames.length == 0) {
    6. throw new ApplicationContextException(
    7. "Unable to start ServletWebServerApplicationContext due to missing "
    8. + "ServletWebServerFactory bean.");
    9. }
    10. if (beanNames.length > 1) {
    11. throw new ApplicationContextException(
    12. "Unable to start ServletWebServerApplicationContext due to multiple "
    13. + "ServletWebServerFactory beans : "
    14. + StringUtils.arrayToCommaDelimitedString(beanNames));
    15. }
    16. return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
    17. }

     

    我們再回頭去看factory.getWebServer(getSelfInitializer()) ,轉到定義就會看到很熟悉的名字tomcat

    1. public WebServer getWebServer(ServletContextInitializer... initializers) {
    2. //tomcat這位大哥出現了
    3. Tomcat tomcat = new Tomcat();
    4. File baseDir = (this.baseDirectory != null ? this.baseDirectory
    5. : createTempDir("tomcat"));
    6. tomcat.setBaseDir(baseDir.getAbsolutePath());
    7. Connector connector = new Connector(this.protocol);
    8. tomcat.getService().addConnector(connector);
    9. customizeConnector(connector);
    10. tomcat.setConnector(connector);
    11. tomcat.getHost().setAutoDeploy(false);
    12. configureEngine(tomcat.getEngine());
    13. for (Connector additionalConnector : this.additionalTomcatConnectors) {
    14. tomcat.getService().addConnector(additionalConnector);
    15. }
    16. prepareContext(tomcat.getHost(), initializers);
    17. return getTomcatWebServer(tomcat);
    18. }

    內置的Servlet容器就是在onRefresh() 方法里面啟動的,至此一個Servlet容器就啟動OK了。

    總結:

    1、new了一個SpringApplication對象,使用SPI技術加載加載 ApplicationContextInitializerApplicationListener 接口實例

    2、調用SpringApplication.run() 方法

    3、調用createApplicationContext()方法創建上下文對象,創建上下文對象同時會注冊spring的核心組件類(ConfigurationClassPostProcessor 、AutowiredAnnotationBeanPostProcessor 等)。

    4、調用refreshContext() 方法啟動Spring容器和內置的Servlet容器

    本人也是一個剛入門的小白,也是看了很多大神寫的這類文章,按照自己思路整理一下,就是為了加深下印象。但也希望對大家有幫助

     

     

      本站是提供個人知識管理的網絡存儲空間,所有內容均由用戶發布,不代表本站觀點。請注意甄別內容中的聯系方式、誘導購買等信息,謹防詐騙。如發現有害或侵權內容,請點擊一鍵舉報。
      轉藏 分享 獻花(0

      0條評論

      發表

      請遵守用戶 評論公約

      類似文章 更多

      主站蜘蛛池模板: 亚洲AV无码一区二区三区性色| 国产精品中文字幕免费| 性欧美vr高清极品| 国产精品 欧美 亚洲 制服| 隔壁老王国产在线精品| 内射女校花一区二区三区| 亚洲国产一区二区三区亚瑟| 久久精品国产亚洲AV高清热| 最新国产AV最新国产在钱| 日韩精品久久久肉伦网站| 99久久婷婷国产综合精品青草漫画| 国产精品福利自产拍在线观看| 高清有码国产一区二区| 久久精品国产99久久久古代| 日韩乱码人妻无码中文字幕视频| 日产精品一卡2卡三卡四乱码| 日本欧美大码a在线观看| 无码成人午夜在线观看 | 亚洲V天堂V手机在线 | 国产成人午夜福利在线播放| 亚洲人成电影网站 久久影视| 国产精品自在欧美一区| 日韩精品亚洲专在线电影| 久久久亚洲AV成人网站| 92精品国产自产在线观看481页| 国精品人妻无码一区免费视频电影| 精品中文字幕人妻一二| 冲田杏梨AV一区二区三区| 国产免费一区二区不卡| 久久久久波多野结衣高潮| 国产精品亚洲二区在线播放| 夜夜躁狠狠躁日日躁| 欧美成人一卡二卡三卡四卡 | 成人免费一区二区三区| 377P欧洲日本亚洲大胆| 久久综合色之久久综合| 亚洲AV无码精品色午夜果冻| 国产精品成人中文字幕| 精品人妻伦九区久久AAA片| 国产欧美在线一区二区三| 夜夜爽一区二区三区精品|