SpringMVC源码总结(一)HandlerMapping和HandlerAdapter入门

时间:2023-03-09 19:14:09
SpringMVC源码总结(一)HandlerMapping和HandlerAdapter入门

SpringMVC在使用过程中,大多是使用注解,对它的实现接口之类的关系理解变得模糊, 通过对XML配置的理解,可以理清各个类的关系,譬如控制器类要实现Controller接口。

接触SpringMVC,对它的xml文件配置一直比较模模糊糊,最近花了一点时间稍微看了下源代码,再加上调试,开始逐渐理解它,网上的类似的内容有很多,写本文主要是自己加深一下理解。本文适合用过SpringMVC的开发者,言归正传,首先搭建一个最简单的工程体验一下。

该工程是基于maven的,pom配置不再说明,所使用的spring版本4.0.5。 
首先是web.xml文件配置,最简单的配置

  1. <!DOCTYPE web-app PUBLIC
  2. "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
  3. "http://java.sun.com/dtd/web-app_2_3.dtd" >
  4. <web-app>
  5. <display-name>Archetype Created Web Application</display-name>
  6. <servlet>
  7. <servlet-name>mvc</servlet-name>
  8. <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  9. <load-on-startup>1</load-on-startup>
  10. </servlet>
  11. <servlet-mapping>
  12. <servlet-name>mvc</servlet-name>
  13. <url-pattern>/*</url-pattern>
  14. </servlet-mapping>
  15. </web-app>

然后是mvc-servlet.xml文件的配置,上面配置DispatcherServlet会默认加载[servlet-name]-servlet.xml文件。对于我的配置,会去加载mvc-servlet.xml文件。 
mvc-servlet.xml文件的内容:

  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:util="http://www.springframework.org/schema/util" xmlns:context="http://www.springframework.org/schema/context"
  3. xsi:schemaLocation="http://www.springframework.org/schema/beans
  4. http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
  5. http://www.springframework.org/schema/mvc
  6. http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
  7. http://www.springframework.org/schema/util
  8. http://www.springframework.org/schema/util/spring-util-2.0.xsd
  9. http://www.springframework.org/schema/context
  10. http://www.springframework.org/schema/context/spring-context-3.2.xsd">
  11. <bean name="/index" class="com.lg.mvc.HomeAction"></bean>
  12. <bean class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
  13. <property name="templateLoaderPath" value="/WEB-INF/views" />
  14. <property name="defaultEncoding" value="utf-8" />
  15. <property name="freemarkerSettings">
  16. <props>
  17. <prop key="locale">zh_CN</prop>
  18. </props>
  19. </property>
  20. </bean>
  21. <bean class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
  22. <property name="suffix" value=".html" />
  23. <property name="contentType" value="text/html;charset=utf-8" />
  24. <property name="requestContextAttribute" value="request" />
  25. <property name="exposeRequestAttributes" value="true" />
  26. <property name="exposeSessionAttributes" value="true" />
  27. </bean>
  28. </beans>

在该配置中定义了一个HomeAction的Bean。内容为:

  1. package com.lg.mvc;
  2. import javax.servlet.http.HttpServletRequest;
  3. import javax.servlet.http.HttpServletResponse;
  4. import org.springframework.web.servlet.ModelAndView;
  5. import org.springframework.web.servlet.mvc.Controller;
  6. public class HomeAction implements Controller{
  7. @Override
  8. public ModelAndView handleRequest(HttpServletRequest request,
  9. HttpServletResponse response) throws Exception {
  10. return new ModelAndView("hello");
  11. }
  12. }

这是最原始的mvc做法,要继承Controller接口,先从原始的说起,最后再过渡到@Controller和@RequestMapping注解式的配置。它在mvc-serlet.xml文件中的配置有一个关键的属性name="/index"。 
WEB-INF/view目录下有一个简单的hello.html,内容为:

  1. <html>
  2. <head>
  3. </head>
  4. <body>
  5. hello lg !
  6. </body>
  7. </html>

至此该工程就写完了,部署到tomcat中,项目路径为/,运行一下。 
访问 http://localhost:8080/index 
SpringMVC源码总结(一)HandlerMapping和HandlerAdapter入门 
至此整个工程就算搭建成功了。

下面就要说说原理了。 
用过python Django框架的都知道Django对于访问方式的配置就是,一个url路径和一个函数配对,你访问这个url,就会直接调用这个函数,简单明了。对于java的面向对象来说,就要分两步走。第一步首先要找到是哪个对象,即handler,本工程的handler则是HomeAction对象。第二步要找到访问的函数,即HomeAction的handleRequest方法。所以就出现了两个源码接口 HandlerMapping和HandlerAdapter,前者负责第一步,后者负责第二步。借用网上的SpringMVC架构图。 
SpringMVC源码总结(一)HandlerMapping和HandlerAdapter入门
HandlerMapping接口的实现(只举了我认识的几个) :

      BeanNameUrlHandlerMapping :通过对比url和bean的name找到对应的对象
      SimpleUrlHandlerMapping :也是直接配置url和对应bean,比BeanNameUrlHandlerMapping功能更多
      DefaultAnnotationHandlerMapping : 主要是针对注解配置@RequestMapping的,已过时
      RequestMappingHandlerMapping :取代了上面一个

HandlerAdapter 接口实现:

      HttpRequestHandlerAdapter : 要求handler实现HttpRequestHandler接口,该接口的方法为                                                             void handleRequest(HttpServletRequest request, HttpServletResponse response)也就是  handler必须有一个handleRequest方法
      SimpleControllerHandlerAdapter:要求handler实现Controller接口,该接口的方法为ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response),也就是本工程采用的
      AnnotationMethodHandlerAdapter :和上面的DefaultAnnotationHandlerMapping配对使用的,也已过时
      RequestMappingHandlerAdapter : 和上面的RequestMappingHandlerMapping配对使用,针对@RequestMapping

先简单的说下这个工程的流程,访问http://localhost:8080/index首先由DispatcherServlet进行转发,通过BeanNameUrlHandlerMapping(含有 /index->HomeAction的配置),找到了HomeAction,然后再拿HomeAction和每个adapter进行适配,由于HomeAction实现了Controller接口,所以最终会有SimpleControllerHandlerAdapter来完成对HomeAction的handleRequest方法的调度。然后就顺利的执行了我们想要的方法,后面的内容不在本节中说明。

了解了大概流程,然后就需要看源代码了。 
首先就是SpringMVC的入口类,DispatcherServlet,它实现了Servlet接口,不再详细说DispatcherServlet的细节,不然又是一大堆的内容。每次请求都会调用它的doService->doDispatch,我们关注的重点就在doDispatch方法中。

  1. protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
  2. HttpServletRequest processedRequest = request;
  3. HandlerExecutionChain mappedHandler = null;
  4. boolean multipartRequestParsed = false;
  5. WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
  6. try {
  7. ModelAndView mv = null;
  8. Exception dispatchException = null;
  9. try {
  10. processedRequest = checkMultipart(request);
  11. multipartRequestParsed = (processedRequest != request);
  12. //这个是重点,第一步由HandlerMapping找到对应的handler
  13. // Determine handler for the current request.
  14. mappedHandler = getHandler(processedRequest);
  15. if (mappedHandler == null || mappedHandler.getHandler() == null) {
  16. noHandlerFound(processedRequest, response);
  17. return;
  18. }
  19. // Determine handler adapter for the current request.
  20. //这是第二步,找到合适的HandlerAdapter,然后由它来调度执行handler的方法
  21. HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
  22. // Process last-modified header, if supported by the handler.
  23. String method = request.getMethod();
  24. boolean isGet = "GET".equals(method);
  25. if (isGet || "HEAD".equals(method)) {
  26. long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
  27. if (logger.isDebugEnabled()) {
  28. logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
  29. }
  30. if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
  31. return;
  32. }
  33. }
  34. if (!mappedHandler.applyPreHandle(processedRequest, response)) {
  35. return;
  36. }
  37. try {
  38. // Actually invoke the handler.
  39. mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
  40. }
  41. finally {
  42. if (asyncManager.isConcurrentHandlingStarted()) {
  43. return;
  44. }
  45. }
  46. applyDefaultViewName(request, mv);
  47. mappedHandler.applyPostHandle(processedRequest, response, mv);
  48. }
  49. catch (Exception ex) {
  50. dispatchException = ex;
  51. }
  52. processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
  53. }
  54. catch (Exception ex) {
  55. triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
  56. }
  57. catch (Error err) {
  58. triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
  59. }
  60. finally {
  61. if (asyncManager.isConcurrentHandlingStarted()) {
  62. // Instead of postHandle and afterCompletion
  63. mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
  64. return;
  65. }
  66. // Clean up any resources used by a multipart request.
  67. if (multipartRequestParsed) {
  68. cleanupMultipart(processedRequest);
  69. }
  70. }
  71. }

第一步详细查看:

  1. protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
  2. for (HandlerMapping hm : this.handlerMappings) {
  3. if (logger.isTraceEnabled()) {
  4. logger.trace(
  5. "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
  6. }
  7. HandlerExecutionChain handler = hm.getHandler(request);
  8. if (handler != null) {
  9. return handler;
  10. }
  11. }
  12. return null;
  13. }

可以看到就是通过遍历所有已注册的HandlerMapping来找到对应的handler,然后构建出一个HandlerExecutionChain,它包含了handler和HandlerMapping本身的一些拦截器,如下

  1. public class HandlerExecutionChain {
  2. private final Object handler;
  3. private HandlerInterceptor[] interceptors;
  4. private List<HandlerInterceptor> interceptorList;
  5. //其他代码省略
  6. }

其中HandlerMapping的getHandler实现:

  1. public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
  2. Object handler = getHandlerInternal(request);
  3. if (handler == null) {
  4. handler = getDefaultHandler();
  5. }
  6. if (handler == null) {
  7. return null;
  8. }
  9. // Bean name or resolved handler?
  10. if (handler instanceof String) {
  11. String handlerName = (String) handler;
  12. handler = getApplicationContext().getBean(handlerName);
  13. }
  14. return getHandlerExecutionChain(handler, request);
  15. }

这里的getHandlerInternal(request)是个抽象方法,由具体的HandlerMapping来实现,获取到的handler如果为空,则获取默认配置的handler,如果handler为String类型,则表示这个则会去Spring容器里面去找这样名字的bean。 
再看下BeanNameUrlHandlerMapping的getHandlerInternal(request)的具体实现(通过一系列的接口设计,之后再好好看看这个设计,到BeanNameUrlHandlerMapping这只用实现该方法中的一部分),如下

  1. public class BeanNameUrlHandlerMapping extends AbstractDetectingUrlHandlerMapping {
  2. /**
  3. * Checks name and aliases of the given bean for URLs, starting with "/".
  4. */
  5. @Override
  6. protected String[] determineUrlsForHandler(String beanName) {
  7. List<String> urls = new ArrayList<String>();
  8. if (beanName.startsWith("/")) {
  9. urls.add(beanName);
  10. }
  11. String[] aliases = getApplicationContext().getAliases(beanName);
  12. for (String alias : aliases) {
  13. if (alias.startsWith("/")) {
  14. urls.add(alias);
  15. }
  16. }
  17. return StringUtils.toStringArray(urls);
  18. }
  19. }

