SpringBoot集成SpringMVC之参数解析

SpringMVC之参数解析器

一、参数解析器

1.1、什么是参数解析器?

参数解析器就是HandlerMethod对应的方法中到底能够写什么种类的数据类型。

确定将要执行的目标方法的每一个参数的值是什么;SpringMVC目标方法能写多少种参数类型。取决于参数解析器。

如下所示:

@RestController
@RequestMapping(path = "/hello")
public class HelloController {
    @RequestMapping(path = "/test")
    public String test(HttpServletRequest request,User user){
        return "success";
    }
}

如这里的两个参数:HttpServletRequest和User类型,springmvc到底是如何来进行解析的呢?

1.2、参数解析器接口

public interface HandlerMethodArgumentResolver {
    
	// 判断是否支持当前参数类型
	boolean supportsParameter(MethodParameter parameter);

    // 支持当前参数类型之后,如何解析参数
	Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;

}

如上面的:

@RestController
@RequestMapping(path = "/hello")
public class HelloController {
    @RequestMapping(path = "/test")
    public String test(HttpServletRequest request,User user){
        return "success";
    }
}

参数解析器针对HttpServletRequest是否支持,如果支持该种数据类型,应该如何解析?参数解析器针对User是否支持,如果支持该种数据类型,应该如何解析?

1.3、SpringMVC自带的参数解析器初始化

因为参数解析器是和RequestMappingHandlerAdapter绑定在一起的,所以看下RequestMappingHandlerAdapter类。

RequestMappingHandlerAdapter实现了InitializingBean接口,那么就重写了afterPropertiesSet方法。所以简单看下这里的方法是如何来实现的。

public void afterPropertiesSet() {
    // Do this first, it may add ResponseBody advice beans
    initControllerAdviceCache();
    
	// 参数解析器
    if (this.argumentResolvers == null) {
        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);
    }
}

下面重点分析一下参数解析器的初始化过程。

if (this.argumentResolvers == null) {
    // 获取得到默认的参数解析器
    List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
    // 赋值给RequestMappingHandlerAdapter的属性argumentResolvers
    this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}

所以看一下默认的参数解析器:

private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
    List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>(30);

    // Annotation-based argument resolution
    // 注解版参数
    
    // 处理@RequestParam
    resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
    resolvers.add(new RequestParamMapMethodArgumentResolver());
    // 处理@PathVariable   
    resolvers.add(new PathVariableMethodArgumentResolver());
    resolvers.add(new PathVariableMapMethodArgumentResolver());
    // 处理@atrixVariable       
    resolvers.add(new MatrixVariableMethodArgumentResolver());
    resolvers.add(new MatrixVariableMapMethodArgumentResolver());
    // 处理@ModelAttribute和简单类型参数的
    resolvers.add(new ServletModelAttributeMethodProcessor(false));
    // 处理@RequestBody注解
    resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
    // 处理@RequestPart注解和@RequestParam注解
    resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));
    // 处理@RequestHeader且方法参数不是Map类型的
    resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
    // 处理@RequestHeader且方法参数是Map类型    
    resolvers.add(new RequestHeaderMapMethodArgumentResolver());
    // 处理@CookieValue注解
    resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));
    // 处理@Value注解
    resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
    resolvers.add(new SessionAttributeMethodArgumentResolver());
    // 处理@SessionAttribute注解
    resolvers.add(new RequestAttributeMethodArgumentResolver());

    // Type-based argument resolution
    // 基于类型的参数解析器
    // 处理ServletRequest类型参数
    resolvers.add(new ServletRequestMethodArgumentResolver());
    // 处理ServletResponse类型参数    
    resolvers.add(new ServletResponseMethodArgumentResolver());
    // 处理HttpEntity或者RequestEntity类型参数 
    resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
    // 处理RedirectAttributes类型参数 
    resolvers.add(new RedirectAttributesMethodArgumentResolver());
    // 处理Model类型参数 
    resolvers.add(new ModelMethodProcessor());
    // 处理Map类型且没有注解
    resolvers.add(new MapMethodProcessor());
    // 处理Errors类型
    resolvers.add(new ErrorsMethodArgumentResolver());
    // 处理SessionStatus类型
    resolvers.add(new SessionStatusMethodArgumentResolver());
    // 处理UriComponentsBuilder或者ServletUriComponentsBuilder类型
    resolvers.add(new UriComponentsBuilderMethodArgumentResolver());

    // Custom arguments
    // 自定义的参数解析器
    // 支持什么需要用户自定义
    if (getCustomArgumentResolvers() != null) {
        resolvers.addAll(getCustomArgumentResolvers());
    }

    // Catch-all
    // 所有类型的参数解析器
    // 处理@RequestPart注解和@RequestParam注解
    resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
    // 支持@ModelAttribute注解和简单类型
    resolvers.add(new ServletModelAttributeMethodProcessor(true));

    return resolvers;
}

1.4、自定义参数解析器

参考链接:https://blog.csdn.net/x763795151/article/details/88756290

二、参数支持类型

2.1、注解:

@PathVariable、@RequestHeader、@ModelAttribute、@RequestParam、@MatrixVariable、@CookieValue、@RequestBody

@RestController
public class ParameterTestController {


