《SpringMVC系列》第三章:请求处理

重点:

  1. Controller层支持的各种注解以及负责处理这个注解参数解析器
  2. Servert Api 默认注入的参数
  3. 参数是如何自动绑定的,包括单个参数和对象参数

一、参数解析器

1.方法参数处理解析器

在SpringMVC中请求调用目标方法需要处理参数,那么不同的参数肯定处理方式不同,SpringMVC提供了一个接口HandlerMethodArgumentResolver,然后多个子类重写,用于处理不同的参数

这个接口提供了两个方法,也很好理解,第1个判断是否为该类型的参数,第2个负责接续

public interface HandlerMethodArgumentResolver {

    // 判断是否可以用于这个参数解析器
	boolean supportsParameter(MethodParameter parameter);

    // 解析参数
	@Nullable
	Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;

}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k4TRf17O-1652195877781)(E:\Gitee\spingboot\HandlerMethodArgumentResolver.png)]

2.参数解析器初始化

上面用到的参数解析器,是如何初始化的呢?

我们用到的适配器 RequestMappingHandlerAdapter 这个类实现了 InitializingBean接口,当SpringMVC容器启动的适合,就会调用afterPropertiesSet()

	public void afterPropertiesSet() {
		// Do this first, it may add ResponseBody advice beans
  		//初始化 @ControllerAdvice 注解需要的东西,后面写篇博客介绍下这个注解
		initControllerAdviceCache();

  		// 获取方法参数处理解析器
		if (this.argumentResolvers == null) {
        	//Spring提供的一系列的HandlerMethodArgumentResolver,同时这儿也会调用用户自己添加的HandlerMethodArgumentResolver
			List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
			this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
		}
  		// 获取桥接方法的参数
		if (this.initBinderArgumentResolvers == null) {
      		//Spring提供的一系列的HandlerMethodArgumentResolver,同时这儿也会调用用户自己添加的HandlerMethodArgumentResolver
			List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
			this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
		}
  		// 获取方法返回值处理器
		if (this.returnValueHandlers == null) {
      		//Spring提供的一系列的HandlerMethodReturnValueHandler,同时这儿也会调用用户自己添加的HandlerMethodReturnValueHandler
			List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
			this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
		}
	}

我们上面用到的参数解析器 和 返回值处理器 就是在这里被初始化的。

	private List<HandlerMethodArgumentResolver> getDefaultInitBinderArgumentResolvers() {
		List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>(20);

		// Annotation-based argument resolution
         // 注意看这里添加第一次RequestParamMethodArgumentResolver
		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 ExpressionValueMethodArgumentResolver(getBeanFactory()));
		resolvers.add(new SessionAttributeMethodArgumentResolver());
		resolvers.add(new RequestAttributeMethodArgumentResolver());

		// Type-based argument resolution
		resolvers.add(new ServletRequestMethodArgumentResolver());
		resolvers.add(new ServletResponseMethodArgumentResolver());

		// Custom arguments
		if (getCustomArgumentResolvers() != null) {
			resolvers.addAll(getCustomArgumentResolvers());
		}

		// Catch-all
		resolvers.add(new PrincipalMethodArgumentResolver());
         // // 注意看这里添加第二次RequestParamMethodArgumentResolver
		resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));

		return resolvers;
	}

注意上面是加了两个RequestParamMethodArgumentResolver对象,只不过第一次的参数的值是false,第二次参数的值为true。那么这个两个值的区别是啥?我们还需要看 RequestParamMethodArgumentResolver 类的supportsParameter方法,具体的代码如下:

public boolean supportsParameter(MethodParameter parameter) {
     // 是否存在 RequestParam 注解
	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 {
         // 是否存在RequestPart注解 该注解用于复杂的请求域
		if (parameter.hasParameterAnnotation(RequestPart.class)) {
			return false;
		}
		parameter = parameter.nestedIfOptional();
         // 是否是请求的参数
		if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {
			return true;
		}
        // 区别在这,如果是true的话,基本的类型是会返回true 否则直接返回false
		else if (this.useDefaultResolution) {
			return BeanUtils.isSimpleProperty(parameter.getNestedParameterType());
		}
		else {
			return false;
		}
	}
}

上面两个RequestParamMethodArgumentResolver的区别就在于如果是true的话,基本的类型是会返回true,如果不是true,就直接跳过,代表我们匹配参数的时候,基本数据类型放到最后在匹配,优先匹配引用类型去匹配,也就是包装类的优先级高于基本数据类

二、参数注解

@RequestParam

获取请求参数

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam {

	@AliasFor("name")
	String value() default "";

	@AliasFor("value")
	String name() default "";
	
    // 是否必须提交
	boolean required() default true;

    // 不存在时默认值
	String defaultValue() default ValueConstants.DEFAULT_NONE;
}