这里面注释说,bean的name必须以/开头,它才处理,将信息存储在Map<String, Object> handlerMap中,对于本工程来说就是{'/index':HomeAction对象}。 
至此这里完成了第一步,下面开始第二步,即方法HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());的具体实现:

  1. protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
  2. for (HandlerAdapter ha : this.handlerAdapters) {
  3. if (logger.isTraceEnabled()) {
  4. logger.trace("Testing handler adapter [" + ha + "]");
  5. }
  6. if (ha.supports(handler)) {
  7. return ha;
  8. }
  9. }
  10. throw new ServletException("No adapter for handler [" + handler +
  11. "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
  12. }

遍历所有的HandlerAdapter,判断他们是否支持这个handler。 
我们来看下HttpRequestHandlerAdapter的supports(handler)方法:

  1. public class HttpRequestHandlerAdapter implements HandlerAdapter {
  2. @Override
  3. public boolean supports(Object handler) {
  4. //就是判断handler是否实现了HttpRequestHandler接口
  5. return (handler instanceof HttpRequestHandler);
  6. }
  7. @Override
  8. public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
  9. throws Exception {
  10. //若handler实现了HttpRequestHandler接口,则调用该接口的方法,执行我们在该方法中写的业务逻辑
  11. ((HttpRequestHandler) handler).handleRequest(request, response);
  12. return null;
  13. }
  14. @Override
  15. public long getLastModified(HttpServletRequest request, Object handler) {
  16. if (handler instanceof LastModified) {
  17. return ((LastModified) handler).getLastModified(request);
  18. }
  19. return -1L;
  20. }
  21. }

