在我们之前的web开发中,通常都是将应用打成war包或者将编译之后的应用放到tomcat的webapps目录下(其他的web服务器放到相应的目录下),但是我们在用SpringBoot进行web开发的时候,只是启动了一个main类,然后就会神奇的发现tomcat竟然也被启动了(SpringBoot也内置了Jetty),SpringBoot是怎么做到的呢?下面我将慢慢揭开它的神秘面纱:
我们之前说过在SpringBoot中web的上下文是AnnotationConfigEmbeddedWebApplicationContext这个类,我们先看简单的一下这个类的UML图:
AnnotationConfigEmbeddedWebApplicationContext这个类继承了EmbeddedWebApplicationContext类,GenericWebApplicationContext类(这个要注意),它还实现了BeanDefinitionRegistry这个接口,还实现了ResourceLoader这个接口,这个类可以说是一个全能类了,这个个类我们先不多说,主要是看它的父类EmbeddedWebApplicationContext这个类。在这个类中重写了refresh方法、onRefresh方法、onClose方法、finishRefresh方法。这里我们先看onRefresh这个方法,
@Override
protected void onRefresh() {
//调用父类的onRefresh方法(GenericWebApplicationContext)
super.onRefresh();
try {
//创建嵌入式的Servlet容器
createEmbeddedServletContainer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start embedded container",
ex);
}
}
这里我们要重点分析的就是createEmbeddedServletContainer这个方法。
private void createEmbeddedServletContainer() {
//先获取embeddedServletContainer
EmbeddedServletContainer localContainer = this.embeddedServletContainer;
//获取Servlet上下文
ServletContext localServletContext = getServletContext();
//EmbeddedWebApplicationContext没有为EmbeddedServletContainer和ServletContext赋初值,也之前也没有调用set方法,所以这里都是为null
if (localContainer == null && localServletContext == null) {
//获取EmbeddedServletContainerFactory的实例 1)
EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
//获取EmbeddedServletContainer 2)
this.embeddedServletContainer = containerFactory
.getEmbeddedServletContainer(getSelfInitializer());
}
else if (localServletContext != null) {
try {
getSelfInitializer().onStartup(localServletContext);
}
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context",
ex);
}
}
//初始化属性信息3)
initPropertySources();
}
我们先看1)处的代码:
protected EmbeddedServletContainerFactory getEmbeddedServletContainerFactory() {
// Use bean names so that we don't consider the hierarchy
//从BeanFactory中获取EmbeddedServletContainerFactory类型的Bean,这里没有考虑父BeanFactory
String[] beanNames = getBeanFactory()
.getBeanNamesForType(EmbeddedServletContainerFactory.class);
//如果没有获取到EmbeddedServletContainerFactory类型的Bean,则抛出异常
if (beanNames.length == 0) {
throw new ApplicationContextException(
"Unable to start EmbeddedWebApplicationContext due to missing "
+ "EmbeddedServletContainerFactory bean.");
}
//如果有一个以上的EmbeddedServletContainerFactory类型的Bean,则抛出异常
if (beanNames.length > 1) {
throw new ApplicationContextException(
"Unable to start EmbeddedWebApplicationContext due to multiple "
+ "EmbeddedServletContainerFactory beans : "
+ StringUtils.arrayToCommaDelimitedString(beanNames));
}
//从BeanFactory中获取EmbeddedServletContainerFactory类型的Bean
return getBeanFactory().getBean(beanNames[0],
EmbeddedServletContainerFactory.class);
}
上面的这一段代码,干了这样三件事,查看Spring容器中是否有EmbeddedServletContainerFactory类型的Bean,EmbeddedServletContainerFactory类型的Bean是否多于一个,从Spring容器中获取EmbeddedServletContainerFactory类型的Bean,那么EmbeddedServletContainerFactory的Bean是什么时候被注入到Spring容器中的呢?我们先看一下EmbeddedServletContainerFactory的层次结构:
从上图中我们可以看到,在SpringBoot中为我们内置了三种Web服务器的实现类,TomCat、Jetty、Undertow(没接触过)。我们之前说SpringBoot的四大神器,其中之一是自动配置的功能,关于自动配置的内容我们不做更多的展开,在SpringBoot中有这样一个类EmbeddedServletContainerAutoConfiguration
这个类配置在spring.factories中,它的key为org.springframework.boot.autoconfigure.EnableAutoConfiguration,这里你需要知道,在SpringBoot启动的时候会加载这个类,并且这个类上带有Configuration这个注解,所以这个类会被注入到Spring容器中,然后这个类要生效还要有一个条件,即当前环境是web环境!在EmbeddedServletContainerAutoConfiguration中有这样的一段代码:
@Configuration
@ConditionalOnClass({ Servlet.class, Tomcat.class })
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedTomcat {
@Bean
public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
return new TomcatEmbeddedServletContainerFactory();
}
}
如果当前类路径下有Servlet和Tomcat这两个类,且在当前上下文中没有EmbeddedServletContainerFactory类型的Bean存在,则创建TomcatEmbeddedServletContainerFactory对象并注入到Spring容器中。SpringBoot中内置了TomCat相关的jar(spring-boot-starter-tomcat)。所以这里注入到Spring容器中的EmbeddedServletContainerFactory类型的Bean是TomcatEmbeddedServletContainerFactory。下面我们接着看2)处的代码:
this.embeddedServletContainer = containerFactory
.getEmbeddedServletContainer(getSelfInitializer());
这里的containerFactory是TomcatEmbeddedServletContainerFactory的实例,所以这里调用的也是TomcatEmbeddedServletContainerFactory中的getEmbeddedServletContainer方法,
@Override
public EmbeddedServletContainer getEmbeddedServletContainer(
ServletContextInitializer... initializers) {
//创建一个TomCat对象
Tomcat tomcat = new Tomcat();
//创建web容器base目录
File baseDir = (this.baseDirectory != null ? this.baseDirectory
: createTempDir("tomcat"));
tomcat.setBaseDir(baseDir.getAbsolutePath());
//创建一个连接器
Connector connector = new Connector(this.protocol);
//向tomcat的service中添加连接器
tomcat.getService().addConnector(connector);
//定制化Connector
customizeConnector(connector);
tomcat.setConnector(connector);
//自动部署设置为false
tomcat.getHost().setAutoDeploy(false);
//配置Engine
configureEngine(tomcat.getEngine());
//想tomcat中添加其他的Connector
for (Connector additionalConnector : this.additionalTomcatConnectors) {
tomcat.getService().addConnector(additionalConnector);
}
//准备ServletContext的上下文
prepareContext(tomcat.getHost(), initializers);
//创建TomcatEmbeddedServletContainer
return getTomcatEmbeddedServletContainer(tomcat);
}
上面的代码是不是看到很晕乎?准确的说上面的内容是TomCat的核心结构部分了。我们先从createTempDir这个简单的方法来开始:
protected File createTempDir(String prefix) {
try {
//创建临时文件夹
File tempDir = File.createTempFile(prefix + ".", "." + getPort());
//如果之前这个文件夹存在的话,则删除这个文件
tempDir.delete();
//创建临时文件夹
tempDir.mkdir();
//当虚拟机退出的时候删除这个临时文件夹
tempDir.deleteOnExit();
return tempDir;
}
}
大家第一次看到TomCat这个类的时候是不是有点奇怪,怎么会有一个类叫做TomCat呢?既然敢叫TomCat,那肯定不一般,这个类也确实不一般,它整合了TomCat的整个生命周期,串联了TomCat的各个组件。我们接下来再继续说。