RequestParamMethodArgumentResolver

该类负责

public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver implements UriComponentsContributor {
     
    
    @Override
	public boolean supportsParameter(MethodParameter parameter) {
         // 判断参数是否存在 @RequestParam 注解
		if (parameter.hasParameterAnnotation(RequestParam.class)) {
             // 判断嵌套参数类型 Optional
			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;
			}
		}
	}

    // reloveName是父类的抽象方法
    @Override
	@Nullable
	protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
		HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
		
          
		if (servletRequest != null) {
             // 调用方法,获取到文件参数,如果没有,则返回一个new Object
			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) {
             // 从请求里面获取参数值 name在父类已经解析好了
			String[] paramValues = request.getParameterValues(name);
			if (paramValues != null) {
                 // 长度为1 返回1个 否则返回数组
				arg = (paramValues.length == 1 ? paramValues[0] : paramValues);
			}
		}
		return arg;
	}
    
     // 省略其它代码...
}

@RequestPart

上传文件的时候可以使用该注解

RequestPartMethodArgumentResolver

public class RequestPartMethodArgumentResolver extends AbstractMessageConverterMethodArgumentResolver {

	// 判断是否可以应用此参数处理器
	@Override
	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());
		}
	}

	@Override
	@Nullable
	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);
			}
		}
         // 判断是否包装成Optional
		return adaptArgumentIfNecessary(arg, parameter);
	}
}

@RequestBody

  1. 获取前端传递的JSON字符串数据,可以和@RequestParam同时使用,@RequestBody 接收的是请求体里面的数据;@RequestParam接收的是key-value里面的参数
  2. 一个请求,@RequestBody只能存在一个,而@RequestParam可以使用多个,因为请求体只有1个
  3. 获取的是请求体的数据,所以使用POST请求
  4. 在参数上添加该注解,Spring就会将请求体中的json/xml对象解析成该参数类型的Javabean对象
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestBody {

	boolean required() default true;
}

RequestResponseBodyMethodProcessor

public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {

    // 判断该参数是否存在 @RequestBody 注解 很好理解
	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		return parameter.hasParameterAnnotation(RequestBody.class);
	}

	// 解析参数
	@Override
	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());
			}
		}
		// 判断参数是否需要包装成Optional
		return adaptArgumentIfNecessary(arg, parameter);
	}

	// 省略部分代码...
}

@PathVariable

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PathVariable {

	@AliasFor("name")
	String value() default "";

	@AliasFor("value")
	String name() default "";

	boolean required() default true;
}

路径变量,不仅可以把路径变量封装到单个参数内,也可以封装到一个Map里面

	@GetMapping("/car/{id}/owner/{username}")
    public Map<String,Object> getCar(@PathVariable("id") Integer id,
                                     @PathVariable("username") String name,
                                     @PathVariable Map<String,String> pv){

PathVariableMethodArgumentResolver

public class PathVariableMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver
		implements UriComponentsContributor {

	private static final TypeDescriptor STRING_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(String.class);


    // 方法很简单 判断是否存在 @PathVariable 注解
	@Override
	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;
	}

	// 解析
	@Override
	@SuppressWarnings("unchecked")
	@Nullable
	protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
         // 这个是直接从 request里面获取,因为请求的时候会直接存进去
		Map<String, String> uriTemplateVars = (Map<String, String>) request.getAttribute(
				HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
		return (uriTemplateVars != null ? uriTemplateVars.get(name) : null);
	}

 	// 省略部分代码...   
}

@RequestHeader

获取到请求头中的信息

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestHeader {

	@AliasFor("name")
	String value() default "";

	@AliasFor("value")
	String name() default "";

	boolean required() default true;

	String defaultValue() default ValueConstants.DEFAULT_NONE;
}

@RequestAttribute

@RequestAttribute可以获取到域中属性值

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestAttribute {

	@AliasFor("name")
	String value() default "";

	@AliasFor("value")
	String name() default "";

	boolean required() default true;
}

测试代码

// 下面要使用页面重定向 这里不是使用@RestController
@Controller
public class IndexController {

    @GetMapping(value = "/first")
    public String m1(HttpServletRequest request) {
        // 将参数添加到域中
        request.setAttribute("hello", "world");
        return "forward:/second";
    }

    @ResponseBody
    @GetMapping(value = "second")
    // 通过 @RequestAttribute 获取到域中的属性
    public String m2(@RequestAttribute("hello") String hello) {
        return hello;
    }
}

@CookieValue