同理SimpleControllerHandlerAdapter也是这样类似的逻辑

  1. public class SimpleControllerHandlerAdapter implements HandlerAdapter {
  2. @Override
  3. public boolean supports(Object handler) {
  4. return (handler instanceof Controller);
  5. }
  6. @Override
  7. public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
  8. throws Exception {
  9. return ((Controller) handler).handleRequest(request, response);
  10. }
  11. @Override
  12. public long getLastModified(HttpServletRequest request, Object handler) {
  13. if (handler instanceof LastModified) {
  14. return ((LastModified) handler).getLastModified(request);
  15. }
  16. return -1L;
  17. }
  18. }

剩余两个AnnotationMethodHandlerAdapter和RequestMappingHandlerAdapter就比较复杂,我也没看。 
按照本工程的配置,则SimpleControllerHandlerAdapter是支持HomeAction的,然后就会执行SimpleControllerHandlerAdapter的handle(processedRequest, response, mappedHandler.getHandler())方法。本质上就会调用HomeAction实现Controller接口的方法。至此就分析完了。 
了解过程了之后,然后就是最重要的也是经常配置出问题的地方。DispatcherServlet的handlerMappings和handlerAdapters的来源问题。

DispatcherServlet初始化的时候,会调用一个方法如下:

  1. protected void initStrategies(ApplicationContext context) {
  2. initMultipartResolver(context);
  3. initLocaleResolver(context);
  4. initThemeResolver(context);
  5. //初始化一些HandlerMapping
  6. initHandlerMappings(context);
  7. //初始化一些HandlerAdapter
  8. initHandlerAdapters(context);
  9. initHandlerExceptionResolvers(context);
  10. initRequestToViewNameTranslator(context);
  11. initViewResolvers(context);
  12. initFlashMapManager(context);
  13. }

