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

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的。但是,应该尽量避免这样的事情发生,因为这样看起来模糊不清,并且它本身就是一个错误。


 


发布了51 篇原创文章 · 获赞 11 · 访问量 25万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章