前言

  • springboot 相较 springmvc 最明显的改变就是有一个自己的启动类,本篇将从这个启动类入手,围绕类上注解 @SpringBootApplication 和 类内调用方法 SpringApplication.run() 来学习 springboot 的一些特性,比如配置类是如何加载和调用的,他们之间的关系是什么,springboot 是如何调用和实现内置 tomcat 的?
  • SpringBoot 的启动主要是通过实例化 SpringApplication 来启动的,启动过程主要做了以下几件事情:配置属性、获取监听器,发布应用开始启动事件、初始化输入参数、配置环境,输出 banner、创建上下文、预处理上下文、刷新上下文、再刷新上下文、发布应用已经启动事件、发布应用启动完成事件;

    • 其中 配置属性、获取监听器,发布应用开始启动事件、初始化输入参数、配置环境,通过 @SpringBootApplication 完成;
    • 其中 输出 banner、创建上下文、预处理上下文、刷新上下文、再刷新上下文、发布应用已经启动事件、发布应用启动完成事件,通过 SpringApplication.run() 实现;
      1. @SpringBootApplication
      2. public class Application {
      3. public static void main(String[] args) {
      4. SpringApplication.run(Application.class, args);
      5. }
      6. }
  • 知识准备

  • @SpringBootApplication 有三个重要的子注解:@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan;

    1. @Target(ElementType.TYPE) // 注解的适用范围,其中TYPE用于描述类、接口(包括包注解类型)或enum声明
    2. @Retention(RetentionPolicy.RUNTIME) // 注解的生命周期,保留到class文件中(三个生命周期)
    3. @Documented // 表明这个注解应该被javadoc记录
    4. @Inherited // 子类可以继承该注解
    5. @SpringBootConfiguration // 继承了Configuration,表示当前是注解类
    6. @EnableAutoConfiguration // 开启springboot的注解功能,springboot的四大神器之一,其借助@import的帮助
    7. @ComponentScan(excludeFilters = { // 扫描路径设置(具体使用待确认)
    8. @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
    9. @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
    10. public @interface SpringBootApplication {
    11. ...
    12. }

    1.1 三个子注解

    1.1.1 @Configuration

  • bean 之间的依赖关系通过直接调用对应的类中创建方法;

  • 注:已经使用 @Bean 注解的 dao 类不能在原 dao 类上再使用 @Component 等注解;

    1. @Configuration
    2. public class MockConfiguration{
    3.   @Bean
    4.   public MockService mockService(){
    5.   return new MockServiceImpl(dependencyService());
    6.   }
    7.   @Bean
    8.   public DependencyService dependencyService(){
    9.   return new DependencyServiceImpl();
    10.   }
    11. }

    1.1.2 @ComponentScan

  • 从指定的配置文件或 Config 类中自动扫描并加载符合条件的组件(@Component 等),并将这些组件加载到 IOC 容器中;

  • 可以通过 basePackages 等属性来定制 @ComponentScan 自动扫描的范围,如果不指定,则默认 Spring 框架实现会从声明 @ComponentScan 所在类的 package 进行扫描;
  • SpringBoot 的启动类最好是放在 root package 下,因为默认不指定 basePackages;
  • 使用:将不在默认扫描范围的新包也纳入扫描的范围

    • 方案1:将扫描范围扩大至上级,如:@ComponentScan(“com.cyt.springboot”);
    • 方案2:定义分别扫描两个包,如:@ComponentScan({“com.cyt.springboot.package1”,”com.cyt.springboot.package2”})

      1.1.3 @EnableAutoConfiguration

  • 在 spring 框架中以 @Enable 开头的注解基本功能是借助 @Import 的支持,收集和注册特定场景相关的bean 定义;

  • @EnableAutoConfiguration 的主要作用就是将(内外部)指定范围内的 bean 导入 register 容器,其中
    • 通过 @AutoConfigurationPackage 导入 @SpringBootApplication 所在类的包下的所有组件;
    • 通过 @Import(AutoConfigurationImportSelector.class) 导入外部 jar 包的组件; ```java @SuppressWarnings(“deprecation”) @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited //以下两个注解重点! @AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration { … }

//1. @AutoConfigurationPackage 注解 @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Import({Registrar.class}) public @interface AutoConfigurationPackage { } …..1.1 进一步看 @Import({Registrar.class}) static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports { Registrar() { } //注册当前主程序类的同级以及子级的包中的符合条件的Bean的定义 public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { AutoConfigurationPackages.register(registry, (new AutoConfigurationPackages.PackageImport(metadata)).getPackageName()); } … } ….1.2 Registrar 类的 registerBeanDefinitions 方法实现 bean 的 register public static void register(BeanDefinitionRegistry registry, String… packageNames) { if (registry.containsBeanDefinition(BEAN)) { BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN); ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues(); constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames)); } else { GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); beanDefinition.setBeanClass(AutoConfigurationPackages.BasePackages.class); beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames); beanDefinition.setRole(2); registry.registerBeanDefinition(BEAN, beanDefinition); } } //2. Import(AutoConfigurationImportSelector.class) 2.1 AutoConfigurationImportSelector implates DeferredImportSelector(extends ImportSelector) 2.2 selectImports(): 从 “META-INF/spring.factories 路径下导入外部 jar 包 public String[] selectImports(AnnotationMetadata annotationMetadata) { … List configurations = getCandidateConfigurations(annotationMetadata, … } protected List getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { List configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader()); … } public final class SpringFactoriesLoader{ public static final String FACTORIES_RESOURCE_LOCATION = “META-INF/spring.factories”; public static List loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) { String factoryTypeName = factoryType.getName(); return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList()); }

  1. private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
  2. Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
  3. LinkedMultiValueMap result = new LinkedMultiValueMap();
  4. ....

} }


- loadSpringFactories 函数中外部 jar 包的加载类路径

![image.png](https://cdn.nlark.com/yuque/0/2020/png/611598/1584930117130-9fd408e2-0927-4230-8ea2-e2c938e480de.png#align=left&display=inline&height=670&name=image.png&originHeight=670&originWidth=1303&size=206181&status=done&style=none&width=1303)
<a name="0XJW4"></a>
### 1.1.4 springboot 启动原理图
![](https://cdn.nlark.com/yuque/0/2020/png/611598/1584924007553-1be50c53-fd27-4ea6-8776-989c09a1f27e.png#align=left&display=inline&height=315&originHeight=609&originWidth=1090&size=0&status=done&style=none&width=564)<br />![](https://cdn.nlark.com/yuque/0/2020/jpeg/611598/1584932461756-aea54ff7-2867-494c-bcc4-c6b253597891.jpeg#align=left&display=inline&height=1652&originHeight=1652&originWidth=1314&size=0&status=done&style=none&width=1314)
<a name="lvAtR"></a>
# 2. SpringApplication.run( )

- 通过深入 SpringApplication.run( ) 方法来看 tomcat 在 springboot 中是怎么启动的,在 run 方法中,重点应关注创建应用上下文(createApplicationContext)和刷新上下文(refreshContext);
- 在SpringBoot中启动tomcat的工作在刷新上下这一步。而tomcat的启动主要是实例化两个组件:Connector、Container,一个tomcat实例就是一个Server,一个Server包含多个Service,也就是多个应用程序,每个Service包含多个Connector和一个Container,而一个Container下又包含多个子容器。
```java
//1. SpringApplication.run(SpringBootWebRestfulcurdApplication.class, args);
//2. 在 SpringApplication.class 中调用对应的 run 函数
    public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
        return run(new Class[]{primarySource}, args);
    }
    public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
        return (new SpringApplication(primarySources)).run(args);
    }