    //  car/2/owner/zhangsan
    @GetMapping("/car/{id}/owner/{username}")
    public Map<String,Object> getCar(@PathVariable("id") Integer id,
                                     @PathVariable("username") String name,
                                     @PathVariable Map<String,String> pv,
                                     @RequestHeader("User-Agent") String userAgent,
                                     @RequestHeader Map<String,String> header,
                                     @RequestParam("age") Integer age,
                                     @RequestParam("inters") List<String> inters,
                                     @RequestParam Map<String,String> params,
                                     @CookieValue("_ga") String _ga,
                                     @CookieValue("_ga") Cookie cookie){


        Map<String,Object> map = new HashMap<>();

//        map.put("id",id);
//        map.put("name",name);
//        map.put("pv",pv);
//        map.put("userAgent",userAgent);
//        map.put("headers",header);
        map.put("age",age);
        map.put("inters",inters);
        map.put("params",params);
        map.put("_ga",_ga);
        System.out.println(cookie.getName()+"===>"+cookie.getValue());
        return map;
    }


    @PostMapping("/save")
    public Map postMethod(@RequestBody String content){
        Map<String,Object> map = new HashMap<>();
        map.put("content",content);
        return map;
    }


    //1、语法: 请求路径:/cars/sell;low=34;brand=byd,audi,yd
    //2、SpringBoot默认是禁用了矩阵变量的功能
    //      手动开启:原理。对于路径的处理。UrlPathHelper进行解析。
    //              removeSemicolonContent(移除分号内容)支持矩阵变量的
    //3、矩阵变量必须有url路径变量才能被解析
    @GetMapping("/cars/{path}")
    public Map carsSell(@MatrixVariable("low") Integer low,
                        @MatrixVariable("brand") List<String> brand,
                        @PathVariable("path") String path){
        Map<String,Object> map = new HashMap<>();

        map.put("low",low);
        map.put("brand",brand);
        map.put("path",path);
        return map;
    }

    // /boss/1;age=20/2;age=10

    @GetMapping("/boss/{bossId}/{empId}")
    public Map boss(@MatrixVariable(value = "age",pathVar = "bossId") Integer bossAge,
                    @MatrixVariable(value = "age",pathVar = "empId") Integer empAge){
        Map<String,Object> map = new HashMap<>();

        map.put("bossAge",bossAge);
        map.put("empAge",empAge);
        return map;

    }

}

2.2、Servlet API:

WebRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId

ServletRequestMethodArgumentResolver 以上的部分参数

@Override
	public boolean supportsParameter(MethodParameter parameter) {
		Class<?> paramType = parameter.getParameterType();
		return (WebRequest.class.isAssignableFrom(paramType) ||
				ServletRequest.class.isAssignableFrom(paramType) ||
				MultipartRequest.class.isAssignableFrom(paramType) ||
				HttpSession.class.isAssignableFrom(paramType) ||
				(pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) ||
				Principal.class.isAssignableFrom(paramType) ||
				InputStream.class.isAssignableFrom(paramType) ||
				Reader.class.isAssignableFrom(paramType) ||
				HttpMethod.class == paramType ||
				Locale.class == paramType ||
				TimeZone.class == paramType ||
				ZoneId.class == paramType);
	}

2.3、复杂参数:

MapModel(map、model里面的数据会被放在request的请求域 request.setAttribute)、Errors/BindingResult、RedirectAttributes( 重定向携带数据)ServletResponse(response)、SessionStatus、UriComponentsBuilder、ServletUriComponentsBuilder

Map<String,Object> map,  Model model, HttpServletRequest request 都是可以给request域中放数据,
request.getAttribute();

Map、Model类型的参数,会返回 mavContainer.getModel();---> BindingAwareModelMap 是Model 也是Map

mavContainer.getModel(); 获取到值的

img

img

img

2.4、自定义对象参数:

可以自动类型转换与格式化,可以级联封装。

/**
 *     姓名: <input name="userName"/> <br/>
 *     年龄: <input name="age"/> <br/>
 *     生日: <input name="birth"/> <br/>
 *     宠物姓名:<input name="pet.name"/><br/>
 *     宠物年龄:<input name="pet.age"/>
 */
@Data
public class Person {
    
    private String userName;
    private Integer age;
    private Date birth;
    private Pet pet;
    
}

@Data
public class Pet {

    private String name;
    private String age;

}

result

三、例举常用参数解析器

那么来确定下每个参数都支持哪些参数的解析

RequestParamMethodArgumentResolver

判断是否支持
	public boolean supportsParameter(MethodParameter parameter) {
		if (parameter.hasParameterAnnotation(RequestParam.class)) {
			if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
				RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
				return (requestParam != null && StringUtils.hasText(requestParam.name()));
			}
			else {
				return true;
			}
		}
		else {
			if (parameter.hasParameterAnnotation(RequestPart.class)) {
				return false;
			}
			parameter = parameter.nestedIfOptional();
			if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {
				return true;
			}
			else if (this.useDefaultResolution) {
				return BeanUtils.isSimpleProperty(parameter.getNestedParameterType());
			}
			else {
				return false;
			}
		}
	}

下面画个图来描述一下这里的过程

