【Spring】原来SpringBoot是这样玩的

  |   0 评论   |   4 浏览

菜瓜:我自己去调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应用需要的工具和组建

标题:【Spring】原来SpringBoot是这样玩的
作者:simulate
地址:https://simulate.net.cn/solo/articles/2020/06/29/1593444082442.html

评论

发表评论