Spring MVC各组件近距离接触--上--02

x33g5p2x  于2022-07-14 转载在 Spring  
字(38.0k)|赞(0)|评价(0)|浏览(626)

忙碌的协调人HandlerMapping

HandlerMapping帮助DispathcerServlet进行Web请求的URL到具体处理类的匹配。

之所以被称为HandlerMapping是因为,在Spring MVC中,并不只局限于使用org.springframework.web.servlet.mvc.Controller作为DispatcherSevlet的次级控制器来进行具体的Web请求的处理。

实际上,在稍后介绍HandlerAdaptor的时候,就会知道,我们也可以使用其他类型的次级控制器,包括Spring MVC提供的除了Controller之外的次级控制器类型,或者第三方Web开发框架中的Page controller组件,而所有这些次级控制器类型,在Spring MVC中都被称为Handler。

因此HandlerMapping要处理的其实就是Web请求到相应Handler之间的映射关系。

HandlerMapping的定义如下:

  1. public interface HandlerMapping {
  2. String BEST_MATCHING_HANDLER_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingHandler";
  3. @Deprecated
  4. String LOOKUP_PATH = HandlerMapping.class.getName() + ".lookupPath";
  5. String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = HandlerMapping.class.getName() + ".pathWithinHandlerMapping";
  6. String BEST_MATCHING_PATTERN_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingPattern";
  7. String INTROSPECT_TYPE_LEVEL_MAPPING = HandlerMapping.class.getName() + ".introspectTypeLevelMapping";
  8. String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".uriTemplateVariables";
  9. String MATRIX_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".matrixVariables";
  10. String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName() + ".producibleMediaTypes";
  11. default boolean usesPathPatterns() {
  12. return false;
  13. }
  14. @Nullable
  15. HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
  16. }
  • getHandler方法为什么返回HandlerExecutionChain

HandlerMapping支持请求映射过程中添加对应的拦截器,这就是我们熟悉的并经常在Spring mvc中使用的拦截器链,这也就是解释了,为什么getHandler方法返回的是一个HandlerExecutionChain,因为该HandlerExecutionChain中会包含可以应用到当前handler上的一堆拦截器还有handler自身。

当DispathcerServlet要调用该handler时,会先去调用拦截器链中每个拦截器的preHandler方法,如果都返回true,再去调用对应的hanler。
tomcat在调用某个servlet之前,也会去调用该servlet相关的过滤器链,然后最终再去调用servlet的service方法
对于spring mvc的DispathcerServlet来说,要先等到tomcat的相关过滤器链执行完毕,再去执行DispathcherServlet,然后DispathcerServlet再将请求分发给具体Handler之前,还需要先调用当前handler关联的一堆拦截器

  • 上面给出的一堆ATTRIBUTE,被各个HandlerMapping子实现类所支持,对应的子实现类,会取出自身支持的属性,然后根据结果做相关事情(这个下面会讲)

可用的HandlerMapping

Spring MVC默认提供了多个HandlerMapping的实现:

我们重点关注下面几个实现类:

  • BeanNameUrlHandlerMapping: 用url最后一个资源名作为beanName去匹配对应的handler,例如: http://host:port/hhh.do会去当前容器内寻找名为/hhh.do的Controller定义。
  • SimpleUrlHandlerMapping: 该实现类进一步接触了请求URL与Handler的beanName之间的耦合,并且支持更灵活的映射表达方式
  • ControllerClassNameHandlerMapping: 我们将在后面章节再进行介绍。
  • DefaultAnnotationHandlerMapping: Spring 2.5之后的Spring MVC引入了基于注解的配置方式。

BeanNameUrlHandlerMapping

BeanNameUrlHandlerMapping类的定义如下:

  1. public class BeanNameUrlHandlerMapping extends AbstractDetectingUrlHandlerMapping {
  2. //当前handler可以映射到哪些URLS上去
  3. @Override
  4. protected String[] determineUrlsForHandler(String beanName) {
  5. List<String> urls = new ArrayList<>();
  6. //如果当前Handler的beanName以/开头,那么对应handler就可以映射到该请求上
  7. if (beanName.startsWith("/")) {
  8. urls.add(beanName);
  9. }
  10. //获取当前bean的别名数组
  11. String[] aliases = obtainApplicationContext().getAliases(beanName);
  12. //如果别名中也有/开头的,那么也加入URLS集合
  13. for (String alias : aliases) {
  14. if (alias.startsWith("/")) {
  15. urls.add(alias);
  16. }
  17. }
  18. //返回的是当前handler可以处理的URLS集合
  19. return StringUtils.toStringArray(urls);
  20. }
  21. }

这里不需要去看看其父类中的实现,只需要根据BeanNameUrlHandlerMapping实现的determineUrlsForHandler方法即可推断出,该HandlerMapping的具体映射规则。

  • 即用以"/“开头的beanName或者以”/"开头的别名,作为当前handler映射到的URLS数组

所以,如果要使用BeanNameUrlHandlerMapping,只需要在对应[servletName]-servlet.xml配置文件中,注入该bean即可: (当然,不注入的话,也会默认使用BeanNameUrlHandlerMapping)

  1. <bean id="handlerMapping" class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
  2. <bean name="/hhh.do" class="com.example.controller.HelloController">
  3. <property name="helloService" ref="helloService"/>
  4. <property name="viewName" value="hello"/>
  5. </bean>

SimpleUrlHandlerMapping

使用BeanNameUrlHandlerMapping进行Web请求到具体Handler的映射管理,需要我们保证视图模板中的请求路径,必须与容器中对应的Handler的beanName一致。

BeanNameUrlHandlerMapping的固定映射模式,并没有对映射做个过程做过多的关注,而是简单的直接匹配。

SimpleUrlHandlerMapping比BeanNameUrlHandlerMapping做的工作多一些,它可以使视图一方和handler一方自由活动,最后由SimpleUrlHandlerMapping进行统筹。

  • SimpleUrlHandlerMapping基本使用如下:
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  5. <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
  6. <property name="prefix" value="/WEB-INF/jsp/"/>
  7. <property name="suffix" value=".jsp"/>
  8. </bean>
  9. <bean id="handlerMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
  10. <property name="mappings">
  11. <props>
  12. <prop key="/hello.do">helloController</prop>
  13. </props>
  14. </property>
  15. </bean>
  16. <bean name="helloController" class="com.example.controller.HelloController">
  17. <property name="helloService" ref="helloService"/>
  18. <property name="viewName" value="hello"/>
  19. </bean>
  20. </beans>

这里所有请求后面要加.do是因为在web.xml中配置的DispatcherServlet拦截的路径是 * .do

现在控制器Controller可以起任何名字,视图中的链接也可以独立提供,只需要通过SimpleUrlHandlerMapping指定一下二者的对应关系即可。

SimpleUrlHandlerMapping 的源码也非常的简单:

  1. public class SimpleUrlHandlerMapping extends AbstractUrlHandlerMapping {
  2. //存放controller控制器和其对应映射到的请求路径
  3. private final Map<String, Object> urlMap = new LinkedHashMap<>();
  4. //下面两个方法对应xml中往urlMap中注入属性的两种不同方式
  5. public void setMappings(Properties mappings) {
  6. CollectionUtils.mergePropertiesIntoMap(mappings, this.urlMap);
  7. }
  8. public void setUrlMap(Map<String, ?> urlMap) {
  9. this.urlMap.putAll(urlMap);
  10. }
  11. public Map<String, ?> getUrlMap() {
  12. return this.urlMap;
  13. }
  14. = //该方法会在父类中被调用,因为父类实现了ApplicationContextAware接口
  15. //初始化时,会去调用setApplicationContext方法,其中会调用 initApplicationContext方法
  16. @Override
  17. public void initApplicationContext() throws BeansException {
  18. super.initApplicationContext();
  19. //注册好handler的相关映射
  20. registerHandlers(this.urlMap);
  21. }
  22. protected void registerHandlers(Map<String, Object> urlMap) throws BeansException {
  23. if (urlMap.isEmpty()) {
  24. logger.warn("Neither 'urlMap' nor 'mappings' set on SimpleUrlHandlerMapping");
  25. }
  26. else {
  27. //这里注意是处理配置文件中,给出请求路径时,没加/的情况,和空格处理
  28. urlMap.forEach((url, handler) -> {
  29. // Prepend with slash if not already present.
  30. if (!url.startsWith("/")) {
  31. url = "/" + url;
  32. }
  33. // Remove whitespace from handler bean name.
  34. if (handler instanceof String) {
  35. handler = ((String) handler).trim();
  36. }
  37. //调用父类方法进行注册---注册到父类的handlerMap映射集合中去
  38. registerHandler(url, handler);
  39. });
  40. }
  41. }
  42. }
  • 读取xml配置文件,初始化SimpleUrlHandlerMapping 时,会将我们在配置文件中设置好的请求映射放入urlMap集合中去
  • 然后再初始化initApplicationContext时,又通过registerHandler,将相关映射加入父类的handlerMap映射集合中去
  • 因为,每次请求来时,是去父类的handlerMap映射集合查找

SimpleUrlHandlerMapping还可以使用类似于ANT路径形式的模式匹配,这样我们就可以通过各种表达式,将一组或多组拥有某种相似特征的Web处理请求映射给相应的Handler处理。

  1. <bean id="handlerMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
  2. <property name="mappings">
  3. <props>
  4. <prop key="/**/*hello.do">helloController</prop>
  5. </props>
  6. </property>
  7. </bean>
  8. <bean name="helloController" class="com.example.controller.HelloController">
  9. <property name="helloService" ref="helloService"/>
  10. <property name="viewName" value="hello"/>
  11. </bean>

当然,其实使用BeanNameUrlMapping也可以使用ANT路径匹配,只不过这里要使用ANT路径的地方,是对应控制器的beanName

  1. <bean id="handlerMapping" class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
  2. <bean name="/h*h.do" class="com.example.controller.HelloController">
  3. <property name="helloService" ref="helloService"/>
  4. <property name="viewName" value="hello"/>
  5. </bean>

为什么,可以玩呢?

  • 其实,这和他们的父类AbstractUrlHandlerMapping中查找某个请求映射到的handler的lookupHandler方法有很大关系
  • lookupHandler是查找请求映射handler的通用父类逻辑
  1. @Nullable
  2. protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
  3. // Direct match?
  4. //直接匹配--去handlerMap中,直接根据urlPath去进行精确查找
  5. Object handler = this.handlerMap.get(urlPath);
  6. if (handler != null) {
  7. //如果精确查找到了,那么判断此时handlerMap中找到的这个handler是不是一个字符串
  8. //为什么会是一个字符串呢?
  9. //因为,如果该handler是一个多例类型,那么每次都需要new一个新的实例,因此handlerMap保存的是该handler的beanName
  10. //每次通过getBean重新创建该handler
  11. // Bean name or resolved handler?
  12. if (handler instanceof String) {
  13. String handlerName = (String) handler;
  14. handler = obtainApplicationContext().getBean(handlerName);
  15. }
  16. validateHandler(handler, request);
  17. //以当前handler构建一个空的HandlerExecutionChain,暂时还未加入相关拦截器
  18. return buildPathExposingHandler(handler, urlPath, urlPath, null);
  19. }
  20. //采用ant模式进行路径匹配
  21. // Pattern match?
  22. List<String> matchingPatterns = new ArrayList<>();
  23. for (String registeredPattern : this.handlerMap.keySet()) {
  24. //getPathMatcher返回的就是AntPathMatcher---对注册好的请求路径与当前请求路径进行ant匹配
  25. if (getPathMatcher().match(registeredPattern, urlPath)) {
  26. //如果满足条件,则加入集合
  27. matchingPatterns.add(registeredPattern);
  28. }
  29. //useTrailingSlashMatch默认为false
  30. //即不会把"/users"当做"/users/"
  31. else if (useTrailingSlashMatch()) {
  32. //如果注册的映射路径不以/结尾,这里加上/,再进行ant匹配,判断是否满足
  33. //such as "/users" also matches to "/users/"
  34. if (!registeredPattern.endsWith("/") && getPathMatcher().match(registeredPattern + "/", urlPath)) {
  35. matchingPatterns.add(registeredPattern + "/");
  36. }
  37. }
  38. }
  39. //如果上面ant匹配存在多个matchingPattern,那么下面会挑选出一个最佳匹配
  40. //遵循的是精确优先
  41. String bestMatch = null;
  42. Comparator<String> patternComparator = getPathMatcher().getPatternComparator(urlPath);
  43. if (!matchingPatterns.isEmpty()) {
  44. matchingPatterns.sort(patternComparator);
  45. if (logger.isDebugEnabled()) {
  46. logger.debug("Matching patterns for request [" + urlPath + "] are " + matchingPatterns);
  47. }
  48. bestMatch = matchingPatterns.get(0);
  49. }
  50. //判断ant匹配是否可以得到一个结果
  51. if (bestMatch != null) {
  52. //如果得到了,那么拿到对应的handler
  53. handler = this.handlerMap.get(bestMatch);
  54. //如果handler为空,那么尝试取出掉bestMatch最后的/,再次进行查找匹配
  55. if (handler == null) {
  56. if (bestMatch.endsWith("/")) {
  57. handler = this.handlerMap.get(bestMatch.substring(0, bestMatch.length() - 1));
  58. }
  59. //如果还是没找到,则抛出异常
  60. if (handler == null) {
  61. throw new IllegalStateException(
  62. "Could not find handler for best pattern match [" + bestMatch + "]");
  63. }
  64. }
  65. // Bean name or resolved handler?
  66. //处理多例的情况
  67. if (handler instanceof String) {
  68. String handlerName = (String) handler;
  69. handler = obtainApplicationContext().getBean(handlerName);
  70. }
  71. validateHandler(handler, request);
  72. String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestMatch, urlPath);
  73. // There might be multiple 'best patterns', let's make sure we have the correct URI template variables
  74. // for all of them
  75. Map<String, String> uriTemplateVariables = new LinkedHashMap<>();
  76. for (String matchingPattern : matchingPatterns) {
  77. if (patternComparator.compare(bestMatch, matchingPattern) == 0) {
  78. Map<String, String> vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, urlPath);
  79. Map<String, String> decodedVars = getUrlPathHelper().decodePathVariables(request, vars);
  80. uriTemplateVariables.putAll(decodedVars);
  81. }
  82. }
  83. if (logger.isDebugEnabled()) {
  84. logger.debug("URI Template variables for request [" + urlPath + "] are " + uriTemplateVariables);
  85. }
  86. return buildPathExposingHandler(handler, bestMatch, pathWithinMapping, uriTemplateVariables);
  87. }
  88. // No handler found...
  89. return null;
  90. }

先精确查找,再模糊匹配,如果存在多个匹配结果,那么精确优先。

HandlerMapping的执行序列(chain of handlermapping)

在基于Spirng MVC的Web应用程序中,我们可以为DispathcerServlet提供多个HandlerMapping,DispatcherServlet在选用HandlerMapping的过程中,会先对HandlerMapping集合按照优先级进行排序,然后按照依次询问每个HandlerMapping。
initHandlerMappings方法中会对HandlerMappings集合进行排序,并且该方法是在onRefresh方法中被调用的

如果某一个HandlerMapping能够返回可用的Handler,则DispathcerServlet使用当前Handler进行Web请求的处理,而不再询问其他HandlerMapping。

  • 下面给出的是DispathcerServlet中获取某个请求对应handler的方法
  1. protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
  2. //handlerMappings集合中存放的是当前捕获到可用的handlerMapping集合
  3. if (this.handlerMappings != null) {
  4. //按照优先级顺序依次遍历
  5. for (HandlerMapping hm : this.handlerMappings) {
  6. if (logger.isTraceEnabled()) {
  7. logger.trace(
  8. "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
  9. }
  10. //如果某个handlerMapping返回了值,那么直接退出循环
  11. HandlerExecutionChain handler = hm.getHandler(request);
  12. if (handler != null) {
  13. return handler;
  14. }
  15. }
  16. }
  17. return null;
  18. }

HandlerMapping的优先级规定遵循了Spring框架内一贯的Oredered接口所规定的语义,数值越小,优先级越大,具体配置实例如下:

  1. <bean id="handlerMapping" class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping">
  2. <property name="order" value="1"/>
  3. </bean>

亲密伙伴Controller

Controller是Spring MVC框架支持的用于处理具体Web请求的Handler类型之一。

要实现一个具体的Controller,我们可以直接实现Controller接口:

  1. @FunctionalInterface
  2. public interface Controller {
  3. @Nullable
  4. ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception;
  5. }
  • handleRequest方法解释: 处理请求并返回一个ModelAndView,DispathcerServlet会通过ModelAndView来进行渲染工作,如果返回null值,表示Controller内部自己完成了渲染,不需要DispathcerServlet来进行渲染

直接实现Controller接口当然没有问题,但是这需要我们去处理很多的通用细节问题,例如: 请求参数的抽取,请求编码的设定,国际化信息处理,Session数据的管理等。

而实际上,这些关注点是所有Controller都需要的,我们就应该想办法将这些通用的逻辑进行复用,这就是Spring MVC提供了一套Controller实现体系的原因。它帮助我们更好地处理了Web请求过程中的某些通用关注点。

Controller的继承体系如下:

  • 自由挥洒派的Controller: 对于该派而言,其提供的就是接近原生Servlet API服务,我们从HttpServletRequest中获取请求参数,然后验证,调用业务层逻辑,最终返回一个ModelAndView。甚至,可以直接通过HttpServletResponse输出视图。虽然很自由,但是需要处理的细节也很多,因此大家可以根据使用场景,自行判断是否需要。
    Spring 4开始,对原有的Controller架构进行了大调整,大家上面看到的这些类基本都被移除了,Spring 4开始的Controller架构如下,我们讲3,再看4

  • 规范操作派的Controller: 以BASECommandController为首的规范操作派,对Web处理过程中某些通用逻辑进行了进一步的规范化封装处理,规范化的方面主要包括:

  • 自动抽取请求参数并绑定到指定的Command对象

  • 提供了统一的数据验证方式,BaseCommandController及其子类可以接收一组Validator以进行数据验证

  • 规范化了表单的请求处理流程,并对简单的多页面表单请求处理提供支持

AbstractController

AbstractControlle是整个Controller继承体系的起源,该类通过模板方法模式帮助我们解决了如下几个通用关注点:

  • 管理当前Controller所支持的请求方法类型(GET/POST)
  • 是否需要session,如果需要尝试获取,但是不会选择创建,如果不存在,则抛出异常
  • 管理页面的缓存设置,即是否允许浏览器缓存当前页面
  • 管理执行流程在会话上的同步,session mutex

我们要做的,就是在AbstractController所公开的handleRequestInternal模板方法中实现具体Wbe请求处理过程中的其他逻辑。

