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外,其余的容器都是可以有多个。

 

posted @ 2020-02-13 17:44  hxwang  阅读(690)  评论(0编辑  收藏  举报