来自:掘金 (作者:木木匠)

原文链接:

https://juejin.im/post/5d3f95ebf265da039e12959e

前言

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

从 Main 方法说起

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

@SpringBootApplicationpublic class TomcatdebugApplication {

public static void main(String[] args) { SpringApplication.run(TomcatdebugApplication.class, args); }

}

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

public ConfigurableApplicationContext run(String... args) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null; Collection exceptionReporters = new ArrayList<>();//设置系统属性『java.awt.headless』,为true则启用headless模式支持 configureHeadlessProperty();//通过*SpringFactoriesLoader*检索*META-INF/spring.factories*,//找到声明的所有SpringApplicationRunListener的实现类并将其实例化,//之后逐个调用其started()方法,广播SpringBoot要开始执行了 SpringApplicationRunListeners listeners = getRunListeners(args);//发布应用开始启动事件 listeners.starting();try {//初始化参数 ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);//创建并配置当前SpringBoot应用将要使用的Environment(包括配置要使用的PropertySource以及Profile),//并遍历调用所有的SpringApplicationRunListener的environmentPrepared()方法,广播Environment准备完毕。 ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); configureIgnoreBeanInfo(environment);//打印banner Banner printedBanner = printBanner(environment);//创建应用上下文 context = createApplicationContext();//通过*SpringFactoriesLoader*检索*META-INF/spring.factories*,获取并实例化异常分析器 exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,new Class[] { ConfigurableApplicationContext.class }, context);//为ApplicationContext加载environment,之后逐个执行ApplicationContextInitializer的initialize()方法来进一步封装ApplicationContext,//并调用所有的SpringApplicationRunListener的contextPrepared()方法,【EventPublishingRunListener只提供了一个空的contextPrepared()方法】,//之后初始化IoC容器,并调用SpringApplicationRunListener的contextLoaded()方法,广播ApplicationContext的IoC加载完成,//这里就包括通过**@EnableAutoConfiguration**导入的各种自动配置类。 prepareContext(context, environment, listeners, applicationArguments, printedBanner);//刷新上下文 refreshContext(context);//再一次刷新上下文,其实是空方法,可能是为了后续扩展。 afterRefresh(context, applicationArguments); stopWatch.stop();if (this.logStartupInfo) {new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch); }//发布应用已经启动的事件 listeners.started(context);//遍历所有注册的ApplicationRunner和CommandLineRunner,并执行其run()方法。//我们可以实现自己的ApplicationRunner或者CommandLineRunner,来对SpringBoot的启动过程进行扩展。 callRunners(context, applicationArguments); }catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, listeners);throw new IllegalStateException(ex); }try {//应用已经启动完成的监听事件 listeners.running(context); }catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, null);throw new IllegalStateException(ex); }return context; }

其实这个方法我们可以简单的总结下步骤为

  1. 配置属性

  2. 获取监听器,发布应用开始启动事件

  3. 初始化输入参数

  4. 配置环境,输出banner

  5. 创建上下文

  6. 预处理上下文

  7. 刷新上下文

  8. 再刷新上下文

  9. 发布应用已经启动事件

  10. 发布应用启动完成事件

其实上面这段代码,如果只要分析tomcat内容的话,只需要关注两个内容即可,上下文是如何创建的,上下文是如何刷新的,分别对应的方法就是createApplicationContext() 和refreshContext(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) {throw new IllegalStateException("Unable create a default ApplicationContext, " + "please specify an ApplicationContextClass", ex); } }return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass); }

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

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

//类:SpringApplication.java

private void refreshContext(ConfigurableApplicationContext context) {//直接调用刷新方法 refresh(context);if (this.registerShutdownHook) {try { context.registerShutdownHook(); }catch (AccessControlException ex) {// Not allowed in some environments. } } }//类:SpringApplication.java

protected void refresh(ApplicationContext applicationContext) { Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext); ((AbstractApplicationContext) applicationContext).refresh(); }

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