DispatcherServlet会去调用AbstractController实现Controller接口的handleReqeuest方法,而AbstractController为了做一些准备工作,因此需要在handleReqeuest提前做好,然后将真正请求处理过程再通过抽象方法handleRequestInternal暴露给子类去实现。

  • handleRequest是AbstractController模板方法模式的体现
  1. public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
  2. throws Exception {
  3. // Delegate to WebContentGenerator for checking and preparing.
  4. //做上面讲的通用关注点处理--由父类WebContentGenerator完成的
  5. checkAndPrepare(request, response, this instanceof LastModified);
  6. // Execute handleRequestInternal in synchronized block if required.
  7. //是否需要开启session同步锁机制
  8. if (this.synchronizeOnSession) {
  9. HttpSession session = request.getSession(false);
  10. if (session != null) {
  11. Object mutex = WebUtils.getSessionMutex(session);
  12. synchronized (mutex) {
  13. return handleRequestInternal(request, response);
  14. }
  15. }
  16. }
  17. //进入真正处理请求的方法逻辑
  18. return handleRequestInternal(request, response);
  19. }
  • WebContentGenerator父类实现的通用关注点分离方法:
  1. protected final void checkAndPrepare(
  2. HttpServletRequest request, HttpServletResponse response, int cacheSeconds, boolean lastModified)
  3. throws ServletException {
  4. // Check whether we should support the request method.
  5. //是否支持当前请求方法
  6. String method = request.getMethod();
  7. if (this.supportedMethods != null && !this.supportedMethods.contains(method)) {
  8. throw new HttpRequestMethodNotSupportedException(
  9. method, StringUtils.toStringArray(this.supportedMethods));
  10. }
  11. // Check whether a session is required.
  12. //是否需要session
  13. if (this.requireSession) {
  14. if (request.getSession(false) == null) {
  15. throw new HttpSessionRequiredException("Pre-existing session required but none found");
  16. }
  17. }
  18. // Do declarative cache control.
  19. // Revalidate if the controller supports last-modified.
  20. //是否需要设置缓存相关响应头
  21. applyCacheSeconds(response, cacheSeconds, lastModified);
  22. }

可以看到AbstractController并没有做很多事情,因此如果我们实现AbstractController来完成业务逻辑编写,会发现还是需要关注参数抽取,数据验证等细节。

MultiActionController

该类在Spring 4中已经被移除了

MultiActionController用于对一组逻辑上相近的Web请求进行封装,例如针对同一个对象的CRUD进行封装。

而不需要像AbstaractController那样为每一个请求单独实现一个继承AbstaractController的处理类。

MultiActionController提供了以下功能:

  • 请求参数到Command对象的绑定。
  • 通过Validator的数据验证。
  • 细化的异常处理方法。

为了在MultiActionController中处理多个Web请求,我们需要定义多个Web请求处理方法,分别对应每个Web请求的处理。

这些Web请求处理方法可以定义在MultiActionController的子类中,也可以定义在某一个将来可以指定给MultiActionController的委派对象(delegate)内,但Web请求的处理方法的签名必须符合一定的要求:

  1. public (ModelAndView | Map | String | void) actionName
  2. (HttpServletRequest request, HttpServletResponse response, [,HttpSession] [,AnyObject]);

Web请求处理方法的名称可以取任何有意义的名字,但是前面两个方法参数是必须的,第三个参数是可选的,可以是HttpSession类型也可以是Object类型,如果是Object类型,则表明对应的是Command对象,那么MultiActionController就会帮我们绑定数据并执行数据验证了。

最后一个参数可以是我们自定义的对象,只需要给其中对应的属性提供get和set方法,MultiActionController便会在参数绑定时,去请求参数中尝试将同名属性绑定到对应的对象属性上面去

方法的返回值有三种类型,分别对应如下语义:

  • 返回ModelAndView表示正常的Web处理方法,后继的ViewResolver和View处理流程,依照之前的DispathcerServlet的流程进行。
  • 返回Map表明只返回了模型数据,而没有返回逻辑视图名。这时,将寻求默认的视图名。这个工作由org.springframework.web.servlet.RequestToViewNameTranslator负责,该类能按照某种规则提供一个默认的逻辑视图名。
  • 返回void,则表明既没有返回模型数据,也没有返回逻辑视图名。这时,我们认为,当前Web请求处理方法自行处理掉了视图的渲染和输出。
  • 另外,Spring 2.5中可以返回String ,代表逻辑视图名,没有相关的模型数据。

现在还有一个问题: MultiActionController如何知道将当前请求映射到哪个方法上呢?

MethodNameResolver决定当前Web请求交给哪个方法来处理,其定义如下:

  1. public interface MethodNameResolver {
  2. String getHandlerMethodName(HttpServletRequest request) throws NoSuchRequestHandlingMethodException;
  3. }

有了MethodNameResolver 之后,当请求到来时,MultiActionController只需要询问一下MethodNameResolver 需要调用哪个方法即可:

  1. protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
  2. throws Exception {
  3. try {
  4. String methodName = this.methodNameResolver.getHandlerMethodName(request);
  5. return invokeNamedMethod(methodName, request, response);
  6. }
  7. catch (NoSuchRequestHandlingMethodException ex) {
  8. return handleNoSuchRequestHandlingMethod(ex, request, response);
  9. }
  10. }

MethodNameResolver 为MultiActionController提供了灵活的Web请求到对应处理方法的映射策略,包括根据Web请求的URL进行映射,或者根据某个参数值进行映射等。

我们下面来看看Spring为我们提供好了哪些MethodNameResolver 实现:

MethodNameResolver

MethodNameResolver本身是一个策略接口,Spring默认提供了三个实现类:

AbstractUrlMethodNameResolver是基于请求URL到具体处理方法的抽象解析器父类,该类中有两个重要的方法:

  1. //根据请求获得对应handlerMethod的方法名
  2. public final String getHandlerMethodName(HttpServletRequest request)
  3. throws NoSuchRequestHandlingMethodException {
  4. //查询出当前请求对应的URL路径
  5. String urlPath = this.urlPathHelper.getLookupPathForRequest(request);
  6. //调用子类实现的抽象方法,来获取到URL具体映射到的方法名
  7. String name = getHandlerMethodNameForUrlPath(urlPath);
  8. if (name == null) {
  9. throw new NoSuchRequestHandlingMethodException(urlPath, request.getMethod(), request.getParameterMap());
  10. }
  11. if (logger.isDebugEnabled()) {
  12. logger.debug("Returning handler method name '" + name + "' for lookup path: " + urlPath);
  13. }
  14. return name;
  15. }
  16. protected abstract String getHandlerMethodNameForUrlPath(String urlPath);
InternalPathMethodNameResolver

如果没有为MultiActionController指定任何MethodNameResolver,那么InternalPathMethodNameResolver将作为默认的MethodNameResolver,以进行Web请求与具体处理方法间的映射解析。

InternalPathMethodNameResolver将提取URL最后一个/之后的部分并去除扩展名,作为要返回的方法名称,比如:

  • /dhy/xpy/list.do ----> list
  • /dhy/xpy/update.do ----> update
  • /dhy/xpy/delete.do ----> delete
  • /dhy/xpy/create.do ----> create

我们还可以通过prefix和suffix来为上面截取得到的methodName加上前后缀,作为最终会去查询得到的方法名:

  1. <bean id="internalPathMethodNameResolver" class="org.springframework.web.servlet.mvc.multiaction.InternalPathMethodNameResolver">
  2. <property name="prefix" value="dhy_"/>
  3. <property name="suffix" value="_xpy"/>
  4. </bean>