//3. 
public ConfigurableApplicationContext run(String... args) {
    ...
    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
        this.configureIgnoreBeanInfo(environment);
        //打印banner,这里你可以自己涂鸦一下,换成自己项目的logo
        Banner printedBanner = this.printBanner(environment);

        //创建应用上下文
        context = this.createApplicationContext();
        exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);

        //预处理上下文
        this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);

        //刷新上下文
        this.refreshContext(context);

        //再刷新上下文
        this.afterRefresh(context, applicationArguments);

        listeners.started(context);
        ...
}

2.1 创建应用上下文(createApplicationContext)

//创建的 AnnotationConfigServletWebServerApplicationContext 类继承了 ServletWebServerApplicationContext,而这个类是最终集成了AbstractApplicationContext。
protected ConfigurableApplicationContext createApplicationContext() {
    Class<?> contextClass = this.applicationContextClass;
    if (contextClass == null) {
        try {
            switch(this.webApplicationType) {
                case SERVLET:
                    //创建AnnotationConfigServletWebServerApplicationContext
                    contextClass = Class.forName("org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext");
                    break;
                case REACTIVE:
                    contextClass = Class.forName("org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext");
                    break;
                default:
                    contextClass = Class.forName("org.springframework.context.annotation.AnnotationConfigApplicationContext");
            }
        } catch (ClassNotFoundException var3) {
            throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", var3);
        }
    }
    return (ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass);
}

2.2 刷新上下文(refreshContext)

//1. SpringApplication.java
private void refreshContext(ConfigurableApplicationContext context) {
    this.refresh(context);
    if (this.registerShutdownHook) {
        try {
            context.registerShutdownHook();
        } catch (AccessControlException var3) {
        }
    }
}
    //这里直接调用最终父类 AbstractApplicationContext.refresh()方法
    protected void refresh(ApplicationContext applicationContext) {
        ((AbstractApplicationContext)applicationContext).refresh();
    }

