SpringMVC 容器的生命周期(9 个阶段)
阶段 1:Servlet 容器初始化
阶段 2:创建父容器
阶段 3:创建 springmvc 容器
阶段 4:Servlet 容器中注册 DispatcherServlet
阶段 5:启动父容器:ContextLoaderListener
阶段 6:启动 springmvc 容器:DispatcherServlet#init()
阶段 7:springmvc 容器启动过程中处理@WebMVC
阶段 8:组装 DispatcherServlet 中各种 SpringMVC 需要的组件
阶段 9:销毁 2 个容器
参看:SpringMVC这篇文章吃透了,最少最少涨5000 - 知乎
一.初始化:AbstractAnnotationConfigDispatcherServletInitializer
代码如下,这个类需要继承 AbstractAnnotationConfigDispatcherServletInitializer,会有 web 容器来调用,这个类中有 4 个方法需要实现,干了 4 件事情
getRootConfigClasses():获取父容器的配置类
getServletConfigClasses():获取 springmvc 容器的配置类,这个配置类相当于 springmvc xml 配置文件的功能
getServletMappings():获取 DispatcherServlet 能够处理的 url,相当于 web.xml 华国 servlet 指定的 url-pattern
getServletFilters():定义所有的 Filter
/**
* ①:1、创建Mvc初始化类,需要继承AbstractAnnotationConfigDispatcherServletInitializer类
*/
public class WebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
/**
* springmvc容器的父容器spring配置类
* 实际工作中我们的项目比较复杂,可以将controller层放在springmvc容器中
* 其他层,如service层、dao层放在父容器了,bean管理起来更清晰一些
* 也可以没有父容器,将所有bean都放在springmvc容器中
*
* @return
*/
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[0];
}
/**
* ②:2、设置springmvc容器的spring配置类
*
* @return
*/
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{MvcConfig.class};
}
/**
* ③:3、配置DispatcherServlet的url-pattern
*
* @return
*/
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
/**
* ④:4、注册拦截器
*
* @return
*/
@Override
protected Filter[] getServletFilters() {
//添加拦截器,解决乱码问题
CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
characterEncodingFilter.setEncoding("UTF-8");
characterEncodingFilter.setForceRequestEncoding(true);
characterEncodingFilter.setForceResponseEncoding(true);
return new Filter[]{characterEncodingFilter};
}
}
看一下类的继承关系
下面讲WebApplicationInitializer
WebApplicationInitializer简介
servlet3.0 中新增了一个接口:ServletContainerInitializer,这个接口功能特别的牛逼,有了它之后,web.xml 配置文件可要可不要了。
public interface ServletContainerInitializer {
public void onStartup(Set<Class<?>> c, ServletContext ctx)
throws ServletException;
}
这个接口的实现类,如果满足下面 2 个条件,Servlet 容器启动的过程中会自动实例化这些类,然后调用他们的 onStartUp 方法,然后我们就可以在这些类的 onStartUp 方法中干活了,在 web.xml 干的所有事情,都可以在这个方法中干,特别强大:
这个类必须实现
ServletContainerInitializer 接口,且非抽象类
这个类的全类名必须要放在
META-INF/services/javax.servlet.ServletContainerInitializer这个文件中
下面重点来了,springmvc 提供了一个类SpringServletContainerInitializer,满足了上面个条件。
spring-web-5.3.6.jar!\META-INF\services\javax.servlet.ServletContainerInitializer
****************************************************20240125补充 start************************************************
@HandlesTypes
注解是 Java Servlet 规范的一部分,具体是在 Servlet 3.0 中引入的。它用于指示 Servlet 容器(比如 Tomcat 或 Jetty)在 Web 应用程序初始化过程中自动发现和处理某种类型的类。在你的例子中,
@HandlesTypes({WebApplicationInitializer.class})
表示 Servlet 容器应该发现并处理实现了WebApplicationInitializer
接口的类。WebApplicationInitializer
接口是 Servlet 3.0 规范的一部分,它允许你以编程方式配置 Servlet 容器,而不是使用部署描述符(比如 web.xml)。当你使用这个注解时,Servlet 容器会扫描类路径以找到实现了
WebApplicationInitializer
接口的类,然后在 Web 应用程序初始化期间调用它们的onStartup
方法。这里是一个简单的示例,展示了如何使用
WebApplicationInitializer
:import javax.servlet.ServletContext; import javax.servlet.ServletException; public class MyInitializer implements WebApplicationInitializer { @Override public void onStartup(ServletContext servletContext) throws ServletException { // 在这里进行初始化逻辑 } }
通过使用
@HandlesTypes({WebApplicationInitializer.class})
,你告诉 Servlet 容器在 Web 应用程序启动时发现和处理类似于MyInitializer
的类。这是一种与传统的web.xml
文件相比的配置替代方案。
****************************************************20240125补充 end************************************************
所以 SpringServletContainerInitializer 的 onStart 方法会 servlet 容器自动被调用
这个类的源码,大家先看一下,这个类干的事情:
类上有@HandlesTypes(
WebApplicationInitializer.class) 这个注解,注解的值为
WebApplicationInitializer.class,所以 onStartup 方法的第一个参数是WebApplicationInitializer类型的集合,这个集合由 web 容器自动扫描获取,然后传入进来
实例化 WebApplicationInitializer 集合
对 WebApplicationInitializer 集合进行排序
循环调用 WebApplicationInitializer 的 onStartup 方法
下面重点要看WebApplicationInitializer接口了。
1.WebApplicationInitializer的定义
从起初的Spring配置文件,到后来的Spring支持注解到后来的SpringBoot,Spring框架在一步步的使用注解的方式来去除Spring的配置的发展过程。WebApplicationInitializer就是取代web.xml配置的一个接口。
public interface WebApplicationInitializer {
void onStartup(ServletContext var1) throws ServletException;
}
通过覆盖接口提供的onStartup
方法我们可以往Servlet容器
里面添加我们需要的servlet
、listener
等,并且在Servlet容器
启动的过程中就会加载这个接口的实现类,从而起到和web.xml
相同的中作用,从而可以替代以前在web.xml中所做的配置。
2.实现原理
在与WebApplicationInitializer类同路径下有个SpringServletContainerInitializer类。这个类的作用就是发现所有实现了WebApplicationInitializer的类。
我们首先可以从Spring源码中找到SpringServletContainerInitializer
实现类。
@HandlesTypes({WebApplicationInitializer.class})
public class SpringServletContainerInitializer implements ServletContainerInitializer {
public SpringServletContainerInitializer() {
}
public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {
LinkedList initializers = new LinkedList();
Iterator var4;
if(webAppInitializerClasses != null) {
var4 = webAppInitializerClasses.iterator();
while(var4.hasNext()) {
Class initializer = (Class)var4.next();
if(!initializer.isInterface() && !Modifier.isAbstract(initializer.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(initializer)) {
try {
initializers.add((WebApplicationInitializer)initializer.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 initializer1 = (WebApplicationInitializer)var4.next();
initializer1.onStartup(servletContext);
}
}
}
}
这个类上面有@HandlesTypes({WebApplicationInitializer.class})
这个注解,这个注解的作用是将其value中配置的一些类放入到ServletContainerInitializer
。
initializers.add((WebApplicationInitializer)initializer.newInstance());
最后通过循环去执行WebApplicationInitializer
中的onStartup
方法来实现里面的具体的具体的逻辑。
while(var4.hasNext()) {
WebApplicationInitializer initializer1 = (WebApplicationInitializer)var4.next();
initializer1.onStartup(servletContext);
}
那问题来了,Tomcat容器怎么知道先执行这个SpringServletContainerInitializer类?
这里涉及一个知识点SPI机制,SPI全称为 Service Provider Interface,是一种服务发现机制。SPI机制是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。正因此特性,我们可以很容易的通过 SPI机制为我们的程序提供拓展功能。
Tomcat启动过程中查找所有的ServletContainerInitializer实现类然后添加到StandardContext的initializers集合中,然后执行里面的onStartup方法。
Spring中SPI就是通过SpringServletContainerInitializer类来实现的
关于Web应用的启动过程在《一个基于注解配置的Web项目的启动流程分析》这篇文章写的很好。
3.利用SPI我们能做什么?
可以加一些自己的启动配置信息,把自己的Servlet打成jar包放到Tomcat服务器或者其他工程中执行。我们可以实现一个自己的SPI接口。
- 定义一个MyWebAppInitializer
public interface MyWebAppInitializer {
void loadOnStart(ServletContext var1) throws ServletException;
}
- 定义一个MySpringServletContainerInitializer
实现ServletContainerInitializer接口
修改@HandlesTypes为我们自己定义的MyWebAppInitializer.class
实例化MyWebAppInitializer的实现类,并且调用接口的loadOnStart方法
@HandlesTypes(MyWebAppInitializer.class)
public class MySpringServletContainerInitializer implements ServletContainerInitializer{
@Override
public void onStartup(Set<Class<?>> set, ServletContext servletcontext)
throws ServletException {
if (set != null) {
for (Class<?> waiClass : set) {
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
MyWebAppInitializer.class.isAssignableFrom(waiClass)) {
try {
//创建MyWebAppInitializer实现类的对象,并调用loadOnStart方法
((MyWebAppInitializer) waiClass.newInstance()).loadOnStart(servletcontext);
} catch (Throwable ex) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
}
}
}
}
}
}
- 写一个
MyWebAppInitializer
的实现类
public class MyWebApplicationInitializerTest implements MyWebAppInitializer{
@Override
public void loadOnStart(ServletContext servletContext){
System.out.println("启动执行MyWebApplicationInitializerTest的loadOnStart方法");
//注册一个为名字call的servlet
ServletRegistration.Dynamic servletReg = servletContext.addServlet("call", CallServlet.class);
servletReg.setLoadOnStartup(1);
servletReg.addMapping("/call");
}
}
其中里面的CallServlet.java
如下
public class CallServlet extends HttpServlet {
private static final long serialVersionUID = 3684613967452881093L;
@Override
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String name = req.getParameter("name");
resp.getWriter().write(name + ", if you like me, please call me!");
}
}
- 添加配置文件javax.servlet.ServletContainerInitializerr
添加位置为:src -> main -> resources-> META-INF-> services ->javax.servlet.ServletContainerInitializerr
内容:com.leo.spi.MySpringServletContainerInitializer
- 启动项目测试
启动日志:
启动执行MyWebApplicationInitializerTest的loadOnStart方法
浏览器测试:http://localhost:8080/springmvc/call?name=leo825
上面也提到了,可以把这个SPI的方式打成jar包在其他项目或者直接在Tomcat容器这种运行。
本文的相关源码请参考:chapter-5-springmvc-zero-configuration
spi代码包路径:com.leo.spi
参考:
SpringMVC学习(五)——零配置实现SpringMVC_springmvc零配置-CSDN博客
spring-framework-learning-example: 关于spring框架学习的例子
Spring中对于WebApplicationInitializer的理解-CSDN博客
二.配置:WebMvcConfigurer简介
WebMvcConfigurer配置类其实是Spring
内部的一种配置方式,采用JavaBean
的形式来代替传统的xml
配置文件形式进行针对框架个性化定制,可以自定义一些Handler,Interceptor,ViewResolver,MessageConverter。基于java-based方式的spring mvc配置,需要创建一个配置类并实现WebMvcConfigurer
接口;
在Spring Boot 1.5版本都是靠重写WebMvcConfigurerAdapter的方法来添加自定义拦截器,消息转换器等。SpringBoot 2.0 后,该类被标记为@Deprecated(弃用)。官方推荐直接实现WebMvcConfigurer或者直接继承WebMvcConfigurationSupport,方式一实现WebMvcConfigurer接口(推荐),方式二继承WebMvcConfigurationSupport类,具体实现可看这篇文章。继承WebMvcConfigurationSupport后自动配置不生效的问题及如何配置拦截器
下面是一些WebMvcConfigurer接口中常用的方法:
-
addViewControllers:用于注册简单的视图控制器。
-
addInterceptors:用于注册拦截器,可以在请求处理之前或之后执行一些逻辑。
-
addResourceHandlers:用于注册静态资源处理器,可以将静态资源映射到指定的URL路径。
-
configureViewResolvers:用于配置视图解析器,可以将逻辑视图名称解析为实际的视图。
-
configureContentNegotiation:用于配置内容协商策略,可以根据请求头中的Accept字段来返回不同的响应格式。
-
configureDefaultServletHandling:用于配置静态资源的处理方式,可以将请求转发给默认的Servlet。
-
addArgumentResolvers:用于注册自定义的方法参数解析器,可以将请求参数解析为控制器方法的参数。
-
addReturnValueHandlers:用于注册自定义的返回值处理器,可以将控制器方法的返回值转换为响应体。
总之,WebMvcConfigurer接口提供了很多方法来定制Spring MVC的行为,可以满足不同的需求。
具体参考: