您的位置:首页 > 编程语言 > Java开发

【原创】遨游springmvc之HandlerMethodArgumentResolver

2016-08-13 22:48 393 查看

1.前言

记得大三刚开始接触springmvc的时候,我们总是会写如下方法

public String doSomethine(HttpSerlvetRequest request){
//doSomethine
}

然后现在看了这简直看不下去。

因为:1.加重了我们对请求传过来来的值的取值代码,会使控制器中request.getParamater()之类的代码越来越多;2.不利于测试;3.request.getParamater()只能获取string,如果是Long等其他类型的参数还需要强转,使用起来非常不方便。

所以springmvc从3.1开始便加强了这方便的功能,那就是HandlerMethodArgumentResolver,springmvc通过HandlerMethodArgumentResolver对传入的参数进行了一些列的装配绑定。

2.原理

2.1 接口说明

HandlerMethodArgumentResolver只有2个方法,supportParameter()决定了传入的参数是否启用该解析器,resolveArgument则是真正解析参数的过程,并且返回。

源码2.1.1
public interface HandlerMethodArgumentResolver {

/**
* Whether the given {@linkplain MethodParameter method parameter} is
* supported by this resolver.
* @param parameter the method parameter to check
* @return {@code true} if this resolver supports the supplied parameter;
* {@code false} otherwise
*/
boolean supportsParameter(MethodParameter parameter);

/**
* Resolves a method parameter into an argument value from a given request.
* A {@link ModelAndViewContainer} provides access to the model for the
* request. A {@link WebDataBinderFactory} provides a way to create
* a {@link WebDataBinder} instance when needed for data binding and
* type conversion purposes.
* @param parameter the method parameter to resolve. This parameter must
* have previously been passed to {@link #supportsParameter} which must
* have returned {@code true}.
* @param mavContainer the ModelAndViewContainer for the current request
* @param webRequest the current request
* @param binderFactory a factory for creating {@link WebDataBinder} instances
* @return the resolved argument value, or {@code null}
* @throws Exception in case of errors with the preparation of argument values
*/
Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception;

}


2.2 原理介绍

在我们对springmvc的实际使用中,经常会看到@RequestParam、@PathVariable、@ModelAttribute等注解在某个控制器方法的参数前面,springmvc通过这些注解在HandlerMethodArgumentResolver中的supportParameter()中进行判定,去寻找对应的参数解析器,并在解析器程序中处理了参数绑定的一些逻辑。

springmvc在适配器RequestMappingHandlerAdapter中加入了一系列默认的参数解析器

源码2.2.1
private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>();

// Annotation-based argument resolution
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
resolvers.add(new RequestParamMapMethodArgumentResolver());
resolvers.add(new PathVariableMethodArgumentResolver());
resolvers.add(new PathVariableMapMethodArgumentResolver());
resolvers.add(new MatrixVariableMethodArgumentResolver());
resolvers.add(new MatrixVariableMapMethodArgumentResolver());
resolvers.add(new ServletModelAttributeMethodProcessor(false));
resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));
resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
resolvers.add(new RequestHeaderMapMethodArgumentResolver());
resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));
resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
resolvers.add(new SessionAttributeMethodArgumentResolver());
resolvers.add(new RequestAttributeMethodArgumentResolver());

// Type-based argument resolution
resolvers.add(new ServletRequestMethodArgumentResolver());
resolvers.add(new ServletResponseMethodArgumentResolver());
resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
resolvers.add(new RedirectAttributesMethodArgumentResolver());
resolvers.add(new ModelMethodProcessor());
resolvers.add(new MapMethodProcessor());
resolvers.add(new ErrorsMethodArgumentResolver());
resolvers.add(new SessionStatusMethodArgumentResolver());
resolvers.add(new UriComponentsBuilderMethodArgumentResolver());

// Custom arguments
if (getCustomArgumentResolvers() != null) {
resolvers.addAll(getCustomArgumentResolvers());
}

// Catch-all
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
resolvers.add(new ServletModelAttributeMethodProcessor(true));