这里可以看到,它会初始化一些HandlerMapping和HandlerAdapter,这两个方法非常重要,理解了这两个方法你就会知道,配置不对问题出在哪里,下面具体看下这两个方法:

  1. private void initHandlerMappings(ApplicationContext context) {
  2. this.handlerMappings = null;
  3. if (this.detectAllHandlerMappings) {
  4. // Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
  5. Map<String, HandlerMapping> matchingBeans =
  6. BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
  7. if (!matchingBeans.isEmpty()) {
  8. this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
  9. // We keep HandlerMappings in sorted order.
  10. OrderComparator.sort(this.handlerMappings);
  11. }
  12. }
  13. else {
  14. try {
  15. HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
  16. this.handlerMappings = Collections.singletonList(hm);
  17. }
  18. catch (NoSuchBeanDefinitionException ex) {
  19. // Ignore, we'll add a default HandlerMapping later.
  20. }
  21. }
  22. // Ensure we have at least one HandlerMapping, by registering
  23. // a default HandlerMapping if no other mappings are found.
  24. if (this.handlerMappings == null) {
  25. this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
  26. if (logger.isDebugEnabled()) {
  27. logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
  28. }
  29. }
  30. }

detectAllHandlerMappings是DispatcherServlet的一个属性,你是可以在web.xml中配置的,默认是true,如果为true,则会去从本工程mvc-servlet.xml文件中去探测所有实现了HandlerMapping的bean,如果有,则加入DispatcherServlet的handlerMappings中。如果detectAllHandlerMappings为false,则直接去容器中找id="handlerMapping"且实现了HandlerMapping的bean.如果以上都没找到,则会去加载默认的HandlerMapping。

  1. /** Detect all HandlerMappings or just expect "handlerMapping" bean? */
  2. private boolean detectAllHandlerMappings = true;

