【稀里糊涂学springmvc】如何把请求中的参数放到controller的方法中的

使用springmvc的同学们对于如何在Controller的方法中来接收请求中的参数,应该都相当熟悉了。例如:
public void method1(String name)
public void method1(@ReqeustParam String name)
.....

当我们通过@RequestMapping映射后,那么为什么方法中有一个名为name的参数,就会把请求中的name的值传给方法中的参数name呢?为什么些@ReqeustName,他有什么作用呢?等等等的问题,本章,将看一下这里面的来龙去脉。

本章节涉及到的springmvc中的类有如下几个:

  • RequestParamMethodArgumentResolver
  • ConfigurableWebBindingInitializer
  • SimpleTypeConverter extends TypeConverterSupport
  • DefaultFormattingConversionService
  • InvocableHandlerMethod
  • HandlerMethodArgumentResolverComposite



下图是当请求发起后,springmvc是如何一步一步获取请求参数,然后调用controller总方法的总体过程。每步对应的原码都在下面的code中对应。其中核心就在HandlerMethodArgumentResolverComposite的resolveArgument,这里就是获取参数的入口

code1:

// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

code2

@Override
protected ModelAndView handleInternal(HttpServletRequest request,
			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
    mav = invokeHandlerMethod(request, response, handlerMethod);
}

protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
    invocableMethod.invokeAndHandle(webRequest, mavContainer);
}

code3

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {

    Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
}

code4

public Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {
    // 获取到reqeust中的参数的值
    Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
    // 带着参数的值去调用controller中的方法,最后获取到方法的返回值
    Object returnValue = doInvoke(args);
    return returnValue;
}

/**
* Get the method argument values for the current request.
*/
private Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {
    //对应我们controller中方法的参数,有几个参数,这里的数组就会有几个对象,观察下面图1。
    MethodParameter[] parameters = getMethodParameters();
    Object[] args = new Object[parameters.length];
    for (int i = 0; i < parameters.length; i++) {
        MethodParameter parameter = parameters[i];
        parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
        args[i] = resolveProvidedArgument(parameter, providedArgs);
        if (args[i] != null) {
            continue;
        }
        // 调用HandlerMethodArgumentResolverComposite的supportsParameter
        // 根据方法中的参数的写法(比如,是否使用了@RequestParam等注解)获取一个解析器resolver
        // 此处进入到第4.1步	
        if (this.argumentResolvers.supportsParameter(parameter)) {
            // 通过resolver获取request中对应的参数的值,
            args[i] = this.argumentResolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
            continue;
		}
    return args;
}


getMethodParameters()方法将获取到2个对象,如下:
图1
因为我的rest1这个方法中有两个参数,一个是name,一个是age,所以这里就会有两个对象,也就是说你的方法中有几个参数,这里就会有几个对象。

code5 

HandlerMethodArgumentResolverComposite.java

@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
    //根据方法中的参数的写法(比如,是否使用了@RequestParam等注解)获取一个解析器resolver, 看下面图1
    HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
    // 使用对应的resolver去获取request中参数的值。不同的resolver从request中获取参数值的方法各不相同
    return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}

/**
* Find a registered HandlerMethodArgumentResolver that supports the given method parameter.
 */
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
    HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
    if (result == null) {
        // HandlerMethodArgumentResolver 是个啥,看下面的截图(1),说白了,
        //当方法中的参数有不同的写法,比如@ReqeustParam注解,@PathVariable注解,
        //需要不同的解析器,在这里根据parameter获取对应的resolver
        for (HandlerMethodArgumentResolver methodArgumentResolver:this.argumentResolvers) {
            //判断方法中的参数是否可以通过这个resolver来处理
            if (methodArgumentResolver.supportsParameter(parameter)) {
                // 因为我们例子中的方法的参数没有
                // @RequestParam或者其他的东西,只是用了最简单类+参数名,所以这里的resolver会是
                // RequestParamMethodArgumentResolver,可看下图 (2)
                result = methodArgumentResolver;
                this.argumentResolverCache.put(parameter, result);
                break;
            }
        }
    }
    return result;
}

 

图1
图2

code6:

AbstractNamedValueMethodArgumentResolver.java

@Override
public final Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {

    NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
    MethodParameter nestedParameter = parameter.nestedIfOptional();

    Object resolvedName = resolveStringValue(namedValueInfo.name);
    if (resolvedName == null) {
        throw new IllegalArgumentException("Specified name must not resolve to null: [" + namedValueInfo.name + "]");
    }

    Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
    if (binderFactory != null) {
        WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
        // 将request中过去的参数的值,转换为方法中对应的参数的类型
        arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
    }

    handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
    // 转换类型后的参数返回,就可以在后面的代码中正常的赋值给方法中的参数了
    return arg;
}

code7

@Override
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
    HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
    MultipartHttpServletRequest multipartRequest =WebUtils.getNativeRequest(servletRequest, MultipartHttpServletRequest.class);

    Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
    if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {
			return mpArg;
    }

    Object arg = null;
    if (multipartRequest != null) {
        List<MultipartFile> files = multipartRequest.getFiles(name);
        if (!files.isEmpty()) {
            arg = (files.size() == 1 ? files.get(0) : files);
        }
    }
    if (arg == null) {
        // 从reqeust中获取到与方法参数名相同的那个参数的值
        String[] paramValues = request.getParameterValues(name);
        if (paramValues != null) {
            arg = (paramValues.length == 1 ? paramValues[0] : paramValues);
        }
    return arg;
}

code8

DefaultDataBinderFactory.java

@Override
public final WebDataBinder createBinder(NativeWebRequest webRequest, Object target, String objectName){

    WebDataBinder dataBinder = createBinderInstance(target, objectName, webRequest);
    if (this.initializer != null) {
        // reqeust中参数会自动转换为方法中参数的类型,就是在这里的原因。这里设置了用于转换的类。
        this.initializer.initBinder(dataBinder, webRequest);
    }
    initBinder(dataBinder, webRequest);
    return dataBinder;
}

上面的部分讲述了整个过程的整体路程,下面来讲述这个过程中的一些关键点:

 

第一点:为什么controller中的方法里的参数前可以写@RequestParam,或者不写。为什么方法中的参数既可以是String类型,也可是是一个类类型(method(Person p))或者是Map,List等?
这是因为springmvc中定义了多种针对不同类型进行相对处理的Resolver。根据方法中参数的不同类型,会生成不同的Resolver,每个Resolver通过自己的方式解析request中参数的值,比如当我们的方法中的参数是Map类型时,便会调用下面的RequestParamMapMethodArgumentResolver

RequestParamMapMethodArgumentResolver.java
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
    // 得到controller方法中参数的类型
    Class<?> paramType = parameter.getParameterType();
    // 从request中将请求的参数以map的形式返回
    Map<String, String[]> parameterMap = webRequest.getParameterMap();
    
    ...省略部分代码...
    
    // 遍历组成map返回
    Map<String, String> result = new LinkedHashMap<String, String>(parameterMap.size());
    for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
        if (entry.getValue().length > 0) {
            result.put(entry.getKey(), entry.getValue()[0]);
        }
    }
    return result;
}


这些Resolver都是在HandlerMethodArgumentResolverComposite的getArgumentResolver(MethodParameter parameter)方法中获取到的。根据controller中方法的参数类型的不同,这里得到的也不同。器实现方式就是遍历所有的resolver,然后通过supportsParameter(parameter)方法,找出适合的resolver,如下图:

 

对于这些resolver都是从缓存中拿到的,那么是什么时候存到缓存中的呢?就是在实例化ReqeustMappingHandlerAdapter后,自动调用afterPropertiesSet方法的时候,如下:

ReqeustMappingHandlerAdapter.java
@Override
public void afterPropertiesSet() {
    // Do this first, it may add ResponseBody advice beans
    initControllerAdviceCache();

    if (this.argumentResolvers == null) {
        // 这里就是获取所有默认的resolver,然后添加到缓存中
        // 注意关注这个方法中的代码,看他默认加了多少的resolver
        List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
        this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
    }
    if (this.initBinderArgumentResolvers == null) {
        List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
        this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
    }
    if (this.returnValueHandlers == null) {
        List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
        this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
    }
}

