一、使用内置的 Servlet 容器
嵌入式 Servlet 容器:可以把应用打成可执行的 jar 包
优点:简单、便捷;
缺点:默认不支持JSP、优化定制比较复杂
定制方法:
1、使用定制器 ServerProperties 进行配置;
2、自定义 EmbeddedServletContainerCustomizer 组件;
3、自己编写嵌入式Servlet容器的创建工厂 【EmbeddedServletContainerFactory】
外置的Servlet容器:外面安装Tomcat---应用war包的方式打包;
二、使用外部 Servlet 容器
1、使用Spring Initializr 创建一个 war 项目,利用 idea 创建好目录结构
使用 idea 修改文件目录
(1)指定 webapp 路径
(2)指定 web.xml 的路径
目录结构:
2、编写测试代码
页面请求:
<a href="/hello">hello</a>
控制器方法:
@Controller
public class HelloController {
@GetMapping(value = "/hello")
public String hello(Model model) {
model.addAttribute("msg", "你好哇!");
return "success";
}
}
配置视图解析器:
spring.mvc.view.prefix=/WEB-INF/pages/
spring.mvc.view.suffix=.jsp
目录:
3、将嵌入式的Tomcat指定为provided;
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
4、必须编写一个SpringBootServletInitializer的子类,并调用configure方法
public class ServletInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
//传入SpringBoot应用的主程序
return application.sources(SpringBoot04WebJspApplication.class);
}
}
5、启动服务器就可以使用了
三、外部 Servlet 容器启动原理
1、回顾
jar包:执行SpringBoot主类的 main方法,启动ioc容器,创建嵌入式的Servlet容器;
war包:启动服务器,服务器启动SpringBoot应用【SpringBootServletInitializer】,启动ioc容器;
2、Servlet3.0 新特性
8.2.4 Shared libraries / runtimes pluggability:
规则:
(1)服务器启动(web应用启动)会创建当前web应用里面每一个 jar 包里面ServletContainerInitializer实例;
(2)ServletContainerInitializer的实现放在jar包的META-INF/services文件夹下,有一个名为
javax.servlet.ServletContainerInitializer的文件,内容就是 ServletContainerInitializer 的实现类的全类名;
(3)还可以使用@HandlesTypes,在应用启动的时候加载我们感兴趣的类;
参考文章:Servlet3.0 新特性之 ServletContainerInitializer
3、流程
(1)启动 Tomcat 容器
(2)找每一个 jar 包下的 ServletContainerInitializer 实例:
会在下面的目录中找到
org\springframework\spring-web\4.3.13.RELEASE\spring-web-4.3.13.RELEASE.jar!\META-INF\services\javax.servlet.ServletContainerInitializer
Spring的web模块里面有这个文件:org.springframework.web.SpringServletContainerInitializer
并且创建该类的实例
(3)SpringServletContainerInitializer将@HandlesTypes(WebApplicationInitializer.class)标注的所有这个类型的类都传入到onStartup方法的Set<Class>;为这些WebApplicationInitializer类型的类创建实例;
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
/**
* Delegate the {@code ServletContext} to any {@link WebApplicationInitializer}
* implementations present on the application classpath.
* <p>Because this class declares @{@code HandlesTypes(WebApplicationInitializer.class)},
* Servlet 3.0+ containers will automatically scan the classpath for implementations
* of Spring's {@code WebApplicationInitializer} interface and provide the set of all
* such types to the {@code webAppInitializerClasses} parameter of this method.
* <p>If no {@code WebApplicationInitializer} implementations are found on the classpath,
* this method is effectively a no-op. An INFO-level log message will be issued notifying
* the user that the {@code ServletContainerInitializer} has indeed been invoked but that
* no {@code WebApplicationInitializer} implementations were found.
* <p>Assuming that one or more {@code WebApplicationInitializer} types are detected,
* they will be instantiated (and <em>sorted</em> if the @{@link
* org.springframework.core.annotation.Order @Order} annotation is present or
* the {@link org.springframework.core.Ordered Ordered} interface has been
* implemented). Then the {@link WebApplicationInitializer#onStartup(ServletContext)}
* method will be invoked on each instance, delegating the {@code ServletContext} such
* that each instance may register and configure servlets such as Spring's
* {@code DispatcherServlet}, listeners such as Spring's {@code ContextLoaderListener},
* or any other Servlet API componentry such as filters.
* @param webAppInitializerClasses all implementations of
* {@link WebApplicationInitializer} found on the application classpath
* @param servletContext the servlet context to be initialized
* @see WebApplicationInitializer#onStartup(ServletContext)
* @see AnnotationAwareOrderComparator
*/
@Override
public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
List<WebApplicationInitializer> initializers = new LinkedList<WebApplicationInitializer>();
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 {
initializers.add((WebApplicationInitializer) 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) {
initializer.onStartup(servletContext);
}
}
}
(4)每一个WebApplicationInitializer都调用自己的onStartup;
servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
AnnotationAwareOrderComparator.sort(initializers);
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
WebApplicationInitializer 接口的实现:
(5)相当于我们的SpringBootServletInitializer的类会被创建对象,并执行onStartup方法
(6)SpringBootServletInitializer实例执行onStartup的时候会createRootApplicationContext;创建容器
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
// Logger initialization is deferred in case a ordered
// LogServletContextInitializer is being used
this.logger = LogFactory.getLog(getClass());
//创建容器
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");
}
}
protected WebApplicationContext createRootApplicationContext(
ServletContext servletContext) {
//1、创建SpringApplicationBuilder
SpringApplicationBuilder builder = createSpringApplicationBuilder();
StandardServletEnvironment environment = new StandardServletEnvironment();
environment.initPropertySources(servletContext, null);
builder.environment(environment);
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(AnnotationConfigEmbeddedWebApplicationContext.class);
//调用configure方法,子类重写了这个方法,将SpringBoot的主程序类传入了进来
builder = configure(builder);
//使用builder创建一个Spring应用
SpringApplication application = builder.build();
if (application.getSources().isEmpty() && AnnotationUtils
.findAnnotation(getClass(), Configuration.class) != null) {
application.getSources().add(getClass());
}
Assert.state(!application.getSources().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.getSources().add(ErrorPageFilterConfiguration.class);
}
//启动Spring应用
return run(application);
}
(7)Spring的应用就启动并且创建IOC容器
protected WebApplicationContext run(SpringApplication application) {
return (WebApplicationContext) application.run();
}
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
FailureAnalyzers analyzers = null;
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
analyzers = new FailureAnalyzers(context);
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
//刷新IOC容器
refreshContext(context);
afterRefresh(context, applicationArguments);
listeners.finished(context, null);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
return context;
}
catch (Throwable ex) {
handleRunFailure(context, listeners, analyzers, ex);
throw new IllegalStateException(ex);
}
}
启动 servlet 容器,再启动 SpringBoot 应用
4、总结
(1)实现 SpringBootServletInitializer 重写 configure 方法;
(2)SpringApplicationBuilder:builder.source(@SpringBootApplication 类);
(3)启动原理
① Servlet3.0标准ServletContainerInitializer扫描所有jar包中METAINF/services/javax.servlet.ServletContainerInitializer文件指定的类并加载
② 加载spring web包下的SpringServletContainerInitializer;
③ 扫描@HandleType(WebApplicationInitializer);
④ 加载SpringBootServletInitializer并运行onStartup方法;
⑤ 加载@SpringBootApplication主类,启动容器等;