SpringMVC源码阅读:Controller中参数解析

1.前言

SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧

本文将通过源码(基于Spring4.3.7)分析,弄清楚Controller是如何匹配我们传入的参数,并定义简单的参数解析器

2.源码分析

demo源码在这里,回到DispatcherServlet的doDispatch方法,DispatchServlet分析见SpringMVC源码阅读:核心分发器DispatcherServlet

SpringMVC源码阅读:Controller中参数解析

doDispatch方法943行获取了HandlerAdapter,ctrl+h打开类继承图,找到RequestMappingHandlerAdapter,RequestMappingHandlerAdapter支持HandlerMethod的方法参数和返回类型,HandlerMethod是3.1版本引入的,为参数、返回值和注解提供便捷的封装

SpringMVC源码阅读:Controller中参数解析

在RequestMappingHandlerAdapter的invokeHandlerMethod方法中,设置ArgumentResolver和ReturnValueHandler

SpringMVC源码阅读:Controller中参数解析

798行和799行给RequestMappingHandlerAdapter定义的ArgumentResolver和ReturnValueHandler赋值,4.2版本以前在createRequstMapping方法,此方法在4.2已被删除

SpringMVC源码阅读:Controller中参数解析

827行ServletInvocableHandlerMethod调用invokeAndHandle方法,通过定义的HandlerMethodReturnValueHandler处理返回值,点开invokeAndHandle方法进入ServletInvocableHandlerMethod类

SpringMVC源码阅读:Controller中参数解析

116行处理通过HandlerMethodArgumentResolver来解析参数,132~133行使用注册过的HandlerMethodReturnValueHandler

SpringMVC源码阅读:Controller中参数解析

afterPropertiesSet方法实现了InitializingBean接口初始化了Handler和Resolver,简单地说,启动服务才会运行afterPropertiesSet

517行设置ArgumentResolver,525行设置ReturnValueHandler

点看getDefaultArgumentResolvers方法,看看它到底做了什么

SpringMVC源码阅读:Controller中参数解析

getDefaultArgumentResolvers方法把各种HandlerMethodArgumentResolver放入List并返回

SpringMVC源码阅读:Controller中参数解析

同理,getDefaultReturnValueHandlers方法把各种HandlerMethodReturnValueHandler放入List并返回

现在在回到ServletInvocableHandlerMethod类,我们发现了returnValueHandlers是HandlerMethodReturnValueHandlerComposite类型的,神秘的HandlerMethodReturnValueHandlerComposite是什么?

查看类实现图,我们发现HandlerMethodReturnValueHandlerComposite继承HandlerMethodReturnValueHandler

SpringMVC源码阅读:Controller中参数解析

我们可以看到,HandlerMethodReturnValueHandlerComposite类里有HandlerMethodReturnValueHandler类型的list,做过树结构的园友们应该知道,这里用到了组合模式,即类包含自身对象组。但是呢,它的类名后面加上了Composite,不能严格意义上说是组合模式,可以说是组合模式的变种,因为HandlerMethodReturnValueHandler是个Interface,所以不是严格意义上的“组合模式”

我们用同样的思路寻找与HandlerMethodArgumentResolver对应的Composite类,我在ServletInvocableHandlerMethod没有找到HandlerMethodArgumentResolverComposite,(在4.3版本之前可以在ServletInvocableHandlerMethod找到),不用担心,使用绝招

快捷键ctrl+shift+r,用idea强大的全局搜索来找HandlerMethodArgumentResolverComposite的踪迹

这里注意一下,全局搜索选择Scope,才可以在文件所有路径下搜索(包括Maven源码包)

SpringMVC源码阅读:Controller中参数解析

最后我们看到了HandlerMethodArgumentResolverComposite在InvocableHandlerMethod出现,这个类名觉得有些眼熟吧,它是ServletInvocableHandlerMethod的父类

ServletInvocableHandlerMethod调用InvocableHandlerMethod的invokeForRequest方法中使用了HandlerMethodArgumentResolverComposite

打开HandlerMethodArgumentResolverComposite,和HandlerMethodReturnValueHandlerComposite类似,使用组合模式的变种

SpringMVC源码阅读:Controller中参数解析

好了,参数解析基本流程完毕,我们现在来具体看看支持和参数相映射的注解的参数解析类,对着HandlerMethodArgumentResolver按ctrl+h

SpringMVC源码阅读:Controller中参数解析

可以看到庞大的类继承图,我们看支持@RequestBody的RequestResponseBodyMethodProcessor类

可能会有园友好奇,为什么我知道RequestResponseBodyMethodProcessor类支持@RequestBody?