获取Cookie值

	// 获取单个Cookie值  和 全部的Cookie
    @GetMapping("/car/{id}/owner/{username}")
    public Map<String,Object> getCar(@CookieValue("_ga") String _ga,
                                     @CookieValue("_ga") Cookie cookie){

@MatrixVariable

矩阵变量

默认我们通过请求体来发送参数,但是也可以通过矩阵变量将参数绑定在路径变量中传递到后台,后台根据此获取参数

// 矩阵变量路径使用实例
/boss/1/2        					 找到序号1中的序号2
/boss/1;age=23/2;age=45  			  找到序号1并且年龄=23中的序号2并且年龄=45
/boss/1;age=23;height=170/2;height=20  可以传递多个参数
/boss/1;age=24;height=170,175,180      传递的每个参数包含多个值

矩阵变量使用注意事项:

  1. 此功能默认关闭,需要开启
  2. 矩阵变量需要结合路径变量@PathVariable来使用
  3. 矩阵变量中的参数多个以分号隔开,每个参数的值可以是1个,也可以是多个
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MatrixVariable {

	@AliasFor("name")
	String value() default "";

	@AliasFor("value")
	String name() default "";

    // 规定解析的路径变量
	String pathVar() default ValueConstants.DEFAULT_NONE;

	boolean required() default true;

	String defaultValue() default ValueConstants.DEFAULT_NONE;
}

测试代码

1.需要使用矩阵变量,需要我们开启配置

@Configuration(proxyBeanMethods = false)
public class UserConfig {
    
    // 自定义一个WebMvcConfigurer,重写里面的configurePathMatch
    @Bean
    public WebMvcConfigurer webMvcConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void configurePathMatch(PathMatchConfigurer configurer) {
                UrlPathHelper urlPathHelper = new UrlPathHelper();
                // WebMvcAutoConfiguration自动配置的UrlPathHelper是删除分号后面的内容
                // 但是我们使用矩阵变量 肯定需要留着的 所以这里设置false
                urlPathHelper.setRemoveSemicolonContent(false);
                configurer.setUrlPathHelper(urlPathHelper);
            }
        };
    }
}

2.编写接口

	// 测试地址 /test5/food;eat=rice;drink=water,tea
    // @MatrixVariable 需要与 @PathVariable 结合使用
    // 获取矩阵变量
    @GetMapping(value = "/test5/{path}")
    public Object m5(@MatrixVariable("eat") String eat,
                     @MatrixVariable("drink") List<String> drink,
                     @PathVariable("path") String path) {
        // 将接收的参数返回,方便查看
        Map<String, Object> map = new HashMap<>(16);
        map.put("eat", eat);
        map.put("drink", drink);
        map.put("path",path);
        return map;
    }

    // 测试地址 /test6/path1;eat=rice/path2;drink=water,tea
    // 当存在多个矩阵变量的时候,@MatrixVariable提供了一个pathVar属性来规定获取哪个矩阵变量
    @GetMapping(value = "/test6/{path1}/{path2}")
    public Object m6(@MatrixVariable(value = "eat",pathVar = "path1") String eat,
                     @MatrixVariable(value = "drink",pathVar = "path2") List<String> drink,
                     @PathVariable("path1") String path1,
                     @PathVariable("path2") String path2) {
        Map<String, Object> map = new HashMap<>(16);
        map.put("eat", eat);
        map.put("drink", drink);
        map.put("path1",path1);
        map.put("path2",path2);
        return map;
    }	

源码解析

同样这个关系到SpringMVC,自然就跟自动配置类WebMvcAutoConfiguration有关,在其中的静态内部类WebMvcAutoConfigurationAdapter继承了WebMvcConfigurer,根据SpringBoot官网给出的提示,继承WebMvcConfigurer则代表对SpringMVC进行自定义

我们看一下其中的路径匹配方法configurePathMatch,借用了UrlPathHelper

		@Override
		public void configurePathMatch(PathMatchConfigurer configurer) {
			if (this.mvcProperties.getPathmatch()
					.getMatchingStrategy() == WebMvcProperties.MatchingStrategy.PATH_PATTERN_PARSER) {
				configurer.setPatternParser(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 = new UrlPathHelper();
					urlPathHelper.setAlwaysUseFullPath(true);
					configurer.setUrlPathHelper(urlPathHelper);
				}
			});
		}

UrlPathHelper

该类属于Url地址的一个工具类,在其中有1个属性,来规定是否舍弃分号后面的内容,而我们的矩阵变量就正好是用分号后面的内容,所以就对应了上面的内容,创建一个新的UrlPathHelper设置该属性为false,然后自定义SpringMVC

public class UrlPathHelper {
    
    private boolean removeSemicolonContent = true;
    
    // 省略大量代码...
}

面试题

