SpringBoot第七天 - Web参数处理

SpringBoot - Web参数处理

探讨SpringBoot接收和处理参数的方式。

1. 注解方式

SpringMVC为我们提供了许多注解来接收参数。

1.1 常用注解

SpringMVC常用的9个参数绑定注解:

  1. @RequestParam 接收请求中的参数;
  2. @RequestBody 接收请求体内容;
  3. @RequestHeader 接收请求头内容;
  4. @RequestAttribute 接收Request域中的参数;
  5. @PathVariable 接收请求路径中的路径变量;
  6. @CookieValue 接收Cookie中的参数;
  7. @SessionAttributes 接收Session域中的参数;
  8. @ModelAttribute 接收隐含模型中的参数;
  9. @MatrixVariable 接收请求路径中的矩阵变量。

1.2 @MatrixVariable

其他注解在学习SpringMVC时就已经了解过了,现在单独学习@MatrixVariable。

1.2.1 矩阵变量

一道经典的面试题:

现在Cookie被禁用了,如何获取Session域中的内容?

由于服务器是按照Cookie中的一个参数JSESSIONID(初次访问服务器才会有)来查找Session对象,
从而获取Session域中的内容。现在Cookie被禁用,服务器无法获取JSESSIONID,
所以也就无法获取Session域的内容。

我们可以使用URL重写的方式让URL携带JSESSIONID:

localhost:8080/example;JSESSIONID=ghco9xdnaco31gmafukxchph

使用这种方式传递的参数就叫矩阵变量,可以使用@MatrixVariable注解接收并绑定。

1.2.2 矩阵变量的语法

在路径变量后面加分号后写在后面,多个矩阵变量用分号隔开,
可以在任何路径变量后加:

localhost:8080/example;id=1/example;id=2;name=张三

1.2.3 矩阵变量的类型

  • 单个类型,不可再分的类型(如数字,字符串等):
localhost:8080/example;name=张三;sex=男
  • 集合类型(List):
第一种写法:变量值可以写在一起,多个值用逗号隔开:
localhost:8080/example;sex=男,女

第二种写法:变量值不写在一起,多个值用分号隔开:
localhost:8080/example;sex=男;sex=女

1.2.4 作用

注解@MatrixVariable用来接收请求路径中的矩阵变量。

1.2.5 作用范围

  • 方法参数(ElementType.PARAMETER)

1.2.6 属性

  • String name/value:指定接收的矩阵变量的名称;
  • String pathVar:指定矩阵变量所在的路径变量:

    当不同的路径变量位置的矩阵变量同名时,可以指定此值来作为区分;
  • boolean required:指定该变量不存在时是否抛出异常;
  • String defaultValue:指定该变量不存在时的默认值。

1.2.7 使用方式(原理)

SpringBoot默认禁用了矩阵变量,原理分析:

在SpringBoot对WebMVC的自动配置类中:

// org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration
// WebMvcAutoConfiguration.java Line:179~322

// ... 省略其他代码
// 此类为WebMvcAutoConfiguration自动配置类的内部类,为WebMVC的配置类,实现了WebMvcConfigurer接口。
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {

    // ... 省略其他代码
    
    // 此方法配置了路径匹配规则
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        if (this.mvcProperties.getPathmatch()
                .getMatchingStrategy() == WebMvcProperties.MatchingStrategy.PATH_PATTERN_PARSER) {
            configurer.setPatternParser(new PathPatternParser());
        }
        configurer.setUseSuffixPatternMatch(this.mvcProperties.getPathmatch().isUseSuffixPattern());
        configurer.setUseRegisteredSuffixPatternMatch(
                this.mvcProperties.getPathmatch().isUseRegisteredSuffixPattern());
        this.dispatcherServletPath.ifAvailable((dispatcherPath) -> {
            String servletUrlMapping = dispatcherPath.getServletUrlMapping();
            if (servletUrlMapping.equals("/") && singleDispatcherServlet()) {
                // 使用UrlPathHelper帮助解析请求路径
                UrlPathHelper urlPathHelper = new UrlPathHelper();
                urlPathHelper.setAlwaysUseFullPath(true);
                configurer.setUrlPathHelper(urlPathHelper);
            }
        });
    }
    
    // ... 省略其他代码
    
}

SpringMVC是使用了UrlPathHelper来帮助解析请求路径的,
对矩阵变量的处理如下:

// org.springframework.web.util.UrlPathHelper
// UrlPathHelper.java 

