• 前言[1]
  • 从 Main 方法说起[2]
  • 走进 Tomcat 内部[3]
  • 总结[4]

  • 《Java 2019 超神之路》
  • 《Dubbo 实现原理与源码解析 —— 精品合集》
  • 《Spring 实现原理与源码解析 —— 精品合集》
  • 《MyBatis 实现原理与源码解析 —— 精品合集》
  • 《Spring MVC 实现原理与源码解析 —— 精品合集》
  • 《Spring Boot 实现原理与源码解析 —— 精品合集》
  • 《数据库实体设计合集》
  • 《Java 面试题 —— 精品合集》
  • 《Java 学习指南 —— 精品合集》

前言

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

从 Main 方法说起

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

@SpringBootApplication
public 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<springbootexceptionreporter> exceptionReporters = new ArrayList&lt;&gt;();//设置系统属性『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);//打印bannerBanner 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.javaprivate void refreshContext(ConfigurableApplicationContext context) {//直接调用刷新方法refresh(context);if (this.registerShutdownHook) {try {context.registerShutdownHook();}catch (AccessControlException ex) {// Not allowed in some environments.}}}
//类:SpringApplication.javaprotected void refresh(ApplicationContext applicationContext) {Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);((AbstractApplicationContext) applicationContext).refresh();}

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

// 类:AbstractApplicationContext
public 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。

//类:ServletWebServerApplicationContext
protected 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 &amp;&amp; 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 <code>setParent()</code> method* should throw <code>IllegalArgumentException</code>.** @author Craig R. McClanahan*/
public interface Engine extends Container {//省略代码
}
/*** <p>* 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.* </p><p>* 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 {
//省略代码}
/*** </p><p>* 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.* </p><p>* The child containers attached to a Context are generally implementations* of Wrapper (representing individual servlet definitions).* </p><p>** @author Craig R. McClanahan*/
public interface Context extends Container, ContextBind {//省略代码
}
/**</p><p>* 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.* </p><p>* Child Containers are not allowed on Wrapper implementations, so the* <code>addChild()</code> method should throw an* <code>IllegalArgumentException</code>.** @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 sourceConfigFileLoader.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 paramsWrapper 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 外,其余的容器都是可以有多个。

class没有发布到tomcat_Tomcat 在 SpringBoot 中是如何启动的相关推荐

  1. class没有发布到tomcat_面试官扎心一问:Tomcat 在 SpringBoot 中是如何启动的?

    作者:木木匠 my.oschina.net/luozhou/blog/3088908 前言 我们知道 SpringBoot 给我们带来了一个全新的开发体验,我们可以直接把 web 程序达成 jar 包 ...

  2. tomcat源码_从源码角度讲Tomcat在SpringBoot 中是如何启动的?

    我们知道 SpringBoot 给我们带来了一个全新的开发体验,我们可以直接把 web 程序达成 jar 包,直接启动,这就得益于 SpringBoot 内置了容器,可以直接启动,本文将以 Tomca ...

  3. Tomcat 在 SpringBoot 中是如何启动的

    转载自木木匠. 前言 我们知道SpringBoot给我们带来了一个全新的开发体验,我们可以直接把web程序达成jar包,直接启动,这就得益于SpringBoot内置了容器,可以直接启动,本文将以Tom ...

  4. springboot 不响应字段为空_面试官扎心一问:Tomcat 在 SpringBoot 中是如何启动的?...

    作者:木木匠 http://my.oschina.net/luozhou/blog/3088908 前言 我们知道 SpringBoot 给我们带来了一个全新的开发体验,我们可以直接把 web 程序达 ...

  5. 面试官扎心一问:Tomcat 在 SpringBoot 中是如何启动的?

    作者:木木匠 my.oschina.net/luozhou/blog/3088908 前言 我们知道 SpringBoot 给我们带来了一个全新的开发体验,我们可以直接把 web 程序达成 jar 包 ...

  6. 如何不让tomcat在启动时弹窗_Tomcat在SpringBoot中是如何启动的

    来自:掘金 (作者:木木匠) 原文链接: https://juejin.im/post/5d3f95ebf265da039e12959e 前言 我们知道SpringBoot给我们带来了一个全新的开发体 ...

  7. java通过麒麟实现开机自启动,京东四面:说说Tomcat 在 SpringBoot 中是如何启动的!...

    前言 我们知道SpringBoot给我们带来了一个全新的开发体验,我们可以直接把web程序达成jar包,直接启动,这就得益于SpringBoot内置了容器,可以直接启动,本文将以Tomcat为例,来看 ...

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

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

  9. 【SpringBoot】面试必杀技-泰山陨石坠,SpringBoot中主启动类的run方法启动流程是什么?

    开头导语由Chatgpt完成 当今互联网行业中,Java后端开发岗位的竞争异常激烈,对于面试者来说,掌握一些技巧和知识点将有助于脱颖而出.而对于SpringBoot框架的使用和运行机制,更是Java后 ...

最新文章

  1. 搞懂单链表常见面试题
  2. 谷歌浏览器同步功能无法加载_谷歌浏览器Chrome 86正式发布,全新重磅功能上线——密码检查...
  3. JZOJ 4675. 【NOIP2016提高A组模拟7.21】Double-row
  4. 斐波那契数列python递归 0、1、1、2、3_python实现斐波那契数列的多种方式
  5. 工业物联网的应用领域和方向
  6. 计量经济学建模_浅谈统计学模型(兼计量经济学模型)
  7. Android 获取短信验证码,自动填充
  8. Jeecg-Boot 2.1.4 版本发布,基于SpringBoot低代码平台
  9. vnc报错 font catalog is not properly configured
  10. Hibernate--Criteria Query and DetachedCriteria
  11. 谷歌开源缩放模型EfficientNets:ImageNet准确率创纪录,效率提高10倍
  12. error “Device supports x86, but APK only supports armeabi-v7a”
  13. 七月算法机器学习 9 推荐系统与应用 小案例
  14. 单片机控制三相异步电动机正反转c语言程序,请用PLC控制一台普通三相异步电动机的正反转控制,设计其控制程序梯形图及主电路...
  15. 倾向得分匹配PSM案例分析
  16. M - Help Hanzo(模拟素数筛思想+优化)
  17. python cv2什么意思_Python CV2的基本操作
  18. 使用JSONObject 读取 jason对象中的key
  19. 查找算法【平衡二叉树】 - 平衡二叉树的删除
  20. 使用mycat实现分库分表

热门文章

  1. 使用SSH和Nginx做内网HTTP映射
  2. Oracl 12c (课本)
  3. 有原型的对象和没有原型的对象
  4. ARM Linux 基于S3C2451的AD9833波形发生器/Linux字符驱动的理解
  5. nonatomic, retain,weak,strong用法详解
  6. javascript编译与运行机理(1)--
  7. 通过代理进行页面传值
  8. GitHub上整理的一些工具【转载】
  9. 【URAL】1091 Tmutarakan Exams
  10. matlab图形绘制基础(东北大学MOOC笔记)