这里也就说明了,@RequestParam也支持文件上传,也支持获取得到请求参数,也支持参数上写的是Map但是指定了对应的name的值。那么接下来看看是如何来进行解析的。

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

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

		Object resolvedName = resolveEmbeddedValuesAndExpressions(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 (arg == null) {
			if (namedValueInfo.defaultValue != null) {
				arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
			}
			else if (namedValueInfo.required && !nestedParameter.isOptional()) {
				handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
			}
			arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
		}
		else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
			arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
		}

		if (binderFactory != null) {
			WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
			try {
				arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
			}
			catch (ConversionNotSupportedException ex) {
				throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),
						namedValueInfo.name, parameter, ex.getCause());
			}
			catch (TypeMismatchException ex) {
				throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),
						namedValueInfo.name, parameter, ex.getCause());
			}
			// Check for null value after conversion of incoming argument value
			if (arg == null && namedValueInfo.defaultValue == null &&
					namedValueInfo.required && !nestedParameter.isOptional()) {
				handleMissingValueAfterConversion(namedValueInfo.name, nestedParameter, webRequest);
			}
		}

		handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);

		return arg;
	}

首先获取得到@RequestParam的name或者是value的值,如果为空,抛出异常。

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

然后获取得到参数上的名称,这里针对了上面的是否是文件还是普通的获取得到请求参数的值。

如果是文件,可以看到直接转换成了对应的集合名称;

如果是路径变量,这里直接从reqeust域对象中来获取得到对应的值

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

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

		Object arg = null;
		MultipartRequest multipartRequest = request.getNativeRequest(MultipartRequest.class);
		if (multipartRequest != null) {
			List<MultipartFile> files = multipartRequest.getFiles(name);
			if (!files.isEmpty()) {
				arg = (files.size() == 1 ? files.get(0) : files);
			}
		}
		if (arg == null) {
			String[] paramValues = request.getParameterValues(name);
			if (paramValues != null) {
				arg = (paramValues.length == 1 ? paramValues[0] : paramValues);
			}
		}
		return arg;
	}

然后从WebDataBinder来将对应的值来进行转换得到我们想到转换的类型。因为这里从前端传递过来的参数都是byte类型的。

如果是文件,那么需要转换到文件类型;如果提交的是普通字符串的字符,那么我们需要转换成对应的类型。

这里的转换过程后面会继续提到。

RequestParamMapMethodArgumentResolver

判断
	public boolean supportsParameter(MethodParameter parameter) {
		RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
		return (requestParam != null && Map.class.isAssignableFrom(parameter.getParameterType()) &&
				!StringUtils.hasText(requestParam.name()));
	}

直接判断方法参数上是否有RequestParam注解,并且是Map类型的以及RequestParam注解中的属性不能有值。

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

		ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);

		if (MultiValueMap.class.isAssignableFrom(parameter.getParameterType())) {
			// MultiValueMap
			Class<?> valueType = resolvableType.as(MultiValueMap.class).getGeneric(1).resolve();
			if (valueType == MultipartFile.class) {
				MultipartRequest multipartRequest = MultipartResolutionDelegate.resolveMultipartRequest(webRequest);
				return (multipartRequest != null ? multipartRequest.getMultiFileMap() : new LinkedMultiValueMap<>(0));
			}
			else if (valueType == Part.class) {
				HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
				if (servletRequest != null && MultipartResolutionDelegate.isMultipartRequest(servletRequest)) {
					Collection<Part> parts = servletRequest.getParts();
					LinkedMultiValueMap<String, Part> result = new LinkedMultiValueMap<>(parts.size());
					for (Part part : parts) {
						result.add(part.getName(), part);
					}
					return result;
				}
				return new LinkedMultiValueMap<>(0);
			}
			else {
				Map<String, String[]> parameterMap = webRequest.getParameterMap();
				MultiValueMap<String, String> result = new LinkedMultiValueMap<>(parameterMap.size());
				parameterMap.forEach((key, values) -> {
					for (String value : values) {
						result.add(key, value);
					}
				});
				return result;
			}
		}

		else {
			// Regular Map
			Class<?> valueType = resolvableType.asMap().getGeneric(1).resolve();
			if (valueType == MultipartFile.class) {
				MultipartRequest multipartRequest = MultipartResolutionDelegate.resolveMultipartRequest(webRequest);
				return (multipartRequest != null ? multipartRequest.getFileMap() : new LinkedHashMap<>(0));
			}
			else if (valueType == Part.class) {
				HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
				if (servletRequest != null && MultipartResolutionDelegate.isMultipartRequest(servletRequest)) {
					Collection<Part> parts = servletRequest.getParts();
					LinkedHashMap<String, Part> result = CollectionUtils.newLinkedHashMap(parts.size());
					for (Part part : parts) {
						if (!result.containsKey(part.getName())) {
							result.put(part.getName(), part);
						}
					}
					return result;
				}
				return new LinkedHashMap<>(0);
			}
			else {
				Map<String, String[]> parameterMap = webRequest.getParameterMap();
				Map<String, String> result = CollectionUtils.newLinkedHashMap(parameterMap.size());
				parameterMap.forEach((key, values) -> {
					if (values.length > 0) {
						result.put(key, values[0]);
					}
				});
				return result;
			}
		}
	}