一个简便的方法是直接看类名,开源项目Spring的代码质量非常高,它们的类名言简意赅,看类名大概就知道它是做什么的;类名如果看不出来,就点进去看注释,注释很规范、详细

打开RequestResponseBodyMethodProcessor类

SpringMVC源码阅读:Controller中参数解析

支持带有@RequestBody的参数,支持带有@ResponseBody的返回值

写一个方法进行测试

   @RequestMapping(value = "/testRb",produces={"application/json; charset=UTF-8"},method = RequestMethod.POST)
@ResponseBody
public Employee testRb(@RequestBody Employee e) {
return e;
}

http://localhost:8080/springmvcdemo/test/testRb,传入参数为{"age":1,"id":2},我用的Postman测试请求,直接浏览器地址栏输入,默认Get请求会报错,不嫌麻烦可以自己手写Ajax,参数类型设置成Json测试

SpringMVC源码阅读:Controller中参数解析

header写成application/json,请求类型写POST

SpringMVC源码阅读:Controller中参数解析

Body传入Json格式参数

现在我们进入resolveArgument方法

SpringMVC源码阅读:Controller中参数解析

127行获取参数信息,128行调用readWithMessageConverters方法获取参数值

131行创建WebDataBinder,用于校验数据格式是否正确

点开128行readWithMessageConverters方法,看看它做什么

SpringMVC源码阅读:Controller中参数解析

148行获取请求信息,如头信息

SpringMVC源码阅读:Controller中参数解析

我们看到Content-Type正是我们在Postman中设置的"application/json"

150行获取参数,调用父类AbstractMessageConverterMethodArgumentResolver的readWithMessageConverters方法,父类方法用于从请求信息中读取方法参数值

152行查看参数注解是否是@RequestBody

继续深入,进入AbstractMessageConverterMethodArgumentResolver的readWithMessageConverters方法

SpringMVC源码阅读:Controller中参数解析

167行从Headers取得Content-Type

172~175行如果Content-Type为空,默认给我们Content-Type设置"application/octet-stream"

185行获取Http请求方法

191行用消息转换器读取请求体

接下来,我们分析下常用的@RequestParam注解是如何处理参数的

用这个测试方法

    @RequestMapping("/auth")
public String auth(@RequestParam String username, HttpServletRequest req) {
req.getSession().setAttribute("loginUser", username);
return "redirect:/";
}

找到RequestParamMethodArgumentResolver类,该类的核心方法是resolveName方法

SpringMVC源码阅读:Controller中参数解析

158行获取请求信息

159行获取MultipartHttpServletRequest请求信息,用于文件上传

175行获取参数值

打断点我发现,在RequestParamMethodArgumentResolver的父类AbstractNamedValueMethodArgumentResolver中,resolveArgument方法会先执行。后续我们自定义的参数解析器主要就是重写resolveArgument方法

SpringMVC源码阅读:Controller中参数解析

97行获取参数名称

103行调用resolveName方法获取参数值,该方法被AbstractNamedValueMethodArgumentResolver子类RequestParamMethodArgumentResolver实现,刚才我们已经分析过

我再说下其他常用的HandlerArgumentResolver实现类,就不源码分析了,有时间我会补充上,园友可以自行打断掉调试查看之

1.PathVariableMethodArgumentResolver

支持带有@PathVariable注解的参数,用来获得请求url中的动态参数

2.MatrixVariableMethodArgumentResolver

支持带有@MatrixVariable注解的参数,顾名思义,矩阵变量,多个变量可以使用“;”分隔

3.RequestParamMethodArgumentResolver

支持带有@RequestParam注解的参数,也支持MultipartFile类型的参数,本文已分析

4.RequestResponseBodyMethodProcessor

支持带有@RequestBody、@ResponseBody注解的参数,本文已分析

再看看常用的HandlerMethodReturnValueHandler

1.ModelAndViewMethodReturnValueHandler

返回ModelAndView,把view和model信息赋值给ModelAndViewContainer

2.ViewMethodReturnValueHandler

返回View

3.HttpHeadersReturnValueHandler

返回HttpHeaders

4.SteamingResponseBodyReturnValueHandler

返回ResponseEntity<StreamingResponseBody>

3.实例

    @RequestMapping(value = "/testRb",produces={"application/json; charset=UTF-8"},method = RequestMethod.POST)