以前面试的时候,经常会问到这样一道题,页面开发,Cookie禁用了session里面的内容怎么使用?

正常我们做页面开发,会将用户信息存入 session.set(a,b);每个用户对应一个 sessionid,这个发送到页面存储在cookie里面,但是cookie被禁用,每次请求的时候就无法发送sessionid,这个时候就可以使用矩阵变量把cookie的值传递过来

三、其它参数

Servlet API

例如我们在接口参数里面添加一个HttpServlerRequest ,这个在我们执行目标方法的时候就会自动赋值而不需要参数传递,这个是因为 ServletRequestMethodArgumentResolver的存在

ServletRequestMethodArgumentResolver

public class ServletRequestMethodArgumentResolver implements HandlerMethodArgumentResolver {

	@Nullable
	private static Class<?> pushBuilder;

    // 当参数是以下这些类型的时候,会自动赋值,里面就包含我们常用的 HttpServletRequest
	@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) && !parameter.hasParameterAnnotations()) ||
				InputStream.class.isAssignableFrom(paramType) ||
				Reader.class.isAssignableFrom(paramType) ||
				HttpMethod.class == paramType ||
				Locale.class == paramType ||
				TimeZone.class == paramType ||
				ZoneId.class == paramType);
	}

    // 负责解析参数
	@Override
	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));
	}

	// 省略部分代码...
}

复杂参数

Map、Model(map、model里面的数据会被放在request的请求域,相当于调用了request.setAttribute)、

HttpServlerRequest、Errors/BindingResult、RedirectAttributes( 重定向携带数据)ServletResponse(response)、SessionStatus、UriComponentsBuilder、ServletUriComponentsBuilder

测试代码

在一个请求里面,接收了Map,Model,Request,这个时候后台会把Model、Map里面的数据存入Request,然后页面重定向,在另1个请求里面获取这些数据

	@GetMapping("/test1")
    public String m1(Map<String,Object> map,
                     Model model,
                     HttpServletRequest request,
                     HttpServletResponse response){
        
        // 将信息添加到 Map 和 Model 里面
        map.put("zhang","san");
        model.addAttribute("li","si");
        request.setAttribute("wang","wu");
        Cookie cookie = new Cookie("zhao","liu");
        response.addCookie(cookie);
        return "forward:/test2";
    }

    @ResponseBody
    @GetMapping("/test2")
    public Map m2(HttpServletRequest request,HttpServletResponse response){
        // 上面重定向以后, Map 和 Model里面的数据 在request里面都能获取到
        Map<String,Object> map = new HashMap<>();
        map.put("zhang",request.getAttribute("zhang"));
        map.put("li",request.getAttribute("li"));
        map.put("wang",request.getAttribute("wang"));
        return map;
    }

Map和Model是同1个对象

MapMethodProcessor

该类负责处理Map参数,处理方式是mavContainer.getModel()

public class MapMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {

	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		return (Map.class.isAssignableFrom(parameter.getParameterType()) &&
				parameter.getParameterAnnotations().length == 0);
	}

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

		Assert.state(mavContainer != null, "ModelAndViewContainer is required for model exposure");
         // 这里是从 mavContainer 直接获取Model
		return mavContainer.getModel();
	}
 	
    // 省略部分代码...
}

ModelMethodProcessor

该类负责处理Model参数,处理方式是mavContainer.getModel()

public class ModelMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {

	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		return Model.class.isAssignableFrom(parameter.getParameterType());
	}

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

		Assert.state(mavContainer != null, "ModelAndViewContainer is required for model exposure");
         // 这里也是从 mavContainer 直接获取Model 
		return mavContainer.getModel();
	}
    
     // 省略部分代码...
}

ModelAndViewContainer

Map、Model类型的参数,会返回 mavContainer.getMode(),也就是上面默认创建的new BindingAwareModelMap(),其是Model 也是Map

从方法中看到不管是Model还是Map,返回的都是defaultModel,这也代表我们在参数中的Map和Model是一个对象

public class ModelAndViewContainer {
    
    private final ModelMap defaultModel = new BindingAwareModelMap();
    
    public ModelMap getModel() {
		if (useDefaultModel()) {
             // 返回上面创建的默认的defaultModel
			return this.defaultModel;
		}
		else {
			if (this.redirectModel == null) {
				this.redirectModel = new ModelMap();
			}
			return this.redirectModel;
		}
	}
    
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cRiROD66-1652195877783)(E:/Gitee/spingboot/ModelMap.png)]

Model和Map数据如何存到request

上面测试代码已经试验了ModeMap会存储到request,那么如何存储的呢?

在最底层的实现肯定很简单,也就是把数据取出来,然后遍历通过request.setAttribute()存进去,那么就看一下如何实现的