如果加上了前后缀限制,那么上面的请求URL最终映射得到的方法名如下:

  • /dhy/xpy/list.do ----> dhy_list_xpy
  • /dhy/xpy/update.do ----> dhy_update_xpy
  • /dhy/xpy/delete.do ----> dhy_delete_xpy
  • /dhy/xpy/create.do ----> dhy_create_xpy

InternalPathMethodNameResolver的源码实现也非常简单:

  1. public class InternalPathMethodNameResolver extends AbstractUrlMethodNameResolver {
  2. private String prefix = "";
  3. private String suffix = "";
  4. private final Map<String, String> methodNameCache = new ConcurrentHashMap<String, String>(16);
  5. //....省略get和set方法
  6. //根据URL路径去获取到对应的handlerName
  7. @Override
  8. protected String getHandlerMethodNameForUrlPath(String urlPath) {
  9. //首选查询缓存
  10. String methodName = this.methodNameCache.get(urlPath);
  11. if (methodName == null) {
  12. //提取URL最后一个/之后的部分并去除扩展名,作为要返回的方法名称
  13. methodName = extractHandlerMethodNameFromUrlPath(urlPath);
  14. //加上前后缀
  15. methodName = postProcessHandlerMethodName(methodName);
  16. this.methodNameCache.put(urlPath, methodName);
  17. }
  18. //返回
  19. return methodName;
  20. }
  21. protected String extractHandlerMethodNameFromUrlPath(String uri) {
  22. return WebUtils.extractFilenameFromUrlPath(uri);
  23. }
  24. protected String postProcessHandlerMethodName(String methodName) {
  25. return getPrefix() + methodName + getSuffix();
  26. }
  27. }
PropertiesMethodNameResolver

PropertiesMethodNameResolver与InternalPathMethodNameResolver唯一的相同点在于,他们都是基于请求的URL进行映射(因为都继承了AbstractUrlMethodNameResolver)。

但它比InternalPathMethodNameResolver灵活,如果HandlerMapping与MethodNameResolver都是处理映射这一点来看,InternalPathMethodNameResolver相当于BeanNameURLHandlerMapping,而PropertiesMethodNameResolver相当于SimpleUrlHandlerMapping.

PropertiesMethodNameResolver可以指定完全匹配的映射关系,或者使用ANT形式的路径匹配模式所表达的映射关系。

例如:

  1. <bean id="propertiesMethodNameResolver" class="org.springframework.web.servlet.mvc.multiaction.PropertiesMethodNameResolver">
  2. <property name="mappings">
  3. <value>
  4. /list.do=list
  5. /update=update
  6. /list*=list
  7. </value>
  8. </property>
  9. </bean>

PropertiesMethodNameResolver的源码也非常简单:

  1. public class PropertiesMethodNameResolver extends AbstractUrlMethodNameResolver
  2. implements InitializingBean {
  3. private Properties mappings;
  4. private PathMatcher pathMatcher = new AntPathMatcher();
  5. ...
  6. @Override
  7. protected String getHandlerMethodNameForUrlPath(String urlPath) {
  8. //先进行精确匹配
  9. String methodName = this.mappings.getProperty(urlPath);
  10. if (methodName != null) {
  11. return methodName;
  12. }
  13. //如果精确匹配没找到,再进行模糊匹配
  14. Enumeration propNames = this.mappings.propertyNames();
  15. while (propNames.hasMoreElements()) {
  16. String registeredPath = (String) propNames.nextElement();
  17. //这里按顺序挨个匹配,当遇到第一个匹配成功,则直接返回
  18. //因此不会进行最佳匹配
  19. if (this.pathMatcher.match(registeredPath, urlPath)) {
  20. return (String) this.mappings.get(registeredPath);
  21. }
  22. }
  23. return null;
  24. }
  25. }
ParameterMethodNameResolver

ParameterMethodNameResolver允许我们根据请求中的某个参数的值作为映射的方法名,也允许我们使用请求中的一组参数来处理映射的方法名称.

  • 下面是两种策略的详细情况

(1) 根据请求中的某个参数的值作为映射后的方法名。在Web请求提交之后,我们可以附带一个参数,专门指定由MultiActionController中的那个方法来处理当前请求

ParameterMethodNameResolver默认检测的参数名称为action,我们也可以通过setParamName方法来修改默认的参数名称。

  1. <bean class="org.springframework.web.servlet.mvc.multiaction.ParameterMethodNameResolver">
  2. <property name="paramName" value="methodName"/>
  3. </bean>

那么,HTTP GET形式发送的URL看起来就类似于: http://host:port//dhy/xpy?methodName=list.

(2) 根据请求中的一组参数作为映射后的方法名,在某个页面中存在多种行为选择的时候,可以让每一种行为对应一个参数。

我们通过methodParamNames属性为ParameterMethodNameResolver指定一组要检测的参数名。

ParameterMethodNameResolver将以指定的一组参数名作为基准,对Web请求中的参数进行检测。

如果发现存在其中某个参数,则将当前Web请求映射到与参数相同名称的处理方法。
我们还可以通过指定defaultMethodName,来提供一个兜底方法,该方法会在请求无法找到合适处理方法的时候,作为默认方法进行处理

  1. <bean class="org.springframework.web.servlet.mvc.multiaction.ParameterMethodNameResolver">
  2. <property name="paramName" value="methodName"/>
  3. <property name="methodParamNames" value="list,update,delete"/>
  4. <property name="defaultMethodName" value="list"/>
  5. </bean>

如果像上面一样,同时指定paramName和methodParamNames,那么谁的优先级高呢?