public class UrlPathHelper {
    
    // ... 省略其他代码

    // 是否移除分号内容(矩阵变量)
	private boolean removeSemicolonContent = true;
	
	// 是否移除矩阵变量的Setter
	public void setRemoveSemicolonContent(boolean removeSemicolonContent) {
		checkReadOnly();
		this.removeSemicolonContent = removeSemicolonContent;
	}
	
	// 获取是否应该移除矩阵变量
	public boolean shouldRemoveSemicolonContent() {
		checkReadOnly();
		return this.removeSemicolonContent;
	}

    // 过滤URI
	private String decodeAndCleanUriString(HttpServletRequest request, String uri) {
		// 此处将矩阵变量从URI中移除
	    uri = removeSemicolonContent(uri);
		uri = decodeRequestString(request, uri);
		uri = getSanitizedPath(uri);
		return uri;
	}

	// 移除矩阵变量
	public String removeSemicolonContent(String requestUri) {
	    // 判断是否需要移除矩阵变量,如果是那就移除,如果不是仅移除JSESSIONID
		return (this.removeSemicolonContent ?
				removeSemicolonContentInternal(requestUri) : removeJsessionid(requestUri));
	}

	// 移除矩阵变量的具体过程,这里就不在分析
	private static String removeSemicolonContentInternal(String requestUri) {
		int semicolonIndex = requestUri.indexOf(';');
		if (semicolonIndex == -1) {
			return requestUri;
		}
		StringBuilder sb = new StringBuilder(requestUri);
		while (semicolonIndex != -1) {
			int slashIndex = sb.indexOf("/", semicolonIndex + 1);
			if (slashIndex == -1) {
				return sb.substring(0, semicolonIndex);
			}
			sb.delete(semicolonIndex, slashIndex);
			semicolonIndex = sb.indexOf(";", semicolonIndex);
		}
		return sb.toString();
	}
	
	// ... 省略其他代码
	
}

可以看到UrlPathHelper是默认移除分号后的内容的,即禁用了矩阵变量。

我们可以定制化SpringBoot来启用矩阵变量。有两种定制化方式:

  1. 在容器中添加一个WebMvcConfigurer接口的实现类(可以使用匿名内部类),
    重写configurePathMatch方法并设置UrlPathHelper不移除分号内容:
@Bean
public WebMvcConfigurer matrixVariableEnabledWebConfigurer() {
    return new WebMvcConfigurer() {
        @Override
        public void configurePathMatch(PathMatchConfigurer configurer) {
            UrlPathHelper urlPathHelper = new UrlPathHelper();
            // 设置启用矩阵变量
            urlPathHelper.setRemoveSemicolonContent(false);
            configurer.setUrlPathHelper(urlPathHelper);
        }
    };
}
  1. 编写一个配置类并实现WebMvcConfigurer接口,
    重写configurePathMatch方法并设置UrlPathHelper不移除分号内容:
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        UrlPathHelper urlPathHelper = new UrlPathHelper();
        // 设置启用矩阵变量
        urlPathHelper.setRemoveSemicolonContent(false);
        configurer.setUrlPathHelper(urlPathHelper);
    }
}

启用了矩阵变量之后就可以使用@MatrixVariable编写处理器了:

// 矩阵变量需要绑定一个路径变量
@RequestMapping("/example/{param}")
@ResponseBody
public Object matrixVariable(
    @PathVariable("param") String param,
    @MatrixVariable("id") Integer id,
    @MatrixVariable("value") List<String> value
) {
    return "param=" + "\nid=" + id + "\nvalue=" + value.toString();
}

目标URL:

localhost:8081/example/param;id=123;value=123,abc,ABC

测试结果:

param=param
id=123
value=[79, asd, 123]

1.3 参数绑定原理&注解型参数绑定原理

通过上节的分析,我们知道了SpringMVC处理请求的核心方法为
DispatcherServlet中的doDispatch方法,像上次那样继续分析。

1.3.1 调试流程

调试运行到doDispatch方法的1043行:

// org.springframework.web.servlet.DispatcherServlet
// DispatcherServlet.java Line:1043

// 为当前请求确定一个处理器适配器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

HandlerAdapter,即处理器适配器, 根据处理器映射器找到的处理器信息,
按照特定的规则去执行相关的处理器。

1.3.2 处理器适配器

