Spring MVC > DispatcherServlet的初始化
来源:互联网 发布:淘宝客服教程视频在线 编辑:程序博客网 时间:2024/05/22 10:53
Spring MVC的逻辑实现主要在DispatcherServlet中,它是实现servlet接口的实现类。servlet在初始化时会调用init方法,所以DispatcherServlet也不例外。
默认情况下,Servlet在用户第一次请求时才会被servlet容器创建和初始化,而在使用Spring MVC时通常在web.xml定义DispatcherServlet的load-on-startup为1,即让容器(如tomcat)启动时就加载这个Servlet。(注:load-on-startup的详细介绍可参考:http://www.blogjava.net/xzclog/archive/2011/09/29/359789.html)
本文的目的就是要探究DispatcherServlet在init方法中做了哪些初始化动作。研究的源码版本是4.2.2。
在DispatcherServlet类的定义中,并未找到init方法,它一定是继承了父类中的init方法。它的继承结构如下:
我在HttpServletBean里面找到final修饰的init方法,这意味着它的子类只能继承,不能重写。init方法中关键的代码如下(省略了日志、异常处理等非必要信息)。我在分析代码时,本着先总后分的原则,先将程序分为几个主要的步骤,了解这几个步骤大致做了什么,再对重要的步骤深入。
public final void init() throws ServletException { //1)DispatcherServlet配置的init-param信息存放在当前Servlet对应的ServletConfig对象中,这里把init-param封装到PropertyValues类中; PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties); //2)将当前这个Servlet类转化为一个BeanWrapper类型的实例,目的是方便使用Spring提供的注入功能,完成对对应属性的注入。之所以说是对应属性,因为DispatcherServlet的父类FrameworkServlet有和init-param同名的成员变量; BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this); ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext()); //3)注册自定义的属性编辑器,一旦遇到Resource类型的属性就会使用使用ResourceEditor进行解析; bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment())); //4)空实现 initBeanWrapper(bw); //5)属性注入 bw.setPropertyValues(pvs, true); //6)空实现 initServletBean();}
4)和6)的实现为什么是空的?原因是模板方法模式,1)到6)这六个步骤组成了一个模板方法,其中某些步骤的具体实现可以让子类实现。
HttpServletBean的子类FrameworkServlet实现了步骤6),且以final修饰,核心代码:
protected final void initServletBean() throws ServletException { //1)创建或刷新WebApplicationContext实例,并对servlet功能所使用的变量进行初始化; this.webApplicationContext = initWebApplicationContext(); //2)空实现,又是模板方法的应用; initFrameworkServlet();}
继续追踪代码到initWebApplicationContext()方法,核心代码:
protected WebApplicationContext initWebApplicationContext() { WebApplicationContext rootContext =WebApplicationContextUtils.getWebApplicationContext(getServletContext()); WebApplicationContext wac = null; //如果this.webApplicationContext!=null,可以判定this.webApplicationContext已经通过构造函数初始化; if (this.webApplicationContext != null) { wac = this.webApplicationContext; if (wac instanceof ConfigurableWebApplicationContext) { ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac; //如果这个context(cwac)尚未被刷新过 if (!cwac.isActive()) { if (cwac.getParent() == null) { cwac.setParent(rootContext); } configureAndRefreshWebApplicationContext(cwac); } } } if (wac == null) { //根据web.xml中配置的servlet参数contextAttribute来查找ServletContext中对应的WebApplicationContext; wac = findWebApplicationContext(); } //如果通过上面两种方式仍然无法找到WebApplicationContext实例,那么只能新创建一个了; if (wac == null) { wac = createWebApplicationContext(rootContext); } //如果得到的context不是一个支持刷新的ConfigurableApplicationContext或者构造函数中注入的context已经被刷新过了,那么这里手动触发一次onRefresh; if (!this.refreshEventReceived) { onRefresh(wac); } if (this.publishContext) { //attrName的值是:org.springframework.web.servlet.FrameworkServlet.CONTEXT.springMvc,将得到的WebApplicationContext实例放到ServletContext中; String attrName = getServletContextAttributeName(); getServletContext().setAttribute(attrName, wac); } return wac;}
这个方法的实现还是有些长的,我们拿几个关键点逐一分析。
1. wac = createWebApplicationContext(rootContext);
跟进createWebApplicationContext方法:
protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) { return createWebApplicationContext((ApplicationContext) parent);}
继续跟进:
protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) { //contextClass是org.springframework.web.context.support.XmlWebApplicationContext Class<?> contextClass = getContextClass(); //如果contextClass不是ConfigurableWebApplicationContext的子类 if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) { throw new ApplicationContextException("..."); } ConfigurableWebApplicationContext wac =(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); wac.setEnvironment(getEnvironment()); wac.setParent(parent); wac.setConfigLocation(getContextConfigLocation()); configureAndRefreshWebApplicationContext(wac); return wac;}
2. configureAndRefreshWebApplicationContext(wac) ??
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) { if (ObjectUtils.identityToString(wac).equals(wac.getId())) { if (this.contextId != null) { wac.setId(this.contextId); } else { wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +ObjectUtils.getDisplayString(getServletContext().getContextPath()) + "/" + getServletName()); } } wac.setServletContext(getServletContext()); wac.setServletConfig(getServletConfig()); wac.setNamespace(getNamespace()); wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener())); ConfigurableEnvironment env = wac.getEnvironment(); if (env instanceof ConfigurableWebEnvironment) { ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig()); } postProcessWebApplicationContext(wac); applyInitializers(wac); wac.refresh();}
3. onRefresh(wac);
FrameworkServlet的onRefresh方法又是空实现,DispatcherServlet覆盖了这个方法,具体实现:
protected void onRefresh(ApplicationContext context) { initStrategies(context);}
跟进代码:
protected void initStrategies(ApplicationContext context) { initMultipartResolver(context); initLocaleResolver(context); initThemeResolver(context); initHandlerMappings(context); initHandlerAdapters(context); initHandlerExceptionResolvers(context); initRequestToViewNameTranslator(context); initViewResolvers(context); initFlashMapManager(context);}
这个方法里密密麻麻的初始化了很多变量,接下来逐一分析初始化的这些变量到底是干什么的。
1)initMultipartResolver(context)
MultipartResolver主要用来处理文件上传。
private void initMultipartResolver(ApplicationContext context) { this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);}
这里直接从ApplicationContext中获取id为multipartResolver,类型为MultipartResolver的Bean,赋给DispatcherServlet的成员变量multipartResolver。为什么取id为multipartResolver的Bean呢?因为Spring默认是没有multipart处理的,如果想用Spring的multipart,需要在web应用上下文中添加multipart解析器,如下:
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="maxUploadSize"> <value>20971520</value> <!-- 上传文件大小限制为20M,20*1024*1024 --> </property></bean>
2) initLocaleResolver(context);
LocaleResolver用来解析用户地区。
3) initThemeResolver(context);
ThemeResolver用来解析主题。
4) initHandlerMappings(context);
HandlerMapping的作用:当请求发给DispatcherServlet时,DispatcherServlet会把请求转给HandlerMapping处理,HandlerMapping经过处理后会返回DispatcherServlet一个可用的Controller。
源码:
private void initHandlerMappings(ApplicationContext context) { this.handlerMappings = null; //默认情况下,Spring MVC会加载所有实现了HandlerMapping接口的bean;如果期望Spring MVC只加载指定的handlermapping,可以在DispatcherServlet的init-param中将detectAllHandlerMapping设置为false; if (this.detectAllHandlerMappings) { Map<String, HandlerMapping> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false); if (!matchingBeans.isEmpty()) { this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values()); //对handlermappings进行排序; AnnotationAwareOrderComparator.sort(this.handlerMappings); } } else { //查找name为handlerMapping的bean,作为当前唯一的handlermapping; HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class); this.handlerMappings = Collections.singletonList(hm); } if (this.handlerMappings == null) { //DispatcherServlet所在目录的DispatcherServlet.properties中的org.springframework.web.servlet.HandlerMapping来加载handlermapping,值为org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class); }}
5) initHandlerAdapters(context);
HandlerAdapter使用了适配器模式。作用:DispatcherServlet通过handlerMapping获取对应的handler后,会轮询HandlerAdapter,查找能够处理当前Http请求的HandlerAdapter实现,handlerAdapter根据handlerMapping返回的handler类型来选择适当的handlerAdapter实现,从而适配当前的Http请求。
主要代码:
private void initHandlerAdapters(ApplicationContext context) { this.handlerAdapters = null; if (this.detectAllHandlerAdapters) { // 加载所有实现了HandlerAdapter接口的bean; Map<String, HandlerAdapter> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false); if (!matchingBeans.isEmpty()) { this.handlerAdapters = new ArrayList<HandlerAdapter>(matchingBeans.values()); // handlerAdapters排序 AnnotationAwareOrderComparator.sort(this.handlerAdapters); } } else { //获取name为handlerAdapter的bean,作为唯一的handlerAdapter; HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class); this.handlerAdapters = Collections.singletonList(ha); } if (this.handlerAdapters == null) { //从DispatcherServlet所在目录的DispatcherServlet.properties文件的org.springframework.web.servlet.HandlerAdapter加载默认的三个handlerAdapter; this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class); }}
DispatcherServlet所在目录的DispatcherServlet.properties文件的org.springframework.web.servlet.HandlerAdapter默认的三个handlerAdapter是:
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\ org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\ org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
6) initHandlerExceptionResolvers(context);
可以通过实现HandlerExceptionResolver接口来自定义异常的处理,然后把实现类在Spring Context中声明为bean。
和上面的处理流程大同小异,同样是如果detectAllHandlerExceptionResolvers为true,先加载所有实现了HandlerExceptionResolver接口的bean,否则加载name为handlerExceptionResolver的bean为唯一的handlerExceptionResolvers;如果依然没找到合适的handlerExceptionResolver,就去按照DispatcherServlet.properties里的org.springframework.web.servlet.HandlerExceptionResolver去加载。
private void initHandlerExceptionResolvers(ApplicationContext context) { this.handlerExceptionResolvers = null; if (this.detectAllHandlerExceptionResolvers) { Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false); if (!matchingBeans.isEmpty()) { this.handlerExceptionResolvers = new ArrayList<HandlerExceptionResolver>(matchingBeans.values()); AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers); } } else { HandlerExceptionResolver her = context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class); this.handlerExceptionResolvers = Collections.singletonList(her); } if (this.handlerExceptionResolvers == null) { this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class); }}
7) initRequestToViewNameTranslator(context);
当Controller处理器方法没有返回一个View对象或者逻辑视图名称,并且在该方法中没有直接往response的输出流里写数据的时候,Spring就会采用约定好的方式提供一个逻辑视图名,这个逻辑视图名是通过RequestToViewNameTranslator接口的getViewName方法来实现的。
8) initViewResolvers(context);
初始化步骤和上面类似。ViewResolver接口定义了resolveViewName方法,根据viewName创建合适类型的View实现。
9) initFlashMapManager(context);
Spring MVC Flash Attributes提供了请求存储功能,可供其他请求使用,在使用重定向时很必要,例如Post/Redirect/Get模式。Flash attributes在重定向之前暂存以便重定向之后还能使用,并立即删除。
Spring MVC有两个主要的抽象来支持flash attributes,FlashMap用于保持flash attributes,而FlashMapManager用于存储、检索、管理FlashMap实例。
private void initFlashMapManager(ApplicationContext context) { this.flashMapManager = context.getBean(FLASH_MAP_MANAGER_BEAN_NAME,FlashMapManager.class);}
Spring MVC Flash Attributes可参考:http://www.open-open.com/lib/view/open1397266120028.html
- Spring MVC > DispatcherServlet的初始化
- Spring MVC DispatcherServlet的初始化
- Spring MVC DispatcherServlet的启动以及初始化
- 详解spring-MVC DispatcherServlet初始化
- Spring DispatcherServlet的初始化流程
- spring之spring mvc的DispatcherServlet
- 【Spring】DispatcherServlet的启动和初始化
- Spring MVC - DispatcherServlet Example
- Spring MVC DispatcherServlet说明
- Spring MVC DispatcherServlet配置
- spring mvc dispatcherServlet
- Spring MVC中的DispatcherServlet
- Spring MVC:DispatcherServlet详解
- spring MVC(一) DispatcherServlet
- spring mvc DispatcherServlet作用
- Spring MVC DispatcherServlet
- Spring MVC DispatcherServlet 配置
- Spring MVC DispatcherServlet配置
- Reveal高级技巧(越狱设备)
- 背包问题中的01背包和完全背包
- 360移动安全岗位实习生笔试和面试之旅
- ChemDraw编辑多媒体课件的技巧有哪些
- “FreeTextBox”控件使用——Word编辑器
- Spring MVC > DispatcherServlet的初始化
- HLS/MPEG-DASH/RTMP with nginx
- linux vi退出操作
- MYSQL 解锁与锁表
- -stdcall详解
- angularJs入门第二篇:全局函数
- cubic-bezier贝塞尔曲线CSS3动画工具
- Unity 使用ShareSDK 3.X 版本发布Xcode进入AR闪退
- Android TextView部分文字指定颜色