/**
*这里面就是初始化默认的那些resolver
*/
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;
}

//同理,其实比如方法返回值的处理handler也是在这个类中添加的。不过这里就不贴代码了


这里介绍几个例子,来看一下不同的写法会调用那个resolver
method(String name)
调用 :AbstractNamedValueMethodArgumentResolver implements HandlerMethodArgumentResolver
子类的resolveArgument(),这种写法获取参数后,还会有一个类型转换的操作,第二点中会介绍。

method(@RequestParam Map myMap)
调用:RequestParamMapMethodArgumentResolver implements HandlerMethodArgumentResolver
自己的resolveArgument()



​​​​​​

第二点:从reqeust中获取的参数的值,是怎么转换为controller中的方法中参数的类型的?
在第一点中说过,如果你的方法中是Integer,String这样的类型,springmvc会自动将reqeust中的值进行转换为你方法中参数的类型。
为什么请求中的参数可以转换为方法中参数的类型,是由那个类处理的呢,就是下面的这些类完成的转换工作:

下面来看看以下,转换相关的代码:

AbstractNamedValueMethodArgumentResolver.java
// 从request中解析出参数值
@Override
public final Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {

    NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
    MethodParameter nestedParameter = parameter.nestedIfOptional();

    Object resolvedName = resolveStringValue(namedValueInfo.name);
    // 这里从reqeust获取参数的值
    Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);	
    WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
    // 这里对值进行转换,parameter.getParameterType()就是controller中方法的参数对应的类型
    arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
    handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
    return arg;
}

类型转换这一步不是每个resolver都有的。具体的业务请参考resolver中的resolverArgument方法。

第三点:解析一下各种resolver,无法全部都解析,尽可能的写
1)RequestHeaderMapMethodArgumentResolver 

// 使用@ReqeustParam注解时,便会用到这个resolver
public String rest4(@RequestHeader Map myMap){
    return (String)myMap.get("name") ;
}

2)RequestHeaderMethodArgumentResolver

// 此处的参数名称可以为任何名字,因为最后是通过反射将参数传过来的。跟这里的名字没关系
public String rest4(@RequestHeader String host){
    return host ;
}

3)ServletModelAttributeMethodProcessor


public String rest4( Person p){
    return p.getName() ;
}

class Person {
	private String name;
	public String getName() {return name;}
    public void setName(String name) {this.name = name;}
}

这里我们稍微来看一下这个ServletModelAttributeMethodProcessor获取request参数的业务是怎么做的

@Override
public final Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
    String name = ModelFactory.getNameForParameter(parameter);
    //整个下面的逻辑就是,如果在程序中使用过model就去覆盖原先里面的attribute
    Object attribute = (mavContainer.containsAttribute(name) ? mavContainer.getModel().get(name) :
    createAttribute(name, parameter, binderFactory, webRequest));
    WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
    // 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);
}

4)RequestResponseBodyMethodProcessor

//参数使用@ReqeustBody注解,就会使用这个Resolver
public String rest4(@RequestBody Person p){
    return p.getName() ;
}

class Person {
	private String name;
	public String getName() {return name;}
	public void setName(String name) {this.name = name;}
}

 postman中发送的数据如下:

 这里把这个resolver类的解析方法粘贴了一部分代码,主要目的是为了展示,这个地方json转person对象使用了前面HttpMessageConverter章节的converter来实现转换的。

@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {

    parameter = parameter.nestedIfOptional();
    // 这里用到了我们之前讲的HttpMessageConverter来将reqeust中的body中json转换为Person
    // 具体使用到的MappingJackson2HttpMessageConverter。这里请关注下
    // arg便是person对象,里面放着json中的数据
    // 这里面还会调用JsonViewRequestBodyAdvice,通过它可以实现将某些属性不赋值到person对象中
    Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
      ...省略其他....

    return adaptArgumentIfNecessary(arg, parameter);
}


最后,简单总结一下,获取resolver获取参数的入口便是HandlerMethodArgumentResolverComposite的resolveArgument方法。

在方法的最后将resolver解析出的参数通过return返回。

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值