得到对应值来进行转换,可以看到这里也可以书写其他类型的map,甚至里面保存了文件类型都是可以的。这里不在来进行赘述了。

PathVariableMethodArgumentResolver

判断是否支持
	public boolean supportsParameter(MethodParameter parameter) {
		if (!parameter.hasParameterAnnotation(PathVariable.class)) {
			return false;
		}
		if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
			PathVariable pathVariable = parameter.getParameterAnnotation(PathVariable.class);
			return (pathVariable != null && StringUtils.hasText(pathVariable.value()));
		}
		return true;
	}

方法上如果没有PathVariable直接返回false,如果是map类型,必须要有值。和上面的RequestParamMethodArgumentResolver类似

解析

解析过程和RequestParamMethodArgumentResolver是一样的。

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

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

		Object resolvedName = resolveEmbeddedValuesAndExpressions(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 (arg == null) {
			if (namedValueInfo.defaultValue != null) {
				arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
			}
			else if (namedValueInfo.required && !nestedParameter.isOptional()) {
				handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
			}
			arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
		}
		else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
			arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
		}

		if (binderFactory != null) {
			WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
			try {
				arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
			}
			catch (ConversionNotSupportedException ex) {
				throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),
						namedValueInfo.name, parameter, ex.getCause());
			}
			catch (TypeMismatchException ex) {
				throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),
						namedValueInfo.name, parameter, ex.getCause());
			}
			// Check for null value after conversion of incoming argument value
			if (arg == null && namedValueInfo.defaultValue == null &&
					namedValueInfo.required && !nestedParameter.isOptional()) {
				handleMissingValueAfterConversion(namedValueInfo.name, nestedParameter, webRequest);
			}
		}

		handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);

		return arg;
	}

PathVariableMapMethodArgumentResolver

路径变量map解析器,那么对应的肯定还有一个PathVariableMethodArgumentResolver,因为这里是去掉map的。

判断是否支持
	public boolean supportsParameter(MethodParameter parameter) {
		PathVariable ann = parameter.getParameterAnnotation(PathVariable.class);
		return (ann != null && Map.class.isAssignableFrom(parameter.getParameterType()) &&
				!StringUtils.hasText(ann.value()));
	}

判断是否有PathVariable注解以及是否是Map类型以及对应的值不能够有值,因为是获取得到所有的。

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

		@SuppressWarnings("unchecked")
		Map<String, String> uriTemplateVars =
				(Map<String, String>) webRequest.getAttribute(
						HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);

		if (!CollectionUtils.isEmpty(uriTemplateVars)) {
			return new LinkedHashMap<>(uriTemplateVars);
		}
		else {
			return Collections.emptyMap();
		}
	}

获取得到所有的参数来进行封装成LinkedHashMap,然后将其返回。所以可以看到为什么从外部接收来到的参数是LinkedHashMap类型的,这里需要注意下

MatrixVariableMethodArgumentResolver

MatrixVariableMapMethodArgumentResolver

ServletRequestMethodArgumentResolver

判断是否支持
	public boolean supportsParameter(MethodParameter parameter) {
		Class<?> paramType = parameter.getParameterType();
		return (WebRequest.class.isAssignableFrom(paramType) ||
				ServletRequest.class.isAssignableFrom(paramType) ||
				MultipartRequest.class.isAssignableFrom(paramType) ||
				HttpSession.class.isAssignableFrom(paramType) ||
				(pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) ||
				(Principal.class.isAssignableFrom(paramType) && !parameter.hasParameterAnnotations()) ||
				InputStream.class.isAssignableFrom(paramType) ||
				Reader.class.isAssignableFrom(paramType) ||
				HttpMethod.class == paramType ||
				Locale.class == paramType ||
				TimeZone.class == paramType ||
				ZoneId.class == paramType);
	}

这些是用来判断是否原生的请求方式,也就是说我们的参数上可以写这种原生的请求参数类型

解析

这里的注释很明显了,那么就不再赘述了

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

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

		// 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 / HttpServletRequest / MultipartRequest / MultipartHttpServletRequest
		if (ServletRequest.class.isAssignableFrom(paramType) || MultipartRequest.class.isAssignableFrom(paramType)) {
			return resolveNativeRequest(webRequest, paramType);
		}

		// HttpServletRequest required for all further argument types
		return resolveArgument(paramType, resolveNativeRequest(webRequest, HttpServletRequest.class));
	}

ServletRequestMapMethodArgumentResolver

一看就是和上面的类似

RequestResponseBodyMethodProcessor

判断是否支持
	public boolean supportsParameter(MethodParameter parameter) {
		return parameter.hasParameterAnnotation(RequestBody.class);
	}

直接判断是否有RequestBody注解,这个也是我们常用的一种方式

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

		parameter = parameter.nestedIfOptional();
		Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
		String name = Conventions.getVariableNameForParameter(parameter);

		if (binderFactory != null) {
			WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
			if (arg != null) {
				validateIfApplicable(binder, parameter);
				if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
					throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
				}
			}
			if (mavContainer != null) {
				mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
			}
		}

		return adaptArgumentIfNecessary(arg, parameter);
	}


	protected Object adaptArgumentIfNecessary(@Nullable Object arg, MethodParameter parameter) {
		if (parameter.getParameterType() == Optional.class) {
			if (arg == null || (arg instanceof Collection && ((Collection<?>) arg).isEmpty()) ||
					(arg instanceof Object[] && ((Object[]) arg).length == 0)) {
				return Optional.empty();
			}
			else {
				return Optional.of(arg);
			}
		}
		return arg;
	}

