DispatcherServlet是Spring MVC的核心,在这里请求会第一次接触到框架,它负责将请求路由到其他组件之中。
传统配置DispatcherServlet是采用web.xml文件的方式。一般如下:
<?xml version="1.0" encoding="UTF-8"?> <web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"> <!-- 监听器配置 --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- 上下文参数,在监听器中被使用 --> <!-- 监听器配置文件路径 --> <context-param> <param-name>contextConfigLocation</param-name> <param-value> classpath:applicationContext.xml </param-value> </context-param> <!-- 前端控制器配置 --> <servlet> <!--在DispatcherServlet的初始化过程中,框架会在web应用的WEB-INF文件夹下默认寻找名为[servlet-name]-servlet.xml 的配置文件,生成文件中定义的bean。如以下默认寻找dispatcher-servlet.xml--> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <!-- 前端控制器配置文件路径 --> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext-mvc.xml</param-value> </init-param> <!--是启动顺序,让这个Servlet随Servletp容器一起启动。--> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <!--这个Servlet的名字是dispatcher,可以有多个DispatcherServlet,是通过名字来区分的。每一个DispatcherServlet有自己的WebApplicationContext上下文对象。同时保存的ServletContext中和Request对象中.--> <!--ApplicationContext是Spring的核心,Context我们通常解释为上下文环境,我想用“容器”来表述它更容易理解一些,ApplicationContext则是“应用的容器”了:P,Spring把Bean放在这个容器中,在需要的时候,用getBean方法取出--> <servlet-name>dispatcher</servlet-name> <!--Servlet拦截匹配规则可以自已定义,当映射为@RequestMapping("/user/add")时,为例,拦截哪种URL合适?--> <!--1、拦截*.do、*.htm, 例如:/user/add.do,这是最传统的方式,最简单也最实用。不会导致静态文件(jpg,js,css)被拦截。--> <!--2、拦截/,例如:/user/add,可以实现现在很流行的REST风格。很多互联网类型的应用很喜欢这种风格的URL。弊端:会导致静态文件(jpg,js,css)被拦截后不能正常显示。 --> <url-pattern>/</url-pattern> </servlet-mapping> </web-app> 复制代码
在Servlet3规范中,容器会在类路径中查找实现ServletContainerInitializer接口的类,如果能发现的话就用它来配置Servlet容器。
Spring中提供了这个接口的实现,名为SpringServletContainerInitializer,这个类反过来又会查找实现WebApplicationInitializer的类并将配置任务交给它们继续完成。Spring3.2中,提供了一个便利的WebApplicationInitializer的基础实现,即AbstractAnnotationConfigDispatcherServletInitializer。所以采用java配置DispatcherServlet只需要扩展AbstractAnnotationConfigDispatcherServletInitializer就可以了。
并且扩展AbstractAnnotationConfigDispatcherServletInitializer的任意类都会自动的配置DispatcherServlet和SpringApplicationContext。
一般java配置如下:
public class WebMVCInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { /** * 根应用上下文配置文件 */ @Override protected Class<?>[] getRootConfigClasses() { return new Class<?>[]{ RootConfig.class }; } /** * web 应用上下文配置文件 */ @Override protected Class<?>[] getServletConfigClasses() { return new Class<?>[]{ WebConfig.class }; } @Override protected String[] getServletMappings() { return new String[]{ "/" }; } @Override public void onStartup(ServletContext servletContext) throws ServletException { super.onStartup(servletContext); servletContext.addListener(new SessionListener()); } } 复制代码
在java配置文件中,我们发现了两个应用上下文:根应用上下文,web应用上下文。
首先我们先看 DispatcherServlet 和 ContextLoaderListener。
DispatcherServlet 本质是一个Servlet,它扩展了HTTPServlet,其主要目的是处理与配置URL模式匹配的传入Web的请求。
每一个DispatcherServlet定义一个Spring的web application,并且都与一个WebApplicationContext相关。当DispatcherServlet启动的时候,它会创建WebApplicationContext,并加载配置文件。
从Spring 3.x开始,方法DispatcherServlet(WebApplicationContext webApplicationContext)通过给定的web application context创建一个新DispatcherServlet。只有在Servlet 3.x环境中才有可能通过ServletContext.addServlet(java.lang.String, java.lang.String)的API支持。
ContextLoaderListener是spring框架对servlet监听器的一个封装,本质上还是一个servlet监听器,它创建了一个根应用程序上下文(ApplicationContext),并与所有DispatcherServlet上下文创建的子上下文共享。
ContextLoaderListener 包含全局可见的bean 的上下文,如服务,存储库,基础结构bean等。创建根应用程序上下文后,它将 ServletContext 作为属性存储,名称为:
// org/springframework/web/context/ContextLoader.java servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context); //Where attibute is defined in /org/springframework/web/context/WebApplicationContext.java as WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT"; 复制代码
要 在Spring控制器中获取根应用程序上下文 ,可以使用 WebApplicationContextUtils 类。
// Controller.java @Autowired ServletContext context; ApplicationContext ac = WebApplicationContextUtils.getWebApplicationContext(context); if(ac == null){ return "root application context is null"; } 复制代码
在Spring Web应用程序中,有两种类型的容器,ApplicationContext和WebAppilicationContext.
ApplicationContext是由ContextLoaderListener创建并配置的或Web.xml。
而WebApplicationContext是ApplicationContext的子上下文环境。 是由DispatcherServlet 启动时创建配置的 。
所以说如果没有配置listener参数,只配置了dispatcherServlet时,tomcat启动时是不会初始化Spring Web上下文的,因为Spring Web是基于Spring的,你没有配置Spring,所以也不会启动它的子上下文Spring Web。这一点在Spring MVC启动源码中也可以看到。
下图中详细的描述了整个关系。
ContextLoaderListener DispatcherServlet ServletContext WebApplicationContextUtils
二者具体关系参考自 ContextLoaderListener vs DispatcherServlet
Tomcat启动时会优先加载Servlet监听器组件,并调用ContextInitialized方法。而ContextLoaderListener继承了ServletContextListener实现了ContextInitialized方法。在其中调用initwebApplicationContext方法初始化Spring Web Context。 这就是 Spring MVC 的入口。
public class ContextLoaderListener extends ContextLoader implements ServletContextListener { public ContextLoaderListener() { } public ContextLoaderListener(WebApplicationContext context) { super(context); } /** * Initialize the root web application context. */ @Override public void contextInitialized(ServletContextEvent event) { initWebApplicationContext(event.getServletContext()); } /** * Close the root web application context. */ @Override public void contextDestroyed(ServletContextEvent event) { closeWebApplicationContext(event.getServletContext()); ContextCleanupListener.cleanupAttributes(event.getServletContext()); } } 复制代码
一般DispatcherServlet加载包含Web组件的bean(控制器,视图解析器,以及处理器映射),根上下文加载应用中的其他bean(安全性,事务,服务,中间层和数据层组件等)。
参考文献:
howtodoinjava.com/spring-mvc/…
blog.csdn.net/seagal890/a…
juejin.im/post/5b207d…