@ResponseBody
public Employee testRb(@RequestBody Employee e) {
return e;
} @RequestMapping(value="/testCustomObj", produces={"application/xml; charset=UTF-8"},method = RequestMethod.GET)
@ResponseBody
public XmlActionResult<Employee> testCustomObj(@RequestParam(value = "id") int id,
@RequestParam(value = "name") String name) {
XmlActionResult<Employee> actionResult = new XmlActionResult<Employee>();
Employee e = new Employee();
e.setId(id);
e.setName(name);
e.setAge(20);
e.setDept(new Dept(2,"部门"));
actionResult.setCode("200");
actionResult.setMessage("Success with XML");
actionResult.setData(e);
return actionResult;
} @RequestMapping(value = "/testCustomObjWithRp", produces={"application/json; charset=UTF-8"})
@ResponseBody
public Employee testCustomObjWithRp(Employee e) {
return e;
} @RequestMapping(value = "/testDate", produces={"application/json; charset=UTF-8"})
@ResponseBody
public Date testDate(Date date) {
return date;
}

3.1 测试@RequestBody

在Postman中输入请求http://localhost:8080/springmvcdemo/test/testRb

SpringMVC源码阅读:Controller中参数解析

SpringMVC源码阅读:Controller中参数解析

发出请求,进入了RequestResponseBody的resolveArgument方法,参数我们可以看到

SpringMVC源码阅读:Controller中参数解析

源码分析参照   2.源码分析

3.2 测试@RequestParam

在浏览器中输入http://localhost:8080/springmvcdemo/test/testCustomObjWithRp?id=1&name=s

返回结果如下,返回的是XML格式,(下一部分我再叙述MessageConverter部分的知识,我们这里只关注@RequestParam)

SpringMVC源码阅读:Controller中参数解析

输入请求后,进入了RequestParamMethodArgumentResolver的父类AbstracNamedValueMethodArgumentResolver的reloveArgument方法,因我们有两个@RequestParam,会进入reloveArgument两次

SpringMVC源码阅读:Controller中参数解析

3.3 测试无注解参数为自定义对象

浏览器输入请求http://localhost:8080/springmvcdemo/test/testCustomObjWithRp?id=1&name=s,返回结果如下

SpringMVC源码阅读:Controller中参数解析

无注解我们怎么找到底是哪个HandlerMethodArgumentResolver实现类在处理呢?只要你认真看了第二部分源码分析,相信你可以轻松找到

在HandlerMethodArgumentResolverComposite(HandlerMethodArgumentResolver的实现类)第117行getArgumentResolver方法打上断点,看看庐山真面目

SpringMVC源码阅读:Controller中参数解析

原来,是ServletModelAttributeMethodProcessor为我们处理了自定义对象

3.4 测试参数为简单对象

在浏览器输入请求http://localhost:8080/springmvcdemo/test/testDate?date=2018-01-30

在当前Controller加入InitBinder,使参数规范化传递

    //自定义属性编辑器——日期
@InitBinder
public void initBinderDate(WebDataBinder binder) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
}

返回了一个Unix时间戳

SpringMVC源码阅读:Controller中参数解析

在HandlerMethodArgumentResolverComposite的resolveArugument方法打断点,发现被RequestParamMethodArgumentResovler所解析

SpringMVC源码阅读:Controller中参数解析

在AbstracNamedValueMethodArgumentResolver的reloveArgument方法找到了我们的参数,方法同测试3.2

SpringMVC源码阅读:Controller中参数解析

4.自定义参数解析器

自定义参数注解TestObj

@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface TestObj {
//参数别名
String value() default "";
}

自定义参数解析器TestObjArgumentResolver实现HandlerMethodArgumentResolver,解决两个自定义类参数传参的问题

public class TestObjArgumentResolver implements HandlerMethodArgumentResolver {

