SpringMVC源码解析 - HandlerMethod

简介: HandlerMethod及子类主要用于封装方法调用相关信息,子类还提供调用,参数准备和返回值处理的职责. 分析下各个类的职责吧(顺便做分析目录): HandlerMethod 封装方法定义相关的信息,如类,方法,参数等.

HandlerMethod及子类主要用于封装方法调用相关信息,子类还提供调用,参数准备和返回值处理的职责.

分析下各个类的职责吧(顺便做分析目录):

HandlerMethod 封装方法定义相关的信息,如类,方法,参数等.

  使用场景:HandlerMapping时会使用

InvocableHandlerMethod 添加参数准备,方法调用功能

  使用场景:执行使用@ModelAttribute注解会使用

ServletInvocableHandlerMethod 添加返回值处理职责,ResponseStatus处理

  使用场景:执行http相关方法会使用,比如调用处理执行

 

1. HandlerMethod

HandlerMethod其实可以简单理解为保持方法信息的pojo.

所以这边主要就是看下定义的属性:

复制代码
 1 package org.springframework.web.method;
 2 public class HandlerMethod {
 3     /** 什么鬼,给子类提供logger,到现在为止源码中不多见 */
 4     protected final Log logger = LogFactory.getLog(HandlerMethod.class);
 5     // 方法所在的类,如果是String类型,可以去容器中获取
 6     private final Object bean;
 7     // 方法
 8     private final Method method;
 9     // 类管理的容器
10     private final BeanFactory beanFactory;
11     // 方法的参数
12     private final MethodParameter[] parameters;
13     // 如果方法是bridged方法,则对应原始方法
14     private final Method bridgedMethod;
15     // ...
16 }
复制代码


大部分应该是看看注释就能理解了,我们解释下下面:

  这边所有的熟悉都是final类型的,不可修改,所以如果出现修改需要new.

  如果bean是string,是在createWithResolvedBean找容器获取实例的.

  MethodParameter类封装了参数相关的信息.

  提供获取返回值,判断是否void类型,还有读取注解

 

createWithResolvedBean逻辑其实很简单:

确认下如果bean是String类型的,那么从容器BeanFactory中获取,并使用private HandlerMethod(HandlerMethod handlerMethod, Object handler) new一个新的.

复制代码
1 // HandlerMethod
2     public HandlerMethod createWithResolvedBean() {
3         Object handler = this.bean;
4         if (this.bean instanceof String) {
5             String beanName = (String) this.bean;
6             handler = this.beanFactory.getBean(beanName);
7         }
8         return new HandlerMethod(this, handler);
9     }
复制代码

 

MethodParameter,可以根据method方法和parameterIndex(参数下标)唯一确定,其他属性都可以根据他们两获取.

由于反射中没有参数名的信息,而这边需要,所以Spring添加了一个参数名查找器ParameterNameDiscover.

这边返回值的类型是存储在parameters属性中的,下标用-1区分.

MethodParameter在HandlerMethod有两个内部类的子类.

复制代码
 1 package org.springframework.core;
 2 public class MethodParameter {
 3     // 参数所在方法
 4     private final Method method;
 5     // 参数的构造方法
 6     private final Constructor constructor;
 7     // 参数下标
 8     private final int parameterIndex;
 9     // 参数类型
10     private Class<?> parameterType;
11     // Type类型的参数类型
12     private Type genericParameterType;
13     // 参数使用的注解
14     private Annotation[] parameterAnnotations;
15     // 参数名查找器
16     private ParameterNameDiscoverer parameterNameDiscoverer;
17     // 参数名
18     private String parameterName;
19     // 参数嵌套级别,如Map<String>中map为1,string为2
20     private int nestingLevel = 1;
21     // 每层的下标
22     /** Map from Integer level to Integer type index */
23     Map<Integer, Integer> typeIndexesPerLevel;
24     // 什么鬼?就一个构造方法用了,其他都没有使用
25     Map<TypeVariable, Type> typeVariableMap;
26     // ... 
27 }
复制代码

 

 

2. InvocableHandlerMethod

习惯性的,看到Invocable,会想到Spring会不会定义一个接口来定义可调用概念,不过没有.

这边添加了2个职责:参数准备和方法执行.

参数准备委托HandlerMethodArgumentResolver进行具体的解析.解析的时候需要用到WebDataBinder,所以顺便带上.对参数解析器有兴趣可以移步这里

 

2.1 先来看看参数准备工作部分吧.

查找某个参数值的逻辑:

  a, 先委托参数名查找器获取参数名

  b,从外部提供的参数清单中查找值(竟然是根据类型判断的)

  c,如果没有直接提供,使用参数解析器创建

  d,如果还是没有获得,直接报错