直接拿到消息转换器来进行转换,我们最常用的是将这里的@RequestBody添加对应的自定义类型,接收得到json数据来进行转换。

RequestPartMethodArgumentResolver

判断是否支持
	public boolean supportsParameter(MethodParameter parameter) {
		if (parameter.hasParameterAnnotation(RequestPart.class)) {
			return true;
		}
		else {
			if (parameter.hasParameterAnnotation(RequestParam.class)) {
				return false;
			}
			return MultipartResolutionDelegate.isMultipartArgument(parameter.nestedIfOptional());
		}
	}

方法参数上如果标注了RequestPart注解或者是RequestPart对应的集合或者是数组类型,那么直接返回true,

这里就非常类似RequestParamMethodArgumentResolver的解析过程。

解析
	public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest request, @Nullable WebDataBinderFactory binderFactory) throws Exception {

		HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
		Assert.state(servletRequest != null, "No HttpServletRequest");

		RequestPart requestPart = parameter.getParameterAnnotation(RequestPart.class);
		boolean isRequired = ((requestPart == null || requestPart.required()) && !parameter.isOptional());

		String name = getPartName(parameter, requestPart);
		parameter = parameter.nestedIfOptional();
		Object arg = null;

		Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
		if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {
			arg = mpArg;
		}
		else {
			try {
				HttpInputMessage inputMessage = new RequestPartServletServerHttpRequest(servletRequest, name);
				arg = readWithMessageConverters(inputMessage, parameter, parameter.getNestedGenericParameterType());
				if (binderFactory != null) {
					WebDataBinder binder = binderFactory.createBinder(request, arg, name);
					if (arg != null) {
						validateIfApplicable(binder, parameter);
						if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
							throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
						}
					}
					if (mavContainer != null) {
						mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
					}
				}
			}
			catch (MissingServletRequestPartException | MultipartException ex) {
				if (isRequired) {
					throw ex;
				}
			}
		}

		if (arg == null && isRequired) {
			if (!MultipartResolutionDelegate.isMultipartRequest(servletRequest)) {
				throw new MultipartException("Current request is not a multipart request");
			}
			else {
				throw new MissingServletRequestPartException(name);
			}
		}
		return adaptArgumentIfNecessary(arg, parameter);
	}

RequestHeaderMethodArgumentResolver

判断是否支持
	public boolean supportsParameter(MethodParameter parameter) {
		return (parameter.hasParameterAnnotation(RequestHeader.class) &&
				!Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType()));
	}

判断上是否有RequestHeader注解,而且参数类型不是map

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

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

		Object resolvedName = resolveEmbeddedValuesAndExpressions(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 (arg == null) {
			if (namedValueInfo.defaultValue != null) {
				arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
			}
			else if (namedValueInfo.required && !nestedParameter.isOptional()) {
				handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
			}
			arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
		}
		else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
			arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
		}

		if (binderFactory != null) {
			WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
			try {
				arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
			}
			catch (ConversionNotSupportedException ex) {
				throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),
						namedValueInfo.name, parameter, ex.getCause());
			}
			catch (TypeMismatchException ex) {
				throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),
						namedValueInfo.name, parameter, ex.getCause());
			}
			// Check for null value after conversion of incoming argument value
			if (arg == null && namedValueInfo.defaultValue == null &&
					namedValueInfo.required && !nestedParameter.isOptional()) {
				handleMissingValueAfterConversion(namedValueInfo.name, nestedParameter, webRequest);
			}
		}

		handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);

		return arg;
	}


	protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
		String[] headerValues = request.getHeaderValues(name);
		if (headerValues != null) {
			return (headerValues.length == 1 ? headerValues[0] : headerValues);
		}
		else {
			return null;
		}
	}

直接调用原生的request从请求头中来进行获取得到对应的名称的响应头

RequestHeaderMapMethodArgumentResolver

判断是否支持

和上面的类似:

	public boolean supportsParameter(MethodParameter parameter) {
		return (parameter.hasParameterAnnotation(RequestHeader.class) &&
				Map.class.isAssignableFrom(parameter.getParameterType()));
	}
解析
	public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

		Class<?> paramType = parameter.getParameterType();
		if (MultiValueMap.class.isAssignableFrom(paramType)) {
			MultiValueMap<String, String> result;
			if (HttpHeaders.class.isAssignableFrom(paramType)) {
				result = new HttpHeaders();
			}
			else {
				result = new LinkedMultiValueMap<>();
			}
			for (Iterator<String> iterator = webRequest.getHeaderNames(); iterator.hasNext();) {
				String headerName = iterator.next();
				String[] headerValues = webRequest.getHeaderValues(headerName);
				if (headerValues != null) {
					for (String headerValue : headerValues) {
						result.add(headerName, headerValue);
					}
				}
			}
			return result;
		}
		else {
			Map<String, String> result = new LinkedHashMap<>();
			for (Iterator<String> iterator = webRequest.getHeaderNames(); iterator.hasNext();) {
				String headerName = iterator.next();
				String headerValue = webRequest.getHeader(headerName);
				if (headerValue != null) {
					result.put(headerName, headerValue);
				}
			}
			return result;
		}
	}

