DispatcherServlet 初始化
该类是 spring-mvc
的核心,该类进行真正逻辑实现,DisptacherServlet
实现了 Servlet
接口。
介绍:
servlet
是一个Java
编写的程序,基于Http
协议,例如我们常用的Tomcat
,也是按照servlet
规范编写的一个Java
类
servlet
的生命周期是由servlet
的容器来控制,分为三个阶段:初始化、运行和销毁。
在 servlet
初始化阶段会调用其 init
方法:
HttpServletBean#init
public final void init() throws ServletException { // 解析 init-param 并封装到 pvs 变量中 PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties); // 将当前的这个 Servlet 类转换为一个 BeanWrapper,从而能够以 Spring 的方式对 init—param 的值注入 BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this); ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext()); // 注册自定义属性编辑器,一旦遇到 Resource 类型的属性将会使用 ResourceEditor 进行解析 bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment())); // 空实现,留给子类覆盖 initBeanWrapper(bw); bw.setPropertyValues(pvs, true); // 初始化 servletBean (让子类实现,这里它的实现子类是 FrameworkServlet) initServletBean(); }
在这里初始化 DispatcherServlet
,主要是通过将当前的 servlet
类型实例转换为 BeanWrapper
类型实例,以便使用 Spring
中提供的注入功能进行相应属性的注入。
从上面注释,可以看出初始化函数的逻辑比较清晰,封装参数、转换成 BeanWrapper
实例、注册自定义属性编辑器、属性注入,以及关键的初始化 servletBean
。
容器初始化
下面看下初始化关键逻辑:
FrameworkServlet#initServletBean
剥离了日志打印后,剩下的两行关键代码
protected final void initServletBean() throws ServletException { // 仅剩的两行关键代码 this.webApplicationContext = initWebApplicationContext(); // 留给子类进行覆盖实现,但我们例子中用的 DispatcherServlet 并没有覆盖,所以先不用管它 initFrameworkServlet(); }
WebApplicationContext 的初始化
FrameworkServlet#initWebApplicationContext
该函数的主要工作就是创建或刷新 WebApplicationContext
实例并对 servlet
功能所使用的变量进行初始化。
protected WebApplicationContext initWebApplicationContext() { // 从根容器开始查找 WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext()); WebApplicationContext wac = null; if (this.webApplicationContext != null) { // 有可能在 Spring 加载 bean 时,DispatcherServlet 作为 bean 加载进来了 // 直接使用在构造函数被注入的 context 实例 wac = this.webApplicationContext; if (wac instanceof ConfigurableWebApplicationContext) { ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac; if (!cwac.isActive()) { if (cwac.getParent() == null) { cwac.setParent(rootContext); } // 刷新上下文环境 configureAndRefreshWebApplicationContext(cwac); } } } if (wac == null) { // 根据 contextAttribute 属性加载 WebApplicationContext wac = findWebApplicationContext(); } if (wac == null) { // 经过上面步骤都没找到,那就来创建一个 wac = createWebApplicationContext(rootContext); } if (!this.refreshEventReceived) { synchronized (this.onRefreshMonitor) { // 刷新,初始化很多策略方法 onRefresh(wac); } } if (this.publishContext) { // Publish the context as a servlet context attribute. String attrName = getServletContextAttributeName(); getServletContext().setAttribute(attrName, wac); } return wac; }
根容器查找
我们最常用到的 spring-mvc
,是 spring
容器和 web
容器共存,这时 rootContext
父容器就是 spring
容器。
在前面的 web.xml
配置的监听器 ContextLaoderListener
,已经将 Spring
父容器进行了加载
WebApplicationContextUtils#getWebApplicationContext(ServletContext)
public static WebApplicationContext getWebApplicationContext(ServletContext sc) { // key 值 :WebApplicationContext.class.getName() + ".ROOT" // (ServletContext) sc.getAttribute(attrName) , return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE); }
同时,根据上面代码,了解到 Spring
父容器,是以 key
值为 : WebApplicationContext.class.getName() + ".ROOT"
保存到 ServletContext
上下文中。
根据 contextAttribute 寻找
虽然有默认 key
,但用户可以重写初始化逻辑(在 web.xml
文件中设定 servlet
参数 contextAttribute
),使用自己创建的 WebApplicaitonContext
,并在 servlet
的配置中通过初始化参数 contextAttribute
指定 key
。
protected WebApplicationContext findWebApplicationContext() { String attrName = getContextAttribute(); if (attrName == null) { return null; } // attrName 就是用户在`web.xml` 文件中设定的 `servlet` 参数 `contextAttribute` WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName); if (wac == null) { throw new IllegalStateException("No WebApplicationContext found: initializer not registered?"); } return wac; }
重新创建实例
通过前面的方法都没找到,那就来重新创建一个新的实例:
FrameworkServlet#createWebApplicationContext(WebApplicationContext)
protected WebApplicationContext createWebApplicationContext(@Nullable WebApplicationContext parent) { return createWebApplicationContext((ApplicationContext) parent); } protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) { // 允许我们自定义容器的类型,通过 contextClass 属性进行配置 // 但是类型必须要继承 ConfigurableWebApplicationContext,不然将会报错 Class<?> contextClass = getContextClass(); if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) { throw new ApplicationContextException(); } // 通过反射来创建 contextClass ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); wac.setEnvironment(getEnvironment()); wac.setParent(parent); // 获取 contextConfigLocation 属性,配置在 servlet 初始化函数中 String configLocation = getContextConfigLocation(); wac.setConfigLocation(configLocation); // 初始化 Spring 环境包括加载配置环境 configureAndRefreshWebApplicationContext(wac); return wac; }
获取上下文类 contextClass
默认使用的是 XmlWebApplicationContext
,但如果需要配置自定义上下文,可以在 web.xml
中的 <init-param>
标签中修改 contextClass
属性对应的 value
,但需要注意图中提示:
configureAndRefreshWebApplicationContext
使用该方法,用来对已经创建的 WebApplicaitonContext
进行配置以及刷新
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) { // 遍历 ApplicationContextInitializer,执行 initialize 方法 applyInitializers(wac); // 关键的刷新,加载配置文件及整合 parent 到 wac wac.refresh(); }
ApplicationContextInitializer
该类可以通过 <init-param>
的 contextInitializerClasses
进行自定义配置:
<init-param> <param-name>contextInitializerClasses</param-name> <param-value>自定义类,需继承于 `ApplicationContextInitializer`</param-value> </init-param>
正如代码中的顺序一样,是在 mvc
容器创建前,执行它的 void initialize(C applicationContext)
方法:
protected void applyInitializers(ConfigurableApplicationContext wac) { AnnotationAwareOrderComparator.sort(this.contextInitializers); for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : this.contextInitializers) { initializer.initialize(wac); } }
所有如果没有配置的话,默认情况下 contextInitializers
列表为空,表示没有 ApplicationContextInitializer
需要执行。
加载 Spring 配置
wac.refresh()
,实际调用的是我们之前就很熟悉的刷新方法:
org.springframework.context.support.AbstractApplicationContext#refresh
从图中能够看出,刷新方法的代码逻辑与之前一样,通过父类 AbstractApplicationContext
的 refresh
方法,进行了配置文件的加载。
从图中能够看出,刷新方法的代码逻辑与之前一样,通过父类 AbstractApplicationContext
的 refresh
方法,进行了配置文件的加载。
在例子中的 web.xml
配置中,指定了加载 spring-mvc.xml
配置文件
<!-- 配置 DispatcherServlet --> <servlet> <servlet-name>dispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring-mvc.xml</param-value> </init-param> </servlet>
注册 mvc 解析器
由于我们配置了 contextConfigLocation
,指定了加载资源的路径,所以在 XmlWebApplicationContext
初始化的时候,加载的 Spring
配置文件路径是我们指定 spring-mvc.xml
:
在 spring-mvc.xml
配置中,主要配置了三项
<!--扫描包,自动注入bean--> <context:component-scan base-package="web.controller"/> <!--使用注解开发spring mvc--> <mvc:annotation-driven/> <!--视图解析器--> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/views/"/> <property name="suffix" value=".jsp"/> </bean>
同样老套路,使用了 <mvc:annotation>
自定义注解的话,要注册相应的解析器后,Spring
容器才能解析元素:
org.springframework.web.servlet.config.MvcNamespaceHandler
public void init() { // MVC 标签解析需要注册的解析器 registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser()); registerBeanDefinitionParser("default-servlet-handler", new DefaultServletHandlerBeanDefinitionParser()); registerBeanDefinitionParser("interceptors", new InterceptorsBeanDefinitionParser()); registerBeanDefinitionParser("resources", new ResourcesBeanDefinitionParser()); registerBeanDefinitionParser("view-controller", new ViewControllerBeanDefinitionParser()); registerBeanDefinitionParser("redirect-view-controller", new ViewControllerBeanDefinitionParser()); registerBeanDefinitionParser("status-controller", new ViewControllerBeanDefinitionParser()); registerBeanDefinitionParser("view-resolvers", new ViewResolversBeanDefinitionParser()); registerBeanDefinitionParser("tiles-configurer", new TilesConfigurerBeanDefinitionParser()); registerBeanDefinitionParser("freemarker-configurer", new FreeMarkerConfigurerBeanDefinitionParser()); registerBeanDefinitionParser("groovy-configurer", new GroovyMarkupConfigurerBeanDefinitionParser()); registerBeanDefinitionParser("script-template-configurer", new ScriptTemplateConfigurerBeanDefinitionParser()); registerBeanDefinitionParser("cors", new CorsBeanDefinitionParser()); }
可以看到,mvc
提供了很多便利的注解,有拦截器、资源、视图等解析器,但我们常用的到的是 anntation-driven
注解驱动,这个注解通过 AnnotationDrivenBeanDefinitionParser
类进行解析,其中会注册两个重要的 bean
:
class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser { public static final String HANDLER_MAPPING_BEAN_NAME = RequestMappingHandlerMapping.class.getName(); public static final String HANDLER_ADAPTER_BEAN_NAME = RequestMappingHandlerAdapter.class.getName(); ... }
跳过其他熟悉的 Spring
初始化配置,通过上面的步骤,完成了 Spring
配置文件的解析,将扫描到的 bean
加载到了 Spring
容器中。
那么下面就正式进入 mvc
的初始化。
mvc 初始化
onRefresh
方法是 FrameworkServlet
类中提供的模板方法,在子类 DispatcherServlet
进行了重写,主要用来刷新 Spring
在 Web
功能实现中所必须用到的全局变量:
protected void onRefresh(ApplicationContext context) { initStrategies(context); } protected void initStrategies(ApplicationContext context) { // 初始化 multipartResolver 文件上传相关 initMultipartResolver(context); // 初始化 LocalResolver 与国际化相关 initLocaleResolver(context); // 初始化 ThemeResolver 与主题更换相关 initThemeResolver(context); // 初始化 HandlerMapping 与匹配处理器相关 initHandlerMappings(context); // 初始化 HandlerAdapter 处理当前 Http 请求的处理器适配器实现,根据处理器映射返回相应的处理器类型 initHandlerAdapters(context); // 初始化 HandlerExceptionResolvers,处理器异常解决器 initHandlerExceptionResolvers(context); // 初始化 RequestToViewNameTranslator,处理逻辑视图名称 initRequestToViewNameTranslator(context); // 初始化 ViewResolver 选择合适的视图进行渲染 initViewResolvers(context); // 初始化 FlashMapManager 使用 flash attributes 提供了一个请求存储属性,可供其他请求使用(重定向时常用) initFlashMapManager(context); }
该函数是实现 mvc
的关键所在,先来大致介绍一下初始化的套路:
- 寻找用户自定义配置
- 没有找到,使用默认配置
显然,Spring
给我们提供了高度的自定义,可以手动设置想要的解析器,以便于扩展功能。
如果没有找到用户配置的 bean
,那么它将会使用默认的初始化策略: getDefaultStrategies
方法