// 类:AbstractApplicationContextpublic void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {// Prepare this context for refreshing. prepareRefresh();

// Tell the subclass to refresh the internal bean factory. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

// Prepare the bean factory for use in this context. prepareBeanFactory(beanFactory);

try {// Allows post-processing of the bean factory in context subclasses. postProcessBeanFactory(beanFactory);

// Invoke factory processors registered as beans in the context. invokeBeanFactoryPostProcessors(beanFactory);

// Register bean processors that intercept bean creation. registerBeanPostProcessors(beanFactory);

// Initialize message source for this context. initMessageSource();

// Initialize event multicaster for this context. initApplicationEventMulticaster();

// Initialize other special beans in specific context subclasses.这里的意思就是调用各个子类的onRefresh() onRefresh();

// Check for listener beans and register them. registerListeners();

// Instantiate all remaining (non-lazy-init) singletons. finishBeanFactoryInitialization(beanFactory);

// Last step: publish corresponding event. finishRefresh(); }

catch (BeansException ex) {if (logger.isWarnEnabled()) { logger.warn("Exception encountered during context initialization - " +"cancelling refresh attempt: " + ex); }

// Destroy already created singletons to avoid dangling resources. destroyBeans();

// Reset 'active' flag. cancelRefresh(ex);

// Propagate exception to caller.throw ex; }

finally {// Reset common introspection caches in Spring's core, since we// might not ever need metadata for singleton beans anymore... resetCommonCaches(); } } }

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

//类:ServletWebServerApplicationContextprotected void onRefresh() {super.onRefresh();try { createWebServer(); }catch (Throwable ex) {throw new ApplicationContextException("Unable to start web server", ex); } }

private void createWebServer() { WebServer webServer = this.webServer; ServletContext servletContext = getServletContext();if (webServer == null && servletContext == null) { ServletWebServerFactory factory = getWebServerFactory();this.webServer = factory.getWebServer(getSelfInitializer()); }else if (servletContext != null) {try { getSelfInitializer().onStartup(servletContext); }catch (ServletException ex) {throw new ApplicationContextException("Cannot initialize servlet context", ex); } } initPropertySources(); }

到这里,其实庐山真面目已经出来了,createWebServer()就是启动web服务,但是还没有真正启动Tomcat,既然webServer是通过ServletWebServerFactory来获取的,我们就来看看这个工厂的真面目。

走进Tomcat内部

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

@Overridepublic 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); }

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

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; }

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

上图中,我们看到了4个子接口,分别是Engine,Host,Context,Wrapper。我们从继承关系上可以知道他们都是容器,那么他们到底有啥区别呢?我看看他们的注释是怎么说的。

/** If used, an Engine is always the top level Container in a Catalina * hierarchy. Therefore, the implementation's setParent() method * should throw IllegalArgumentException. * * @author Craig R. McClanahan */public interface Engine extends Container {//省略代码}/** * 


* The parent Container attached to a Host is generally an Engine, but may
* be some other implementation, or may be omitted if it is not necessary.
*


* The child containers attached to a Host are generally implementations
* of Context (representing an individual servlet context).
*
* @author Craig R. McClanahan
*/


public interface Host extends Container {
//省略代码

}
/***


* The parent Container attached to a Context is generally a Host, but may
* be some other implementation, or may be omitted if it is not necessary.
*


* The child containers attached to a Context are generally implementations
* of Wrapper (representing individual servlet definitions).
*


*
* @author Craig R. McClanahan
*/


public interface Context extends Container, ContextBind {
//省略代码
}
/**


* The parent Container attached to a Wrapper will generally be an
* implementation of Context, representing the servlet context (and
* therefore the web application) within which this servlet executes.
*


* Child Containers are not allowed on Wrapper implementations, so the
* addChild() method should throw an
* IllegalArgumentException.
*
* @author Craig R. McClanahan
*/


public interface Wrapper extends Container {

//省略代码
}

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

//部分源码,其余部分省略。public class Tomcat {//设置连接器public void setConnector(Connector connector) { Service service = getService();boolean found = false;for (Connector serviceConnector : service.findConnectors()) {if (connector == serviceConnector) { found = true; } }if (!found) { service.addConnector(connector); } }//获取servicepublic Service getService() {return getServer().findServices()[0]; }//设置Host容器public void setHost(Host host) { Engine engine = getEngine();boolean found = false;for (Container engineHost : engine.findChildren()) {if (engineHost == host) { found = true; } }if (!found) { engine.addChild(host); } }//获取Engine容器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; }//获取serverpublic Server getServer() {

if (server != null) {return server; }

 System.setProperty("catalina.useNaming", "false");

 server = new StandardServer();

 initBaseDir();

// Set configuration source ConfigFileLoader.setSource(new CatalinaBaseConfigurationSource(new File(basedir), null));

 server.setPort( -1 );

 Service service = new StandardService(); service.setName("Tomcat"); server.addService(service);return server; }

