SpringBoot之使用外置Servlet容器
一、SpringBoot嵌入式Servlet容器与外置Servlet容器的比较
- 嵌入式Servlet容器应用将程序打成Jar包,外置Servlet容器应用将程序打成War包。
- 嵌入式Servlet容器的优点:简单、便携。缺点:默认不支持JSP,优化定制较复杂。
嵌入式Servlet容器优化定制的方法:
① 使用定制器:ServerProperties、自定义 EmbeddedServletContainerCustomizer
② 自己编写嵌入式Servlet容器的创建工厂 【EmbeddedServletContainerFactory】
二、如何使用外置Servlet容器的步骤
1、创建一个War包项目
;File——》New——Project,选中Spring Initializr,然后单击Next,进入到如下页面,将默认的Jar包改成War,然后Next,直到Finish。
创建出来的目录结构:
由于创建的是War包项目,所以以上的目录结构还不完整,我们需要手动创建web.xml和webapp。有两种创建方式:
① 手动在src/main下创建webapp目录及在/src/main/webapp/WEB-INF下创建web.xml文件。
② 在IDEA的Project Structure中完善目录结构。
点击Module,选中web,来到如下页面:
双击上图圈出来的url处,弹出以下对话框:
单击OK,选择Yes。
还是在Project Structure页面,点击如下图所示的“+”,弹出如下对话框,修改生成web.xml文件的目录/src/main/webapp,然后点击OK。
在上一步中点击OK后,先点击Apply,然后选择OK,即可创建出webapp目录和web.xml文件。
完善后的目录结构如下:
2、将嵌入式的tomcat指定为provided
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
3、必须写一个SpringBootServletInitializer 的实现子类。
目的是调用configure方法,传入springboot应用的主程序。
public class ServletInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(SpringBootWebJspApplication.class);
}
}
4、启动服务器
三、外部Servlet启动SpringBoot应用原理(1.5.x)
- 将项目打成 jar包启动方式:执行SpringBoot主类的main方法,启动IOC容器,创建嵌入式的Servlet容器。
- 将项目打成 war包启动方式:启动服务器,服务器启动SpringBoot应用【
SpringBootServletInitializer
】,然后启动IOC容器。
在Spring3.0(Spring注解版)的8.2.4 Shared libraries / runtimes pluggability中,有如下规则:
【1】在服务器(web应用)启动时,会创建当前web应用里每一个jar包中的ServletContainerInitializer
实例。
【2】ServletContainerInitializer
的实现放在jar包的META-INF/services文件夹下,必须有一个名为 javax.servlet.ServletContainerInitializer 的文件,内容就是ServletContainerInitializer的实现类的全类名。
【3】还可以使用@HandlesTypes
注解,作用是在应用启动的时候加载我们感兴趣的类。
步骤:
【1】启动tomcat服务器;
【2】 在../org/springframework/spring-web/4.3.14.RELEASE/spring-web-4.3.14.RELEASE.jar/META-INF/services
目录下有一个名为javax.servlet.ServletContainerInitializer
的文件,文件内容是:
org.springframework.web.SpringServletContainerInitializer
相当于在web应用启动的时候启动SpringServletContainerInitializer
。
【3】SpringServletContainerInitializer
将@HandlesTypes(WebApplicationInitializer.class)
标注的所有类型的类(感兴趣的类)都传入到onStartup方法的Set<Class<?>> webAppInitializerClasses
,为这些WebApplicationInitializer
类型的类(不是接口、不是抽象类)创建实例。
【4】每一个WebApplicationInitializer
都调用自己的onStartup
,相当于我们的SpringBootServletInitializer
的类会被创建对象,并执行onStartup
方法。WebApplicationInitializer
是一个接口,它的实现类如下:
//Set<Class<?>> webAppInitializerClasses表示感兴趣的类
public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {
List<WebApplicationInitializer> initializers = new LinkedList();
Iterator var4;
if (webAppInitializerClasses != null) {
var4 = webAppInitializerClasses.iterator();
while(var4.hasNext()) {
Class<?> waiClass = (Class)var4.next();
//如果不死接口或者抽象类
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
//加入到WebApplicationInitializer对象的集合中
initializers.add((WebApplicationInitializer)waiClass.newInstance());
} catch (Throwable var7) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", var7);
}
}
}
}
if (initializers.isEmpty()) {
servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
} else {
servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
AnnotationAwareOrderComparator.sort(initializers);
var4 = initializers.iterator();
while(var4.hasNext()) {
WebApplicationInitializer initializer = (WebApplicationInitializer)var4.next();
//每一个WebApplicationInitializer对象调用自己的onStartup方法创建SpringBootServletInitializer对象
initializer.onStartup(servletContext);
}
}
}
public class ServletInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(SpringBootWebJspApplication.class);
}
}
【5】SpringBootServletInitializer
实例执行onStartup
方法的时候会调用createRootApplicationContext
方法创建容器
public void onStartup(ServletContext servletContext) throws ServletException {
this.logger = LogFactory.getLog(this.getClass());
WebApplicationContext rootAppContext = this.createRootApplicationContext(servletContext);
if (rootAppContext != null) {
servletContext.addListener(new ContextLoaderListener(rootAppContext) {
public void contextInitialized(ServletContextEvent event) {
}
});
} else {
this.logger.debug("No ContextLoaderListener registered, as createRootApplicationContext() did not return an application context");
}
}
protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) {
//1、创建SpringApplicationBuilder对象
SpringApplicationBuilder builder = this.createSpringApplicationBuilder();
StandardServletEnvironment environment = new StandardServletEnvironment();
environment.initPropertySources(servletContext, (ServletConfig)null);
builder.environment(environment);
builder.main(this.getClass());
ApplicationContext parent = this.getExistingRootWebApplicationContext(servletContext);
if (parent != null) {
this.logger.info("Root context already created (using as parent).");
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, (Object)null);
builder.initializers(new ApplicationContextInitializer[]{new ParentContextApplicationContextInitializer(parent)});
}
builder.initializers(new ApplicationContextInitializer[]{new ServletContextApplicationContextInitializer(servletContext)});
builder.contextClass(AnnotationConfigEmbeddedWebApplicationContext.class);
//调用configure方法,在子类重写了这个方法,传入SpringBoot的主程序类
builder = this.configure(builder);
//使用builder创建spring应用
SpringApplication application = builder.build();
if (application.getSources().isEmpty() && AnnotationUtils.findAnnotation(this.getClass(), Configuration.class) != null) {
application.getSources().add(this.getClass());
}
Assert.state(!application.getSources().isEmpty(), "No SpringApplication sources have been defined. Either override the configure method or add an @Configuration annotation");
if (this.registerErrorPageFilter) {
application.getSources().add(ErrorPageFilterConfiguration.class);
}
//启动spring应用
return this.run(application);
}
protected SpringApplicationBuilder createSpringApplicationBuilder() {
return new SpringApplicationBuilder(new Object[0]);
}
【6】启动spring应用,并创建IOC容器
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
FailureAnalyzers analyzers = null;
this.configureHeadlessProperty();
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
Banner printedBanner = this.printBanner(environment);
context = this.createApplicationContext();
new FailureAnalyzers(context);
this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//刷新IOC容器
this.refreshContext(context);
this.afterRefresh(context, applicationArguments);
listeners.finished(context, (Throwable)null);
stopWatch.stop();
if (this.logStartupInfo) {
(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
}
return context;
} catch (Throwable var9) {
this.handleRunFailure(context, listeners, (FailureAnalyzers)analyzers, var9);
throw new IllegalStateException(var9);
}
}
一句话总结:先启动Servlet容器,启动spring应用,再创建IOC容器。
推荐阅读
-
SpringBoot 源码解析 (六)----- Spring Boot的核心能力 - 内置Servlet容器源码分析(Tomcat)
-
springboot系列之03-使用IDEA完成第一个示例程序
-
SpringBoot起飞系列-配置嵌入式Servlet容器(八)
-
Springboot 之 使用POI操作excel
-
SpringBoot学习笔记11-SpringBoot 中使用 Servlet
-
IDEA配置SpringBoot使用外部Servlet容器(Tomcat)
-
Go标准容器之Ring的使用说明
-
springBoot之配置文件的读取以及过滤器和拦截器的使用
-
Docker学习之Container容器的具体使用
-
SpringBoot里使用Servlet进行请求的实现示例