SpringMVC有四个处理器适配器:

  • RequestMappingHandlerAdapter:

    处理使用@RequestMapping注解及其派生注解的处理器适配器;
  • HandlerFunctionAdapter:

    处理使用函数式(响应式)编程的处理器适配器;
  • HttpRequestHandlerAdapter:

    处理实现了HttpRequestHandler接口的处理器适配器;
  • SimpleControllerHandlerAdapter:

    处理在XML中使用SimpleControllerHandler配置的处理器适配器。

我们使用注解开发一般只需关注RequestMappingHandlerAdapter处理器适配器即可。

由于调试的目标方法是使用了@RequestMapping注解的处理器,
所以果不其然使用了RequestMappingHandlerAdapter处理器适配器。

调试继续往下运行到1060行:

// org.springframework.web.servlet.DispatcherServlet
// DispatcherServlet.java Line:1060

// 使用处理器适配器执行处理器
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

此段代码使用找到的处理器适配器,传入了Request和Response以及处理器,
返回ModelAndView对象,说明此段代码是真正执行处理器方法的,进入调试查看细节。

进入调试后,调试转到了AbstractHandlerMethodAdapter的handle方法:

// org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter
// AbstractHandlerMethodAdapter.java Line:82~88

@Override
@Nullable
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    //又调用一个方法继续执行
    return handleInternal(request, response, (HandlerMethod) handler);
}

继续深入,调试跳转到RequestMappingHandlerAdapter处理器适配器的handleInternal方法:

// org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
// RequestMappingHandlerAdapter.java Line:785~821

@Override
protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

    ModelAndView mav;
    checkRequest(request);

    // 如果需要线程同步,怎么处理
    if (this.synchronizeOnSession) {
        HttpSession session = request.getSession(false);
        if (session != null) {
            Object mutex = WebUtils.getSessionMutex(session);
            synchronized (mutex) {
                mav = invokeHandlerMethod(request, response, handlerMethod);
            }
        }
        else {
            mav = invokeHandlerMethod(request, response, handlerMethod);
        }
    }
    // 不需要线程同步的话,直接调用执行目标处理器方法
    else {
        mav = invokeHandlerMethod(request, response, handlerMethod);
    }

    // 设置浏览器缓存策略
    if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
        if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
            applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
        }
        else {
            prepareResponse(response);
        }
    }

    // 返回ModelAndView
    return mav;
}

继续调试,来到了invokeHandlerMethod方法:

// org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
// RequestMappingHandlerAdapter.java Line:850~904

// 真正使用处理器适配器执行目标处理器的方法
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
    try {
        // ... 省略其他代码
        // 给执行方法设置参数解析器
        if (this.argumentResolvers != null) {
            invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
        }
        // 给执行方法设置返回值解析器
        if (this.returnValueHandlers != null) {
            invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
        }
        // ... 省略其他代码
        // 调用处理器方法并处理请求
        invocableMethod.invokeAndHandle(webRequest, mavContainer);
        // ... 省略其他代码
    }
    finally {
        webRequest.requestCompleted();
    }
}

1.3.3 参数解析器

ArgumentResolver(参数解析器):SpringMVC使用参数解析器来处理请求中的参数,
把请求中的参数处理后与处理器方法的参数一一对应。

参数解析器本质是SpringMVC提供的一个接口:
HandlerMethodArgumentResolver。

这个接口有两个方法:

  • boolean supportsParameter:判断是否支持当前参数,如果支持则执行另一个方法;
  • Object resolveArgument:处理当前参数的方法。

SpringMVC提供了多达27种参数解析器实现类(Spring 2.4.5,严格顺序):

