springboot 使用外置servlet 容器
一、嵌入是和外置容器优缺点对比
嵌入式Servlet容器:应用打成可执行的jar
优点:简单、便携;
缺点:默认不支持JSP、优化定制比较复杂;
外置的Servlet容器:外面安装Tomcat - - - 应用war包的方式打包;
二、使用外置的Servlet容器步骤
(1)、使用Spring初始化向导创建warSpringBoot项目
(2)在Project Structure窗口创建目录结构
(3) 在运行窗口配置外置Tomcat
(4) 将嵌入式的Tomcat指定为provided(创建想的时候其实就是了)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
(5) 必须编写一个SpringBootServletInitializer的子类,并调用configure方法
public class ServletInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
//传入SpringBoot应用的主程序
return application.sources(SpringbootwarApplication.class);
}
}
(6) 修改 application.properties
application.properties
spring.mvc.view.prefix=/WEB-INF/
spring.mvc.view.suffix=.jsp
(7) 现在就可以愉快的写代码测试了
index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>index</title>
</head>
<body>
<h1>Hello JSP</h1>
<a href="abc">abc</a>
</body>
</html>
success.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>SUCCESS</h1>
<h3>${msg}</h3>
</body>
</html>
HelloController
@Controller
public class HelloController {
@GetMapping("/abc")
public String hello(Model model){
model.addAttribute("msg","你好");
return "success";
}
}
启动项目测试:
三、外置Servlet容器启动原理
jar包启动步骤:
执行SpringBoot主类的main方法,启动ioc容器,创建嵌入式的Servlet容器;
war包启动步骤:
启动服务器,服务器启动SpringBoot应用【SpringBootServletInitializer】
,启动ioc容器;
规则
- 1.服务器启动(web应用启动)会创建当前web应用里面每一个jar包里面ServletContainerInitializer实例;
- 2.ServletContainerInitializer的实现放在jar包的META-INF/services文件夹下,有一个名为javax.servlet.ServletContainerInitializer的文件,内容就是ServletContainerInitializer的实现类的全类名
- 3.还可以使用@HandlesTypes,在应用启动的时候加载我们感兴趣的类;
流程:启动Servlet容器,再启动SpringBoot应用
1、首先我们启动tomcat
2、应用启动ServletContainerInitializer
Spring的web模块里面有这个文件: javax.servlet.ServletContainerInitializer
内容为:
org.springframework.web.SpringServletContainerInitializer
3、为这些WebApplicationInitializer类型的类创建实例;
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
//SpringServletContainerInitializer将@HandlesTypes(WebApplicationInitializer.class)
//标注的这个类型的所有类都传入到onStartup方法的Set<Class<?>>集合里边
@Override
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
List<WebApplicationInitializer> initializers = new LinkedList<>();
if (webAppInitializerClasses != null) {
for (Class<?> waiClass : webAppInitializerClasses) {
// Be defensive: Some servlet containers provide us with invalid classes,
// no matter what @HandlesTypes says...
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
//为这些WebApplicationInitializer类型的类创建实例;
initializers.add((WebApplicationInitializer)
ReflectionUtils.accessibleConstructor(waiClass).newInstance());
}
catch (Throwable ex) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
}
}
}
}
if (initializers.isEmpty()) {
servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
return;
}
servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
AnnotationAwareOrderComparator.sort(initializers);
for (WebApplicationInitializer initializer : initializers) {
//调用每个实例自己的onStartup方法
initializer.onStartup(servletContext);
}
}
}
4.每一个WebApplicationInitializer都调用自己的onStartup;
5.相当于我们的SpringBootServletInitializer的类会被创建对象,并执行onStartup方法
6.SpringBootServletInitializer实例执行onStartup的时候会createRootApplicationContext;创建容器
public abstract class SpringBootServletInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
// Logger initialization is deferred in case an ordered
// LogServletContextInitializer is being used
this.logger = LogFactory.getLog(getClass());
//createRootApplicationContext(servletContext) 这是创建容器的方法
WebApplicationContext rootAppContext = createRootApplicationContext(servletContext);
if (rootAppContext != null) {
servletContext.addListener(new ContextLoaderListener(rootAppContext) {
@Override
public void contextInitialized(ServletContextEvent event) {
// no-op because the application context is already initialized
}
});
}
else {
this.logger.debug("No ContextLoaderListener registered, as createRootApplicationContext() did not "
+ "return an application context");
}
}
}
7.Spring的应用就启动并且创建IOC容器
protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) {
//1、创建SpringApplicationBuilder
SpringApplicationBuilder builder = createSpringApplicationBuilder();
builder.main(getClass());
ApplicationContext parent = getExistingRootWebApplicationContext(servletContext);
if (parent != null) {
this.logger.info("Root context already created (using as parent).");
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null);
builder.initializers(new ParentContextApplicationContextInitializer(parent));
}
builder.initializers(new ServletContextApplicationContextInitializer(servletContext));
builder.contextClass(AnnotationConfigServletWebServerApplicationContext.class);
//调用configure方法,子类重写了这个方法,将SpringBoot的主程序类传入了进来
builder = configure(builder);
builder.listeners(new WebEnvironmentPropertySourceInitializer(servletContext));
//使用builder创建了一个spring应用
SpringApplication application = builder.build();
if (application.getAllSources().isEmpty()
&& MergedAnnotations.from(getClass(), SearchStrategy.TYPE_HIERARCHY).isPresent(Configuration.class)) {
application.addPrimarySources(Collections.singleton(getClass()));
}
Assert.state(!application.getAllSources().isEmpty(),
"No SpringApplication sources have been defined. Either override the "
+ "configure method or add an @Configuration annotation");
// Ensure error pages are registered
if (this.registerErrorPageFilter) {
application.addPrimarySources(Collections.singleton(ErrorPageFilterConfiguration.class));
}
//启动spring应用
return run(application);
}
7.spring的应用启动并且创建ioc容器
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//刷新iooc容器
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
推荐阅读
-
SpringBoot应用部署于外置Tomcat容器的方法
-
SpringBoot应用部署于外置Tomcat容器的方法
-
SpringBoot 源码解析 (六)----- Spring Boot的核心能力 - 内置Servlet容器源码分析(Tomcat)
-
SpringBoot起飞系列-配置嵌入式Servlet容器(八)
-
SpringBoot学习笔记11-SpringBoot 中使用 Servlet
-
IDEA配置SpringBoot使用外部Servlet容器(Tomcat)
-
SpringBoot里使用Servlet进行请求的实现示例
-
SpringBoot 源码解析 (六)----- Spring Boot的核心能力 - 内置Servlet容器源码分析(Tomcat)
-
springboot中如何使用minio存储容器
-
使用gradle构建一个springboot工程并用docker部署在阿里云镜像容器仓库的镜像