SpringMVC中自定义参数解析器及内置类型的绑定

时间:2021-12-16 09:47:48

SpringMVC中自定义参数解析器及内置类型的绑定

      前一篇文章讲述了使用标注方式进行参数绑定背后的参数解析原理,今天来整理一下没有使用标注的参数是怎样解析出来的。
一,自定义参数解析器   有的时候我们希望在HandlerMethod中直接使用一些对象,而不需要主动去创建或获取。例如,我们想在一个请求方法的第一行打印出系统登录用户的相关信息,而这个用户信息已经再Request中作为Attribute保存好了。这时候通常的做法就是下面这样:
    @RequestMapping("paramTest")
@ResponseBody
public Object paramTest(HttpServletRequest request){
User user = (User) request.getAttribute("user");
System.out.println(user);
return "ok";
}
  这样的方式显然有一些麻烦,最好可以达到下面的效果
    @RequestMapping("paramTest")
@ResponseBody
public Object paramTest(User user){
System.out.println(user);
return "ok";
}
  这样一来,就方便了好多。有人可能会有些怀疑,不就是少了一行代码吗,这里只是举一个简单的例子,如果现实中需要成百上千行的代码来构造这个User呢?那么构造这个User的逻辑就可以封装在自定义的参数解析器中。
  首先,写一个自己的参数解析器,像下面这样:
public class UserArgumentResolver implements HandlerMethodArgumentResolver {

@Override
public boolean supportsParameter(MethodParameter parameter) {
if (parameter.getParameterType().equals(User.class)) {
return true;
}
return false;
}

@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
return webRequest.getAttribute("user", RequestAttributes.SCOPE_REQUEST);
}

}
  它的第一个方法用于匹配参数的类型,如果是User类型,则用第二个方法解析出一个User对象返回。然后还需要把这个参数解析器注册到HandlerAdapter中,像下面这样:
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">

<property name="customArgumentResolvers">
<list>
<bean class="com.ebaoyang.controller.resolver.UserArgumentResolver" />
</list>
</property>
<property name="order" value="0" />
</bean>
  通过上面的配置就可以实现自定义类型User的绑定,可以在HandlerMethod中直接使用。

二,内置类型的绑定   最初接触SpringMVC的时候就发现它的一些用法很神奇,诸如在方法中直接声明HttpServletRequest就可以直接使用。一直很好奇隐藏在它背后的原理是什么,后来系统的学习spring的源码才了解到这样的参数绑定机制。其实可以值这样进行绑定的参数类型不只是HttpServletRequest,还有很多其他的类型,看一看ServletRequestMethodArgumentResolver的supportsParameter方法就知道了:
public boolean supportsParameter(MethodParameter parameter) {
Class<?> paramType = parameter.getParameterType();
return WebRequest.class.isAssignableFrom(paramType) ||
ServletRequest.class.isAssignableFrom(paramType) ||
MultipartRequest.class.isAssignableFrom(paramType) ||
HttpSession.class.isAssignableFrom(paramType) ||
Principal.class.isAssignableFrom(paramType) ||
Locale.class.equals(paramType) ||
InputStream.class.isAssignableFrom(paramType) ||
Reader.class.isAssignableFrom(paramType);
}
  这些类型的参数的绑定方法见resolveArgument方法:
public Object resolveArgument(
MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory)
throws IOException {

Class<?> paramType = parameter.getParameterType();
if (WebRequest.class.isAssignableFrom(paramType)) {
return webRequest;
}

HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
if (ServletRequest.class.isAssignableFrom(paramType) || MultipartRequest.class.isAssignableFrom(paramType)) {
Object nativeRequest = webRequest.getNativeRequest(paramType);
if (nativeRequest == null) {
throw new IllegalStateException(
"Current request is not of type [" + paramType.getName() + "]: " + request);
}
return nativeRequest;
}
else if (HttpSession.class.isAssignableFrom(paramType)) {
return request.getSession();
}
else if (Principal.class.isAssignableFrom(paramType)) {
return request.getUserPrincipal();
}
else if (Locale.class.equals(paramType)) {
return RequestContextUtils.getLocale(request);
}
else if (InputStream.class.isAssignableFrom(paramType)) {
return request.getInputStream();
}
else if (Reader.class.isAssignableFrom(paramType)) {
return request.getReader();
}
else {
// should never happen..
Method method = parameter.getMethod();
throw new UnsupportedOperationException("Unknown parameter type: " + paramType + " in method: " + method);
}
}

三,一般类型参数的绑定
  如果HandlerMethod的参数即没有PathVariable这样的标注,也没有自定义的参数解析器来处理它,也不是HttpServlet这样的内置类型,它是如何绑定的呢?其实,这样的参数属性会到查询参数里面去寻找。比如刚才的例子,如果我们没有定义自己的UserArgumentResolver ,而客户端传来的请求行中是这样的/paramTest?id=456,那么user的id就会被设置为456。对于没有对应查询参数的属性会设置为默认值,若客户端传来的是数组,则会设置为取其中一个值。在spring中,大多数情况下客户端若传来id=123&id=456这样的数字过来,服务端是可以解析出来int类型的id的。但是,应该尽量避免这样的事情发生,因为这样看起来模糊不清,并且它本身就是一个错误。