名称 作用
RequestParamMethodArgumentResolver
useDefaultResolution = false
解析带有@RequestParam注解的参数
RequestParamMapMethodArgumentResolver 解析带有@RequestParam注解且类型为Map的参数
PathVariableMethodArgumentResolver 解析带有@PathVariable注解的参数
PathVariableMapMethodArgumentResolver 解析带有@PathVariable注解且类型为Map的参数
MatrixVariableMethodArgumentResolver 解析带有@MatrixVariable注解的参数
MatrixVariableMapMethodArgumentResolver 解析带有@MatrixVariable注解且类型为Map的参数
ServletModelAttributeMethodProcessor
annotationNotRequired = false
解析带有@ModelAttribute注解的参数
RequestResponseBodyMethodProcessor 解析带有@RequestBody注解的参数
RequestPartMethodArgumentResolver 解析带有@RequestPart注解的参数
RequestHeaderMethodArgumentResolver 解析带有@RequestHeader注解的参数
RequestHeaderMapMethodArgumentResolver 解析带有@RequestHeader注解且类型为Map的参数
ServletCookieValueMethodArgumentResolver 解析带有@CookieValue注解的参数
ExpressionValueMethodArgumentResolver 解析带有@Value注解且包含占位符或SpEL表达式的参数
SessionAttributeMethodArgumentResolver 解析带有@SessionAttribute注解的参数
RequestAttributeMethodArgumentResolver 解析带有@RequestAttribute注解的参数
ServletRequestMethodArgumentResolver 解析类型与请求相关的Servlet API的参数(如HttpServletRequest)
ServletResponseMethodArgumentResolver 解析类型与响应相关的Servlet API的参数(如HttpServletResponse)
HttpEntityMethodProcessor 解析类型为HttpEntity或RequestEntity的参数
RedirectAttributesMethodArgumentResolver 解析类型为RedirectAttributes的参数
ModelMethodProcessor 解析类型为Model的参数
MapMethodProcessor 解析类型为Map的参数
ErrorsMethodArgumentResolver 解析类型为Error的参数(如BindingResult)
SessionStatusMethodArgumentResolver 解析类型为SessionStatus的参数
UriComponentsBuilderMethodArgumentResolver 解析类型为UriComponentsBuilder的参数
PrincipalMethodArgumentResolver 解析类型为Principal的参数
RequestParamMethodArgumentResolver
useDefaultResolution = true
解析不带@RequestParam注解且类型为简单类型的参数(即请求中的基本数据类型参数,无需使用@RequestParam注解即可匹配,要求请求参数名和方法参数名一一对应)
ServletModelAttributeMethodProcessor
annotationNotRequired = true
解析不带@ModelAttribute注解且类型不为简单类型的参数(如自定义POJO类型参数)

这些解析器有各有各的判断是否支持的方法,也各有各的解析方法。

进入invocableMethod.invokeAndHandle(webRequest, mavContainer);语句,来到执行处理器的方法invokeAndHandle

// org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod
// ServletInvocableHandlerMethod.java Line:103~133

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

    // 执行处理器方法并接收返回值
    Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
    
    // ... 省略其他代码
}

发现invokeForRequest方法执行后直接就是返回值,说明这句语句才是真正执行处理器方法的。

深入调试此语句,来到了invokeForRequest方法:

@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
    // 获取处理器方法参数的值
    Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
    // 日志记录
    if (logger.isTraceEnabled()) {
        logger.trace("Arguments: " + Arrays.toString(args));
    }
    // 通过反射调用处理器方法
    return doInvoke(args);
}

1.3.4 如何确定参数的值

我们终于找到了处理方法参数的方法getMethodArgumentValues

// org.springframework.web.method.support.InvocableHandlerMethod
// InvocableHandlerMethod.java Line:150~184

protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
    // 1. 获取处理器方法的参数列表
    MethodParameter[] parameters = getMethodParameters();
    // 2. 如果参数列表为空,则此处理器方法不存在参数,返回
    if (ObjectUtils.isEmpty(parameters)) {
        return EMPTY_ARGS;
    }
    // 3. 根据参数列表的长度创建一个Object数组用来存放参数的值
    Object[] args = new Object[parameters.length];
    // 4. 遍历参数列表
    for (int i = 0; i < parameters.length; i++) {
        // 5. 从参数列表中拿到第i个参数
        MethodParameter parameter = parameters[i];
        // 6. 初始化当前参数的名称查找器
        parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
        // 7. 使用当前参数的名称查找器为当前参数从传入的已提供的参数中查找对应的值,并放入Object数组
        args[i] = findProvidedArgument(parameter, providedArgs);
        // 8. 如果已提供的参数查找到了,跳过本次循环,如果没有查找到,则到下面使用参数解析器从请求中解析参数的值
        if (args[i] != null) {
            continue;
        }
        // 9. 调用参数解析器组合的supportsParameter方法判断是否存在能解析此参数的解析器,不存在的话抛出异常
        if (!this.resolvers.supportsParameter(parameter)) {
            throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
        }
        try {
            // 10. 调用参数解析器组合的resolveArgument方法处理参数,并把处理后获取的值放入Object数组
            args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
        }
        catch (Exception ex) {
            // Leave stack trace for later, exception may actually be resolved and handled...
            if (logger.isDebugEnabled()) {
                String exMsg = ex.getMessage();
                if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
                    logger.debug(formatArgumentError(parameter, exMsg));
                }
            }
            throw ex;
        }
    }
    return args;
}

