06.Servlet容器
1.在 web.xml 中配置前端处理器
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
容器启动时先初始化load-on-startup值小的 Servlet,
若不配置该标签(或值为负数),则 Servlet 会在第一次被客户端请求时才初始化。
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
将所有的请求都交给 DispatcherServlet 处理。
</servlet-mapping>
2.DispatcherServlet 继承链:
DispatcherServlet → FrameworkServlet→HttpServletBean→HttpServlet→GenericServlet→Servlet
-
① GenericServlet 持有 ServletConfig 对象,实现 Servlet 规范,ServletConfig 对象是由 web 容器创建的,而非 DispatcherServlet 自己生成。当 web 容器初始化 DispatcherServlet 时,会先创建 ServletConfig 实例(封装该 Servlet 的
配置),再通过init(ServletConfig config)方法(顶层触发)将其注入到 GenericServlet 中。最终持有 ServletConfig 的是 GenericServlet,GenericServlet 实现了init(ServletConfig config),将传入的对象赋值给自己的私有成员(private ServletConfig config),因此,DispatcherServlet 作为子类,会继承这个成员变量,且默认提供无参 init() 方法(重载 init(ServletConfig)),子类可直接重写无参 init() 完成初始化(无需关注 ServletConfig 传递)。 -
②HttpServlet针对 HTTP 协议的专用 Servlet 抽象类,核心作用是协议解析与请求分发:
- 重写 service(ServletRequest, ServletResponse),将参数强转为HttpServletRequest/HttpServletResponse
- 根据 HTTP 请求方法(GET/POST/PUT/DELETE 等),分发到对应 doXxx() 方法(如 doGet()、doPost())
-
③HttpServletBean 由 GenericServlet 里面的 init(ServletConfig config)内部会自动调用由本类重写的init()方法:
-
获取到 ServletConfig 对象,由 GenericServlet 提供。
ServletConfig config = getServletConfig(); // 该方法由GenericServlet提供ServletConfig的设计职责:ServletConfig是单个Servlet的“配置对象”,存储当前Servlet专属的初始化参数(
),提供getInitParameter(String name)方法,仅能获取当前Servlet自己的参数。 -
将参数映射到当前实例的属性中(如给contextConfigLocation赋值)
PropertyValues pvs = new ServletConfigPropertyValues(config, this.requiredProperties)ServletConfigPropertyValues会遍历config.getInitParameterNames(),将所有
<init-param>转换为PropertyValues集合(ServletConfig本质上是一个键值对容器,而当前类的作用是把这些键值对提取出来,转换为Spring能处理的属性格式),都是键值对,但是pvs有更为强大的API。pvs中的值只是字符串(如maxSize的值是“1024”),但目标Bean的属性可能是int(即int maxSize),这时需结合BeanWrapper进行类型转换。获取当前对象的BeanWrapper实例:
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this) bw.setPropertyValues(pvs, true) // 将pvs注入到当前bean属性中BeanWrapper是什么?
是操作Bean属性的核心工具,它提供了:
- 属性读取、设置(通过反射实现)
- 类型转换(将字符串等类型转换为Bean支持的目标类型,如String转为int)
为什么非要将ServletConfig转换为PropertyValues?
数据结构不统一
ServletConfig通过 Enumerationname = config.getInitParameterNames()提供键名,再通过键名获取键值,这种“枚举+逐个获取”的方式是Servlet规范定义的标准接口,不是Spring能直接操作的“集合形态”。而PropertyValues内部是List 集合,每个PropertyValue直接封装“属性名+原始值”,每个PropertyValue的name直接对应Bean的属性名。如 new PropertyValue("maxFiles", "1024"), BeanWrapper会遍历集合,通过name = "maxFiles",找到setter方法setMxFiles,并将 value="1024" 转换为 int 类型后注入。说到底是 Servlet 的枚举错误,且进行类型转换时,仅返回字符串,需要手写转换逻辑,但是 PropertyValues 可以被 ConversionService(可能背后逻辑仅支持集合获取对应的值吧) 识别自动根据 Bean 属性类型完成转换 。 注意:HttpServletBean 只对框架约定参数做特殊绑定,自定义参数仅存储不处理
对于框架约定参数(如 contextConfigLocation):
HttpServletBean 中预先定义了对应的属性(private String contextConfigLocation;),会通过反射将参数值绑定到该属性上,供后续 FrameworkServlet 使用;
对于自定义参数(如 my.custom.param1):
HttpServletBean 中没有对应的属性,因此不会做 “属性绑定” 这个特殊处理,但它不会丢弃这些参数,而是将这些参数完整保留在 ServletConfig 对象中(Servlet 规范的原生存储),后续你可以通过 ServletConfig API 随时获取。那么这里的 requiredProperties 又是什么?
字符串数组 requiredProperties 是一种防御编程性手段,通过明确声明“哪些属性必须配置”,可以在应用启动阶段校验配置的完整性,避免因缺少相关参数导致运行时出现难以排查的异常。在将格式转换为 pvs 中会检查 requiredProperties 中指定的属性是否缺失,如果缺失会直接抛出异常,只有所有的属性都存在时,才会正常创建 PropertyValues 对象,继续后面的属性注入流程。
// 留给子类的初始化入口 initServletBean();//该方法由本类重写的init()方法调用,留给FrameworkServlet重写
-
-
④ FrameworkServlet 里实现子容器的诞生
-
初始化或查找 WebApplicationContext
@Override protected final void initServletBean() throws ServletException { // 初始化 Spring WebApplicationContext(核心逻辑) this.webApplicationContext = initWebApplicationContext();//这个方法就是“催生”子容器的关键 // 扩展方法(空实现,留给子类按需重写) initFrameworkServlet(); } -
先看看有没有现成可用的现成容器。
-
- 有没有提前准备好的?比如开发者主动创建了一个 WebApplicationContext 并绑定了 DispatcherServlet,如果有,直接用现成的。
-
- 有没有之前剩下的?从 Web 应用的全局环境(ServletContext)里找找,有没有之前创建的子容器。
疑惑与解答:程序结束后不都要销毁容器吗,为啥会出现去寻找之前创建的子容器呀
1.Servlet 容器热部署 / 热加载:比如 Tomcat 开启了热部署,修改了web.xml或类文件后,Servlet 容器会重新加载DispatcherServlet,但ServletContext(Web 应用全局环境)不会销毁;
2.多个DispatcherServlet共享子容器:一个 Web 应用中可以配置多个DispatcherServlet(比如按模块划分),后初始化的DispatcherServlet会尝试从ServletContext中找之前其他DispatcherServlet创建的子容器(避免重复创建);
3.Servlet 异常重启:若DispatcherServlet因异常崩溃,Servlet 容器可能重启该 Servlet,但ServletContext依然保持运行。 -
- 如果都没有,就进入下一步:全新创建。
-
-
全新创建子容器,如果没有现成的,会调用 createWebApplicationContext() 方法。
createWebApplicationContext()里面创建子容器的两种方法-
- 如果用 xml 配置(如 dispatcher-servlet.xml)就用 new XmlWebApplicationContext()。
-
- 如果用纯注解(比如 @Controller、@Configuration)就用 new AnnotationConfigWebApplicationContext()
关键操作是:必须先把之前 ContextLoaderListener 创建的“父容器”设为父容器
setParent(rootContext); -
-
往子容器里面装东西
子容器创建好后,会执行 onRefresh() 方法,加载并初始化所有它该管的东西(如 HandlerMapping、HandlerAdapter、ViewResolver)// FrameworkServlet 中 onRefresh() 是空实现,交给子类 DispatcherServlet 重写 protected void onRefresh(ApplicationContext context) { // For subclasses: do nothing by default. } -
最后子容器会被存到 ServletContext 里,方便后续用随时都能找到它。(比如其他组件需要获取 Controller 时,就从这里查)。
if (this.webApplicationContext == null) { // 没有提前准备好子容器 this.webApplicationContext = findWebApplicationContext(); } else if (this.webApplicationContext == null) { // 也没有找到容器 this.webApplicationContext = createWebApplicationContext(); } -
那怎么主动创建子容器并传给 DispatcherServlet 呢?
实例化子容器:XmlWebApplicationContext childContext = new XmlWebApplicationContext(); childContext.setParent(rootContext); // 设置父容器 childContext.setConfigLocation(getContextConfigLocation()); // Spring-mvc.xml 路径 childContext.refresh(); // 触发容器初始化 Bean、注册组件等 childContext.setServletContext(getServletContext());//关联ServletContext getServletContext().setAttribute(MVC_CONTEXT_KEY,childContext);//将子容器绑定到servletcontext中(提供给dispatcherservlet获取)
-
-
⑤ DispatcherServlet 中重写了 onRefresh(),并初始化所有策略组件。initWebApplicationContext() 调用 onRefresh()
@Override
protected void onRefresh(ApplicationContext context) {
// 核心职责:初始化DispatcherServlet的所有MVC核心组件
initStrategies(context);
}
/**
* 初始化Spring MVC的九大核心策略(组件)
*/
protected void initStrategies(ApplicationContext context) {
// 1. 文件上传解析器(MultipartResolver)
initMultipartResolver(context);
// 2. 本地语言环境解析器(LocaleResolver)
initLocaleResolver(context);
// 4. 处理器映射器(HandlerMapping):映射请求URL与Controller方法
initHandlerMappings(context);
// 5. 处理器适配器(HandlerAdapter):适配执行Controller方法
initHandlerAdapters(context);
// 6. 处理器异常解析器(HandlerExceptionResolver):处理请求异常
initHandlerExceptionResolvers(context);
//=========注意!!!纯全后端分离项目几乎完全用不上这四个组件
// 7. 请求视图名解析器(RequestToViewNameTranslator)
initRequestToViewNameTranslator(context);
// 8. 视图解析器(ViewResolver):解析逻辑视图名为物理视图
initViewResolvers(context);
// 9. FlashMap管理器(FlashMapManager):管理重定向参数传递
initFlashMapManager(context);
// 3. 主题解析器(ThemeResolver)核心作用:用于服务端渲染场景,解析 Web 应用的 “主题”(比如不同的皮肤样式:默认主题、深色主题,对应不同的 CSS / 图片资源),支持动态切换页面样式。
initThemeResolver(context);
}

浙公网安备 33010602011771号