最简单的策略是楼一眼ParameterMethodNameResolver的源码:

  1. public class ParameterMethodNameResolver implements MethodNameResolver {
  2. /**
  3. 默认的映射请求参数名
  4. */
  5. public static final String DEFAULT_PARAM_NAME = "action";
  6. private String paramName = DEFAULT_PARAM_NAME;
  7. private String[] methodParamNames;
  8. //逻辑映射--下面源码中会体现出它的作用
  9. private Properties logicalMappings;
  10. private String defaultMethodName;
  11. ...
  12. public String getHandlerMethodName(HttpServletRequest request) throws NoSuchRequestHandlingMethodException {
  13. String methodName = null;
  14. //首先处理methodParamNames
  15. if (this.methodParamNames != null) {
  16. for (String candidate : this.methodParamNames) {
  17. //按顺序挨个匹配,如果有一个匹配上了,直接返回
  18. if (WebUtils.hasSubmitParameter(request, candidate)) {
  19. //这里是直接将methodParamNames数组中当前元素作为方法名
  20. methodName = candidate;
  21. if (logger.isDebugEnabled()) {
  22. logger.debug("Determined handler method '" + methodName +
  23. "' based on existence of explicit request parameter of same name");
  24. }
  25. break;
  26. }
  27. }
  28. }
  29. //如果methodParamNames没有匹配上,那么再对paramName进行处理
  30. if (methodName == null && this.paramName != null) {
  31. //这里是取出请求参数中的值作为方法名
  32. methodName = request.getParameter(this.paramName);
  33. if (methodName != null) {
  34. if (logger.isDebugEnabled()) {
  35. logger.debug("Determined handler method '" + methodName +
  36. "' based on value of request parameter '" + this.paramName + "'");
  37. }
  38. }
  39. }
  40. //如果上面经过上面处理后,匹配上了,那么下面会把当前methodName作为一个逻辑值,去logicalMappings找到其真正的值
  41. //前提是设置了logicalMappings ---相当于又提供了一层映射,感觉没啥用
  42. if (methodName != null && this.logicalMappings != null) {
  43. // Resolve logical name into real method name, if appropriate.
  44. String originalName = methodName;
  45. //第二个参数是如果找不到,默认值是啥
  46. methodName = this.logicalMappings.getProperty(methodName, methodName);
  47. if (logger.isDebugEnabled()) {
  48. logger.debug("Resolved method name '" + originalName + "' to handler method '" + methodName + "'");
  49. }
  50. }
  51. //防止上面得到的methodName 是空串
  52. if (methodName != null && !StringUtils.hasText(methodName)) {
  53. if (logger.isDebugEnabled()) {
  54. logger.debug("Method name '" + methodName + "' is empty: treating it as no method name found");
  55. }
  56. methodName = null;
  57. }
  58. //如果一番折腾后,没得到,那么使用默认的defaultMethodName
  59. if (methodName == null) {
  60. if (this.defaultMethodName != null) {
  61. // No specific method resolved: use default method.
  62. methodName = this.defaultMethodName;
  63. if (logger.isDebugEnabled()) {
  64. logger.debug("Falling back to default handler method '" + this.defaultMethodName + "'");
  65. }
  66. }
  67. else {
  68. // If resolution failed completely, throw an exception.
  69. throw new NoSuchRequestHandlingMethodException(request);
  70. }
  71. }
  72. return methodName;
  73. }
  74. }
MultiActionController使用演示

这里我们实现一个Stu的CRUD:

  • 先准备一个首页,当我们点击首页跳转按钮时,会跳转到学生管理界面,可以进行增删改查
  1. <%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
  2. <!DOCTYPE html>
  3. <html>
  4. <br>
  5. <h1>大忽悠首页</h1></br></br></br></br>
  6. <a href="/stu">点击跳转到学生信息管理界面</a>
  7. </body>
  8. </html>
  • 准备相关增删改查界面

  • 构建MultiActionController,使用MultiActionController进行一组Web请求的处理有两种实现方式:

  • 继承MultiActionController

  • 为MultiActionController提供一个委派对象

为MultiActionController提供一个委派对象的好处在于,委派对象不需要继承任何父类或者接口。因此这里采用委派对象。

并且使用ParamterMethodNameResolver对请求方法进行映射,具体来说,就是为methodParamNames指定insert,update,delete作为要映射的参数,同时以list作为默认的方法:

  1. <bean id="parameterMethodNameResolver" class="org.springframework.web.servlet.mvc.multiaction.ParameterMethodNameResolver">
  2. <property name="methodParamNames" value="insert,update,delete"/>
  3. <property name="defaultMethodName" value="list"/>
  4. </bean>

这样的话,我们的委派对象就需要实现list,create,update,delete对应名称的方法:

  1. @Data
  2. public class StuController{
  3. private StuService stuService;
  4. private String listViewName;
  5. private String updateViewName;
  6. private String insertViewName;
  7. private String deleteViewName;
  8. public ModelAndView list(HttpServletRequest request, HttpServletResponse response){
  9. return new ModelAndView(listViewName,"stuInfoList",stuService.list());
  10. }
  11. public ModelAndView insert(HttpServletRequest request, HttpServletResponse response){
  12. return new ModelAndView(insertViewName);
  13. }
  14. public ModelAndView update(HttpServletRequest request, HttpServletResponse response){
  15. stuService.update(Integer.valueOf(request.getParameter("sno")),request.getParameter("sname"));
  16. return new ModelAndView(updateViewName);
  17. }
  18. public ModelAndView delete(HttpServletRequest request, HttpServletResponse response, Stu stu){
  19. stuService.delete(stu.getNum());
  20. return new ModelAndView(deleteViewName);
  21. }
  22. }
  • 最后就是完整的配置文件了—记住是[servletName]-servlet.xml配置文件,service要在application.xml中进入注入
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  5. <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
  6. <property name="prefix" value="/WEB-INF/jsp/"/>
  7. <property name="suffix" value=".jsp"/>
  8. </bean>
  9. <bean id="parameterMethodNameResolver" class="org.springframework.web.servlet.mvc.multiaction.ParameterMethodNameResolver">
  10. <property name="methodParamNames" value="insert,update,delete"/>
  11. <property name="defaultMethodName" value="list"/>
  12. </bean>
  13. <bean id="handlerMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
  14. <property name="mappings">
  15. <props>
  16. <prop key="/**/stu">multiActionController</prop>
  17. </props>
  18. </property>
  19. </bean>
  20. <bean id="multiActionController" class="org.springframework.web.servlet.mvc.multiaction.MultiActionController">
  21. <property name="delegate" ref="stuController"/>
  22. <property name="methodNameResolver" ref="parameterMethodNameResolver"/>
  23. </bean>
  24. <bean id="stuController" class="com.example.controller.StuController">
  25. <property name="stuService" ref="stuService"/>
  26. <property name="listViewName" value="list"/>
  27. <property name="updateViewName" value="update"/>
  28. <property name="insertViewName" value="insert"/>
  29. <property name="deleteViewName" value="delete"/>
  30. </bean>
  31. </beans>

通过multiActionController的delegate属性,我们指定了它要使用的委派对象,multiActionController内部将使用反射机制调用相应MethodNameResolver所返回的处理方法。

