十一、SpringBoot之使用外接的Servlet容器及啟動原理
阿新 • • 發佈:2019-01-05
一、嵌入式和外接Servlet容器對比
嵌入式Servlet容器:應用打成可執行的jar
優點:簡單、便攜;
缺點:預設不支援JSP、優化定製比較複雜;
外接的Servlet容器:外面安裝Tomcat---應用war包的方式打包;
二、使用外接的Servlet容器步驟
1、必須建立一個war專案;(利用idea建立好目錄結構)
2、生成web目錄和web.xml
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(SpringBoot04WebJspApplication.class); } }
6.啟動伺服器就可以使用;
controller
@Controller
public class HelloController {
@GetMapping("/abc")
public String hello(Model model){
model.addAttribute("msg","你好");
return "success";
}
}
application.properties
spring.mvc.view.prefix=/WEB-INF/
spring.mvc.view.suffix=.jsp
JSP
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <h1>Hello JSP</h1> <a href="abc">abc</a> </body> </html>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>SUCCESS</h1>
<h3>${msg}</h3>
</body>
</html>
三、外接Servlet啟動原理
jar包啟動步驟:執行SpringBoot主類的main方法,啟動ioc容器,建立嵌入式的Servlet容器;
war包啟動步驟:啟動伺服器,伺服器啟動SpringBoot應用【SpringBootServletInitializer】,啟動ioc容器;
servlet3.0的8.2.4 Shared libraries / runtimes pluggability章節定義了一個規則
1、規則:
- 1.伺服器啟動(web應用啟動)會建立當前web應用裡面每一個jar包裡面ServletContainerInitializer例項;
- 2.ServletContainerInitializer的實現放在jar包的META-INF/services資料夾下,有一個名為javax.servlet.ServletContainerInitializer的檔案,內容就是ServletContainerInitializer的實現類的全類名
- 3.還可以使用@HandlesTypes,在應用啟動的時候載入我們感興趣的類;
2、流程:啟動Servlet容器,再啟動SpringBoot應用
- 1.啟動Tomcat
- 2.應用啟動ServletContainerInitializer
Spring的web模組裡面有這個檔案:org.springframework.web.SpringServletContainerInitializer
- 3.為這些WebApplicationInitializer型別的類建立例項;
@HandlesTypes({WebApplicationInitializer.class})
public class SpringServletContainerInitializer implements ServletContainerInitializer {
public SpringServletContainerInitializer() {
}
//SpringServletContainerInitializer將@HandlesTypes(WebApplicationInitializer.class)
//標註的所有這個型別的類都傳入到onStartup方法的Set<Class<?>>集合裡面,
public void onStartup(@Nullable 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)ReflectionUtils.accessibleConstructor(waiClass, new Class[0]).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();
//每一個例項呼叫自己的onStartup()方法
initializer.onStartup(servletContext);
}
}
}
}
- 4.每一個WebApplicationInitializer都呼叫自己的onStartup;
- 5.相當於我們的SpringBootServletInitializer的類會被建立物件,並執行onStartup方法
- 6.SpringBootServletInitializer例項執行onStartup的時候會createRootApplicationContext;建立容器
public abstract class SpringBootServletInitializer implements WebApplicationInitializer {
public void onStartup(ServletContext servletContext) throws ServletException {
this.logger = LogFactory.getLog(this.getClass());
//createRootApplicationContext;建立容器
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();
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(AnnotationConfigServletWebServerApplicationContext.class);
//呼叫configure方法,子類重寫了這個方法,將SpringBoot的主程式類傳入了進來
builder = this.configure(builder);
builder.listeners(new ApplicationListener[]{new SpringBootServletInitializer.WebEnvironmentPropertySourceInitializer(servletContext)});
//使用builder建立一個spring應用
SpringApplication application = builder.build();
if (application.getAllSources().isEmpty() && AnnotationUtils.findAnnotation(this.getClass(), Configuration.class) != null) {
application.addPrimarySources(Collections.singleton(this.getClass()));
}
Assert.state(!application.getAllSources().isEmpty(), "No SpringApplication sources have been defined. Either override the configure method or add an @Configuration annotation");
if (this.registerErrorPageFilter) {
application.addPrimarySources(Collections.singleton(ErrorPageFilterConfiguration.class));
}
//啟動Spring應用
return this.run(application);
}
- 7.Spring的應用就啟動並且建立IOKOC容器
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
this.configureHeadlessProperty();
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting();
Collection exceptionReporters;
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
this.configureIgnoreBeanInfo(environment);
Banner printedBanner = this.printBanner(environment);
context = this.createApplicationContext();
exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//重新整理IOC容器
this.refreshContext(context);
this.afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
}
listeners.started(context);
this.callRunners(context, applicationArguments);
} catch (Throwable var10) {
this.handleRunFailure(context, var10, exceptionReporters, listeners);
throw new IllegalStateException(var10);
}
try {
listeners.running(context);
return context;
} catch (Throwable var9) {
this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
throw new IllegalStateException(var9);
}
}