在整个 Spring MVC 框架中,DispatcherServlet 处于核心位置,它负责协调和组织不同组件完成请求处理并返回响应工作。在看 DispatcherServlet 类之前,我们先来看一下请求处理的大致流程:
DispatcherServlet 继承自 HttpServlet,它遵循 Servlet 里的“init-service-destroy”三个阶段,首先我们先来看一下它的 init() 阶段。
public class DispatcherServlet extends FrameworkServlet {
...
}
DispatcherServlet 的 init() 方法在其父类 HttpServletBean 中实现的,它覆盖了 GenericServlet 的 init() 方法,主要作用是加载 web.xml 中 DispatcherServlet 的 配置,并调用子类的初始化。下面是 init() 方法的具体代码:
@Override
public final void init() throws ServletException {
// ServletConfigPropertyValues 是静态内部类,使用 ServletConfig 获取 web.xml 中配置的参数
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
// 使用 BeanWrapper 来构造 DispatcherServlet
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}catch (BeansException ex) {
throw ex;
}
}
// 让子类实现的方法,这种在父类定义在子类实现的方式叫做模版方法模式
initServletBean();
}
web.xml 中 DispatcherServlet 的 配置
<servlet>
<servlet-name>DispatcherServletservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>
classpath:servlet-context.xml
param-value>
init-param>
<load-on-startup>1load-on-startup>
servlet>
<servlet-mapping>
<servlet-name>DispatcherServletservlet-name>
<url-pattern>/url-pattern>
servlet-mapping>
在 HttpServletBean 的 init() 方法中调用了 initServletBean() 这个方法,它是在 FrameworkServlet 类中实现的,主要作用是建立 WebApplicationContext 容器(有时也称上下文),并加载 SpringMVC 配置文件中定义的 Bean 到改容器中,最后将该容器添加到 ServletContext 中。下面是 initServletBean() 方法的具体代码:
@Override
protected final void initServletBean() throws ServletException {
try {
// 初始化 WebApplicationContext (即SpringMVC的IOC容器)
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
} catch (ServletException ex) {
} catch (RuntimeException ex) {
}
}
WebApplicationContext 继承于 ApplicationContext 接口,从容器中可以获取当前应用程序环境信息,它也是 SpringMVC 的 IOC 容器。下面是 initWebApplicationContext() 方法的具体代码:
protected WebApplicationContext initWebApplicationContext() {
// 获取 ContextLoaderListener 初始化并注册在 ServletContext 中的根容器,即 Spring 的容器
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
// 因为 WebApplicationContext 不为空,说明该类在构造时已经将其注入
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
// 将 Spring 的容器设为 SpringMVC 容器的父容器
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
// 如果 WebApplicationContext 为空,则进行查找,能找到说明上下文已经在别处初始化。
wac = findWebApplicationContext();
}
if (wac == null) {
// 如果 WebApplicationContext 仍为空,则以 Spring 的容器为父上下文建立一个新的。
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
// 模版方法,由 DispatcherServlet 实现
onRefresh(wac);
}
if (this.publishContext) {
// 发布这个 WebApplicationContext 容器到 ServletContext 中
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
下面是查找 WebApplicationContext 的 findWebApplicationContext() 方法代码:
protected WebApplicationContext findWebApplicationContext() {
String attrName = getContextAttribute();
if (attrName == null) {
return null;
}
// 从 ServletContext 中查找已经发布的 WebApplicationContext 容器
WebApplicationContext wac =
WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);
if (wac == null) {
throw new IllegalStateException("No WebApplicationContext found: initializer not registered?");
}
return wac;
}
建立好 WebApplicationContext(上下文) 后,通过 onRefresh(ApplicationContext context) 方法回调,进入 DispatcherServlet 类中。onRefresh() 方法,提供 SpringMVC 的初始化,具体代码如下:
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
protected void initStrategies(ApplicationContext context) {
//初始化文件上传处理类
initMultipartResolver(context);
//初始化本地化Resolver
initLocaleResolver(context);
//初始化主题Resolver
initThemeResolver(context);
//初始化一些个与处理的HandlerMappings
initHandlerMappings(context);
initHandlerAdapters(context);
//初始化异常处理的handler
initHandlerExceptionResolvers(context);
//初始化请求路径转换为ViewName 的Translator
initRequestToViewNameTranslator(context);
//初始化ViewResolvers 这个就是针对视图处理的Resolvers 比如jsp处理Resolvers 或者freemarker处理Resolvers
initViewResolvers(context);
//初始化 主要管理flashmap,比如RedirectAttributes 的属性会放到这个里面,默认使用的是SessionFlashMapManager
initFlashMapManager(context);
}
在 initStrategies() 方法中进行了各个组件的初始化,先来看一下这些组件的初始化方法,稍后再来详细分析这些组件。
initHandlerMappings() 方法从 SpringMVC 的容器及 Spring 的容器中查找所有的 HandlerMapping 实例,并把它们放入到 handlerMappings 这个 list 中。这个方法并不是对 HandlerMapping 实例的创建,HandlerMapping 实例是在上面 WebApplicationContext 容器初始化,即 SpringMVC 容器初始化的时候创建的。
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
if (this.detectAllHandlerMappings) {
// 从 SpringMVC 的 IOC 容器及 Spring 的 IOC 容器中查找 HandlerMapping 实例
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
// 按一定顺序放置 HandlerMapping 对象
OrderComparator.sort(this.handlerMappings);
}
} else {
try {
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
} catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default HandlerMapping later.
}
}
// 如果没有 HandlerMapping,则加载默认的
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
}
}
private void initHandlerAdapters(ApplicationContext context) {
this.handlerAdapters = null;
if (this.detectAllHandlerAdapters) {
// Find all HandlerAdapters in the ApplicationContext, including ancestor contexts.
Map<String, HandlerAdapter> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerAdapters = new ArrayList<HandlerAdapter>(matchingBeans.values());
// We keep HandlerAdapters in sorted order.
OrderComparator.sort(this.handlerAdapters);
}
} else {
try {
HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);
this.handlerAdapters = Collections.singletonList(ha);
} catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default HandlerAdapter later.
}
}
// Ensure we have at least some HandlerAdapters, by registering
// default HandlerAdapters if no other adapters are found.
if (this.handlerAdapters == null) {
this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
}
}
HttpServlet 提供了 doGet()、doPost() 等方法,DispatcherServlet 中这些方法是在其父类 FrameworkServlet 中实现的,代码如下:
@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
@Override
protected final void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
这些方法又都调用了 processRequest() 方法,我们来看一下代码:
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
// 返回与当前线程相关联的 LocaleContext
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
// 根据请求构建 LocaleContext,公开请求的语言环境为当前语言环境
LocaleContext localeContext = buildLocaleContext(request);
// 返回当前绑定到线程的 RequestAttributes
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
// 根据请求构建ServletRequestAttributes
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
// 获取当前请求的 WebAsyncManager,如果没有找到则创建
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
// 使 LocaleContext 和 requestAttributes 关联
initContextHolders(request, localeContext, requestAttributes);
try {
// 由 DispatcherServlet 实现
doService(request, response);
} catch (ServletException ex) {
} catch (IOException ex) {
} catch (Throwable ex) {
} finally {
// 重置 LocaleContext 和 requestAttributes,解除关联
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}// 发布 ServletRequestHandlerEvent 事件
publishRequestHandledEvent(request, startTime, failureCause);
}
}
DispatcherServlet 的 doService() 方法主要是设置一些 request 属性,并调用 doDispatch() 方法进行请求分发处理,doDispatch() 方法的主要过程是通过 HandlerMapping 获取 Handler,再找到用于执行它的 HandlerAdapter,执行 Handler 后得到 ModelAndView ,ModelAndView 是连接“业务逻辑层”与“视图展示层”的桥梁,接下来就要通过 ModelAndView 获得 View,再通过它的 Model 对 View 进行渲染。doDispatch() 方法如下:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
// 获取当前请求的WebAsyncManager,如果没找到则创建并与请求关联
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
// 检查是否有 Multipart,有则将请求转换为 Multipart 请求
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// 遍历所有的 HandlerMapping 找到与请求对应的 Handler,并将其与一堆拦截器封装到 HandlerExecution 对象中。
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
// 遍历所有的 HandlerAdapter,找到可以处理该 Handler 的 HandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 处理 last-modified 请求头
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
// 遍历拦截器,执行它们的 preHandle() 方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
try {
// 执行实际的处理程序
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
} finally {
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
}
applyDefaultViewName(request, mv);
// 遍历拦截器,执行它们的 postHandle() 方法
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception ex) {
dispatchException = ex;
}
// 处理执行结果,是一个 ModelAndView 或 Exception,然后进行渲染
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
} catch (Exception ex) {
} catch (Error err) {
} finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// 遍历拦截器,执行它们的 afterCompletion() 方法
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
return;
}
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
作用是根据当前请求的找到对应的 Handler,并将 Handler(执行程序)与一堆 HandlerInterceptor(拦截器)封装到 HandlerExecutionChain 对象中。在 HandlerMapping 接口的内部只有一个方法,如下:
public interface HandlerMapping {
HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}
HandlerMapping 是由 DispatcherServlet 调用,DispatcherServlet 会从容器中取出所有 HandlerMapping 实例并遍历,让 HandlerMapping 实例根据自己实现类的方式去尝试查找 Handler,而 HandlerMapping 具体有哪些实现类下面就会详细分析。
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
// 这些 HandlerMapping 在容器初始化时创建,在 initHandlerMappings 时放入集合中
for (HandlerMapping hm : this.handlerMappings) {
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
return null;
}
另外上面说到的 Handler 有可能是一个 HandlerMethod(封装了 Controller 中的方法)对象,也有可能是一个 Controller 对象、 HttpRequestHandler 对象或 Servlet 对象,而这个 Handler 具体是什么对象,也是与所使用的 HandlerMapping 实现类有关。如下图所示,可以看到 HandlerMapping 实现类有两个分支,分别继承自 AbstractHandlerMethodMapping(得到 HandlerMethod)和 AbstractUrlHandlerMapping(得到 HttpRequestHandler、Controller 或 Servlet),它们又统一继承于 AbstractHandlerMapping。
先来看一下 AbstractHandlerMapping,它实现了 HandlerMapping 接口中的 getHandler() 方法,源码如下所示
@Override
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
// 根据请求获取执行程序,具体的获取方式由子类决定,getHandlerInternal() 是抽象方法
Object handler = getHandlerInternal(request);
if (handler == null) {
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = getApplicationContext().getBean(handlerName);
}
// 将 Handler 与一堆拦截器包装到 HandlerExecutionChain 对象中
return getHandlerExecutionChain(handler, request);
}
可以看到在这个方法中又调用了 getHandlerInternal() 方法获取到了 Handler 对象,而 Handler 对象具体内容是由它的子类去定义的。下面就来一看下 AbstractHandlerMapping 的两个分支子类
AbstractUrlHandlerMapping 这个分支获取的 Handler 的类型实际就是一个 Controller 类,所以一个 Controller 只能对应一个请求(或者像 Struts2 那样定位到方法,使同一个业务的方法放在同一个类里),源码如下所示:
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
// 根据当前请求获取“查找路径”
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
// 根据路径获取 Handler(即Controller),先尝试直接匹配,再尝试模式匹配
Object handler = lookupHandler(lookupPath, request);
if (handler == null) {
// We need to care for the default handler directly, since we need to
// expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well.
Object rawHandler = null;
if ("/".equals(lookupPath)) {
rawHandler = getRootHandler();
}
if (rawHandler == null) {
rawHandler = getDefaultHandler();
}
if (rawHandler != null) {
// Bean name or resolved handler?
if (rawHandler instanceof String) {
String handlerName = (String) rawHandler;
rawHandler = getApplicationContext().getBean(handlerName);
}
validateHandler(rawHandler, request);
handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
}
}
return handler;
}
根据控制类的类名找到对应的Controller,访问时类名首字母小写
<bean class="org.springframework.web.servlet.handler.ControllerClassNameHandlerMapping" />
<bean class="com.controller.TestController" />
<a href="testController.do?mapType=3">根据Controller类名找到对应的Controllera><br/>
根据 Bean 名访问 Controller,与 BeanNameUrlHandlerMapping 类似,但是bean名称不用遵循URL公约。
<bean class="org.springframework.web.servlet.handler.ControllerBeanNameHandlerMapping" />
<bean id="test" class="com.controller.TestController" />
利用 BeanName 来作为 URL 使用。
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping" />
<bean id="/test.do" class="com.controller.TestController" />
<a href="test.do?mapType=1">根据beanName找到对应的Controllera><br/>
可以将 URL 与处理器的定义分离,还可以对 URL 进行统一的映射管理。
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<prop key="/test.do">testControllerprop>
<prop key="/hello.do">testControllerprop>
props>
property>
bean>
<bean id="testController" class="com.controller.TestController" />
<a href="test.do?mapType=2">根据URL找到对应的Controllera><br/>
使用 AbstractUrlHandlerMapping 的实现类时,需要让控制层的类实现 Controller 接口(一般继承 AbstractController 即可),另外还有一些已经实现了的 Controller 类,如下图所示。但是不论是自己实现 Controller 接口还是使用系统已经实现的类,都只能处理一个请求(除了 MultiActionController 可以通过参数的方式让一个类可以处理多个请求)。
另外下面所有的 Controller 均采用 SimpleUrlHandlerMapping 方式的。
用于跳转界面,控制器根据请求的URL直接解析出视图名,省去了自己实现 Ccntroller 跳转页面。
<bean id="indexController" class="org.springframework.web.servlet.mvc.UrlFilenameViewController" />
同样用于界面跳转,控制器根据配置的参数来跳转界面,使用方式如下
<bean id="indexController" class="org.springframework.web.servlet.mvc.ParameterizableViewController">
<property name="viewName" value="/index.jsp" />
bean>
将请求转发到 Servlet,使用方式如下
<bean id="indexController" class="org.springframework.web.servlet.mvc.ServletForwardingController">
<property name="servletName" value="indexServlet" />
bean>
另外还要在 web.xml 中配置要转发到的 Servlet
<servlet>
<servlet-name>indexServletservlet-name>
<servlet-class>com.servlet.ServletForwardingservlet-class>
servlet>
将某个 Servlet 包装为 Controller,所有到 ServletWrappingController 的请求实际上是由它内部所包装的这个 Servlet 实例来处理的,这样可以将这个 Servlet 隐藏起来。
一个 Controller 可以写多个方法,分别对应不同的请求,使同一业务的方法可以放在一起了。在使用时让自己的 Controller 类继承 MultiActionController 类,使用方式如下
public class IndexController extends MultiActionController {
public ModelAndView add(HttpServletRequest request,HttpServletResponse response) {
ModelAndView mv = new ModelAndView();
mv.addObject("message","add");
mv.setViewName("add");
return mv;
}
public ModelAndView delete(HttpServletRequest request,HttpServletResponse response) {
ModelAndView mv = new ModelAndView();
mv.addObject("message","delete");
mv.setViewName("delete");
return mv;
}
}
配置自己的 Controller 时要配置一个方法名解析器(默认是 InternalPathMethodNameResolver )
<bean id="indexController" class="com.controller.IndexController">
<property name="methodNameResolver">
<bean class="org.springframework.web.servlet.mvc.multiaction.ParameterMethodNameResolver">
<property name="paramName" value="action" />
bean>
property>
bean>
当我们访问 http://localhost:8080/***/indexAction.do?action=add 时,进入 add() 方法;
当我们访问 http://localhost:8080/***/indexAction.do?action=delete 时,进入 delete() 方法。
AbstractHandlerMethodMapping 这个分支获取的 Handler 的类型是 HandlerMethod,即这个 Handler 是一个方法,它保存了方法的信息(如Method),这样一个 Controller 就可以处理多个请求了,源码如下所示:
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
// 根据当前请求获取“查找路径”
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
// 获取当前请求最佳匹配的处理方法(即Controller类的方法中)
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
上述代码中 lookupHandlerMethod() 方法主要工作是在 Map
protected void initHandlerMethods() {
// 从容器中获取所有 Bean 的名称,detectHandlerMethodsInAncestorContexts 默认false,不从父容器中查找
//即默认只查找 SpringMVC 的 IOC 容器,不查找它的父容器 Spring 的 IOC 容器
String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
getApplicationContext().getBeanNamesForType(Object.class));
for (String beanName : beanNames) {
// 这里的 isHandler()方法由子类实现,判断是否拥有 @Controller 注解或 @RequestMapping 注解
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX) && isHandler(getApplicationContext().getType(beanName))){
// 利用反射得到 Bean 中的 Method 并包装成 HandlerMethod,然后放入 Map 中
detectHandlerMethods(beanName);
}
}
handlerMethodsInitialized(getHandlerMethods());
}
看完上述代码后,可以知道是在 detectHandlerMethods() 方法中将 Bean 的方法转换为 HandlerMethod 对象,具体实现如下:
protected void detectHandlerMethods(final Object handler) {
// 获取这个 Bean 的 Class 对象
Class<?> handlerType = (handler instanceof String ? getApplicationContext().getType((String) handler) : handler.getClass());
// 避免重复调用 getMappingForMethod(),getMappingForMethod() 将重新构建 RequestMappingInfo 实例
final Map<Method, T> mappings = new IdentityHashMap<Method, T>();
// 获取被代理前的原始类型
final Class<?> userType = ClassUtils.getUserClass(handlerType);
// 获取 Method
Set<Method> methods = HandlerMethodSelector.selectMethods(userType, new MethodFilter() {
@Override
public boolean matches(Method method) {
// 根据 Method 和它的 @RequestMapping 注解,创建 RequestMappingInfo 对象。
// 这里的 T 就是 RequestMappingInfo,它封装了 @RequestMapping 信息
T mapping = getMappingForMethod(method, userType);
if (mapping != null) {
mappings.put(method, mapping);
return true;
} else {
return false;
}
}
});
for (Method method : methods) {
// 注册 Method 和它的映射,RequestMappingInfo 储存着映射信息
registerHandlerMethod(handler, method, mappings.get(method));
}
}
最后在 registerHandlerMethod() 方法中,将 RequestMappingInfo 作为 key,把 Method 包装成 HandlerMethod 作为 value 添加到了 Map
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
HandlerMethod newHandlerMethod = createHandlerMethod(handler, method);
HandlerMethod oldHandlerMethod = this.handlerMethods.get(mapping);
if (oldHandlerMethod != null && !oldHandlerMethod.equals(newHandlerMethod)) {
throw new IllegalStateException("");
}
this.handlerMethods.put(mapping, newHandlerMethod);
Set<String> patterns = getMappingPathPatterns(mapping);
for (String pattern : patterns) {
if (!getPathMatcher().isPattern(pattern)) {
this.urlMap.add(pattern, mapping);
}
}
}
根据 Handler 来找到支持它的 HandlerAdapter,通过 HandlerAdapter 执行这个 Handler 得到 ModelAndView 对象。HandlerAdapter 接口中的方法如下:
public interface HandlerAdapter {
// 当前 HandlerAdapter 是否支持这个 Handler
boolean supports(Object handler);
// 利用 Handler 处理请求
ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
long getLastModified(HttpServletRequest request, Object handler);
}
从上面的文章中可以知道,利用 RequestMappingHandlerMapping 获取的 Handler 是 HadnlerMethod 类型,它代表 Controller 里要执行的方法,而 RequestMappingHandlerAdapter 可以执行 HadnlerMethod 对象。
RequestMappingHandlerAdapter 的 handle() 方法是在它的父类 AbstractHandlerMethodAdapter 类中实现的,源码如下所示:
@Override
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return handleInternal(request, response, (HandlerMethod) handler);
}
handleInternal() 方法是由 RequestMappingHandlerAdapter 自己来实现的,源码如下所示:
@Override
protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
// 是否通过 @SessionAttributes 注释声明了 session 属性。
if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
checkAndPrepare(request, response, this.cacheSecondsForSessionAttributeHandlers, true);
} else {
checkAndPrepare(request, response, true);
}
// 是否需要在 synchronize 块中执行
if (this.synchronizeOnSession) {
HttpSession session = request.getSession(false);
if (session != null) {
Object mutex = WebUtils.getSessionMutex(session);
synchronized (mutex) {
// 执行 HandlerMethod
return invokeHandleMethod(request, response, handlerMethod);
}
}
}
// 执行 HandlerMethod,得到 ModelAndView
return invokeHandleMethod(request, response, handlerMethod);
}
继续再来看一下如何得到 ModelAndView,invokeHandlerMethod() 方法如下:
private ModelAndView invokeHandleMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
//
ServletWebRequest webRequest = new ServletWebRequest(request, response);
// 数据绑定
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
// 绑定参数,执行方法
ServletInvocableHandlerMethod requestMappingMethod = createRequestMappingMethod(handlerMethod, binderFactory);
// 创建模型和视图容器
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
// 设置FlasgMap中的值
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
// 初始化模型
modelFactory.initModel(webRequest, mavContainer, requestMappingMethod);
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
asyncWebRequest.setTimeout(this.asyncRequestTimeout);
final WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.setTaskExecutor(this.taskExecutor);
asyncManager.setAsyncWebRequest(asyncWebRequest);
asyncManager.registerCallableInterceptors(this.callableInterceptors);
asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
if (asyncManager.hasConcurrentResult()) {
Object result = asyncManager.getConcurrentResult();
mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
asyncManager.clearConcurrentResult();
requestMappingMethod = requestMappingMethod.wrapConcurrentResult(result);
}
requestMappingMethod.invokeAndHandle(webRequest, mavContainer);
if (asyncManager.isConcurrentHandlingStarted()) {
return null;
}
return getModelAndView(mavContainer, modelFactory, webRequest);
}
HttpRequestHandlerAdapter 可以执行 HttpRequestHandler 类型的 Handler,源码如下
public class HttpRequestHandlerAdapter implements HandlerAdapter {
@Override
public boolean supports(Object handler) {
return (handler instanceof HttpRequestHandler);
}
@Override
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
((HttpRequestHandler) handler).handleRequest(request, response);
return null;
}
@Override
public long getLastModified(HttpServletRequest request, Object handler) {
if (handler instanceof LastModified) {
return ((LastModified) handler).getLastModified(request);
}
return -1L;
}
}
SimpleControllerHandlerAdapter 可以执行 Controller 类型的 Handler,源码如下:
public class SimpleControllerHandlerAdapter implements HandlerAdapter {
@Override
public boolean supports(Object handler) {
return (handler instanceof Controller);
}
@Override
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return ((Controller) handler).handleRequest(request, response);
}
@Override
public long getLastModified(HttpServletRequest request, Object handler) {
if (handler instanceof LastModified) {
return ((LastModified) handler).getLastModified(request);
}
return -1L;
}
}
SimpleServletHandlerAdapter 可以执行 Servlet 类型的 Handler,源码如下
public class SimpleServletHandlerAdapter implements HandlerAdapter {
@Override
public boolean supports(Object handler) {
return (handler instanceof Servlet);
}
@Override
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
((Servlet) handler).service(request, response);
return null;
}
@Override
public long getLastModified(HttpServletRequest request, Object handler) {
return -1;
}
}
负责处理异常的类,负责根据异常来设置 ModelAndView,然后交由 render 渲染界面。HandlerExecptionResolver 接口中只有一个方法,如下:
public interface HandlerExceptionResolver {
ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex);
}
根据视图的名称将其解析为 View 类型的视图,如通过 ModelAndView 中的视图名称将其解析成 View,View 是用来渲染页面的,也就是将 Model 填入模板中,生成 html 或其他格式的文件。
可以设置多个解析策略,如可以根据 JSP 来解析,或者按照 Velocity 模版解析,如果设置了多个解析策略则可以通过 order 属性来设定其优先级,数值越小优先级越高,前面的视图解析器解析后就不会让后面的继续解析。默认的解析策略是 InternalResourceViewResolver,按照 JSP 页面来解析。ViewResolver 接口中的方法如下:
public interface ViewResolver {
View resolveViewName(String viewName, Locale locale) throws Exception;
}
AbstractCachingViewResolver 是带有缓存的 ViewResolver,它每次解析时先从缓存里查找,如果找到视图就返回,没有就创建新的视图,且创建新视图的方法由其子类实现,具体代码如下所示:
@Override
public View resolveViewName(String viewName, Locale locale) throws Exception {
// 是否启用缓存,可通过setCache()方法或setCacheLimit()方法开启缓存,是一个ConcurrentHashMap,默认缓存大小1024
if (!isCache()) {
return createView(viewName, locale);
} else {
// 得到 view 在缓存中的 key 值
Object cacheKey = getCacheKey(viewName, locale);
View view = this.viewAccessCache.get(cacheKey);
// 如果没有找到 view 则创建,采用双重校验的方式进行安全创建
if (view == null) {
synchronized (this.viewCreationCache) {
view = this.viewCreationCache.get(cacheKey);
if (view == null) {
// 具体的创建方式由子类实现
view = createView(viewName, locale);
if (view == null && this.cacheUnresolved) {
view = UNRESOLVED_VIEW;
}
if (view != null) {
this.viewAccessCache.put(cacheKey, view);
this.viewCreationCache.put(cacheKey, view);
}
}
}
}
return (view != UNRESOLVED_VIEW ? view : null);
}
}
ResourceBundleViewResolver 根据 views.properties 文件来解析视图,这个文件位于 classpath 路径下,使用方式如下:
<bean class="org.springframework.web.servlet.view.ResourceBundleViewResolver">
<property name="basename" value="views">property>
bean>
XmlViewResolver 根据 xml 文件来解析视图,使用方式如下:
<bean class="org.springframework.web.servlet.view.XmlViewResolver">
<property name="location">
<value>/WEB-INF/spring-views.xmlvalue>
property>
bean>
UrlBasedViewResolver 提供了拼接 URL 的方式来解析视图,通过 prefix 属性拼接一个前缀,通过 suffix 属性拼接一个后缀,就得到了视图的 URL。还可以加入 redirect: 与 forword: 前缀,使用 redirect: 前缀会调用 HttpServletResponse对象的 sendRedirect() 方法进行重定向,使用 forword: 前缀会利用 RequestDispatcher的forword 方式跳转到指定的地址。另外,使用时还要指定 viewClass 属性,表示要解析成哪种 View,的使用方式如下:
<bean>
class="org.springframework.web.servlet.view.UrlBasedViewResolver">
<property name="prefix" value="/WEB-INF/" />
<property name="suffix" value=".jsp" />
<property name="viewClass" value="org.springframework.web.servlet.view.InternalResourceView"/>
</bean>
InternalResourceViewResolver 是 UrlBasedViewResolver 的子类,将 InternalResourceView 作为默认的 View 类,但如果当前classpath 中有 jstl 的 jar 包时则使用 JstlView 作为 view 来渲染。
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/" />
<property name="suffix" value=".jsp" />
</bean>
BeanNameViewResolver 是通过视图名称去容器中获取对应的 view 对像,所以在使用前需要将 view 对象注册到容器中。它没有缓存,实现方式如下:
@Override
public View resolveViewName(String viewName, Locale locale) throws BeansException {
ApplicationContext context = getApplicationContext();
if (!context.containsBean(viewName)) {
// Allow for ViewResolver chaining...
return null;
}
if (!context.isTypeMatch(viewName, View.class)) {
// Since we're looking into the general ApplicationContext here,
// let's accept this as a non-match and allow for chaining as well...
return null;
}
return context.getBean(viewName, View.class);
}
MultipartResolver 用于处理文件上传,当收到请求时 DispatcherServlet 的 checkMultipart() 方法会调用 MultipartResolver 的 isMultipart() 方法判断请求中是否包含文件。如果请求数据中包含文件,则调用 MultipartResolver 的 resolveMultipart() 方法对请求的数据进行解析,然后将文件数据解析成 MultipartFile 并封装在 MultipartHttpServletRequest (继承了 HttpServletRequest) 对象中,最后传递给 Controller,在 MultipartResolver 接口中有如下方法:
public interface MultipartResolver {
// 是否是 multipart
boolean isMultipart(HttpServletRequest request);
// 解析请求
MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException;
void cleanupMultipart(MultipartHttpServletRequest request);
}
MultipartFile 封装了请求数据中的文件,此时这个文件存储在内存中或临时的磁盘文件中,需要将其转存到一个合适的位置,因为请求结束后临时存储将被清空。在 MultipartFile 接口中有如下方法:
MultipartResolver 是一个接口,它的实现类如下图所示,分为 CommonsMultipartResolver 类和 StandardServletMultipartResolver 类。
其中 CommonsMultipartResolver 使用 commons Fileupload 来处理 multipart 请求,所以在使用时,必须要引入相应的 jar 包;而 StandardServletMultipartResolver 是基于 Servlet 3.0来处理 multipart 请求的,所以不需要引用其他 jar 包,但是必须使用支持 Servlet 3.0的容器才可以,以tomcat为例,从 Tomcat 7.0.x的版本开始就支持 Servlet 3.0了。
CommonsMultipartResolver 实现了 MultipartResolver 接口,resolveMultipart() 方法如下所示,其中 resolveLazily 是判断是否要延迟解析文件(通过XML可以设置)。当 resolveLazily 为 flase 时,会立即调用 parseRequest() 方法对请求数据进行解析,然后将解析结果封装到 DefaultMultipartHttpServletRequest 中;而当 resolveLazily 为 true 时,会在 DefaultMultipartHttpServletRequest 的 initializeMultipart() 方法调用 parseRequest() 方法对请求数据进行解析,而 initializeMultipart() 方法又是被 getMultipartFiles() 方法调用,即当需要获取文件信息时才会去解析请求数据,这种方式用了懒加载的思想。
@Override
public MultipartHttpServletRequest resolveMultipart(final HttpServletRequest request) throws MultipartException {
Assert.notNull(request, "Request must not be null");
if (this.resolveLazily) {
//懒加载,当调用DefaultMultipartHttpServletRequest的getMultipartFiles()方法时才解析请求数据
return new DefaultMultipartHttpServletRequest(request) {
@Override //当getMultipartFiles()方法被调用时,如果还未解析请求数据,则调用initializeMultipart()方法进行解析 protected void initializeMultipart() {
MultipartParsingResult parsingResult = parseRequest(request);
setMultipartFiles(parsingResult.getMultipartFiles());
setMultipartParameters(parsingResult.getMultipartParameters());
setMultipartParameterContentTypes(parsingResult.getMultipartParameterContentTypes());
}
};
} else {
//立即解析请求数据,并将解析结果封装到DefaultMultipartHttpServletRequest对象中
MultipartParsingResult parsingResult = parseRequest(request);
return new DefaultMultipartHttpServletRequest(request, parsingResult.getMultipartFiles(),
parsingResult.getMultipartParameters(), parsingResult.getMultipartParameterContentTypes());
}
}
在上面的代码中可以看到,对请求数据的解析工作是在 parseRequest() 方法中进行的,继续看一下 parseRequest() 方法源码
protected MultipartParsingResult parseRequest(HttpServletRequest request) throws MultipartException {
// 获取请求的编码类型
String encoding = determineEncoding(request);
FileUpload fileUpload = prepareFileUpload(encoding);
try {
List<FileItem> fileItems = ((ServletFileUpload) fileUpload).parseRequest(request);
return parseFileItems(fileItems, encoding);
} catch (...) {}
}
在 parseRequest() 方法中,首先调用了 prepareFileUpload() 方法来根据编码类型确定一个 FileUpload 实例,然后利用这个 FileUpload 实例解析请求数据后得到文件信息,最后将文件信息解析成 CommonsMultipartFile (实现了 MultipartFile 接口) 并包装在 MultipartParsingResult 对象中。
StandardServletMultipartResolver 实现了 MultipartResolver 接口,resolveMultipart() 方法如下所示,其中 resolveLazily 是判断是否要延迟解析文件(通过XML可以设置)。
private void parseRequest(HttpServletRequest request) {
try {
Collection<Part> parts = request.getParts();
this.multipartParameterNames = new LinkedHashSet<String>(parts.size());
MultiValueMap<String, MultipartFile> files = new LinkedMultiValueMap<String, MultipartFile>(parts.size());
for (Part part : parts) {
String disposition = part.getHeader(CONTENT_DISPOSITION);
String filename = extractFilename(disposition);
if (filename == null) {
filename = extractFilenameWithCharset(disposition);
}
if (filename != null) {
files.add(part.getName(), new StandardMultipartFile(part, filename));
} else {
this.multipartParameterNames.add(part.getName());
}
}
setMultipartFiles(files);
} catch (Throwable ex) {}
}
parseRequest() 方法利用了 servlet3.0 的 request.getParts() 方法获取上传文件,并将其封装到 MultipartFile 对象中。
依赖于web框架,在SpringMVC中就是依赖于SpringMVC框架。在实现上基于Java的反射机制,属于面向切面编程(AOP)的一种运用。由于拦截器是基于web框架的调用,因此可以使用Spring的依赖注入(DI)进行一些业务操作,同时一个拦截器实例在一个controller生命周期之内可以多次调用。但是缺点是只能对controller请求进行拦截,对其他的一些比如直接访问静态资源的请求则没办法进行拦截处理。
主要作用:拦截用户请求,进行处理,比如判断用户登录情况,权限验证,主要针对Action请求进行处理。是通过HandlerInterceptor 实现的。
<mvc:interceptors>
<bean class="cn.appsys.testInterceptor">bean>//拦截所有请求
<mvc:interceptor>
<mvc:mapping path="/manager/backend/**"/>
<bean class="cn.appsys.interceptor.SysInterceptor"/>//拦截上面请求
mvc:interceptor>
mvc:interceptors>
一般拦截器可通过实现HandlerInterceptor接口或者继承HandlerInterceptorAdapter实现。代码如下:
//只能对Controller请求进行拦截,对一些静态资源无法拦截。
public class TestInterceptor implements HandlerInterceptor {
//preHandle是在请求到达Controller之前实现,可进行用户校验登录等操作,返回true后,请求到达Controller层;
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
System.out.println("preHandle");
return true;
}
//postHandle方法是在执行完Controller层代码之后,DispatcherServlet进行视图的渲染之前执行,因此可以对ModelAndView 对象进行处理;
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle");
}
//afterCompletion方法是在DispatcherServlet进行视图的渲染之后执行调用,主要是进行一些资源清理等工作。
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
System.out.println("afterCompletion");
}
}
依赖于servlet容器。在实现上基于函数回调,可以对几乎所有请求进行过滤,但是缺点是一个过滤器实例只能在容器初始化时调用一次。使用过滤器的目的是用来做一些过滤操作,获取我们想要获取的数据,应用:1. 在过滤器中修改字符编码;2. 在过滤器中修改HttpServletRequest的一些参数,包括:过滤低俗文字、危险字符等(XXS)
过滤器在web.xml配置如下:
<filter>
<filter-name>testFilterfilter-name>
<filter-class>cn.appsys.TestFilterfilter-class>
filter>
<filter-mapping>
<filter-name>testFilterfilter-name>
<url-pattern>/*url-pattern>
filter-mapping>
一般过滤器可通过实现Filter接口实现。代码如下:
public class TestFilter implements Filter {
@Override
public void destroy() {
System.out.println("filter destroy");
}
@Override
public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2)
throws IOException, ServletException {
//在DispatcherServlet之前执行
System.out.println("filter doFilter before");
arg2.doFilter(arg0, arg1);
//在视图页面返回给客户端之前执行,但是执行顺序在Interceptor之后
System.out.println("filter doFilter after");
}
@Override
public void init(FilterConfig arg0) throws ServletException {
System.out.println("filter init");
}
}