指定的逻辑视图名可以添加相应的前缀(prefix),比如: redirect:viewName,将以重定向的形式跳转到相应的视图,有关视图的转发和重定向将在后面ViewResolver和View部分进行介绍。
注意,需要将web.xml中DispathcerServlet的拦截路径修改为/

  1. <servlet>
  2. <servlet-name>controller</servlet-name>
  3. <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  4. <load-on-startup>2</load-on-startup>
  5. </servlet>
  6. <servlet-mapping>
  7. <servlet-name>controller</servlet-name>
  8. <!-- 拦截除*.jsp以外的所有请求 -->
  9. <url-pattern>/</url-pattern>
  10. </servlet-mapping>
  • 完整流程和下面这幅图画的一样

MultiActionController源码剖析
  • 首先是MultiActionController对代理对象的设置,有三种方式
  1. //无参构造,那么代理对象就是自身,即我们采用的方法是继承MultiActionController类
  2. public MultiActionController() {
  3. this.delegate = this;
  4. //将当前类包括其父类内部所有public方法进行注册
  5. registerHandlerMethods(this.delegate);
  6. }
  7. //有参构造传入代理对象
  8. public MultiActionController(Object delegate) {
  9. setDelegate(delegate);
  10. }
  11. //配置文件中指定属性注入
  12. public final void setDelegate(Object delegate) {
  13. Assert.notNull(delegate, "Delegate must not be null");
  14. this.delegate = delegate;
  15. //注册代理对象内部的方法
  16. registerHandlerMethods(this.delegate);
  17. // There must be SOME handler methods.
  18. if (this.handlerMethodMap.isEmpty()) {
  19. throw new IllegalStateException("No handler methods in class [" + this.delegate.getClass() + "]");
  20. }
  21. }
  • 注册代理对象内部方法的具体细节
  1. private void registerHandlerMethods(Object delegate) {
  2. //清空
  3. this.handlerMethodMap.clear();
  4. this.lastModifiedMethodMap.clear();
  5. this.exceptionHandlerMap.clear();
  6. // Look at all methods in the subclass, trying to find
  7. // methods that are validators according to our criteria
  8. //先获取代理类及其父类中所有的public包括protected方法
  9. Method[] methods = delegate.getClass().getMethods();
  10. for (Method method : methods) {
  11. // 判断该方法是否是用于异常兜底处理方法
  12. if (isExceptionHandlerMethod(method)) {
  13. //如果是的话,就进行注册
  14. registerExceptionHandlerMethod(method);
  15. }
  16. //否则判断是否是普通处理方法
  17. else if (isHandlerMethod(method)) {
  18. //是的话就注册
  19. registerHandlerMethod(method);
  20. registerLastModifiedMethodIfExists(delegate, method);
  21. }
  22. }
  23. }

上面提到了异常兜底处理方法,下面简单介绍一下使用

  • 如果代理对象中提供了如下格式的方法签名,则会被当做异常兜底处理方法加入异常方法映射集合中去,当代理对象方法执行出现异常时,会去寻找对应能处理该异常的异常兜底方法来处理异常
  1. //方法名任意,最后一个参数表示当前方法用来处理哪种类型的异常
  2. public ModelAndView anyMeaningfulName
  3. (HttpServletRequest request, HttpServletResponse response, ExceptionClass exception);
  • 对上面举出的例子进行简单的修改
  1. public ModelAndView list(HttpServletRequest request, HttpServletResponse response) throws IllegalArgumentException {
  2. if(true){
  3. throw new IllegalArgumentException("测试兜底异常捕获");
  4. }
  5. return new ModelAndView(listViewName,"stuInfoList",stuService.list());
  6. }
  7. public ModelAndView catchIllegalArgumentException(HttpServletRequest request, HttpServletResponse response,IllegalArgumentException exception){
  8. return new ModelAndView("error","ex",exception.getMessage());
  9. }

  • 下面先来看看异常方法的判定和注册过程
  1. private boolean isExceptionHandlerMethod(Method method) {
  2. //首先需要满足普通handlerMethod的格式
  3. return (isHandlerMethod(method) &&
  4. //方法参数正好等于三个
  5. method.getParameterTypes().length == 3 &&
  6. //最后一个参数必须继承至Throwable
  7. Throwable.class.isAssignableFrom(method.getParameterTypes()[2]));
  8. }
  1. private void registerExceptionHandlerMethod(Method method) {
  2. //加入exceptionHandlerMap,key是异常类型,val是对应异常兜底方法
  3. this.exceptionHandlerMap.put(method.getParameterTypes()[2], method);
  4. if (logger.isDebugEnabled()) {
  5. logger.debug("Found exception handler method [" + method + "]");
  6. }
  7. }
  • 再来看看普通handlerMethod的判定和注册过程
  1. private boolean isHandlerMethod(Method method) {
  2. //首先判断返回结果是否是下面规定的四种之一
  3. Class returnType = method.getReturnType();
  4. if (ModelAndView.class.equals(returnType) || Map.class.equals(returnType) || String.class.equals(returnType) ||
  5. void.class.equals(returnType)) {
  6. //再对参数值进行判断
  7. Class[] parameterTypes = method.getParameterTypes();
  8. //必须大于等于2,并且前面两个参数类型固定
  9. return (parameterTypes.length >= 2 &&
  10. HttpServletRequest.class.equals(parameterTypes[0]) &&
  11. HttpServletResponse.class.equals(parameterTypes[1]) &&
  12. //下面两个条件不能同时满足,否则说明是继承至Controller的方法
  13. !("handleRequest".equals(method.getName()) && parameterTypes.length == 2));
  14. }
  15. return false;
  16. }
  1. private void registerHandlerMethod(Method method) {
  2. if (logger.isDebugEnabled()) {
  3. logger.debug("Found action method [" + method + "]");
  4. }
  5. //加入handlerMethodMap集合,key是方法名,val是对应的方法
  6. this.handlerMethodMap.put(method.getName(), method);
  7. }
  • 上面在MultiActionController初始化过程中,完成了相关方法的搜集和整理,下面进入正式请求接收处理过程
    因为继承了AbstractController,因此需要重写handleRequestInternal方法
  1. @Override
  2. protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
  3. throws Exception {
  4. try {
  5. //方法解析器解析当前请求得到对应的方法名
  6. String methodName = this.methodNameResolver.getHandlerMethodName(request);
  7. //去调用代理对象的methodName
  8. return invokeNamedMethod(methodName, request, response);
  9. }
  10. catch (NoSuchRequestHandlingMethodException ex) {
  11. return handleNoSuchRequestHandlingMethod(ex, request, response);
  12. }
  13. }
  • 下面进入了代理类目标方法调用的过程
  1. protected final ModelAndView invokeNamedMethod(
  2. String methodName, HttpServletRequest request, HttpServletResponse response) throws Exception {
  3. //根据methodName从handlerMethodMap中得到对应的方法
  4. Method method = this.handlerMethodMap.get(methodName);
  5. //找不到,抛出异常
  6. if (method == null) {
  7. throw new NoSuchRequestHandlingMethodException(methodName, getClass());
  8. }
  9. //参数解析
  10. try {
  11. //首先获取到当前方法的参数列表
  12. Class[] paramTypes = method.getParameterTypes();
  13. //params参数数组大小为4,说明参数个数最多只有四个
  14. List<Object> params = new ArrayList<Object>(4);
  15. // 前面两个参数的位置和类型固定死了
  16. params.add(request);
  17. params.add(response);
  18. //如果参数个数大于等于3个,并且第三个参数为HttpSession,那么设置进去
  19. if (paramTypes.length >= 3 && paramTypes[2].equals(HttpSession.class)) {
  20. HttpSession session = request.getSession(false);
  21. if (session == null) {
  22. throw new HttpSessionRequiredException(
  23. "Pre-existing session required for handler method '" + methodName + "'");
  24. }
  25. params.add(session);
  26. }
  27. // 如果参数类型大于等于3个,并且最后一个参数不是HttpSession类型,这里可能是三个或者四个参数,
  28. //如果是四个参数,那么第三个参数必须是HttpSession类型
  29. if (paramTypes.length >= 3 &&
  30. !paramTypes[paramTypes.length - 1].equals(HttpSession.class)) {
  31. //实例化该对象,这里要求我们给出的自定义对象,必须要有无参构造才行
  32. //因为这里是用无参构造实例化的对象
  33. Object command = newCommandObject(paramTypes[paramTypes.length - 1]);
  34. //加入参数集合
  35. params.add(command);
  36. //进行参数绑定
  37. bind(request, command);
  38. }
  39. //调用代理对象的目标方法
  40. Object returnValue = method.invoke(this.delegate, params.toArray(new Object[params.size()]));
  41. //将返回值通通进行包装
  42. return massageReturnValueIfNecessary(returnValue);
  43. }
  44. //如果出现异常,那么需要对异常进行处理
  45. catch (InvocationTargetException ex) {
  46. // The handler method threw an exception.
  47. return handleException(request, response, ex.getTargetException());
  48. }
  49. catch (Exception ex) {
  50. // The binding process threw an exception.
  51. return handleException(request, response, ex);
  52. }
  53. }
  • 下面再对bind参数绑定过程进行简单的介绍
  1. protected void bind(HttpServletRequest request, Object command) throws Exception {
  2. logger.debug("Binding request parameters onto MultiActionController command");
  3. //创建请求数据绑定器
  4. ServletRequestDataBinder binder = createBinder(request, command);
  5. //进行数据绑定
  6. binder.bind(request);
  7. //validators进行校验
  8. if (this.validators != null) {
  9. for (Validator validator : this.validators) {
  10. if (validator.supports(command.getClass())) {
  11. ValidationUtils.invokeValidator(validator, command, binder.getBindingResult());
  12. }
  13. }
  14. }
  15. //对数据校验过程发生的异常进行处理
  16. binder.closeNoCatch();
  17. }