ServletModelAttributeMethodProcessor

判断是否支持
	public boolean supportsParameter(MethodParameter parameter) {
		return (parameter.hasParameterAnnotation(ModelAttribute.class) ||
				(this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType())));
	}

public static boolean isSimpleValueType(Class<?> type) {
		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.class.isAssignableFrom(type) ||
				URI.class == type ||
				URL.class == type ||
				Locale.class == type ||
				Class.class == type));
	}

方法参数上没有ModelAttribute注解,我们可以在方法上写上对应的自定义的数据类型,所以这种参数处理器就可以来进行处理

解析
	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");

		String name = ModelFactory.getNameForParameter(parameter);
		ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
		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
			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.
			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;
	}

WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);

WebDataBinder :web数据绑定器,将请求参数的值绑定到指定的JavaBean里面

WebDataBinder 利用它里面的 Converters 将请求数据转成指定的数据类型。再次封装到JavaBean中

GenericConversionService:在设置每一个值的时候,找它里面的所有converter那个可以将这个数据类型(request带来参数的字符串)转换到指定的类型(JavaBean -- Integer)

byte -- > file

未来我们可以给WebDataBinder里面放自己的Converter;

**private static final class **StringToNumber<T **extends **Number> **implements **Converter<String, T>

    //1、WebMvcConfigurer定制化SpringMVC的功能
    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return new WebMvcConfigurer() {
            @Override
            public void addFormatters(FormatterRegistry registry) {
                registry.addConverter(new Converter<String, Pet>() {

                    @Override
                    public Pet convert(String source) {
                        // 啊猫,3
                        if(!StringUtils.isEmpty(source)){
                            Pet pet = new Pet();
                            String[] split = source.split(",");
                            pet.setName(split[0]);
                            pet.setAge(Integer.parseInt(split[1]));
                            return pet;
                        }
                        return null;
                    }
                });
            }
        };
    }

比如说springmvc不支持将yyyy-MM-dd的类型的,我们也可以自定义的添加进来进行转换使用,也是非常方便的。

例子:

public class MyDateConverter implements Converter<String, Date> {
    private SimpleDateFormat format = new SimpleDateFormat();

    @Override
    public Date convert(String source) {
        if (source == null) {
            // return null;
            throw new RuntimeException("日期为空,不能转换");
        }

        try {
            //先尝试以yyyy/MM/dd解析转换
            format.applyPattern("yyyy/MM/dd");
            return format.parse(source);
        } catch (ParseException e) {}

        try {
            //再尝试以yyyy-MM-dd解析转换
            format.applyPattern("yyyy-MM-dd");
            return format.parse(source);
        } catch (ParseException e) {
            throw new RuntimeException("不支持的日期格式");
        }
    }
}

RequestResponseBodyMethodProcessor

判断是否支持
public boolean supportsParameter(MethodParameter parameter) {
  return parameter.hasParameterAnnotation(RequestBody.class);
}

这是只是来判断是否支持

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

  parameter = parameter.nestedIfOptional();
  Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
  String name = Conventions.getVariableNameForParameter(parameter);

  if (binderFactory != null) {
    WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
    if (arg != null) {
      validateIfApplicable(binder, parameter);
      if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
        throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
      }
    }
    if (mavContainer != null) {
      mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
    }
  }
  return adaptArgumentIfNecessary(arg, parameter);
}

首先拿到所有的参数解析器来进行解析,也就是说在@RequestBody标注了的情况下,会使用到消息转换器来进行使用。这个也会在响应阶段使用到,但是因为使用到的方法是不同的而已。

	protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter,
			Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {

		HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
		Assert.state(servletRequest != null, "No HttpServletRequest");
		ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(servletRequest);

		Object arg = readWithMessageConverters(inputMessage, parameter, paramType);
		if (arg == null && checkRequired(parameter)) {
			throw new HttpMessageNotReadableException("Required request body is missing: " +
					parameter.getExecutable().toGenericString(), inputMessage);
		}
		return arg;
	}