    @Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(TestObj.class);
} @Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
TestObj testObj = parameter.getParameterAnnotation(TestObj.class); String alias = getAlias(testObj, parameter); //拿到obj, 先从ModelAndViewContainer中拿,若没有则new1个参数类型的实例
Object obj = (mavContainer.containsAttribute(alias)) ?
mavContainer.getModel().get(alias) : createAttribute(parameter); //获得WebDataBinder,这里的具体WebDataBinder是ExtendedServletRequestDataBinder
WebDataBinder binder = binderFactory.createBinder(webRequest, obj, alias); Object target = binder.getTarget(); if(target != null) {
//绑定参数
bindParameters(webRequest, binder, alias);
//JSR303 验证
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors()) {
if (isBindExceptionRequired(binder, parameter)) {
throw new BindException(binder.getBindingResult());
}
}
}
return target;
} private Object createAttribute(MethodParameter parameter) {
return BeanUtils.instantiateClass(parameter.getParameterType());
}
//绑定参数
private void bindParameters(NativeWebRequest request, WebDataBinder binder, String alias) {
ServletRequest servletRequest = request.getNativeRequest(ServletRequest.class); MockHttpServletRequest newRequest = new MockHttpServletRequest(); Enumeration<String> enu = servletRequest.getParameterNames();
while(enu.hasMoreElements()) {
String paramName = enu.nextElement();
if(paramName.startsWith(alias)) {
newRequest.setParameter(paramName.substring(alias.length()+1), request.getParameter(paramName));
}
}
((ExtendedServletRequestDataBinder)binder).bind(newRequest);
} protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {
Annotation[] annotations = parameter.getParameterAnnotations();
for (Annotation annot : annotations) {
if (annot.annotationType().getSimpleName().startsWith("Valid")) {
Object hints = AnnotationUtils.getValue(annot);
binder.validate(hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
break;
}
}
} protected boolean isBindExceptionRequired(WebDataBinder binder, MethodParameter parameter) {
int i = parameter.getParameterIndex();
Class<?>[] paramTypes = parameter.getMethod().getParameterTypes();
boolean hasBindingResult = (paramTypes.length > (i + 1) && Errors.class.isAssignableFrom(paramTypes[i + 1])); return !hasBindingResult;
} //生成别名
private String getAlias(TestObj testObj, MethodParameter parameter) {
//得到TestObj的属性value,也就是对象参数的简称
String alias = testObj.value();
if(alias == null || StringUtils.isBlank(alias)) {
//如果简称为空,取对象简称的首字母小写开头
String simpleName = parameter.getParameterType().getSimpleName();
alias = simpleName.substring(0, 1).toLowerCase() + simpleName.substring(1);
}
return alias;
} }

dispatcher-servlet.xml加入我们自定义的参数解析器

<property name="customArgumentResolvers">
<list>
<bean class="org.format.demo.custom.TestObjArgumentResolver" />
</list>
</property>

测试Controller

@Controller
@RequestMapping(value = "/foc")
public class TestObjController {
@RequestMapping("/test1")
@ResponseBody
public Map test1(@TestObj Dept dept, @TestObj Employee emp) {
Map resultMap = new HashMap();
resultMap.put("Dept",dept);
resultMap.put("Emp",emp);
return resultMap;
} @RequestMapping("/test2")
@ResponseBody
public Map test2(@TestObj("d") Dept dept, @TestObj("e") Employee emp) {
Map resultMap = new HashMap();
resultMap.put("d",dept);
resultMap.put("e",emp);
return resultMap;
} }

浏览器输入http://localhost:8080/springmvcdemo/foc/test1?dept.id=1&dept.name=sss&employee.id=3&employee.name=ddf&employee.age=12

TestObjArgumentResolver中getAlias方法获取别名

返回结果如下

SpringMVC源码阅读:Controller中参数解析

浏览器输入http://localhost:8080/springmvcdemo/foc/test2?d.id=1&d.name=sss&e.id=3&e.name=ddf&e.age=12

参数别名用我们自定义的d和e

SpringMVC源码阅读:Controller中参数解析

5.总结:

两大接口:HandlerMethodArgumentResolver,HandlerMethodReturnValueHandler

ServletInvocableHandlerMethod调用invokeAndHandle方法,使用HandlerMethodReturnValueComposite,使用组合模式,放入HandlerMethodReturnValueHandler的list

同理HandlerMethodArgumentResolverComposite使用组合模式,放入HandlerMethodArgumentResolver的list

在RequestMappingHandlerAdapter中invokeHandlerMethod给ArgumentResolvers和ReturnValueHandlers赋值(4.2以前在createRequstMapping方法,此方法已删除)

afterPropertiesSet方法注入ArgumentResolvers和ReturnValueHandlers到Spring容器

getDefaultArgumentResolvers设置默认的ArgumentResolvers

getDefaultReturnValueHandlers设置默认的ReturnValueHandlers

RequestResponseBodyMethodProcessor负责解析Controller里@RequestBody,支持响应类型是@ResponseBody

RequestParamMethodArgumentResolver负责解析Controller里@RequestParam

无注解情况如果是简单对象(如Date,Integer,Doubule等),由RequestParamMethodArgumentResovler处理,复杂对象(如自定义类)由ServletModelAttributeMethodProcessor处理

resolveArgument解析参数类型和值

6.参考

文章难免有不足之处,欢迎指正

https://docs.spring.io/spring/docs/4.3.7.RELEASE/spring-framework-reference/htmlsingle/#mvc-ann-responsebody

上一篇:【源码阅读】Java集合之一 - ArrayList源码深度解读


下一篇:关于GP的理解