数据绑定过程简单理解就是按照请求参数名和对象属性名进行匹配,如果匹配成功,就设置进去。

validators校验这里暂时不展开,后面会讲。

  • massageReturnValueIfNecessary对返回值进行处理
  • 如果返回ModelAndView,那么直接返回即可
  • 如果返回map,则将map作为附加属性返回,视图名会通过RequestToViewNameTranslator 选择默认的视图名
  • 如果返回String,则作为视图名
  • 如果返回null,则表示具体的渲染逻辑在controller内部完成
  1. private ModelAndView massageReturnValueIfNecessary(Object returnValue) {
  2. if (returnValue instanceof ModelAndView) {
  3. return (ModelAndView) returnValue;
  4. }
  5. else if (returnValue instanceof Map) {
  6. return new ModelAndView().addAllObjects((Map) returnValue);
  7. }
  8. else if (returnValue instanceof String) {
  9. return new ModelAndView((String) returnValue);
  10. }
  11. else {
  12. // Either returned null or was 'void' return.
  13. // We'll assume that the handle method already wrote the response.
  14. return null;
  15. }
  16. }
  • 如果代理对象目标方法调用过程中出现异常了呢?
  1. private ModelAndView handleException(HttpServletRequest request, HttpServletResponse response, Throwable ex)
  2. throws Exception {
  3. //根据异常类型,去exceptionHandlerMap中取出对应处理该异常的方法
  4. Method handler = getExceptionHandler(ex);
  5. //如果不为空
  6. if (handler != null) {
  7. if (logger.isDebugEnabled()) {
  8. logger.debug("Invoking exception handler [" + handler + "] for exception: " + ex);
  9. }
  10. try {
  11. //则调用对应异常兜底方法,然后处理其返回值
  12. Object returnValue = handler.invoke(this.delegate, request, response, ex);
  13. return massageReturnValueIfNecessary(returnValue);
  14. }
  15. catch (InvocationTargetException ex2) {
  16. logger.error("Original exception overridden by exception handling failure", ex);
  17. ReflectionUtils.rethrowException(ex2.getTargetException());
  18. }
  19. catch (Exception ex2) {
  20. logger.error("Failed to invoke exception handler method", ex2);
  21. }
  22. }
  23. else {
  24. //如果代理类目标方法执行过程抛出了异常,但是我们没有指定相关异常兜底方法,则抛出该异常
  25. // If we get here, there was no custom handler or we couldn't invoke it.
  26. ReflectionUtils.rethrowException(ex);
  27. }
  28. throw new IllegalStateException("Should never get here");
  29. }

getExceptionHandler: 根据异常类型去exceptionHandlerMap获取对应的兜底方法

  1. protected Method getExceptionHandler(Throwable exception) {
  2. Class exceptionClass = exception.getClass();
  3. if (logger.isDebugEnabled()) {
  4. logger.debug("Trying to find handler for exception class [" + exceptionClass.getName() + "]");
  5. }
  6. Method handler = this.exceptionHandlerMap.get(exceptionClass);
  7. //如果当前异常类型没匹配上,那么就调用抛出异常的父类型不断去匹配
  8. while (handler == null && !exceptionClass.equals(Throwable.class)) {
  9. if (logger.isDebugEnabled()) {
  10. logger.debug("Trying to find handler for exception superclass [" + exceptionClass.getName() + "]");
  11. }
  12. exceptionClass = exceptionClass.getSuperclass();
  13. handler = this.exceptionHandlerMap.get(exceptionClass);
  14. }
  15. return handler;
  16. }

小结

本节主要对HandlerMapping和Controller相关类进行了简单介绍,下节,我们将继续探寻controller的更多细节实现,和整个DispathcerServlet执行流程

相关文章