// 方法执行链
org.springframework.web.servlet.DispatcherServlet#doDispatch()
 -->org.springframework.web.servlet.DispatcherServlet#processDispatchResult()
  -->org.springframework.web.servlet.DispatcherServlet#render()
    -->org.springframework.web.servlet.view.AbstractView#render()
      -->org.springframework.web.servlet.view.InternalResourceView#renderMergedOutputModel()
        -->org.springframework.web.servlet.view.AbstractView#exposeModelAsRequestAttributes()

最终的方法就是exposeModelAsRequestAttributes(),很简单就是遍历存储

	protected void exposeModelAsRequestAttributes(Map<String, Object> model,
			HttpServletRequest request) throws Exception {
		// 遍历
		model.forEach((name, value) -> {
			if (value != null) {
				request.setAttribute(name, value);
			}
			else {
				request.removeAttribute(name);
			}
		});
	}

四、参数处理解析

1.调用链

在SpringMVC中,通过处理适配器调用目标方法的时候,中间会调用到该方法,方法内容也很好理解,第1步获取到方法参数,第2步反射调用方法

	@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 通过 getMethodArgumentValues()方法来获取参数,第2 通过doInvoke()来执行对应的方法,是通过反射来执行,然后将结果返回,

那么首先来看获取实参的处理,整体的一个思路就是 首先获取到该方法的全部形参,然后通过参数解析器,现在请求里面通过类型找,然后在通过形参名找,如此把请求携带的参数自动赋值到实参里面

	protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {
		// 获取方法的参数, 注意看这里是直接调用方法,前面没有类名,
         // 本方法处于InvocableHandlerMethod类,是HandlerMethod的子类
         // 我们前面获取的映射器是InvocableHandlerMethod的子类,所以直接调用
         // 所以获取到的就是该方法的形参列表数组
         // 点进到 getMethodParameters()方法里面 就是HandlerMethod类的getter方法
         // 下面看到了其关系图
		MethodParameter[] parameters = getMethodParameters();
        // 如果参数为空,代表该方法不需要形参,直接返回结合
		if (ObjectUtils.isEmpty(parameters)) {
			return EMPTY_ARGS;
		}
  		// 创建参对象 存储方法实参
		Object[] args = new Object[parameters.length];
        // 通过遍历,根据方法形参,借助参数解析器,去请求里面查询实参
		for (int i = 0; i < parameters.length; i++) {
			MethodParameter parameter = parameters[i];
            // 初始化参数名称的发现器
			parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
            // 这儿一般的情况下会返回Null
			args[i] = findProvidedArgument(parameter, providedArgs);
			if (args[i] != null) {
				continue;
			}
            // 查找对应的参数的处理器,主要是遍历所有的参数的处理器,调用处理中的support方法,只要满足就返回true
            // 不同的参数类型有不同的处理器
            // 参数类型的初始化在后面介绍
			if (!this.resolvers.supportsParameter(parameter)) {
                 // 如果没有找到参数解析器 就报异常
				throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
			}
			try {
                // 找到了就直接执行参数处理器的resolveArgument() 解析参数 获取实参
				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;
	}

在这里插入图片描述

上面看到我们首先会查询该形参是否存在匹配的参数解析器,如果不存在就报异常,看一下supportsParameter(parameter)方法

	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		return getArgumentResolver(parameter) != null;
	}

继续调用 getArgumentResolver()

	@Nullable
	private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
        // 先尝试从缓存中获取 这个是存在缓存的
		HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
		if (result == null) {
             // 遍历所有的参数处理器,调用每一个参数处理器的 supportsParameter 方法,直到返回的结果为true为止
             // 下面介绍该参数解析器在哪里初始化的
			for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
                  // 依次判断
				if (resolver.supportsParameter(parameter)) {
					result = resolver;
                      // 如果匹配到 就存入缓存
					this.argumentResolverCache.put(parameter, result);
					break;
				}
			}
		}
		return result;
	}

上面我们介绍了如何匹配参数解析器,接下来我们看一下resolveArgument()

当我们,其实注意看一下类结构,这前后用的几个方法 supportsParameter()resolveArgument()getArgumentResolver()都是HandlerMethodArgumentResolverComposite 类的,而且并排的三个方法

   @Override
   @Nullable
   public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
   		NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
   	// 继续调用 getArgumentResolver() 方法获取到对应的参数解析器
   	HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
        // 如果为空 直接报异常 代表没有解析器可以解析该参数
   	if (resolver == null) {
   		throw new IllegalArgumentException("Unsupported parameter type [" +
   				parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
   	}
        // 上面获取到了解析器,接下来解析参数 因为不同的参数解析器,解析方法不同
   	return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
   }