参数解析器组合的supportParameter方法和resolveArgument方法共同调用getArgumentResolver方法获取参数解析器:

// org.springframework.web.method.support.HandlerMethodArgumentResolverComposite
// HandlerMethodArgumentResolverComposite.java Line:128~141

@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
    // 先从缓存中拿到解析器判断
    HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
    // 如果缓存中没有解析器或者解析器不支持,用for循环挨个调用参数解析器列表中解析器的supportsParameter方法
    if (result == null) {
        for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
            // 如果有支持此参数的解析器
            if (resolver.supportsParameter(parameter)) {
                // 返回此解析器
                result = resolver;
                // 并把此解析器放入缓存
                this.argumentResolverCache.put(parameter, result);
                // 跳出循环
                break;
            }
        }
    }
    return result;
}

参数解析器组合(HandlerMethodArgumentResolverComposite)的supportParameter方法:

// org.springframework.web.method.support.HandlerMethodArgumentResolverComposite
// HandlerMethodArgumentResolverComposite.java Line:100~103

@Override
public boolean supportsParameter(MethodParameter parameter) {
    // 直接调用getArgumentResolver判断是否存在能解析此参数的解析器
    return getArgumentResolver(parameter) != null;
}

参数解析器组合(HandlerMethodArgumentResolverComposite)的resolveArgument方法:

// org.springframework.web.method.support.HandlerMethodArgumentResolverComposite
// HandlerMethodArgumentResolverComposite.java Line:111~122

@Override
@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
    // 调用getArgumentResolver获取能够解析此参数的解析器
    HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
    // 如果null,即不存在能够解析此参数的解析器,抛出异常
    if (resolver == null) {
        throw new IllegalArgumentException("Unsupported parameter type [" +
        parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
    }
    // 如果存在,直接调用解析器的resolveArgument方法解析并返回参数的值
    return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}

总结参数处理的具体过程:

  1. SpringMVC处理参数的具体过程在InvocableHandlerMethod的方法getMethodArgumentValues中进行;
  2. 获取处理器方法的参数列表;
  3. 根据参数列表的长度,创建一个相同长度的Object数组来存储对应参数的值;
  4. 遍历参数列表:
    1. 从参数列表拿取参数;
    2. 初始化当前参数的名称查找器(parameterNameDiscoverer);
    3. 如果传入的参数中存在当前参数,直接将值存入Object数组并跳出当前循环,进行下个参数的遍历;
    4. 调用参数解析器组合的supportParameter方法来确定当前参数是否可以被解析;如果不能则抛出异常;
    5. 调用参数解析器组合的resolveArgument方法,解析请求获取当前参数的值,并把值存入Object数组;
  5. 参数列表中所有参数都遍历完成后,返回保存了所有参数的值的Object数组,至此参数处理完成。

研究某类参数的绑定原理,可以从对应参数解析器的supportParameter方法和resolveArgument方法入手。

1.3.5 注解型参数绑定原理

以注解了@RequestHeader且类型为String的参数为例,分析绑定原理。
从RequestHeaderMethodArgumentResolver参数解析器入手。

RequestHeaderMethodArgumentResolver参数解析器的supportParameter方法:

// org.springframework.web.method.annotation.RequestHeaderMethodArgumentResolver
// RequestHeaderMethodArgumentResolver.java Line:60~64

@Override
public boolean supportsParameter(MethodParameter parameter) {
    // 如果参数注解了@RequestHeader且类型不为Map则支持解析此参数
    return (parameter.hasParameterAnnotation(RequestHeader.class) &&
        !Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType()));
}

RequestHeaderMethodArgumentResolver继承于AbstractNamedValueMethodArgumentResolver,
AbstractNamedValueMethodArgumentResolver实现了HandlerMethodArgumentResolver参数解析器接口,
实现的resolveArgument方法主要调用抽象方法resolveName来获取值。
所以RequestHeaderMethodArgumentResolver参数解析器的resolveName方法是来获取值的:

// org.springframework.web.method.annotation.RequestHeaderMethodArgumentResolver
// RequestHeaderMethodArgumentResolver.java Line:73~83

@Override
@Nullable
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
    // 直接调用request的getHeaderValues获取值
    String[] headerValues = request.getHeaderValues(name);
    if (headerValues != null) {
        return (headerValues.length == 1 ? headerValues[0] : headerValues);
    }
    else {
        return null;
    }
}

2. Servlet API