复制代码
 1 package org.springframework.web.method.support;
 2 
 3 public class InvocableHandlerMethod extends HandlerMethod {
 4     // 参数解析器
 5     private HandlerMethodArgumentResolverComposite argumentResolvers = new HandlerMethodArgumentResolverComposite();
 6     // 参数解析器需要用到
 7     private WebDataBinderFactory dataBinderFactory;
 8     // 参数名查找器
 9     private ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
10 
11     private Object[] getMethodArgumentValues(
12             NativeWebRequest request, ModelAndViewContainer mavContainer,
13             Object... providedArgs) throws Exception {
14 
15         MethodParameter[] parameters = getMethodParameters();
16         Object[] args = new Object[parameters.length];
17         for (int i = 0; i < parameters.length; i++) {
18             MethodParameter parameter = parameters[i];
19             parameter.initParameterNameDiscovery(parameterNameDiscoverer);
20             // 查找参数名
21             GenericTypeResolver.resolveParameterType(parameter, getBean().getClass());
22             // 从提供的参数值providedArgs中找值
23             args[i] = resolveProvidedArgument(parameter, providedArgs);
24             if (args[i] != null) {
25                 continue;
26             }
27             // 使用参数解析器解析
28             if (argumentResolvers.supportsParameter(parameter)) {
29                 try {
30                     args[i] = argumentResolvers.resolveArgument(parameter, mavContainer, request, dataBinderFactory);
31                     continue;
32                 } catch (Exception ex) {
33                     throw ex;
34                 }
35             }
36             // 参数获取不到是需要报错的
37             if (args[i] == null) {
38                 String msg = getArgumentResolutionErrorMessage("No suitable resolver for argument", i);
39                 throw new IllegalStateException(msg);
40             }
41         }
42         return args;
43     }
44 
45     private Object resolveProvidedArgument(MethodParameter parameter, Object... providedArgs) {
46         if (providedArgs == null) {
47             return null;
48         }
49         for (Object providedArg : providedArgs) {
50             // 竟然是根据类型判断的
51             if (parameter.getParameterType().isInstance(providedArg)) {
52                 return providedArg;
53             }
54         }
55         return null;
56     }
57 
58     // ...
59 
60 }
复制代码

2.2 方法执行

这边的逻辑其实很简单:

  委托获取方法执行需要的参数

  强制将方法变为可用

  处理方法执行过程中的异常

 

复制代码
 1 package org.springframework.web.method.support;
 2 
 3 public class InvocableHandlerMethod extends HandlerMethod {
 4     // ... 
 5     public final Object invokeForRequest(NativeWebRequest request,
 6                                          ModelAndViewContainer mavContainer,
 7                                          Object... providedArgs) throws Exception {
 8         Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
 9         Object returnValue = invoke(args);
10         return returnValue;
11     }
12 
13     private Object invoke(Object... args) throws Exception {
14         ReflectionUtils.makeAccessible(this.getBridgedMethod());
15         try {
16             return getBridgedMethod().invoke(getBean(), args);
17         }
18         catch (IllegalArgumentException e) {
19             String msg = getInvocationErrorMessage(e.getMessage(), args);
20             throw new IllegalArgumentException(msg, e);
21         }
22         catch (InvocationTargetException e) {
23             // Unwrap for HandlerExceptionResolvers ...
24             Throwable targetException = e.getTargetException();
25             if (targetException instanceof RuntimeException) {
26                 throw (RuntimeException) targetException;
27             }
28             else if (targetException instanceof Error) {
29                 throw (Error) targetException;
30             }
31             else if (targetException instanceof Exception) {
32                 throw (Exception) targetException;
33             }
34             else {
35                 String msg = getInvocationErrorMessage("Failed to invoke controller method", args);
36                 throw new IllegalStateException(msg, targetException);
37             }
38         }
39     }
40 
41 }
复制代码

 

 

3. ServletInvocableHandlerMethod

  委托HandlerMethodReturnValueHandler添加返回值处理功能

  添加@ResponseStatus注解支持.

 

这边使用的@ResponseStatus注解两个属性:value状态码,reason 写入response的说明文字

 

3.1 设置response status时的逻辑:

  responseStatus没设置就返回

  responseReason存在则进入error

  把responseStatus设置到request,RedirectView需要使用 

复制代码
 1 // ServletInvocableHandlerMethod
 2     private void setResponseStatus(ServletWebRequest webRequest) throws IOException {
 3         if (this.responseStatus == null) {
 4             return;
 5         }
 6 
 7         if (StringUtils.hasText(this.responseReason)) {
 8             webRequest.getResponse().sendError(this.responseStatus.value(), this.responseReason);
 9         }
10         else {
11             webRequest.getResponse().setStatus(this.responseStatus.value());
12         }
13 
14         // to be picked up by the RedirectView
15         webRequest.getRequest().setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, this.responseStatus);
16     }
复制代码

