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);
}
}
//获取service
public 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;
}
//获取server
public 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 子容器。
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 外,其余的容器都是可以有多个。