原生Servlet API对象是可以直接作为参数在处理器中使用的。

2.1 Servlet API种类

  1. HttpServletRequest(常用)
  2. HttpServletResponse(常用)
  3. HttpSession(常用)
  4. java.security.Principal(与安全协议有关的,不常用)
  5. Locale(与国际化有关的,不常用)
  6. InputStream(request.getInputStream();)
  7. OutputStream(response.getOutputStream();)
  8. Reader(request.getReader();)
  9. Writer(response.getWriter();)

2.2 Servlet API参数绑定原理

以HttpServletRequest对象为例,分析绑定原理。
从ServletRequestMethodArgumentResolver参数解析器入手。

ServletRequestMethodArgumentResolver参数解析器能够与请求相关的Servlet API的参数。

supportParameter方法:

// org.springframework.web.servlet.mvc.method.annotation.ServletRequestMethodArgumentResolver
// ServletRequestMethodArgumentResolver.java Line:86~101

@Override
public boolean supportsParameter(MethodParameter parameter) {
    Class<?> paramType = parameter.getParameterType();
    // 参数为WebRequest类型或其子类
    return (WebRequest.class.isAssignableFrom(paramType) ||
        // 参数为ServletRequest类型或其子类
        ServletRequest.class.isAssignableFrom(paramType) ||
        // 参数为MultipartRequest类型或其子类
        MultipartRequest.class.isAssignableFrom(paramType) ||
        // 参数为HttpSession类型或其子类
        HttpSession.class.isAssignableFrom(paramType) ||
        // PushBuilder存在且参数为PushBuilder类型或其子类
        (pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) ||
        // 参数为Principal类型或其子类且参数没有使用注解
        (Principal.class.isAssignableFrom(paramType) && !parameter.hasParameterAnnotations()) ||
        // 参数为InputStream类型或其子类
        InputStream.class.isAssignableFrom(paramType) ||
        // 参数为Reader类型或其子类
        Reader.class.isAssignableFrom(paramType) ||
        // 参数为HttpMethod类型
        HttpMethod.class == paramType ||
        // 参数为Locale类型
        Locale.class == paramType ||
        // 参数为TimeZone类型
        TimeZone.class == paramType ||
        // 参数为ZoneId类型
        ZoneId.class == paramType);
	}

ServletRequestMethodArgumentResolver参数解析器支持上述参数类型的解析。

resolveArgument方法:

// org.springframework.web.servlet.mvc.method.annotation.ServletRequestMethodArgumentResolver
// ServletRequestMethodArgumentResolver.java Line:103~125

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

    Class<?> paramType = parameter.getParameterType();

    // 处理WebRequest类型及其子类的参数
    // WebRequest / NativeWebRequest / ServletWebRequest
    if (WebRequest.class.isAssignableFrom(paramType)) {
        if (!paramType.isInstance(webRequest)) {
            throw new IllegalStateException(
                    "Current request is not of type [" + paramType.getName() + "]: " + webRequest);
        }
        return webRequest;
    }

    // 处理ServletRequest类型及其子类的参数
    // ServletRequest / HttpServletRequest / MultipartRequest / MultipartHttpServletRequest
    if (ServletRequest.class.isAssignableFrom(paramType) || MultipartRequest.class.isAssignableFrom(paramType)) {
        // 调用了resolveNativeRequest方法
        return resolveNativeRequest(webRequest, paramType);
    }

    // 其他类型的参数基于HttpServletRequest,在另一个方法中处理
    // HttpServletRequest required for all further argument types
	return resolveArgument(paramType, resolveNativeRequest(webRequest, HttpServletRequest.class));
}

处理ServletRequest类型及其子类的参数时调用了resolveNativeRequest方法:

// org.springframework.web.servlet.mvc.method.annotation.ServletRequestMethodArgumentResolver
// ServletRequestMethodArgumentResolver.java Line:127~134

private <T> T resolveNativeRequest(NativeWebRequest webRequest, Class<T> requiredType) {
    T nativeRequest = webRequest.getNativeRequest(requiredType);
    if (nativeRequest == null) {
        throw new IllegalStateException(
                "Current request is not of type [" + requiredType.getName() + "]: " + webRequest);
    }
    return nativeRequest;
}

NativeWebRequest是一个封装对象,封装了HttpServletRequest和HttpServletResponse。

3. 复杂参数

复杂参数是Spring提供的内部参数,诸如Model,BindingResult这一类的。