3.2 invokeAndHandle

  委托父类执行请求

  添加ResponseStatus支持

  然后判断怎么样才是执行完毕,满足一下任意一个:

    request的notModified为真,使用@ResponseStatus注解,mavContainer的requestHandled为真

  委托HandlerMethodReturnValueHandler封装返回值

复制代码
 1 // ServletInvocableHandlerMethod
 2     public final void invokeAndHandle(ServletWebRequest webRequest,
 3             ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
 4 
 5         Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
 6 
 7         setResponseStatus(webRequest);
 8 
 9         if (returnValue == null) {
10             if (isRequestNotModified(webRequest) || hasResponseStatus() || mavContainer.isRequestHandled()) {
11                 mavContainer.setRequestHandled(true);
12                 return;
13             }
14         }
15         else if (StringUtils.hasText(this.responseReason)) {
16             mavContainer.setRequestHandled(true);
17             return;
18         }
19 
20         mavContainer.setRequestHandled(false);
21 
22         try {
23             this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
24         }
25         catch (Exception ex) {
26             if (logger.isTraceEnabled()) {
27                 logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex);
28             }
29             throw ex;
30         }
31     }
复制代码

 http://www.cnblogs.com/leftthen/p/5229204.html

 

相关文章
|
10月前
|
存储 Java 文件存储
微服务——SpringBoot使用归纳——Spring Boot使用slf4j进行日志记录—— logback.xml 配置文件解析
本文解析了 `logback.xml` 配置文件的详细内容,包括日志输出格式、存储路径、控制台输出及日志级别等关键配置。通过定义 `LOG_PATTERN` 和 `FILE_PATH`,设置日志格式与存储路径;利用 `&lt;appender&gt;` 节点配置控制台和文件输出,支持日志滚动策略(如文件大小限制和保存时长);最后通过 `&lt;logger&gt;` 和 `&lt;root&gt;` 定义日志级别与输出方式。此配置适用于精细化管理日志输出,满足不同场景需求。
2301 1
|
10月前
|
算法 测试技术 C语言
深入理解HTTP/2:nghttp2库源码解析及客户端实现示例
通过解析nghttp2库的源码和实现一个简单的HTTP/2客户端示例,本文详细介绍了HTTP/2的关键特性和nghttp2的核心实现。了解这些内容可以帮助开发者更好地理解HTTP/2协议,提高Web应用的性能和用户体验。对于实际开发中的应用,可以根据需要进一步优化和扩展代码,以满足具体需求。
966 29
|
10月前
|
前端开发 数据安全/隐私保护 CDN
二次元聚合短视频解析去水印系统源码
二次元聚合短视频解析去水印系统源码
427 4
|
10月前
|
JavaScript 算法 前端开发
JS数组操作方法全景图,全网最全构建完整知识网络!js数组操作方法全集(实现筛选转换、随机排序洗牌算法、复杂数据处理统计等情景详解,附大量源码和易错点解析)
这些方法提供了对数组的全面操作,包括搜索、遍历、转换和聚合等。通过分为原地操作方法、非原地操作方法和其他方法便于您理解和记忆,并熟悉他们各自的使用方法与使用范围。详细的案例与进阶使用,方便您理解数组操作的底层原理。链式调用的几个案例,让您玩转数组操作。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
10月前
|
存储 前端开发 JavaScript
在线教育网课系统源码开发指南:功能设计与技术实现深度解析
在线教育网课系统是近年来发展迅猛的教育形式的核心载体,具备用户管理、课程管理、教学互动、学习评估等功能。本文从功能和技术两方面解析其源码开发,涵盖前端(HTML5、CSS3、JavaScript等)、后端(Java、Python等)、流媒体及云计算技术,并强调安全性、稳定性和用户体验的重要性。
|
10月前
|
负载均衡 JavaScript 前端开发
分片上传技术全解析:原理、优势与应用(含简单实现源码)
分片上传通过将大文件分割成多个小的片段或块,然后并行或顺序地上传这些片段,从而提高上传效率和可靠性,特别适用于大文件的上传场景,尤其是在网络环境不佳时,分片上传能有效提高上传体验。 博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
设计模式 前端开发 JavaScript
Spring MVC(一)【什么是Spring MVC】
Spring MVC(一)【什么是Spring MVC】
|
前端开发 Java 关系型数据库
基于ssm框架旅游网旅游社交平台前后台管理系统(spring+springmvc+mybatis+maven+tomcat+html)
基于ssm框架旅游网旅游社交平台前后台管理系统(spring+springmvc+mybatis+maven+tomcat+html)
252 0
|
前端开发 Java Go
Spring MVC 和 Spring Boot 的区别
Spring MVC 和 Spring Boot 的区别
504 0
|
Java Spring
springmvc中spring提供的中文乱码解决问题
可以解决浏览器的乱码问题
181 0

推荐镜像

更多
  • DNS