使用消息转换器来进行读取

	protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
			Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {

		MediaType contentType;
		boolean noContentType = false;
		try {
          	 // 获取得到请求头中的内容类型
			contentType = inputMessage.getHeaders().getContentType();
		}
		catch (InvalidMediaTypeException ex) {
			throw new HttpMediaTypeNotSupportedException(ex.getMessage());
		}
		if (contentType == null) {
			noContentType = true;
			contentType = MediaType.APPLICATION_OCTET_STREAM;
		}
		// handler所在的类的controller
		Class<?> contextClass = parameter.getContainingClass();
      	// 目标类型
		Class<T> targetClass = (targetType instanceof Class ? (Class<T>) targetType : null);
		if (targetClass == null) {
			ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);
			targetClass = (Class<T>) resolvableType.resolve();
		}

		HttpMethod httpMethod = (inputMessage instanceof HttpRequest ? ((HttpRequest) inputMessage).getMethod() : null);
		Object body = NO_VALUE;

		EmptyBodyCheckingHttpInputMessage message;
		try {
          	 // 拿到流对应的数据
			message = new EmptyBodyCheckingHttpInputMessage(inputMessage);
			// 遍历所有的消息转换器来进行转换,可以看到genericConverter.canRead,哪种消息转换器是支持的,支持了之后就会来读取
          	 // 将对应的值读取到参数上去,因为每种参数处理器处理的不同,所以可以支持不同类型的
			for (HttpMessageConverter<?> converter : this.messageConverters) {
				Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
				GenericHttpMessageConverter<?> genericConverter =
						(converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
				if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :
						(targetClass != null && converter.canRead(targetClass, contentType))) {
					if (message.hasBody()) {
						HttpInputMessage msgToUse =
								getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
						body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
								((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
						body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
					}
					else {
						body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
					}
					break;
				}
			}
		}
		catch (IOException ex) {
			throw new HttpMessageNotReadableException("I/O error while reading input message", ex, inputMessage);
		}

		if (body == NO_VALUE) {
			if (httpMethod == null || !SUPPORTED_METHODS.contains(httpMethod) ||
					(noContentType && !message.hasBody())) {
				return null;
			}
			throw new HttpMediaTypeNotSupportedException(contentType,
					getSupportedMediaTypes(targetClass != null ? targetClass : Object.class));
		}

		MediaType selectedContentType = contentType;
		Object theBody = body;
		LogFormatUtils.traceDebug(logger, traceOn -> {
			String formatted = LogFormatUtils.formatValue(theBody, !traceOn);
			return "Read \"" + selectedContentType + "\" to [" + formatted + "]";
		});

		return body;
	}

如果是自定义类型,将会使用JACSON转换工具类来进行转换,也就要求了传递进来的属性要和java中的属性要求一致,才能够进行转换。

但是这里也发现了一个隐蔽的地方:

if (message.hasBody()) {
  HttpInputMessage msgToUse =
    getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
  body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
          ((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
  body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
}
else {
  body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
}

如果方法参数上加了@RequestBody注解,那么在使用的时候可以在读取之前和读取之后来做一些处理以及方法为空的时候来做一些处理。

这里都是可以来进行操作的。

特殊操作

在这里来放两个链接上来:

处理问题:在实际项目中,我们常常需要在请求前后进行一些操作,比如:参数解密/返回结果加密,打印请求参数和返回结果的日志等。这些与业务无关的东西,我们不希望写在controller方法中,造成代码重复可读性变差。这里,我们讲讲使用@ControllerAdvice和RequestBodyAdvice、ResponseBodyAdvice来对请求前后进行处理(本质上就是AOP),来实现日志记录每一个请求的参数和返回结果。

https://blog.csdn.net/pongandnoon/article/details/84555414
https://blog.csdn.net/jsq6681993/article/details/119712621

分为了请求前处理和请求后处理,可以参考下对应的案例。但是我看评论区中,有一个评论说的:

那种name value形式的请求参数,如何在参数绑定成功之后,实现类似的拦截呢 ?

其实这种也可以利用AOP来进行实现即可,也不为一种优雅的方式。

这里也放上我在练习中使用的代码:https://gitee.com/guangjava/springboot-project/blob/master/springboot-web-02/src/main/java/com/guang/springbootrequestversion/aspect/AopLogAspect.java

在对应的writeWithMessageConverters方法中:

protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
                                              ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
  throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

    // .................
    body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
                                       (Class<? extends HttpMessageConverter<?>>) converter.getClass(),
                                       inputMessage, outputMessage);
  if (body != null) {
    Object theBody = body;
    LogFormatUtils.traceDebug(logger, traceOn ->
                              "Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]");
    addContentDispositionHeader(inputMessage, outputMessage);
    if (genericConverter != null) {
      genericConverter.write(body, targetType, selectedMediaType, outputMessage);
    }
    else {
      ((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
    }

在进行响应之前,也可以写入对应的操作,可以来进行自定义操作。

四、自定义参数解析器

从上面可以看到所有的参数解析器都会来实现一个接口:HandlerMethodArgumentResolver,并重写其中的方法来进行操作

public interface HandlerMethodArgumentResolver {
  // 判断是否支持
  boolean supportsParameter(MethodParameter parameter);
  // 支持之后,如何来进行处理。返回值最终需要来赋值给参数
  Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
}

那么我们也可以来自定义一个并将对应的参数解析器放置到容器中进行管理。

示例一

贴上对应的链接:https://www.cnblogs.com/jere/p/15154126.html

一、问题描述

前端把 token 存放在请求头里面,后端接口如果需要获取当前登录用户信息,我们是使用 HttpServletRequest 来获取请求头的 token,但是如果后续很多接口都需要获取当前登录用户信息,此时就出现代码重复问题。

:最佳方式是在拦截器中来进行操作。但是这里为了使用参数解析器,所以在这里来演示一下对应的使用方式。

二、假设分析

我们能不能做出一个接口参数,让所有访问这个具备指定参数的接口都能获取当前登录用户对象,该怎么实现?

三、应用技术

使用技术:SpringMVC 参数解析器
解析器有两种:

  • SpringMVC 默认解析器
  • 自定义参数解析器
    作用:将接口(请求映射方法)UserInfo 类型的参数解析成当前登录用户对象

这两种,选哪个呢?不难看出,因为默认参数解析器是无法实现当前登录用户对象注入,此时选择第二种。

四、代码实现

业务需要区分场景的时候(比如登录与未登录),可以编写一个自定义注解
编写一个自定义注解,放于 annotation 包下

// 注解只能够添加在参数
@Target(ElementType.PARAMETER) 
@Retention(RetentionPolicy.RUNTIME)
public @interface UserParam {}

新建一个 resolver 包,编写自定义解析器

/**
 * 自定义参数解析器
 * 作用:
 * 将接口中声明 UserInfo 类型的参数,解析成当前登录用户对象,并注入
 */
public class UserInfoArgumentResolver implements HandlerMethodArgumentResolver {

  @Autowired
  private IUserInfoRedisService userInfoRedisService;

  //用于识别接口参数类型
  //指定当前解析器能解析接口参数的类型: 此处是UserInfo.class类型
  @Override
  public boolean supportsParameter(MethodParameter methodParameter) {
    return methodParameter.getParameterType() == UserInfo.class
      && methodParameter.hasParameterAnnotation(UserParam.class);
  }


  //用于解析接口参数,并注入参数值
  //前提: supportsParameter 方法返回true之后执行
  @Override
  public Object resolveArgument(MethodParameter methodParameter,
                                ModelAndViewContainer modelAndViewContainer,
                                NativeWebRequest nativeWebRequest,
                                WebDataBinderFactory webDataBinderFactory) throws Exception {
    HttpServletRequest request = nativeWebRequest.getNativeRequest(HttpServletRequest.class);
    String token = request.getHeader("token");
    UserInfo userInfo = userInfoRedisService.getUserByToken(token);
    return userInfo;
  }
}

配置类

@Configuration
public class WebsiteConfig implements WebMvcConfigurer {
  
    // 加进 HandlerMethodArgumentResolver 
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(new UserInfoArgumentResolver());
    }
}

controller层

@GetMapping("/userInfo")
@ResponseBody
public Object user(@UserParam UserInfo user){
  return JsonResult.success(user);
}

以上,代码已实现功能

对应的流程图如下所示:

示例二

1、问题描述

将表单类型的提交的参数添加到自定义的对象属性中去。

前端样例

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<h1>liguang,欢迎您</h1>
测试封装POJO;
<form action="../saveuser" method="post">
    姓名: <input name="userName" value="zhangsan"/> <br/>
    年龄: <input name="age" value="18"/> <br/>
    <!--生日: <input name="birth" value="2019/12/10"/> <br/>-->
    生日: <input name="birth" value="2019-12-10"/> <br/>
<!--        宠物姓名:<input name="cat.name" value="阿猫"/><br/>-->
<!--        宠物年龄:<input name="cat.age" value="5"/>-->
    宠物: <input name="cat" value="啊猫,3"/>
    <input type="submit" value="保存"/>
</form>
<br>
</body>
</html>

想要在提交的的时候自动将啊猫,3封装成对应的Cat对象

2、代码实现
1、定义注解
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface UserParam {}
2、自定义参数解析器
public class BindHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {

  @Override
  public boolean supportsParameter(MethodParameter parameter) {
    return parameter.getParameterType() == Cat.class && parameter.hasParameterAnnotation(UserParam.class);
  }

  @Override
  public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {       
    HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
    String name = request.getParameter("name");
    String age = request.getParameter("age");
    int catAge = Integer.parseInt(age);
    Cat cat = new Cat();
    cat.setAge(catAge);
    cat.setName(name);
    return cat;
  }
}
3、添加到配置中
@Configuration(proxyBeanMethods = false)
public class CustomWebMvcConfigurer implements WebMvcConfigurer {

  private static final Logger logger = LoggerFactory.getLogger(CustomWebMvcConfigurer.class);

  @Override
  public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
    resolvers.add(new BindHandlerMethodArgumentResolver());
  }
}

5、总结

5.1、参数解析器

学会了SpringMVC中是怎么来确定每个参数的值的,循环遍历每个参数,获取得到对应的解析器,通过解析器来进行解析,在解析的过程中会利用到各种工具来进行操作。

5.2、类型转换器

如果是获取得到原生的API的话,那么将会从request域对象中直接来进行获取;

如果获取不是原生的API,而是自定义的数据类型,那么将会调用类型转换器来进行转换,在SpringMVC中内置了很多的类型转换器,如果不符合我们的条件的话,我们也可以自定义对应的数据类型转换器;

5.3、自定义参数解析器

将我们自定义的参数解析器来进行解析

5.4、自定义SpringMVC中的组件

推荐使用@Configuration+WebConfigure方法来进行注入对应的组件到容器中来

5.5、类型转换器和自定义参数解析器的理解

参数解析器是针对参数来说的,看看是否支持解析。而类型转换器是将request域对象中的数据转换到对应的数据类型中来,先支持,然后解析,在解析的过程中来进行数据转换,所以二者相对来说,粒度是不同的。

posted @ 2022-05-15 13:06  雩娄的木子  阅读(458)  评论(0编辑  收藏  举报