本工程由于没有配置HandlerMapping,所以它会去加载默认的,下面看看默认的配置是什么

  1. protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
  2. String key = strategyInterface.getName();
  3. //defaultStrategies存储了默认的配置
  4. String value = defaultStrategies.getProperty(key);
  5. if (value != null) {
  6. String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
  7. List<T> strategies = new ArrayList<T>(classNames.length);
  8. for (String className : classNames) {
  9. try {
  10. Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
  11. Object strategy = createDefaultStrategy(context, clazz);
  12. strategies.add((T) strategy);
  13. }
  14. catch (ClassNotFoundException ex) {
  15. throw new BeanInitializationException(
  16. "Could not find DispatcherServlet's default strategy class [" + className +
  17. "] for interface [" + key + "]", ex);
  18. }
  19. catch (LinkageError err) {
  20. throw new BeanInitializationException(
  21. "Error loading DispatcherServlet's default strategy class [" + className +
  22. "] for interface [" + key + "]: problem with class file or dependent class", err);
  23. }
  24. }
  25. return strategies;
  26. }
  27. else {
  28. return new LinkedList<T>();
  29. }
  30. }

继续看看defaultStrategies是如何初始化的:

  1. private static final Properties defaultStrategies;
  2. static {
  3. // Load default strategy implementations from properties file.
  4. // This is currently strictly internal and not meant to be customized
  5. // by application developers.
  6. try {
  7. //这里的DEFAULT_STRATEGIES_PATH就是DispatcherServlet.properties
  8. ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
  9. defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
  10. }
  11. catch (IOException ex) {
  12. throw new IllegalStateException("Could not load 'DispatcherServlet.properties': " + ex.getMessage());
  13. }
  14. }

这里使用静态代码块来加载配置文件DispatcherServlet.properties,它所在位置就是和DispatcherServlet同一目录下面的,如下图所示:

SpringMVC源码总结(一)HandlerMapping和HandlerAdapter入门
该默认的配置文件的内容如下:

  1. # Default implementation classes for DispatcherServlet's strategy interfaces.
  2. # Used as fallback when no matching beans are found in the DispatcherServlet context.
  3. # Not meant to be customized by application developers.
  4. org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
  5. org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
  6. #这里就是默认的HandlerMapping的配置
  7. org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
  8. org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping
  9. #这里就是默认的HandlerAdapter的配置
  10. org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
  11. org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
  12. org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
  13. org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\
  14. org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
  15. org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
  16. org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
  17. org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
  18. org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager

也就是说,当你什么都没有配置时,默认会加载以上的配置。正是由于有了上述默认配置的BeanNameUrlHandlerMapping(它要求name必须是以/开头的),它才会存储我们在mvc-servlet.xml中配置的<bean name="/index" class="com.lg.mvc.HomeAction"></bean>,同样正是由于有了SimpleControllerHandlerAdapter(由于handler实现了Controller接口,所以它的support方法支持我们的handler),才会调度执行HomeAction的handleRequest方法。