3.1 复杂参数的种类

  1. Map,Model,ModelMap;
  2. Errors/BindingResult;
  3. RedirectAttributes;
  4. ServletResponse;
  5. SessionStatus;
  6. UriComponentsBuilder;
  7. ServletUriComponentsBuilder等。

3.2 复杂参数绑定原理

以Model为例分析复杂参数的绑定原理。

在之前SpringMVC的学习中,了解到使用Model,Map和ModelMap都会在Request域中存放数据,
其本质是BindingAwareModelMap,也叫隐含模型。

ModelMethodProcessor参数解析器解析类型为Model的参数。

supportParameter方法:

// org.springframework.web.method.annotation.ModelMethodProcessor
// ModelMethodProcessor.java Line:42~45

@Override
public boolean supportsParameter(MethodParameter parameter) {
    // 参数为Model类型及其子类
    return Model.class.isAssignableFrom(parameter.getParameterType());
}

resolveArgument方法:

// org.springframework.web.method.annotation.ModelMethodProcessor
// ModelMethodProcessor.java Line:47~54

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

    // 调用ModelAndViewContainer容器的getModel方法
    Assert.state(mavContainer != null, "ModelAndViewContainer is required for model exposure");
    return mavContainer.getModel();
}

ModelAndViewContainer就是保存了Model数据和View视图名的容器。

来到ModelAndViewContainer容器的getModel方法:

// org.springframework.web.method.support.ModelAndViewContainer
// ModelAndViewContainer.java Line:140~150

public ModelMap getModel() {
    if (useDefaultModel()) {
        return this.defaultModel;
    }
    else {
        if (this.redirectModel == null) {
            this.redirectModel = new ModelMap();
        }
        return this.redirectModel;
    }
}

defaultModel是默认使用的Model,也就是BindingAwareModelMap:

// org.springframework.web.method.support.ModelAndViewContainer
// ModelAndViewContainer.java Line:57

private final ModelMap defaultModel = new BindingAwareModelMap();

redirectModel是用于redirect操作时的Model。

Map,和ModelMap的参数绑定原理和上述相同。

4. 自定义POJO类型参数

SpringMVC也能处理自定义POJO类型参数,传过来的参数能够自动封装为POJO对象。

4.1 找到处理自定义POJO类型参数的解析器

供测试的一个简单的POJO类:

public class User implements Serializable {
    private String name;
    private Integer age;
}

刚才学习了参数绑定原理,我们直接调试跳到for循环遍历参数解析器的断点:

// org.springframework.web.method.support.HandlerMethodArgumentResolverComposite
// HandlerMethodArgumentResolverComposite.java Line:128~141

@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
    HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
    if (result == null) {
        for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
            if (resolver.supportsParameter(parameter)) {
                result = resolver;
                this.argumentResolverCache.put(parameter, result);
                break;
            }
        }
    }
    return result;
}

经过调试遍历,最终确定了支持自定义POJO类型参数的解析器为ServletModelAttributeMethodProcessor。

4.2 简单类型

但是ServletModelAttributeMethodProcessor是处理带有@ModelAttribute注解的参数的解析器,为什么会支持我们的自定义POJO类型呢?

查看源码,这个解析器的supportParameter方法使用了父类ModelAttributeMethodProcessor的:

// org.springframework.web.method.annotation.ModelAttributeMethodProcessor
// ModelAttributeMethodProcessor.java Line:104~108
@Override
public boolean supportsParameter(MethodParameter parameter) {
    // 参数是否有@ModelAttribute注解,有的话就为true
    return (parameter.hasParameterAnnotation(ModelAttribute.class) ||
        // 参数不为简单类型且annotationNotRequired为true,才为true
        (this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType())));
}

再次调试,发现这个ServletModelAttributeMethodProcessor的annotationNotRequired为true。

那么如何判断参数不是简单类型呢?进入BeanUtils类的isSimpleProperty方法:

// org.springframework.beans.BeanUtils
// BeanUtils.java Line:658~661

public static boolean isSimpleProperty(Class<?> type) {
    Assert.notNull(type, "'type' must not be null");
    // 此类型为简单类型或者此数组中的元素为简单类型时为true
    return isSimpleValueType(type) || (type.isArray() && isSimpleValueType(type.getComponentType()));
}

判断逻辑在isSimpleValueType方法中:

// org.springframework.beans.BeanUtils
// BeanUtils.java Line:672~684

