【Spring】原來SpringBoot是這樣玩的

渠成 發表於 2020-06-29

菜瓜:我自己去調Mvc的原始碼差點沒給Spring的邏輯秀死。。。難受

水稻:那今天我們們看一個簡單易用的SpringBoot吧

菜瓜:可以,這個我熟悉

水稻:熟悉?

菜瓜:當我沒說,請開始你的表演

水稻:我沒有別的意思,就是單純的反問(手動狗頭)。平時工作中用多了SpringBoot。我們們今天帶著幾個問題來看看它的操作吧

  1. 如何啟動Spring容器
  2. 如何內嵌Tomcat容器
  3. 如何完成自動裝配,就是0配置

菜瓜:你確定這是我熟悉的SpringBoot???

水稻:。。。看過來

  • 啟動類點進去
  • public ConfigurableApplicationContext run(String... args) {
       StopWatch stopWatch = new StopWatch();
       stopWatch.start();
       ...
       listeners.starting();
       try {
          ...
          // ①建立Spring上下文容器物件 - 預設Servlet容器
          context = createApplicationContext();
          ...
          // ②呼叫refresh方法 - 回到熟悉的容器啟動流程
          refreshContext(context);
          afterRefresh(context, applicationArguments);
          ...   
       ...
       return context;
    }
  • ① 建立上下文物件
    • protected ConfigurableApplicationContext createApplicationContext() {
         Class<?> contextClass = this.applicationContextClass;
         if (contextClass == null) {
            try {
               switch (this.webApplicationType) {
               case SERVLET:
                  contextClass = Class.forName(DEFAULT_SERVLET_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) {
               ...
            }
         }
         return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
      }
  • ②啟動容器
    •     @Override
          public void refresh() throws BeansException, IllegalStateException {
              synchronized (this.startupShutdownMonitor) {
                  prepareRefresh();
                  ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
      
                  // Prepare the bean factory for use in this context.
                  prepareBeanFactory(beanFactory);
      
                  try {
                      ...
                      // ①springboot 內嵌tomcat容器
                      onRefresh();
                      ...
      
          }
      
          @Override
          protected void onRefresh() {
              super.onRefresh();
              try {
            // ②建立Servlet容器 預設tomcat
                  createWebServer();
              }
              ...
          }      
           
            
          private void createWebServer() {
              WebServer webServer = this.webServer;
              ServletContext servletContext = getServletContext();
              if (webServer == null && servletContext == null) {
                  ServletWebServerFactory factory = getWebServerFactory();
            // ③看進去 回到mvc整合tomcat的場景
                  this.webServer = factory.getWebServer(getSelfInitializer());
              }
              ...
              initPropertySources();
          }      
               
          @Override
          public WebServer getWebServer(ServletContextInitializer... initializers) {
              Tomcat tomcat = new Tomcat();
              File baseDir = (this.baseDirectory != null) ? this.baseDirectory
                      : createTempDir("tomcat");
              tomcat.setBaseDir(baseDir.getAbsolutePath());
              Connector connector = new Connector(this.protocol);
              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);
              return getTomcatWebServer(tomcat);
          }      

水稻:好了,第一步和第二步完成了

菜瓜:就這???

水稻:是不是極其簡單,令人髮指。重頭戲是後面的自動裝配

  • 回到我們們啟動類的註解上
  • ...
    // 標記自身被掃描  
    @SpringBootConfiguration
    // 下一步 - 自動裝配入口 
    @EnableAutoConfiguration
    // 掃描bean路徑 - 約定是啟動類所在的包:所以沒事別把啟動類挪走(都是淚)
    @ComponentScan(excludeFilters = {
            @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
            @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
    public @interface SpringBootApplication 
      
    ->   
    @Import(AutoConfigurationImportSelector.class)
    public @interface EnableAutoConfiguration 
      
    -> 重要
    public class AutoConfigurationImportSelector ... {
      @Override
      public void process(AnnotationMetadata annotationMetadata,
            DeferredImportSelector deferredImportSelector) {
         ...
         // 獲取以EnableAutoConfiguration命名的/META-INF/Spring.factories檔案中的value去重 
         AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector).getAutoConfigurationEntry(getAutoConfigurationMetadata(),            annotationMetadata);
         // 啟動的時候斷點可以看到
    this.autoConfigurationEntries.add(autoConfigurationEntry); for (String importClassName : autoConfigurationEntry.getConfigurations()) { this.entries.putIfAbsent(importClassName, annotationMetadata); } } }
  • AutoConfigurationImportSelector 中的process是被ConfigurationClassPostProcessor通過processConfigBeanDefinitions方法呼叫(呼叫鏈如下)
    1. this.processConfigBeanDefinitions(registry);
    2. parser.parse(candidates);
    3. this.parse(((AnnotatedBeanDefinition)bd).getMetadata(), holder.getBeanName());
    4. sourceClass = this.doProcessConfigurationClass(configClass, sourceClass);
    5. this.processImports(configClass, sourceClass, this.getImports(sourceClass), true);
    6. this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector)selector);  
    7. handler.processGroupImports();
    8. grouping.getImports().forEach...
    9. this.group.process(...);
    -- 
    蒐集到需要自動裝配的類,封裝成BeanDefinition後續例項化,實現自動裝配功能
    譬如引入WebMvcAutoConfiguration類 - webmvc功能自動整合
    org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration 

菜瓜:原來如此。你把呼叫鏈拎出來就簡單了很多。自動裝配就是通過SPI載入org.springframework.boot.autoconfigure包下的class,封裝成BeanDefinition後交給容器載入

 

總結:SpringBoot只需要一行程式碼便能啟動一個Java應用。完全解放開發者複雜的配置

  • 內嵌Servlet容器,預設tomcat
  • 啟動SpringWeb容器
  • 自動裝配了簡單web應用需要的工具和組建