我们方法的形参有不同的类型,有包装类,字符串,基本数据类型,数组、引用数据类型等等,那么会分成2种情况,第一种就是普通的类型,第二种就是引用对象,SpringMVC会自动把参数封装到对象里面

2.普通形参匹配

普通的参数,是会借助 AbstractNamedValueMethodArgumentResolver 类来解析参数,然后我们查看其中的resolveArgument(),具体的代码如下:

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

    // 获取方法的形参名信息 NamedValueInfo是一个静态内部类 包含这个形参的名称、required、defaultValue信息
    // 如果形参有@RequestParam注解 则注解信息都会保存在NamedValueInfo这里
	NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
    // 获取包装的方法参数的类型
	MethodParameter nestedParameter = parameter.nestedIfOptional();

    // 从上面的namedValueInfo单独取出形参名
	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 = resolveStringValue(namedValueInfo.defaultValue);
		}
		else if (namedValueInfo.required && !nestedParameter.isOptional()) {
            // 判断是否是必须要值,如果是直接抛出异常
			handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
		}
        // 处理空值,如果这个值的类型是Boolean类型,就设置为false,其他的情况下都是原来的默认值
		arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
	}
	else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
         // 获取值为""同时默认值也不为空,那么就将默认值设置进去
		arg = resolveStringValue(namedValueInfo.defaultValue);
	}

    // 进行类型转换 因为URL请求接收的一般都是String类型,而我们的形参存在各种类型
	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());
		}
         // 最极端情况 值为空&&默认值为空&&必填 报异常
         if (arg == null && namedValueInfo.defaultValue == null &&
					namedValueInfo.required && !nestedParameter.isOptional()) {
			handleMissingValueAfterConversion(namedValueInfo.name, nestedParameter, webRequest);
		}
	}

	handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
	
    //返回处理好的参数的
	return arg;
}

由于RequestParamMethodArgumentResolver类没有实现HandlerMethodArgumentResolver接口,所以这儿调用的是父类的resolveArgument方法,但是获取对应参数的值是调用子类RequestParamMethodArgumentResolverresolveName方法,具体的代码如下:

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) {
        // 重点在这里 还是调用请求的API来获取参数 返回是个数组
		String[] paramValues = request.getParameterValues(name);
		if (paramValues != null) {
			arg = (paramValues.length == 1 ? paramValues[0] : paramValues);
		}
	}
	return arg;
}

我们终于看到对应参数的获取方法resolveName(),就是利用了servlet的api来获取指定的参数的值的。我们继续看上面的resolveArgument()剩余的代码,注释内容都在上面写清楚了,这里就不在重复了。

3.自定义对象参数

当接口参数中接收的是一个对象,而不是普通类型的时候,那么SpringMVC依旧能把参数准确的赋值到参数对象中,那么它是如何操作的呢?

这里还是借助我们的参数解析器ServletModelAttributeMethodProcessor,不过方法编写在父类中ModelAttributeMethodProcessor

ModelAttributeMethodProcessor

public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {
	
    @Override
	public boolean supportsParameter(MethodParameter parameter) {
         // 判断是否存在@ModelAttribute注解  || 
		return (parameter.hasParameterAnnotation(ModelAttribute.class) ||
                 // 判断是否为简单类型,也就是一些Number,Data,Enum等
				(this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType())));
	}
    
	@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");

		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;
	}   
}
isSimpleProperty() 是否为简单类型

在上面ModelAttributeMethodProcessor中的supportsParameter()中,当判断参数是否需要使用该解析器的时候,需要抛出简单类型

	public static boolean isSimpleProperty(Class<?> type) {
		Assert.notNull(type, "'type' must not be null");
		return isSimpleValueType(type) || (type.isArray() && isSimpleValueType(type.getComponentType()));
	}

    // 这里判断是否为即简单类型,可以看到,都是一些常用的基本类型
	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));
	}

setPropertyValues()

上面的方法调用到这里会经过几个方法,这里省略了,只截取了重要的方法

这里会通过循环来设置对象属性,但是注意一点:

注意:这里循环的内容是请求发送过来的参数,将接收的参数循环,然后赋值到参数对象中,那么当多个参数对象存在相同字段的时候,那么都会赋值,这里和普通参数获取不同,普通参数是拿着参数名去请求中获取,而参数对象则相反,其是将接收的参数遍历,通过反射,调用setter方法,赋值到参数对象内