public static boolean isSimpleValueType(Class<?> type) {
    // 此类型不为void类型
    return (Void.class != type && void.class != type &&
        // 此类型为基本数据类型或其基本数据类型的包装类
        (ClassUtils.isPrimitiveOrWrapper(type) ||
        // 此类型为枚举类型及其子类
        Enum.class.isAssignableFrom(type) ||
        // 此类型为数组类型及其子类
        CharSequence.class.isAssignableFrom(type) ||
        // 此类型为数字类型及其子类
        Number.class.isAssignableFrom(type) ||
        // 此类型为日期类型及其子类
        Date.class.isAssignableFrom(type) ||
        // 此类型为Temporal类型
        Temporal.class.isAssignableFrom(type) ||
        // 此类型为URI类型
        URI.class == type ||
        // 此类型为URL类型
        URL.class == type ||
        // 此类型为Locale类型
        Locale.class == type ||
        // 此类型为Class类型
        Class.class == type));
}

Spring将符合上述条件的类型称为简单类型。

4.3 自定义POJO类型参数绑定原理

ModelAttributeMethodProcessor的resolveArgument方法:

// org.springframework.web.method.annotation.ModelAttributeMethodProcessor
// ModelAttributeMethodProcessor.java Line:119~186

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

    Assert.state(mavContainer != null, "ModelAttributeMethodProcessor requires ModelAndViewContainer");
    Assert.state(binderFactory != null, "ModelAttributeMethodProcessor requires WebDataBinderFactory");

    // 获取参数的名称(如果有@ModelAttribute注解的话使用注解内的名称,如果没有的话再去调用其他方法获取参数名)
    String name = ModelFactory.getNameForParameter(parameter);
    ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
    // 如果当前参数使用了@ModelAttribute注解,则直接绑定隐含模型中的对应数据
    if (ann != null) {
        mavContainer.setBinding(name, ann.binding());
    }

    Object attribute = null;
    BindingResult bindingResult = null;

    // 如果隐含模型中有同名参数,直接绑定对应数据
    if (mavContainer.containsAttribute(name)) {
        attribute = mavContainer.getModel().get(name);
    }
    else {
        // Create attribute instance
        // 创建一个自定义POJO类型实例
        try {
            attribute = createAttribute(name, parameter, binderFactory, webRequest);
        }
        // 如果创建实例过程有异常怎么处理
        catch (BindException ex) {
            if (isBindExceptionRequired(parameter)) {
                // No BindingResult parameter -> fail with BindException
                throw ex;
            }
            // Otherwise, expose null/empty value and associated BindingResult
            if (parameter.getParameterType() == Optional.class) {
                attribute = Optional.empty();
            }
            else {
                attribute = ex.getTarget();
            }
            bindingResult = ex.getBindingResult();
        }
    }

    // 如果创建实例成功
    if (bindingResult == null) {
        // Bean property binding and validation;
        // skipped in case of binding failure on construction.
        // 为实例和请求对象创建一个绑定器
        // 绑定器中有当前实例(target),类型转换服务(conversionService,内有类型转换器converter))
        WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
        // 获取绑定器中的实例
        if (binder.getTarget() != null) {
            if (!mavContainer.isBindingDisabled(name)) {
                // 进行数据绑定
                // 此方法传入了绑定器和请求对象
                // 数据绑定的大概流程就是使用绑定器利用反射将请求中的参数绑定到实例的属性中
                // 如果遇到类型不匹配的话,使用绑定器内的类型转换服务,调用对应的转换器转换类型
                bindRequestParameters(binder, webRequest);
            }
            // 验证是存在校验问题
            validateIfApplicable(binder, parameter);
            // 如果存在校验问题且需要返回校验错误,则抛出绑定异常
            if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
                throw new BindException(binder.getBindingResult());
            }
        }
        // Value type adaptation, also covering java.util.Optional
        // 判断数据绑定后的实例类型是否和参数类型相同,如果不相同再转换类型
        if (!parameter.getParameterType().isInstance(attribute)) {
            attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
        }
        bindingResult = binder.getBindingResult();
    }

    // Add resolved attribute and BindingResult at the end of the model
    // 将绑定结果放入隐含模型
    Map<String, Object> bindingResultModel = bindingResult.getModel();
    // 将隐含模型中的参数替换为数据绑定后的参数
    mavContainer.removeAttributes(bindingResultModel);
    mavContainer.addAllAttributes(bindingResultModel);

    // 返回实例
    return attribute;
}

你可能感兴趣的:(后端,java,spring,boot,springmvc)