//添加Context容器public Context addContext(Host host, String contextPath, String contextName, String dir) { silence(host, contextName); Context ctx = createContext(host, contextPath); ctx.setName(contextName); ctx.setPath(contextPath); ctx.setDocBase(dir); ctx.addLifecycleListener(new FixContextListener());

if (host == null) { getHost().addChild(ctx); } else { host.addChild(ctx); }

//添加Wrapper容器public static Wrapper addServlet(Context ctx, String servletName, Servlet servlet) {// will do class for name and set init params Wrapper sw = new ExistingStandardWrapper(servlet); sw.setName(servletName); ctx.addChild(sw);

return sw; }

}

阅读Tomcat的getServer()我们可以知道,Tomcat的最顶层是Server,Server就是Tomcat的实例,一个Tomcat一个Server;通过getEngine()我们可以了解到Server下面是Service,而且是多个,一个Service代表我们部署的一个应用,而且我们还可以知道,Engine容器,一个service只有一个;根据父子关系,我们看setHost()源码可以知道,host容器有多个;同理,我们发现addContext()源码下,Context也是多个;addServlet()表明Wrapper容器也是多个,而且这段代码也暗示了,其实Wrapper和Servlet是一层意思。另外我们根据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外,其余的容器都是可以有多个。

下期展望

本期文章通过SpringBoot的启动来窥探了Tomcat的内部结构,下一期,我们来分析下本次文章中的连接器(Connetor)和容器(Container)的作用,敬请期待。

推荐阅读:

  • 漫话:如何给女朋友解释鸿蒙OS是怎样实现跨平台的?

  • 我说我精通字符串,面试官竟然问我Java中的String有没有长度限制!?

  • 看完这篇还不清楚Netty的内存管理,那我就哭了!

  • 挑战10个最难回答的Java面试题(附答案)

喜欢我可以给我设为星标哦

好文章,我 在看 