@Override
	public void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid)
			throws BeansException {

		List<PropertyAccessException> propertyAccessExceptions = null;
		List<PropertyValue> propertyValues = (pvs instanceof MutablePropertyValues ?
				((MutablePropertyValues) pvs).getPropertyValueList() : Arrays.asList(pvs.getPropertyValues()));

		if (ignoreUnknown) {
			this.suppressNotWritablePropertyException = true;
		}
		try {
             // 这里循环的内容是 请求中携带的参数 
			for (PropertyValue pv : propertyValues) {
				try {
                      // 这里设置属性值
					setPropertyValue(pv);
				}
				catch (NotWritablePropertyException ex) {
					if (!ignoreUnknown) {
						throw ex;
					}
					// Otherwise, just ignore it and continue...
				}
				catch (NullValueInNestedPathException ex) {
					if (!ignoreInvalid) {
						throw ex;
					}
					// Otherwise, just ignore it and continue...
				}
				catch (PropertyAccessException ex) {
					if (propertyAccessExceptions == null) {
						propertyAccessExceptions = new ArrayList<>();
					}
					propertyAccessExceptions.add(ex);
				}
			}
		}
		finally {
			if (ignoreUnknown) {
				this.suppressNotWritablePropertyException = false;
			}
		}

		// If we encountered individual exceptions, throw the composite exception.
		if (propertyAccessExceptions != null) {
			PropertyAccessException[] paeArray = propertyAccessExceptions.toArray(new PropertyAccessException[0]);
			throw new PropertyBatchUpdateException(paeArray);
		}
	}

processLocalProperty()

  1. 会判断该参数对象中是否存在这个字段,也就是是否需要赋值
  2. 把数据进行类型转换
  3. 调用方法设置值
private void processLocalProperty(PropertyTokenHolder tokens, PropertyValue pv) {
         // 这里会获取属性处理器
         // 如果接收的参数在参数对象中不存在,则为Null
		PropertyHandler ph = getLocalPropertyHandler(tokens.actualName);
		if (ph == null || !ph.isWritable()) {
			if (pv.isOptional()) {
				if (logger.isDebugEnabled()) {
					logger.debug("Ignoring optional value for property '" + tokens.actualName +
							"' - property not found on bean class [" + getRootClass().getName() + "]");
				}
				return;
			}
			if (this.suppressNotWritablePropertyException) {
				// Optimization for common ignoreUnknown=true scenario since the
				// exception would be caught and swallowed higher up anyway...
				return;
			}
			throw createNotWritablePropertyException(tokens.canonicalName);
		}

		Object oldValue = null;
		try {
			Object originalValue = pv.getValue();
			Object valueToApply = originalValue;
			if (!Boolean.FALSE.equals(pv.conversionNecessary)) {
				if (pv.isConverted()) {
					valueToApply = pv.getConvertedValue();
				}
				else {
					if (isExtractOldValueForEditor() && ph.isReadable()) {
						try {
							oldValue = ph.getValue();
						}
						catch (Exception ex) {
							if (ex instanceof PrivilegedActionException) {
								ex = ((PrivilegedActionException) ex).getException();
							}
							if (logger.isDebugEnabled()) {
								logger.debug("Could not read previous value of property '" +
										this.nestedPath + tokens.canonicalName + "'", ex);
							}
						}
					}
                      // 这里会进行转换,因为接收的参数文本类型,但是我们接收的参数是各种类型
                      // 对于Http来说 所谓万物皆文本
					valueToApply = convertForProperty(
							tokens.canonicalName, oldValue, originalValue, ph.toTypeDescriptor());
				}
				pv.getOriginalPropertyValue().conversionNecessary = (valueToApply != originalValue);
			}
             // 设置值
			ph.setValue(valueToApply);
		}
		catch (TypeMismatchException ex) {
			throw ex;
		}
		catch (InvocationTargetException ex) {
			PropertyChangeEvent propertyChangeEvent = new PropertyChangeEvent(
					getRootInstance(), this.nestedPath + tokens.canonicalName, oldValue, pv.getValue());
			if (ex.getTargetException() instanceof ClassCastException) {
				throw new TypeMismatchException(propertyChangeEvent, ph.getPropertyType(), ex.getTargetException());
			}
			else {
				Throwable cause = ex.getTargetException();
				if (cause instanceof UndeclaredThrowableException) {
					// May happen e.g. with Groovy-generated methods
					cause = cause.getCause();
				}
				throw new MethodInvocationException(propertyChangeEvent, cause);
			}
		}
		catch (Exception ex) {
			PropertyChangeEvent pce = new PropertyChangeEvent(
					getRootInstance(), this.nestedPath + tokens.canonicalName, oldValue, pv.getValue());
			throw new MethodInvocationException(pce, ex);
		}
	}

setValue()

