zoukankan      html  css  js  c++  java
  • SpringBoot中的tomcat是如何启动的?

    (---------转自网上文章-----------)

    前言

    我们知道SpringBoot给我们带来了一个全新的开发体验,我们可以把web程序打包成jar包,直接启动,这就得益于SpringBoot内置了容器,可以直接启动。本文将以Tomcat为例,来看看SpringBoot是如何启动Tomcat的,同时也将展开学习Tomcat的源码,了解Tomcat的设计。

    从Main方法说起

    用过SpringBoot的人都知道,首先要写一个main方法来启动。

    1 @SpringBootApplication
    2 public class TomcatDebugApplication {
    3 
    4     public static void main(String[] args) {
    5         SpringApplication.run(TomcatDebugApplication.class, args);
    6     }
    
    

    我们直接点击run方法的源码,跟踪下来,发现run方法原来是调用ConfigurableApplicationContext 方法,源码如下:

     1 public ConfigurableApplicationContext run(String... args) {
     2         StopWatch stopWatch = new StopWatch();
     3         stopWatch.start();
     4         ConfigurableApplicationContext context = null;
     5         Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
     6         //设置系统属性『java.awt.headless』,为true则启用headless模式支持
     7         configureHeadlessProperty();
     8         /*
     9          * 通过*SpringFactoriesLoader*检索*META-INF/spring.factories*,
    10          * 找到声明的所有SpringApplicationRunListener的实现类并将其实例化,
    11          * 之后逐个调用其started()方法,广播SpringBoot要开始执行了
    12          */
    13         SpringApplicationRunListeners listeners = getRunListeners(args);
    14         //发布应用开始启动事件
    15         listeners.starting();
    16         try {
    17             //初始化参数
    18             ApplicationArguments applicationArguments = new DefaultApplicationArguments(
    19                     args);
    20             /*
    21              * 创建并配置当前SpringBoot应用将要使用的Environment(包括配置要使用的PropertySource以及Profile),
    22              * 并遍历调用所有的SpringApplicationRunListener的environmentPrepared()方法,广播Environment准备完毕。
    23              */
    24             ConfigurableEnvironment environment = prepareEnvironment(listeners,
    25                     applicationArguments);
    26             configureIgnoreBeanInfo(environment);
    27             //打印banner
    28             Banner printedBanner = printBanner(environment);
    29             //创建应用上下文
    30             context = createApplicationContext();
    31             //通过*SpringFactoriesLoader*检索*META-INF/spring.factories*,获取并实例化异常分析器。
    32             exceptionReporters = getSpringFactoriesInstances(
    33                     SpringBootExceptionReporter.class,
    34                     new Class[] { ConfigurableApplicationContext.class }, context);
    35             /*
    36              * 为ApplicationContext加载environment,之后逐个执行ApplicationContextInitializer的initialize()方法来进一步封装ApplicationContext,
    37              * 并调用所有的SpringApplicationRunListener的contextPrepared()方法,【EventPublishingRunListener只提供了一个空的contextPrepared()方法】,
    38              * 之后初始化IoC容器,并调用SpringApplicationRunListener的contextLoaded()方法,广播ApplicationContext的IoC加载完成,
    39              * 这里就包括通过**@EnableAutoConfiguration**导入的各种自动配置类。
    40              */
    41             prepareContext(context, environment, listeners, applicationArguments,
    42                     printedBanner);
    43             //刷新上下文
    44             refreshContext(context);
    45             //再一次刷新上下文,其实是空方法,可能是为了后续扩展。
    46             afterRefresh(context, applicationArguments);
    47             stopWatch.stop();
    48             if (this.logStartupInfo) {
    49                 new StartupInfoLogger(this.mainApplicationClass)
    50                         .logStarted(getApplicationLog(), stopWatch);
    51             }
    52             //发布应用已经启动的事件
    53             listeners.started(context);
    54             /*
    55              * 遍历所有注册的ApplicationRunner和CommandLineRunner,并执行其run()方法。
    56              * 我们可以实现自己的ApplicationRunner或者CommandLineRunner,来对SpringBoot的启动过程进行扩展。
    57              */
    58             callRunners(context, applicationArguments);
    59         }
    60         catch (Throwable ex) {
    61             handleRunFailure(context, ex, exceptionReporters, listeners);
    62             throw new IllegalStateException(ex);
    63         }
    64 
    65         try {
    66             //应用已经启动完成的监听事件
    67             listeners.running(context);
    68         }
    69         catch (Throwable ex) {
    70             handleRunFailure(context, ex, exceptionReporters, null);
    71             throw new IllegalStateException(ex);
    72         }
    73         return context;
    74     }

    其实这个方法可以总结为下面几个步骤:

    1. 配置属性
    2. 获取监听器,发布应用开始启动事件
    3. 初始化输入参数
    4. 配置环境,输出banner
    5. 创建上下文
    6. 预处理上下文
    7. 刷新上下文
    8. 再刷新上下文
    9. 发布应用已经启动事件
    10.发布应用启动完成事件 

    其实上面这段代码,如果只分析Tomcat内容的话,只需要关注两个部分:
    上下文是如何创建的,对应方法:createApplicationContext()
    上下文是如何刷新的,对应方法:refreshContext(context)
    接下来,我们来看看这两个方法做了什么。

     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("org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext");
     8                     break;
     9                 case REACTIVE:
    10                     contextClass = Class.forName("org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext");
    11                     break;
    12                 default:
    13                     contextClass = Class.forName("org.springframework.context.annotation.AnnotationConfigApplicationContext");
    14                 }
    15             } catch (ClassNotFoundException var3) {
    16                 throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", var3);
    17             }
    18         }
    19 
    20         return (ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass);
    21     }

    这里就是根据webApplicationType来判断创建哪种类型的Servlet,代码中分别对应着Web类型(SERVLET),响应式Web类型(REACTIVE),非Web类型(default),我们建立的是Web类型,所以肯定实例化AnnotationConfigServletWebServerApplicationContext类,用图来说明下这个类的关系。

     通过这个类图可以知道,这个类继承的是ServletWebServerApplicationContext,这才是真正的主角,而这个类最终继承了AbstractApplicationContext,了解完创建上下文的情况后,我们再来看看刷新上下文。

     1 //类: SpringApplication
     2     
     3     private void refreshContext(ConfigurableApplicationContext context) {
     4         refresh(context);
     5         if (this.registerShutdownHook) {
     6             try {
     7                 context.registerShutdownHook();
     8             }
     9             catch (AccessControlException ex) {
    10                 // Not allowed in some environments.
    11             }
    12         }
    13     }
    14     
    15     protected void refresh(ApplicationContext applicationContext) {
    16         Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
    17         ((AbstractApplicationContext) applicationContext).refresh();
    18     }

    这里还是直接传递调用本类的refresh(context)方法,最后强转成父类AbstractApplicationContext,调用其refresh()方法,源码如下:

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

    这里,我们看到onRefresh()方法是调用其子类的实现,根据我们上文的分析,我们这里的子类是ServletWebServerApplicationContext

     1 //类: ServletWebServerApplicationContext
     2     
     3     protected void onRefresh() {
     4         super.onRefresh();
     5         try {
     6             createWebServer();
     7         }
     8         catch (Throwable ex) {
     9             throw new ApplicationContextException("Unable to start web server", ex);
    10         }
    11     }
    12 
    13     private void createWebServer() {
    14         WebServer webServer = this.webServer;
    15         ServletContext servletContext = getServletContext();
    16         if (webServer == null && servletContext == null) {
    17             ServletWebServerFactory factory = getWebServerFactory();
    18             this.webServer = factory.getWebServer(getSelfInitializer());
    19         }
    20         else if (servletContext != null) {
    21             try {
    22                 getSelfInitializer().onStartup(servletContext);
    23             }
    24             catch (ServletException ex) {
    25                 throw new ApplicationContextException("Cannot initialize servlet context",
    26                         ex);
    27             }
    28         }
    29         initPropertySources();
    30     }

    到这里,终于见到了庐山真面目,createWebServer()就是启动web服务,但是还没有真正启动Tomcat,既然webServer是通过ServletWebServerFactory来获取的,那就来看看这个工厂的真面目。

    走进Tomcat内部

    根据上图,我们发现工厂类是一个接口,各个具体服务的实现是由各个子类来完成的,所以,就去看看TomcatServletWebServerFactory.getWebServer()的实现。

      public WebServer getWebServer(ServletContextInitializer... initializers) {
            Tomcat tomcat = new Tomcat();
            File baseDir = this.baseDirectory != null ? this.baseDirectory : this.createTempDir("tomcat");
            tomcat.setBaseDir(baseDir.getAbsolutePath());
            Connector connector = new Connector(this.protocol);
            tomcat.getService().addConnector(connector);
            this.customizeConnector(connector);
            tomcat.setConnector(connector);
            tomcat.getHost().setAutoDeploy(false);
            this.configureEngine(tomcat.getEngine());
            Iterator var5 = this.additionalTomcatConnectors.iterator();
    
            while(var5.hasNext()) {
                Connector additionalConnector = (Connector)var5.next();
                tomcat.getService().addConnector(additionalConnector);
            }
    
            this.prepareContext(tomcat.getHost(), initializers);
            return this.getTomcatWebServer(tomcat);
        }

    根据上面的代码,我们发现其实主要做两件事,第一件事就是把Connnctor(我们称之为连接器)对象添加到Tomcat中;第二件事就是configureEngine,这连接器我们勉强能理解(不理解后面会述说),那这个Engine是什么呢?查看tomcat.getEngine()的源码:

     1   public Engine getEngine() {
     2         Service service = getServer().findServices()[0];
     3         if (service.getContainer() != null) {
     4             return service.getContainer();
     5         }
     6         Engine engine = new StandardEngine();
     7         engine.setName( "Tomcat" );
     8         engine.setDefaultHost(hostname);
     9         engine.setRealm(createDefaultRealm());
    10         service.setContainer(engine);
    11         return engine;
    12     }

    根据上面的源码,我们发现,原来这个Engine是容器,继续跟踪源码,找到Container接口。

     上图中,我们看到了4个子接口,分别是Engine、Host、Context、Wrapper。我们从继承关系上可以知道他们都是容器,那么他们之间的区别是什么呢?那就从它们的注释来分析分析:

     1 /**
     2  * If used, an Engine is always the top level Container in a Catalina
     3  * hierarchy. Therefore, the implementation's <code>setParent()</code> method
     4  * should throw <code>IllegalArgumentException</code>.
     5  *
     6  * @author Craig R. McClanahan
     7  */
     8 public interface Engine extends Container {
     9     //省略代码
    10 }
    11 
    12 
    13 /**
    14  * <p>
    15  * The parent Container attached to a Host is generally an Engine, but may
    16  * be some other implementation, or may be omitted if it is not necessary.
    17  * <p>
    18  * The child containers attached to a Host are generally implementations
    19  * of Context (representing an individual servlet context).
    20  *
    21  * @author Craig R. McClanahan
    22  */
    23 public interface Host extends Container {
    24     //省略代码
    25 }
    26 
    27 
    28 /**
    29  * <p>
    30  * The parent Container attached to a Context is generally a Host, but may
    31  * be some other implementation, or may be omitted if it is not necessary.
    32  * <p>
    33  * The child containers attached to a Context are generally implementations
    34  * of Wrapper (representing individual servlet definitions).
    35  * <p>
    36  *
    37  * @author Craig R. McClanahan
    38  */
    39 public interface Context extends Container, ContextBind {
    40     //省略代码
    41 }
    42 
    43 
    44 /**
    45  * <p>
    46  * The parent Container attached to a Wrapper will generally be an
    47  * implementation of Context, representing the servlet context (and
    48  * therefore the web application) within which this servlet executes.
    49  * <p>
    50  * Child Containers are not allowed on Wrapper implementations, so the
    51  * <code>addChild()</code> method should throw an
    52  * <code>IllegalArgumentException</code>.
    53  *
    54  * @author Craig R. McClanahan
    55  */
    56 public interface Wrapper extends Container {
    57     //省略代码
    58 }

    上面的注释翻译过来就是:Engine是最高级别的容器,其子容器是HostHost的子容器是ContextWrapperContext的子容器,所以这4个容器的关系就是父子关系。也就是:
    Engine > Host > Context > Wrapper 接着,再看看Tomcat的源码:

      1 //部分代码
      2 public class Tomcat {
      3 
      4     //设置连接器
      5     public void setConnector(Connector connector) {
      6         Service service = getService();
      7         boolean found = false;
      8         for (Connector serviceConnector : service.findConnectors()) {
      9             if (connector == serviceConnector) {
     10                 found = true;
     11             }
     12         }
     13         if (!found) {
     14             service.addConnector(connector);
     15         }
     16     }
     17 
     18     //获取service
     19     public Service getService() {
     20         return getServer().findServices()[0];
     21     }
     22 
     23     //设置Host容器
     24     public void setHost(Host host) {
     25         Engine engine = getEngine();
     26         boolean found = false;
     27         for (Container engineHost : engine.findChildren()) {
     28             if (engineHost == host) {
     29                 found = true;
     30             }
     31         }
     32         if (!found) {
     33             engine.addChild(host);
     34         }
     35     }
     36 
     37     //获取Engine容器
     38     public Engine getEngine() {
     39         Service service = getServer().findServices()[0];
     40         if (service.getContainer() != null) {
     41             return service.getContainer();
     42         }
     43         Engine engine = new StandardEngine();
     44         engine.setName( "Tomcat" );
     45         engine.setDefaultHost(hostname);
     46         engine.setRealm(createDefaultRealm());
     47         service.setContainer(engine);
     48         return engine;
     49     }
     50 
     51     //获取server
     52     public Server getServer() {
     53 
     54         if (server != null) {
     55             return server;
     56         }
     57 
     58         System.setProperty("catalina.useNaming", "false");
     59 
     60         server = new StandardServer();
     61 
     62         initBaseDir();
     63 
     64         server.setPort( -1 );
     65 
     66         Service service = new StandardService();
     67         service.setName("Tomcat");
     68         server.addService(service);
     69         return server;
     70     }
     71 
     72     //添加context容器
     73     public Context addContext(Host host, String contextPath, String contextName,
     74             String dir) {
     75         silence(host, contextName);
     76         Context ctx = createContext(host, contextPath);
     77         ctx.setName(contextName);
     78         ctx.setPath(contextPath);
     79         ctx.setDocBase(dir);
     80         ctx.addLifecycleListener(new FixContextListener());
     81 
     82         if (host == null) {
     83             getHost().addChild(ctx);
     84         } else {
     85             host.addChild(ctx);
     86         }
     87         return ctx;
     88     }
     89 
     90     //添加Wrapper容器
     91     public Context addWebapp(Host host, String contextPath, String docBase) {
     92         LifecycleListener listener = null;
     93         try {
     94             Class<?> clazz = Class.forName(getHost().getConfigClass());
     95             listener = (LifecycleListener) clazz.getConstructor().newInstance();
     96         } catch (ReflectiveOperationException e) {
     97             // Wrap in IAE since we can't easily change the method signature to
     98             // to throw the specific checked exceptions
     99             throw new IllegalArgumentException(e);
    100         }
    101 
    102         return addWebapp(host,  contextPath, docBase, listener);
    103     }
    104 }

    阅读TomcatgetServer()方法,我们可以知道,Tomcat的最顶层就是Server,也就是Tomcat的实例,一个Tomcat一个Server;通过getEngine()我们可以了解到Server下面是Service,而且是多个,一个Service代表我们部署的一个应用,还可以知道,Engine容器,一个Service只有一个;根据父子关系,通过setHost()源码可知,host容器有多个;同理,我们发现addContext()源码下,Context也是多个;addServlet()表明Wrapper容器也是多个,而且这段代码也暗示了,其实WrapperServlet是一层意思。另外我们根据setConnector源码可以知道,连接器(Connector)是设置在Service下的,而且是可以设置多个连接器(Connector)。

    根据上面的分析,可以总结出Tomcat主要包含了 2 个核心组件:连接器(Connector)和容器(Container),用图表示如下:

     一个Tomcat是一个Server,一个Server下有多个Service,也就是我们部署的多个应用,一个应用下有多个连接器(Connector)和一个容器(Container),容器下有多个子容器,关系用图表示如下:

     Engine下有多个Host子容器,Host下有多个Context子容器,Context下有多个Wrapper子容器。

    总结

    SpringBoot的启动是通过new SpringApplication()实例来启动的,启动过程主要做如下几件事情:

    1. 配置属性
    2. 获取监听器,发布应用开始启动事件
    3. 初始化输入参数
    4. 配置环境,输出banner
    5. 创建上下文
    6. 预处理上下文
    7. 刷新上下文
    8. 刷新上下文
    9. 发布应用已经启动事件
    10.发布应用启动完成事件 

    而启动Tomcat就是在第7步的“刷新上下文”;Tomcat的启动主要是初始化2个核心组件,连接器(Connector)和容器(Container),一个Tomcat实例就是一个Server,一个Server包含多个Service,也就是多个应用程序,每个Service包含多个连接器(Connetor)和一个容器(Container),而容器下又有多个子容器,按照父子关系分别为:Engine、Host、Context、Wrapper,其中除了Engine外,其余的容器都是可以有多个。

  • 相关阅读:
    Mysql常用语句
    Java基础知识总结
    ExtJS4.2学习(三)——入门基础
    ExtJS4.2学习(二)——入门基础
    ExtJS 4.2学习(一)——环境搭建
    大三下半学期“饭店餐饮系统”项目笔记
    GUI练习——列出指定目录内容
    Collections之sort的两个方法(自然排序和自定义比较器排序)
    Java集合总结
    访问数据库
  • 原文地址:https://www.cnblogs.com/whx7762/p/12304434.html
Copyright © 2011-2022 走看看