1.前言
SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧
本文将通过源码(基于Spring4.3.7)分析,弄清楚Controller是如何匹配我们传入的参数,并定义简单的参数解析器
2.源码分析
demo源码在这里,回到DispatcherServlet的doDispatch方法,DispatchServlet分析见SpringMVC源码阅读:核心分发器DispatcherServlet
doDispatch方法943行获取了HandlerAdapter,ctrl+h打开类继承图,找到RequestMappingHandlerAdapter,RequestMappingHandlerAdapter支持HandlerMethod的方法参数和返回类型,HandlerMethod是3.1版本引入的,为参数、返回值和注解提供便捷的封装
在RequestMappingHandlerAdapter的invokeHandlerMethod方法中,设置ArgumentResolver和ReturnValueHandler
798行和799行给RequestMappingHandlerAdapter定义的ArgumentResolver和ReturnValueHandler赋值,4.2版本以前在createRequstMapping方法,此方法在4.2已被删除
827行ServletInvocableHandlerMethod调用invokeAndHandle方法,通过定义的HandlerMethodReturnValueHandler处理返回值,点开invokeAndHandle方法进入ServletInvocableHandlerMethod类
116行处理通过HandlerMethodArgumentResolver来解析参数,132~133行使用注册过的HandlerMethodReturnValueHandler
afterPropertiesSet方法实现了InitializingBean接口初始化了Handler和Resolver,简单地说,启动服务才会运行afterPropertiesSet
517行设置ArgumentResolver,525行设置ReturnValueHandler
点看getDefaultArgumentResolvers方法,看看它到底做了什么
getDefaultArgumentResolvers方法把各种HandlerMethodArgumentResolver放入List并返回
同理,getDefaultReturnValueHandlers方法把各种HandlerMethodReturnValueHandler放入List并返回
现在在回到ServletInvocableHandlerMethod类,我们发现了returnValueHandlers是HandlerMethodReturnValueHandlerComposite类型的,神秘的HandlerMethodReturnValueHandlerComposite是什么?
查看类实现图,我们发现HandlerMethodReturnValueHandlerComposite继承HandlerMethodReturnValueHandler
我们可以看到,HandlerMethodReturnValueHandlerComposite类里有HandlerMethodReturnValueHandler类型的list,做过树结构的园友们应该知道,这里用到了组合模式,即类包含自身对象组。但是呢,它的类名后面加上了Composite,不能严格意义上说是组合模式,可以说是组合模式的变种,因为HandlerMethodReturnValueHandler是个Interface,所以不是严格意义上的“组合模式”
我们用同样的思路寻找与HandlerMethodArgumentResolver对应的Composite类,我在ServletInvocableHandlerMethod没有找到HandlerMethodArgumentResolverComposite,(在4.3版本之前可以在ServletInvocableHandlerMethod找到),不用担心,使用绝招
快捷键ctrl+shift+r,用idea强大的全局搜索来找HandlerMethodArgumentResolverComposite的踪迹
这里注意一下,全局搜索选择Scope,才可以在文件所有路径下搜索(包括Maven源码包)
最后我们看到了HandlerMethodArgumentResolverComposite在InvocableHandlerMethod出现,这个类名觉得有些眼熟吧,它是ServletInvocableHandlerMethod的父类
ServletInvocableHandlerMethod调用InvocableHandlerMethod的invokeForRequest方法中使用了HandlerMethodArgumentResolverComposite
打开HandlerMethodArgumentResolverComposite,和HandlerMethodReturnValueHandlerComposite类似,使用组合模式的变种
好了,参数解析基本流程完毕,我们现在来具体看看支持和参数相映射的注解的参数解析类,对着HandlerMethodArgumentResolver按ctrl+h
可以看到庞大的类继承图,我们看支持@RequestBody的RequestResponseBodyMethodProcessor类
可能会有园友好奇,为什么我知道RequestResponseBodyMethodProcessor类支持@RequestBody?
一个简便的方法是直接看类名,开源项目Spring的代码质量非常高,它们的类名言简意赅,看类名大概就知道它是做什么的;类名如果看不出来,就点进去看注释,注释很规范、详细
打开RequestResponseBodyMethodProcessor类
支持带有@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测试
header写成application/json,请求类型写POST
Body传入Json格式参数
现在我们进入resolveArgument方法
127行获取参数信息,128行调用readWithMessageConverters方法获取参数值
131行创建WebDataBinder,用于校验数据格式是否正确
点开128行readWithMessageConverters方法,看看它做什么
148行获取请求信息,如头信息
我们看到Content-Type正是我们在Postman中设置的"application/json"
150行获取参数,调用父类AbstractMessageConverterMethodArgumentResolver的readWithMessageConverters方法,父类方法用于从请求信息中读取方法参数值
152行查看参数注解是否是@RequestBody
继续深入,进入AbstractMessageConverterMethodArgumentResolver的readWithMessageConverters方法
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方法
158行获取请求信息
159行获取MultipartHttpServletRequest请求信息,用于文件上传
175行获取参数值
打断点我发现,在RequestParamMethodArgumentResolver的父类AbstractNamedValueMethodArgumentResolver中,resolveArgument方法会先执行。后续我们自定义的参数解析器主要就是重写resolveArgument方法
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
发出请求,进入了RequestResponseBody的resolveArgument方法,参数我们可以看到
源码分析参照 2.源码分析
3.2 测试@RequestParam
在浏览器中输入http://localhost:8080/springmvcdemo/test/testCustomObjWithRp?id=1&name=s
返回结果如下,返回的是XML格式,(下一部分我再叙述MessageConverter部分的知识,我们这里只关注@RequestParam)
输入请求后,进入了RequestParamMethodArgumentResolver的父类AbstracNamedValueMethodArgumentResolver的reloveArgument方法,因我们有两个@RequestParam,会进入reloveArgument两次
3.3 测试无注解参数为自定义对象
浏览器输入请求http://localhost:8080/springmvcdemo/test/testCustomObjWithRp?id=1&name=s,返回结果如下
无注解我们怎么找到底是哪个HandlerMethodArgumentResolver实现类在处理呢?只要你认真看了第二部分源码分析,相信你可以轻松找到
在HandlerMethodArgumentResolverComposite(HandlerMethodArgumentResolver的实现类)第117行getArgumentResolver方法打上断点,看看庐山真面目
原来,是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时间戳
在HandlerMethodArgumentResolverComposite的resolveArugument方法打断点,发现被RequestParamMethodArgumentResovler所解析
在AbstracNamedValueMethodArgumentResolver的reloveArgument方法找到了我们的参数,方法同测试3.2
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方法获取别名
返回结果如下
浏览器输入http://localhost:8080/springmvcdemo/foc/test2?d.id=1&d.name=sss&e.id=3&e.name=ddf&e.age=12
参数别名用我们自定义的d和e
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.参考
文章难免有不足之处,欢迎指正