获取setter的赋值方法,然后进行赋值

		@Override
		public void setValue(@Nullable Object value) throws Exception {
             // 获取参数的setter方法,有了setter,就方便赋值了
			Method writeMethod = (this.pd instanceof GenericTypeAwarePropertyDescriptor ?
					((GenericTypeAwarePropertyDescriptor) this.pd).getWriteMethodForActualAccess() :
					this.pd.getWriteMethod());
			if (System.getSecurityManager() != null) {
				AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
					ReflectionUtils.makeAccessible(writeMethod);
					return null;
				});
				try {
					AccessController.doPrivileged((PrivilegedExceptionAction<Object>)
							() -> writeMethod.invoke(getWrappedInstance(), value), acc);
				}
				catch (PrivilegedActionException ex) {
					throw ex.getException();
				}
			}
			else {
				ReflectionUtils.makeAccessible(writeMethod);
                  // 通过反射调用setter
				writeMethod.invoke(getWrappedInstance(), value);
			}
		}	

4.参数转换器

Converter

在普通参数和对象参数最终赋值的时候都会借助一个WebDataBinder来绑定数据,那么它需要转换器Converter来辅助,转换器主要是将参数转换成我们需要的类型,因为从Http请求传递过来的参数都是字符串,对应Http来说,万物接文字,那么需要将传递过来的字符串转换成我们接收参数的类型,就是通过Converter来完成

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a0DSByY8-1652195877784)(E:\Gitee\spingboot\converters.png)]

自定义Converter

正常我们前端发送过来的数据都是一个 key 对应一个Value,但是有的时候我们想1个字段发送2个值

例如: http://127.0.0.1:8080/test8?user=1,张,将两个值一起发送,后台实现解析,赋值到2个字段上

这个时候,我们需要在自定义Converter,来实现这个功能

@Configuration(proxyBeanMethods = false)
public class UserConfig {

    // 还是借助 WebMvcConfigurer 自定义MVC策略
    @Bean
    public WebMvcConfigurer webMvcConfigurer() {
        return new WebMvcConfigurer() {
			
            @Override
            public void addFormatters(FormatterRegistry registry) {
                registry.addConverter(new Converter<String, User>() {
                    @Override
                    public User convert(String source) {
                        if (!source.isEmpty()) {
                            User user = new User();
                            String[] split = source.split(",");
                            user.setId(Integer.valueOf(split[0]));
                            user.setName(split[1]);
                            return user;
                        }
                        return null;
                    }
                });
            }
        };
    }

}

5.注意点

  1. 自定义参数对象,将请求数据自动封装到参数中,请求需要是GET请求,如果是POST,请求体中是不会处理的
  2. 这里循环的内容是请求发送过来的参数,将接收的参数循环,然后赋值到参数对象中,那么当多个参数对象存在相同字段的时候,那么都会赋值
  3. 这里和普通参数获取不同,普通参数匹配是拿着参数名去请求中获取,而参数对象则相反,其是将接收的参数遍历,通过反射,调用setter方法,赋值到参数对象内
    p请求传递过来的参数都是字符串,对应Http来说,万物接文字,那么需要将传递过来的字符串转换成我们接收参数的类型,就是通过Converter来完成

[外链图片转存中…(img-a0DSByY8-1652195877784)]

自定义Converter

正常我们前端发送过来的数据都是一个 key 对应一个Value,但是有的时候我们想1个字段发送2个值

例如: http://127.0.0.1:8080/test8?user=1,张,将两个值一起发送,后台实现解析,赋值到2个字段上

这个时候,我们需要在自定义Converter,来实现这个功能

@Configuration(proxyBeanMethods = false)
public class UserConfig {

    // 还是借助 WebMvcConfigurer 自定义MVC策略
    @Bean
    public WebMvcConfigurer webMvcConfigurer() {
        return new WebMvcConfigurer() {
			
            @Override
            public void addFormatters(FormatterRegistry registry) {
                registry.addConverter(new Converter<String, User>() {
                    @Override
                    public User convert(String source) {
                        if (!source.isEmpty()) {
                            User user = new User();
                            String[] split = source.split(",");
                            user.setId(Integer.valueOf(split[0]));
                            user.setName(split[1]);
                            return user;
                        }
                        return null;
                    }
                });
            }
        };
    }

}

5.注意点

  1. 自定义参数对象,将请求数据自动封装到参数中,请求需要是GET请求,如果是POST,请求体中是不会处理的
  2. 这里循环的内容是请求发送过来的参数,将接收的参数循环,然后赋值到参数对象中,那么当多个参数对象存在相同字段的时候,那么都会赋值
  3. 这里和普通参数获取不同,普通参数匹配是拿着参数名去请求中获取,而参数对象则相反,其是将接收的参数遍历,通过反射,调用setter方法,赋值到参数对象内
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

为人师表好少年

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值