文档章节

【原创】遨游springmvc之HandlerMethodArgumentResolver

开源中国首席脑科主任
 开源中国首席脑科主任
发布于 2016/08/13 22:48
字数 1645
阅读 256
收藏 3
点赞 0
评论 0

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
作品 0
宁波
后端工程师
【原创】遨游springmvc之原理篇

1.Springmvc是什么 spring web mvc是一种基于java实现的请求驱动(请求-响应模型)的web层轻量级框架,spring web mvc采用了MVC(模型-视图-控制器)框架设计,将web层进行职责解耦,围绕核心处理...

开源中国首席脑科主任
2016/07/23
294
0
【原创】遨游springmvc之HandlerMethodReturnValueHandler

1.前言 在springmvc中,很多人都知道@ResponseBody,加了它那就会返回对应的数据结果(json),而不是一张jsp或者其他视图。如果不加,那么它就返回了一个具体的视图,如jsp。那么让我们来深入...

开源中国首席脑科主任
2016/08/20
964
1
SpringMVC 中的Controller返回JSON数据

SpringMVC中的Controller返回JSON问题 ①配置JSON解析器 ②使用注解@ResponseBody返回的是json,没有使用该注解,则会进行页面跳转。 方案一

IamOkay
2014/11/21
0
0
【原创】遨游springmvc之DispatcherServlet

1.机制 Dispatcher是springmvc前端控制器模式的实现,它提供一个集中的请求处理机制,所有的请求都将由一个单一的处理程序处理,Dispatcher负责请求的派遣,它与spring ioc完美继承,从而可以...

开源中国首席脑科主任
2016/07/23
71
0
spring web 4.1处理json

Spring mvc处理json,我们都知道使用@ResponseBody,处理xml也是用此注解。如果想spring mvc的使用@ResponseBody注解处理json,我们需要加入一些处理bean,也可以使用默认spring提供的。 通过...

引鸩怼孑
2015/07/09
0
0
【原创】遨游springmvc之WebDataBinder

1.前言 先上原理图 在我们学习servlet的时候我们知道有一个方法叫做:request.getParameter("paramName"),它返回的是一个String类型,但是如果一切都是这样子我们开发程序的时候就会显得特别...

开源中国首席脑科主任
2016/08/02
471
1
SpringMVC: HttpMessageConverter消息转换器机制

1、概述 在SpringMVC中,可以使用@RequestBody和@ResponseBody两个注解,分别完成请求报文到对象和对象到响应报文的转 换,底层这种灵活的消息转换机制,就是Spring3.x中新引入的HttpMessage...

_Roger_
2015/11/22
0
0
SpringMVC源码剖析(五)-消息转换器HttpMessageConverter

概述 在SpringMVC中,可以使用@RequestBody和@ResponseBody两个注解,分别完成请求报文到对象和对象到响应报文的转换,底层这种灵活的消息转换机制,就是Spring3.x中新引入的HttpMessageConv...

相见欢
2013/10/28
0
20
JWT 在 Spring 上的实践

简介 手头的新项目采用 jwt 做客户端验证,而不再使用 cookie,确实方便很多,起码跨域这事不用考虑了。 jwt 是什么之类的就不多说了,这玩意的介绍满大街都是,这儿只是简单介绍下我在使用过...

郁也风
2017/10/26
0
7
【原创】遨游springmvc之Converter

1.前言 在前一篇WebDataBinder中讲述了了一个PropertyEditor,它通过setAsText满足了字符串到指定类型的转换,但是它实现不了从任意类型转换到目标类型,所以在spring3.x之后引入了Converter...

开源中国首席脑科主任
2016/08/08
41
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

about git flow

  昨天元芳做了git分支管理规范的分享,为了拓展大家关于git分支的认知,这里我特意再分享这两个关于git flow的链接,大家可以看一下。 Git 工作流程 Git分支管理策略   git flow本质上是...

qwfys
今天
2
0
Linux系统日志文件

/var/log/messages linux系统总日志 /etc/logrotate.conf 日志切割配置文件 参考https://my.oschina.net/u/2000675/blog/908189 dmesg命令 dmesg’命令显示linux内核的环形缓冲区信息,我们可...

chencheng-linux
今天
1
0
MacOS下给树莓派安装Raspbian系统

下载镜像 前往 树莓派官网 下载镜像。 点击 最新版Raspbian 下载最新版镜像。 下载后请,通过 访达 双击解压,或通过 unzip 命令解压。 检查下载的文件 ls -lh -rw-r--r-- 1 dingdayu s...

dingdayu
今天
1
0
spring boot使用通用mapper(tk.mapper) ,id自增和回显等问题

最近项目使用到tk.mapper设置id自增,数据库是mysql。在使用通用mapper主键生成过程中有一些问题,在总结一下。 1、UUID生成方式-字符串主键 在主键上增加注解 @Id @GeneratedValue...

北岩
今天
2
0
告警系统邮件引擎、运行告警系统

告警系统邮件引擎 cd mail vim mail.py #!/usr/bin/env python#-*- coding: UTF-8 -*-import os,sysreload(sys)sys.setdefaultencoding('utf8')import getoptimport smtplibfr......

Zhouliang6
今天
1
0
Java工具类—随机数

Java中常用的生成随机数有Math.random()方法及java.util.Random类.但他们生成的随机数都是伪随机的. Math.radom()方法 在jdk1.8的Math类中可以看到,Math.random()方法实际上就是调用Random类...

PrivateO2
今天
2
0
关于java内存模型、并发编程的好文

Java并发编程:volatile关键字解析    volatile这个关键字可能很多朋友都听说过,或许也都用过。在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果。在...

DannyCoder
昨天
1
0
dubbo @Reference retries 重试次数 一个坑

在代码一中设置 成retries=0,也就是调用超时不用重试,结果DEBUG的时候总是重试,不是0吗,0就不用重试啊。为什么还是调用了多次呢? 结果在网上看到 这篇文章才明白 https://www.cnblogs....

奋斗的小牛
昨天
2
0
数据结构与算法3

要抓紧喽~~~~~~~放羊的孩纸回来喽 LowArray类和LowArrayApp类 程序将一个普通的Java数组封装在LowArray类中。类中的数组隐藏了起来,它是私有的,所以只有类自己的方法才能访问他。 LowArray...

沉迷于编程的小菜菜
昨天
1
0
spring boot应用测试框架介绍

一、spring boot应用测试存在的问题 官方提供的测试框架spring-boot-test-starter,虽然提供了很多功能(junit、spring test、assertj、hamcrest、mockito、jsonassert、jsonpath),但是在数...

yangjianzhou
昨天
2
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部