前言

我们知道SpringBoot给我们带来了一个全新的开发体验,我们可以直接把web程序达成jar包,直接启动,这就得益于SpringBoot内置了容器,可以直接启动,本文将以Tomcat为例,来看看SpringBoot是如何启动Tomcat的,同时也将展开学习下Tomcat的源码,了解Tomcat的设计,关于spring方面小编也整理了一套spring全家桶学习笔记,分享给正在阅读的朋友!

从 Main 方法说起

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

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

publicConfigurableApplicationContextrun(String...args){StopWatchstopWatch=newStopWatch();stopWatch.start();ConfigurableApplicationContextcontext=null;CollectionexceptionReporters=newArrayList<>();//设置系统属性『java.awt.headless』,为true则启用headless模式支持configureHeadlessProperty();//通过*SpringFactoriesLoader*检索*META-INF/spring.factories*,//找到声明的所有SpringApplicationRunListener的实现类并将其实例化,//之后逐个调用其started()方法,广播SpringBoot要开始执行了SpringApplicationRunListenerslisteners=getRunListeners(args);//发布应用开始启动事件listeners.starting();try{//初始化参数ApplicationArgumentsapplicationArguments=newDefaultApplicationArguments(args);//创建并配置当前SpringBoot应用将要使用的Environment(包括配置要使用的PropertySource以及Profile),//并遍历调用所有的SpringApplicationRunListener的environmentPrepared()方法,广播Environment准备完毕。ConfigurableEnvironmentenvironment=prepareEnvironment(listeners,applicationArguments);configureIgnoreBeanInfo(environment);//打印bannerBannerprintedBanner=printBanner(environment);//创建应用上下文context=createApplicationContext();//通过*SpringFactoriesLoader*检索*META-INF/spring.factories*,获取并实例化异常分析器exceptionReporters=getSpringFactoriesInstances(SpringBootExceptionReporter.class,newClass[]{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){newStartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(),stopWatch);}//发布应用已经启动的事件listeners.started(context);//遍历所有注册的ApplicationRunner和CommandLineRunner,并执行其run()方法。//我们可以实现自己的ApplicationRunner或者CommandLineRunner,来对SpringBoot的启动过程进行扩展。callRunners(context,applicationArguments);}catch(Throwableex){handleRunFailure(context,ex,exceptionReporters,listeners);thrownewIllegalStateException(ex);}try{//应用已经启动完成的监听事件listeners.running(context);}catch(Throwableex){handleRunFailure(context,ex,exceptionReporters,null);thrownewIllegalStateException(ex);}returncontext;}

其实这个方法我们可以简单的总结下步骤为 > 1. 配置属性 > 2. 获取监听器,发布应用开始启动事件 > 3. 初始化输入参数 > 4. 配置环境,输出banner > 5. 创建上下文 > 6. 预处理上下文 > 7. 刷新上下文 > 8. 再刷新上下文 > 9. 发布应用已经启动事件 > 10. 发布应用启动完成事件

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

protectedConfigurableApplicationContextcreateApplicationContext(){

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,了解完创建上下文的情况后,我们再来看看刷新上下文,相关代码如下:

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

// 类:AbstractApplicationContext

publicvoidrefresh()throwsBeansException, 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。

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

走进Tomcat内部

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

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

根据上面的源码,我们发现,原来这个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类的源码:

//部分源码,其余部分省略。publicclassTomcat{//设置连接器publicvoidsetConnector(Connectorconnector){Serviceservice=getService();booleanfound=false;for(ConnectorserviceConnector:service.findConnectors()){if(connector==serviceConnector){found=true;}}if(!found){service.addConnector(connector);}}//获取servicepublicServicegetService(){returngetServer().findServices()[0];}//设置Host容器publicvoidsetHost(Hosthost){Engineengine=getEngine();booleanfound=false;for(ContainerengineHost:engine.findChildren()){if(engineHost==host){found=true;}}if(!found){engine.addChild(host);}}//获取Engine容器publicEnginegetEngine(){Serviceservice=getServer().findServices()[0];if(service.getContainer()!=null){returnservice.getContainer();}Engineengine=newStandardEngine();engine.setName("Tomcat");engine.setDefaultHost(hostname);engine.setRealm(createDefaultRealm());service.setContainer(engine);returnengine;}//获取serverpublicServergetServer(){if(server!=null){returnserver;}System.setProperty("catalina.useNaming","false");server=newStandardServer();initBaseDir();//SetconfigurationsourceConfigFileLoader.setSource(newCatalinaBaseConfigurationSource(newFile(basedir),null));server.setPort(-1);Serviceservice=newStandardService();service.setName("Tomcat");server.addService(service);returnserver;}//添加Context容器publicContextaddContext(Hosthost,StringcontextPath,StringcontextName,Stringdir){silence(host,contextName);Contextctx=createContext(host,contextPath);ctx.setName(contextName);ctx.setPath(contextPath);ctx.setDocBase(dir);ctx.addLifecycleListener(newFixContextListener());if(host==null){getHost().addChild(ctx);}else{host.addChild(ctx);}//添加Wrapper容器publicstaticWrapperaddServlet(Contextctx,StringservletName,Servletservlet){//willdoclassfornameandsetinitparamsWrappersw=newExistingStandardWrapper(servlet);sw.setName(servletName);ctx.addChild(sw);returnsw;}}

阅读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外,其余的容器都是可以有多个,关于spring方面小编也整理了一套spring全家桶学习笔记,分享给正在阅读的朋友!

本期文章通过SpringBoot的启动来窥探了Tomcat的内部结构,喜欢小编今日的分享,记得关注我点赞哟,感谢支持!重要的事情说三遍,转发+转发+转发,一定要记得转发 关注哦!!!

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

  1. 麒麟系统开机自启动服务、执行脚本、命令

    rc.local是一个较旧Linux启动加载脚本(目前主流系统主要用systemctl控制开机启动),目前仍然可用 1.普通命令可以直接写在rc.local里,(rc.local须有执行权限,没有的话 ...

  2. linux springboot开机启动,SpringBoot 部署到Linux开机自启动和运行

    前文 SpringBoot是一个强大的微服务框架,通常都是打包项目成Jar包,并部署到服务器上,本文以Linux服务器部署为主 开机自启动 运行 Jar包部署到Linux服务器上面,不能使用常用的运行 ...

  3. SuperMap iServer在不同系统中设置开机自启动--Windows篇

    目录 前言 1.删除已有的 SuperMap iServer 系统服务 2.注册 SuperMap iServer 系统服务 3.设置 SuperMap iServer 系统服务开机自启动 实例 作者 ...

  4. 如何在linux操作系统中安装oracle数据库,并设置开机自启动

    1. ping www.baidu.com 查看是否联网成功 2. 不能联网,就将ip修改为动态dhcp模式使之能链接成功. 3. Vi /etc/hosts 写上ip地址与主机名 4. yum in ...

  5. centos7.x设置nginx开机自启动

    设置nginx开机自启动(centos7.x) 第一步:进入到/lib/systemd/system/目录 [root@iz2z init.d]# cd /lib/systemd/system/ 第二 ...

  6. Windows下设置开机自启动的方式(手动/C++代码的形式)

    Windows下设置开机自启动的方式(手动/C++代码的形式) Windows下自启动相关软件的原理 首先讲解一下Windows下是如何会实现开机自启动相关软件的原理,由于Windows本身有注册表机 ...

  7. Oracle数据库开机自启动

    如果服务器断电重启或计划内重启,在服务器的操作系统启动后,需要手工启动数据库实例和监听,本文介绍如何把Oracle数据库的启动和关闭配置成系统服务,在操作系统启动/关闭时,自动启动/关闭Oracle实 ...

  8. linux 设置开机自启动 文件配置开机自启动命令

    linux 设置开机自启动 文件配置开机自启动命令 在/etc/rc.local文件中添加自启动命令 执行命令 执行脚本 在/etc/init.d目录下添加自启动脚本 运行级别设置 在/etc/rc. ...

  9. Windows程序开机自启动

    开机自启动的两种方式 在Windows中想要开机自启动某些应用,可以把程序的快捷方式放到开始菜单->程序->启动目录下,但是自启动又分为用户自启动和系统自启动,前者针对单个用户,后者针对全 ...

最新文章

  1. Android:项目关联Library
  2. 时间序列:五种编辑距离和Python实现
  3. USACO1.1Broken Necklace[环状DP作死]
  4. 校园二手交易平台的开发和利用
  5. 新闻发布系统java ee_Java EE 7发布–反馈和新闻报道
  6. 建议看 | 计算机网络核心概念
  7. 华为面试分配_什么时候不做面试分配
  8. url 获取 geoserver 中对应的style
  9. apt安装openjdk8
  10. html电脑添加高德地图,vue-cli项目h5页面或者PC端页面引入高德地图组件,多点标注,自定义弹窗的详细描述...
  11. STATA:面板数据滞后需要注意(同一家企业滞后出现空缺数据的原因)
  12. RedHat7安装yum并下载gcc
  13. [存档]CxServer系统
  14. 学生图书馆系统mysql数据库设计
  15. 原神3.0上半角色活动祈愿-2源码
  16. 如何从AD中彻底删除Skype For Business(下篇)
  17. 电流型和电压模拟量信号有何区别,如何互相转换
  18. React Firebase 计账软件 Serverless 项目实战视频教程
  19. 互联网时代各行业都在快速更替,金融行业为什么即将成为下一个风口?
  20. TC申请是否需要银行转账记录?

热门文章

  1. 如何把antlr4融合到编译器项目中使用
  2. ACL 访问控制列表
  3. 微软商店出现【0x800706D9】解决方法
  4. 菲涅尔公式实现边缘光效果
  5. 【树莓派】通过SSH或者写SD卡修改树莓派的WiFi账号密码
  6. 基于大数据的银行反欺诈的分析报告
  7. 配置我的新服务器(用于联邦学习实验)
  8. 记录nginx漏洞升级问题处理
  9. html上传图片获取物理路径
  10. 当把目标定为买一栋楼时,买一套房就变得轻而易举,有朋友时,一起疯一起乐一起干活;一个人时,刻苦学习,攻读我的圣经。 不管你身处何方,小黄人软件都是你强大的后盾。