return resolvers;
}

解析器具体负责的方面:

RequestParamMethodArgumentResolver    处理@RequestParam(required=false)
RequestParamMapMethodArgumentResolver 处理@RequestParam Map map
PathVariableMethodArgumentResolver 处理@PathVariable
PathVariableMapMethodArgumentResolver  处理@PathVariable
MatrixVariableMethodArgumentResolver   处理@PathVariable Map map
MatrixVariableMapMethodArgumentResolver 处理@MatrixVariable  多个变量可以使用“;”
ServletModelAttributeMethodProcessor   处理@ModelAttribute(required=false)  或者 非基本类型
RequestResponseBodyMethodProcessor    处理@RequestBody
RequestPartMethodArgumentResolver   处理@RequestPart
RequestHeaderMethodArgumentResolver    处理@RequestHeaderMethodArgumentResolver
RequestHeaderMapMethodArgumentResolver  处理@RequestHeader Map map
ServletCookieValueMethodArgumentResolver   处理@CookieValue
ExpressionValueMethodArgumentResolver   处理@Value
SessionAttributeMethodArgumentResolver   处理@SessionAttribute
RequestAttributeMethodArgumentResolver   处理@RequestAttribute
ServletRequestMethodArgumentResolver  处理ServletRequest、MultipartRequest、HttpSession、Principal、Locale、TimeZone、java.time.ZoneId、InputStream、Reader、org.springframework.http.HttpMethod
ServletResponseMethodArgumentResolver   处理ServletResponse、OutputStream、Writer
HttpEntityMethodProcessor   处理@HttpEntity、@RequestEntity
RedirectAttributesMethodArgumentResolver  处理RedirectAttributes
ModelMethodProcessor   处理Model model
MapMethodProcessor    处理Map map
ErrorsMethodArgumentResolver  处理Errors  数据绑定时使用
SessionStatusMethodArgumentResolver   处理SessionStatus
UriComponentsBuilderMethodArgumentResolver   处理UriComponentsBuilder和ServletUriComponentsBuilder


3.实例

我们一般在使用springmvc的时候玩不了像struts2这样的在参数中接收a1.name=xx,a2.name=xx这样的,但是这并不表示springmvc做不到,我们来自定义一个参数解析器来实现所说的功能。

springmvc默认提供了ModelAttributeMethodProcessor来解析实体,而我们是要实现功能和ModelAttributeMethodProcessor相似的,但又是可以实现a1.name=xx,a2.name=xx来初始化javabean的一个参数解析器

3.1 注解@Multi

用在解析器supportParameter()方法中来判定方法需要经过自定义参数解析器来解析

@Target (ElementType.PARAMETER)
@Retention (RetentionPolicy.RUNTIME)
@Documented
public @interface Multi {

String value() default "";

}


3.2 MultiHandlerMethodArgumentResolver

public class MultiHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {

@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(Multi.class);//参数前面带@Multi
}

@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
{
Multi multi = parameter.getParameterAnnotation(Multi.class);
String name = StringUtils.isEmpty(multi.value()) ? parameter.getParameterName() : multi.value();//参数名:默认去@Multi的value值 如果是""则去获取参数值的命名变量
//String name = ModelFactory.getNameForParameter(parameter);
Object attribute = (mavContainer.containsAttribute(name) ? mavContainer.getModel().get(name) : createAttribute(name, parameter, binderFactory, webRequest));

if (! mavContainer.isBindingDisabled(name)) {
ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
if (ann != null && ! ann.binding()) {
mavContainer.setBindingDisabled(name);
}
}

WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
if (binder.getTarget() != null) {
if (! mavContainer.isBindingDisabled(name)) {
bindRequestParameters(binder, webRequest, name);
}
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new BindException(binder.getBindingResult());
}
}

// Add resolved attribute and BindingResult at the end of the model
Map<String, Object> bindingResultModel = binder.getBindingResult().getModel();
mavContainer.removeAttributes(bindingResultModel);
mavContainer.addAllAttributes(bindingResultModel);

return binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
}
}

protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request, String parameterName) {
ServletRequest servletRequest = request.getNativeRequest(ServletRequest.class);
ServletRequestDataBinder servletBinder = (ServletRequestDataBinder) binder;
//servletBinder.setFieldDefaultPrefix(servletBinder.getObjectName()+".");
servletBinder.setFieldDefaultPrefix(parameterName + ".");
servletBinder.bind(servletRequest);
}

protected Object createAttribute(String attributeName, MethodParameter methodParam, WebDataBinderFactory binderFactory, NativeWebRequest request) throws Exception {

return BeanUtils.instantiateClass(methodParam.getParameterType());
}

protected void validateIfApplicable(WebDataBinder binder, MethodParameter methodParam) {
Annotation[] annotations = methodParam.getParameterAnnotations();
for (Annotation ann : annotations) {
Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {
Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann));
Object[] validationHints = (hints instanceof Object[] ? (Object[]) hints : new Object[]{hints});
binder.validate(validationHints);
break;
}
}
}

protected boolean isBindExceptionRequired(WebDataBinder binder, MethodParameter methodParam) {
int i = methodParam.getParameterIndex();
Class<?>[] paramTypes = methodParam.getMethod().getParameterTypes();
boolean hasBindingResult = (paramTypes.length > (i + 1) && Errors.class.isAssignableFrom(paramTypes[i + 1]));
return ! hasBindingResult;
}

}


3.3 配置

<mvc:annotation-driven >
<mvc:argument-resolvers>
<bean class="com.kings.template.mvc.MultiHandlerMethodArgumentResolver"/>
</mvc:argument-resolvers>
</mvc:annotation-driven>


3.4 控制器

3.4.1 实例1

@RequestMapping (value = "/argumentresolver/1", method = RequestMethod.GET)
@ResponseBody
public List<Person> list(@Multi(value = "p1") Person p1,@Multi(value = "p2") Person p2) {
return Lists.newArrayList(p1,p2);
}

访问:http://localhost:8080/kingstemplate//argumentresolver/1?p1.name=ws&p2.name=kings

结果:

[{"name":"ws","telephone":null,"sex":null,"race":null,"u":null},{"name":"kings","telephone":null,"sex":null,"race":null,"u":null}]


3.4.2 实例2

@RequestMapping (value = "/argumentresolver/1", method = RequestMethod.GET)
@ResponseBody
public List<Person> list(@Multi Person p1,@Multi Person p3) {
return Lists.newArrayList(p1,p3);
}

访问:http://localhost:8080/kingstemplate//argumentresolver/1?p1.name=ws&p3.name=kings

结果:

[{"name":"ws","telephone":null,"sex":null,"race":null,"u":null},{"name":"kings","telephone":null,"sex":null,"race":null,"u":null}]


4.友情附录表

参数注解使用

注解描述
@RequestParam接收基本类型,处理request body部分的注解,不能处理bean类型
@PathVariable接受url中的参数,即 someUrl/{paramId}, 这时的paramId可通过 @Pathvariable注解绑定它传过来的值到方法的参数上。
@MatrixVariable多个变量可以使用“;”来接收
@ModelAttribute用于方法上时: 通常用来在处理@RequestMapping之前,为请求绑定需要从后台查询的model;
用于参数上时: 用来通过名称对应,把相应名称的值绑定到注解的参数bean上;
@RequestBody处理request body部分的注解,处理Content-Type: 不是application/x-www-form-urlencoded编码的内容,例如application/json, application/xml等
@RequestHeader获取Request请求header部分的参数
@RequestPart处理Content-Type:multipart/form-data的参数,如MultipartFile
@CookieValue可以把Request header中关于cookie的值绑定到方法的参数上
@SessionAttribute绑定HttpSession中的attribute对象的值
@RequestAttribute接受request中的attribute

5 总结

参数解析器能帮助我们更加方便的将请求参数绑定到handlermethod上,在控制器方法上帮助我们节约了更多的重复代码。

发现一个机制的导航😳

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息