作者:自由飘落de花瓣 | 来源:互联网 | 2023-05-17 13:23
1. 前言
因为SpringBoot 内置了Tomcat容器,所以可以把 web 程序打成 jar 包,直接启动,而且在开发中也不需要像传统Spring项目那样,需要把项目添加到Tomcat中进行启动。不得不说,Springboot确实在方方面面提高了开发效率。
2. 从 Main 方法说起
SpringBoot 项目有一个启动类,启动类里有一个 main 方法用来启动整个应用: run()方法调用链:
SpringApplication.run(DemoApplication.class, args) run(new Class>[] { primarySource }, args) new SpringApplication(primarySources).run(args) 最后跟踪到:SpringApplication.run()方法: 各步骤说明如下:
设置系统属性『java.awt.headless』,为true则启用headless模式支持; 通过 SpringFactoriesLoader 检索 META-INF/spring.factories ,找到声明的所有SpringApplicationRunListener的实现类并将其实例化,之后逐个调用其started()方法,广播SpringBoot要开始执行了; 发布应用开始启动事件; 初始化参数 创建并配置当前SpringBoot应用将要使用的Environment(包括配置要使用的PropertySource以及Profile),并遍历调用所有的SpringApplicationRunListener的environmentPrepared()方法,广播Environment准备完毕。 打印banner; 创建应用上下文; 通过SpringFactoriesLoader 检索META-INF/spring.factories ,获取并实例化异常分析器; 为ApplicationContext加载environment,之后逐个执行ApplicationContextInitializer的initialize()方法来进一步封装ApplicationContext,并调用所有的SpringApplicationRunListener的contextPrepared()方法,【EventPublishingRunListener只提供了一个空的contextPrepared()方法】,之后初始化IoC容器,并调用SpringApplicationRunListener的contextLoaded()方法,广播ApplicationContext的IoC加载完成,这里就包括通过@EnableAutoConfiguration导入的各种自动配置类; 刷新上下文; 再一次刷新上下文,其实是空方法,可能是为了后续扩展; 发布应用已经启动的事件; 遍历所有注册的ApplicationRunner和CommandLineRunner,并执行其run()方法(可以实现自己的ApplicationRunner或者CommandLineRunner,来对SpringBoot的启动过程进行扩展); 应用已经启动完成的监听事件。 简单的总结下步骤为:1. 配置属性 > 2. 获取监听器,发布应用开始启动事件 > 3. 初始化输入参数 > 4. 配置环境,输出 banner > 5. 创建上下文 > 6. 预处理上下文 > 7. 刷新上下文 > 8. 再刷新上下文 > 9. 发布应用已经启动事件 > 10. 发布应用启动完成事件。
其实上面这段代码,如果只分析 tomcat ,只需要关注上下文是如何创建的和上下文是如何刷新的,分别对应的方法就是 createApplicationContext() 和 refreshContext(context),接下来看看这两个方法。
createApplicationContext()方法: 这里就是根据 webApplicationType 来判断创建哪种类型的 Servlet,代码中分别对应着 Web 类型(SERVLET),响应式 Web 类型(REACTIVE),非 Web 类型(default),我们建立的是 Web 类型,所以肯定实例化 DEFAULT_SERVLET_WEB_CONTEXT_CLASS 指定的类,也就是 AnnotationConfigServletWebServerApplicationContext 类,下面用图来说明下这个类的继承关系: 通过类继承关系可知,这个类继承的是 ServletWebServerApplicationContext,而ServletWebServerApplicationContext最后继承了AbstractApplicationContext。
refreshContext(context)方法: 然后调用AbstractApplicationContext的refresh()方法: 其中onRefresh()方法利用模板方法模式,是一个空方法,留给子类实现: 看ServletWebServerApplicationContext中的onRefresh()方法: 顾名思义,createWebServer()就是创建web服务的: 从代码中可知,webServer是从ServletWebServerFactory工厂中获取的,ServletWebServerFactory是一个接口,实现类有: 所以也可以手动指定内嵌容器为其它容器。
3. 走进 Tomcat
直接进入TomcatServletWebServerFactory的getWebServer()方法: 该方法主要做了两件事情:
把 Connnctor(我们称之为连接器)对象添加到 Tomcat 中; configureEngine 跟踪configureEngine()方法: 这个 Engine 其实就是容器Container的子类。
继续跟踪源码,找到Container接口,看其实现: 其中有4个子接口,分别是Engine,Host,Context,Wrapper。看源码注释可知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) ;
}
}
public Service getService ( ) {
return getServer ( ) . findServices ( ) [ 0 ] ;
}
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) ;
}
}
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;
}
public Server getServer ( ) {
if ( server != null) {
return server;
}
System. setProperty ( "catalina.useNaming" , "false" ) ;
server = new StandardServer ( ) ;
initBaseDir ( ) ;
ConfigFileLoader. setSource ( new CatalinaBaseConfigurationSource ( new File ( basedir) , null) ) ;
server. setPort ( - 1 ) ;
Service service = new StandardService ( ) ;
service. setName ( "Tomcat" ) ;
server. addService ( service) ;
return server;
}
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) ;
}
public static Wrapper addServlet ( Context ctx,
String servletName,
Servlet servlet) {
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 子容器。
4. 总结
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 外,其余的容器都是可以有多个。