如何不让tomcat在启动时弹窗_Tomcat在SpringBoot中是如何启动的相关推荐

  1. 如何不让tomcat在启动时弹窗_Tomcat 在 Spring Boot 中是如何启动的

    优质文章,及时送达 作者 | 木木匠 链接 | my.oschina.net/luozhou/blog/3088908 前言 我们知道SpringBoot给我们带来了一个全新的开发体验,我们可以直接把 ...

  2. MySQL服务启动时显示本地计算机上的MySQL服务启动后停止;mysql服务无法启动

    两个问题: (1) 关闭mysql服务后后再次启动,显示:MySQL服务启动时显示本地计算机上的MySQL服务启动后停止.某些服务在未由其它服务-: (2)cmd窗口输入 net start mysq ...

  3. 服务器启动时Webapp的web.xml中配置的加载顺序

    一 1.启动一个WEB项目的时候,WEB容器会去读取它的配置文件web.xml,读取<listener>和<context-param>两个结点. 2.紧急着,容创建一个Ser ...

  4. SpringBoot中banner个性启动(内附自定义设计网站)

    SpringBoot项目启动时会在控制台打印一个默认的启动图案,这个图案就是banner 1.首先需要在resources下创建一个banner.txt文件 2.打开新建的banner.txt文件 里 ...

  5. 如何以安全模式启动计算机,如何在Windows 10中以安全模式启动计算机

    如何在Windows 10中以安全模式启动计算机 安全模式对于解决程序和驱动程序可能无法正确启动或可能阻止Windows正常启动的问题非常有用.这是在安全模式下启动Windows 10的所有方法 Wi ...

  6. android启动其他app的服务器,Android中通过外部程序启动App的三种方法

    这篇文章主要介绍了Android中通过外部程序启动App的三种方法, 本文讲解了直接通过包名. 通过自定义的Action. 通过Scheme三种方法,并分别给出操作代码,需要的朋友可以参考下 ==== ...

  7. idea 启动选择profiles_玩转SpringBoot 2 之项目启动篇

    SpringBoot 启动方式有哪些? SpringBoot 有4种方式进行启动,具体方式如下: IDEA方式启动 Eclipse 方式启动 Maven 启动方式 通过SpringBoot 程序 ja ...

  8. weblogic8.1在myeclipse中启动正常,在单独的weblogic中无法正常启动的解决方案.

    应用程序服务器weblogic8.1.5,项目在myeclipse中启动正常,在单独的服务器中启动就报错了.错误如下图: 经过观察,发现在myeclipse中设置了以下的jar包.估计是这个问题引起的 ...

  9. rc-local.service服务启动失败,导致rc.local中的开机启动服务不能启动

    chmod  +x   /etc/rc.d/rc.local 打开/etc/rc.local文件,将启动非后台执行的指令的最后添加 &,以使相关指令后台运行,然后启动服务 systemctl  ...

最新文章

  1. 从云端到边缘 AI推动FPGA应用拓展
  2. 如何使用 OpenCV 实现图像均衡?
  3. 聚类算法(五)--层次聚类(系统聚类)及超易懂实例分析
  4. 《零基础看得懂的C++入门教程 》——(10)面向对象
  5. @程序员,使用了 SQL 就不能用 DevOps?
  6. 利用Caffe训练模型(solver、deploy、train_val)+python使用已训练模型
  7. QQ2011的DD包密码验证报文解密密钥计算困惑之二
  8. tomcat与mysql分离部署_apache+tomcat+mysql 实现动静分离
  9. 心理账户、沉没成本、比例偏见
  10. STM32串口通信发送Hello Windows!
  11. hdoj 超级赛亚ACMer (贪心)
  12. sprd9820 来电归属地
  13. 视频画面显示单位fps与Hz的区别
  14. java: 未报告的异常错误java.lang.IllegalAccessException; 必须对其进行捕获或声明以便抛出
  15. Beyond Compare 中文乱码解决
  16. Qt 加载了qm文件翻译无效的bug的分享
  17. iphone怎么更新9.0系统更新服务器,iOS 9 推送前你必须知道的几件事:iOS 9 升级指南...
  18. spring-integration连接MQTT
  19. JS数组的slice()方法传负数和字符串操作函数中的slice()、substr()、substring()
  20. [Xamarin.Android] 不同分辨率下的图片使用概论

热门文章

  1. PS3模拟器使用教程
  2. 基于 Element-ui 的富文本组件quillEditor
  3. 基于ODU恢复truncate表的总结操作
  4. 大数据和物联网哪个更有前景?
  5. 过分了,这些算法妹子们肝了一本1200页的AI全栈技术手册
  6. Redis命令详解:Cluster
  7. BMS的过压、过流、欠压是如何产生的-笔记随笔
  8. 学习Linux命令(33)
  9. SQL Server 视图创建点滴 (转http://www.cnblogs.com/fineboy/archive/2008/05/10/236731.html#1191527)...
  10. 2022-1-16 牛客C++项目 —— Linux多进程编程 —— waitpid函数