//2. AbstractApplicationContext.java
public void refresh() throws BeansException, IllegalStateException {
    synchronized(this.startupShutdownMonitor) {
        this.prepareRefresh();
        ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
        this.prepareBeanFactory(beanFactory);
        try {
            this.postProcessBeanFactory(beanFactory);
            this.invokeBeanFactoryPostProcessors(beanFactory);
            this.registerBeanPostProcessors(beanFactory);
            this.initMessageSource();
            this.initApplicationEventMulticaster();
            //调用各个子类的onRefresh()方法,也就说这里要回到子类:ServletWebServerApplicationContext,调用该类的onRefresh()方法
            this.onRefresh();
            this.registerListeners();
            this.finishBeanFactoryInitialization(beanFactory);
            this.finishRefresh();
        } catch (BeansException var9) {
            this.destroyBeans();
            this.cancelRefresh(var9);
            throw var9;
        } finally {
            this.resetCommonCaches();
        }
    }
}
//3. ServletWebServerApplicationContext.java
protected void onRefresh() {
    super.onRefresh();
    try {
        this.createWebServer();
    } catch (Throwable var2) {

    }
}
    //这里是创建webServer,但是还没有启动tomcat,这里是通过ServletWebServerFactory创建,那么接着看下ServletWebServerFactory
    private void createWebServer() {
        ...
        if (webServer == null && servletContext == null) {
            ServletWebServerFactory factory = this.getWebServerFactory();
            this.webServer = factory.getWebServer(new ServletContextInitializer[]{this.getSelfInitializer()});
        } 
        ...
        this.initPropertySources();
    }
        //接口
        public interface ServletWebServerFactory {
            WebServer getWebServer(ServletContextInitializer... initializers);
        }
        //实现
        AbstractServletWebServerFactory
        JettyServletWebServerFactory
        TomcatServletWebServerFactory //这里我们使用的tomcat,所以我们查看TomcatServletWebServerFactory
        UndertowServletWebServerFactory

//4. TomcatServletWebServerFactory.java
public WebServer getWebServer(ServletContextInitializer... initializers) {
    Tomcat tomcat = new Tomcat();
    File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
    tomcat.setBaseDir(baseDir.getAbsolutePath());
    //创建Connector对象
    Connector connector = new Connector(this.protocol);
    tomcat.getService().addConnector(connector);
    customizeConnector(connector);
    tomcat.setConnector(connector);
    tomcat.getHost().setAutoDeploy(false);
    //4.1 创建 Engine 容器
    configureEngine(tomcat.getEngine());
    for (Connector additionalConnector : this.additionalTomcatConnectors) {
        tomcat.getService().addConnector(additionalConnector);
    }
    prepareContext(tomcat.getHost(), initializers);
    //4.2 启动 tomcat
    return getTomcatWebServer(tomcat);
}
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
    return new TomcatWebServer(tomcat, getPort() >= 0);
}

    //4.1 Tomcat.java,返回 Engine 容器,Engine是最高级别容器,Host是Engine的子容器,Context是Host的子容器,Wrapper是Context的子容器
    public Engine getEngine() {
        Service service = getServer().findServices()[0];
        if (service.getContainer() != null) {
            return service.getContainer();
        }
        Engine engine = new StandardEngine();
        engine.setName( "Tomcat" );
        engine.setDefaultHost(hostname);
        engine.setRealm(createDefaultRealm());
        service.setContainer(engine);
        return engine;
    }

    //4.2 TomcatWebServer.java 调用构造函数实例化TomcatWebServer
    public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
        Assert.notNull(tomcat, "Tomcat Server must not be null");
        this.tomcat = tomcat;
        this.autoStart = autoStart;
        initialize();
    }
    private void initialize() throws WebServerException {
        //在控制台会看到这句日志
        logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
        synchronized (this.monitor) {
            ...
            this.tomcat.start();//===启动tomcat服务===
            ...
        }
    }

2.3 补充

2.3.1 getWebServer 与 ServletContextInitializer

  • 在初始化 servletContext 容器的过程中,可将 springboot 默认或自定义实现的 ServletContextInitializer 组件,如 servlet、filter、listener 等加入容器; ```java //TomcatServletWebServerFactory.java —配置 tomcat 的各种容器 public WebServer getWebServer(ServletContextInitializer… initializers) { prepareContext(tomcat.getHost(), initializers); return getTomcatWebServer(tomcat); } // 1. tomcat 的 prepareContext protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
      ...
      //添加的默认的 defaultServlet 和 jspServlet
      if (this.isRegisterDefaultServlet()) {
          this.addDefaultServlet(context);
      }
      if (this.shouldRegisterJspServlet()) {
          this.addJspServlet(context);
          this.addJasperInitializer(context);
      }
      context.addLifecycleListener(new TomcatServletWebServerFactory.StaticResourceConfigurer(context));
      //处理 ServletContextInitializer   
      ServletContextInitializer[] initializersToUse = this.mergeInitializers(initializers);
      host.addChild(context);
      this.configureContext(context, initializersToUse);
      this.postProcessContext(context);
    
    }
  1. 通过接口 ServletContextInitializer 往 ServletContext 容器中注册 Servlet, Filter 或者 EventListener // ServletContextInitializer 主要被 RegistrationBean 实现 public abstract class RegistrationBean implements ServletContextInitializer, Ordered { … } //RegistrationBean 有四个继承子类,是 ServletContext 容器中的抽象组件 AbstractFilterRegistrationBean DelegatingFilterProxyRegistrationBean DispatcherServletRegistrationBean DynamicRegistrationBean FilterRegistrationBean ServletListenerRegistrationBean ServletRegistrationBean ``` image.png

    总结与思考

    参考