前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >spring mvc之HandlerMethodArgumentResolver

spring mvc之HandlerMethodArgumentResolver

作者头像
BUG弄潮儿
发布2022-06-30 14:54:42
2220
发布2022-06-30 14:54:42
举报
文章被收录于专栏:JAVA乐园

1.前言

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

代码语言:javascript
复制
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

代码语言:javascript
复制
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

代码语言:javascript
复制
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;
	}

解析器具体负责的方面:

代码语言:javascript
复制
RequestParamMethodArgumentResolver    处理@RequestParam(required=false)
RequestParamMapMethodArgumentResolver 处理@RequestParam Map map
PathVariableMethodArgumentResolver 处理@PathVariablePathVariableMapMethodArgumentResolver  处理@PathVariableMatrixVariableMethodArgumentResolver   处理@PathVariable Map map
MatrixVariableMapMethodArgumentResolver 处理@MatrixVariable  多个变量可以使用“;”
ServletModelAttributeMethodProcessor   处理@ModelAttribute(required=false)  或者 非基本类型
RequestResponseBodyMethodProcessor    处理@RequestBodyRequestPartMethodArgumentResolver   处理@RequestPartRequestHeaderMethodArgumentResolver    处理@RequestHeaderMethodArgumentResolverRequestHeaderMapMethodArgumentResolver  处理@RequestHeader Map map
ServletCookieValueMethodArgumentResolver   处理@CookieValue  ExpressionValueMethodArgumentResolver   处理@Value  SessionAttributeMethodArgumentResolver   处理@SessionAttributeRequestAttributeMethodArgumentResolver   处理@RequestAttributeServletRequestMethodArgumentResolver  处理ServletRequest、MultipartRequest、HttpSession、Principal、Locale、TimeZone、java.time.ZoneId、InputStream、Reader、org.springframework.http.HttpMethod
ServletResponseMethodArgumentResolver   处理ServletResponse、OutputStream、Writer
HttpEntityMethodProcessor   处理@HttpEntity、@RequestEntityRedirectAttributesMethodArgumentResolver  处理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()方法中来判定方法需要经过自定义参数解析器来解析

代码语言:javascript
复制
@Target (ElementType.PARAMETER)@Retention (RetentionPolicy.RUNTIME)@Documentedpublic @interface Multi {    
    String value() default "";
    
}

3.2 MultiHandlerMethodArgumentResolver

代码语言:javascript
复制
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 配置

代码语言:javascript
复制
    <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

代码语言:javascript
复制
    @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

结果:

代码语言:javascript
复制
[{"name":"ws","telephone":null,"sex":null,"race":null,"u":null},{"name":"kings","telephone":null,"sex":null,"race":null,"u":null}]

3.4.2 实例2

代码语言:javascript
复制
    @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

结果:

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

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2018-01-31,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 BUG弄潮儿 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.前言
  • 2.原理
    • 2.1 接口说明
      • 2.2 原理介绍
      • 3.实例
        • 3.1 注解@Multi
          • 3.2 MultiHandlerMethodArgumentResolver
            • 3.3 配置
              • 3.4 控制器
                • 3.4.1 实例1
                • 3.4.2 实例2
            • 4.友情附录表
            • 5 总结
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档