【原创】遨游springmvc之HandlerMethodArgumentResolver
【原创】遨游springmvc之HandlerMethodArgumentResolver
【原创】遨游springmvc之HandlerMethodArgumentResolver
  • 发表于 1年前
  • 阅读 224
  • 收藏 3
  • 点赞 0
  • 评论 0

腾讯云 新注册用户 域名抢购1元起>>>   

摘要: 参数解析器HandlerMethodArgumentResolver

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上,在控制器方法上帮助我们节约了更多的重复代码。

发现一个机制的导航😳

共有 人打赏支持
粉丝 61
博文 17
码字总数 18226
×
开源中国首席脑科主任
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: