重点:
- Controller层支持的各种注解以及负责处理这个注解参数解析器
- Servert Api 默认注入的参数
- 参数是如何自动绑定的,包括单个参数和对象参数
一、参数解析器
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
- 获取前端传递的JSON字符串数据,可以和
@RequestParam
同时使用,@RequestBody 接收的是请求体里面的数据;@RequestParam接收的是key-value里面的参数 - 一个请求,
@RequestBody
只能存在一个,而@RequestParam
可以使用多个,因为请求体只有1个 - 获取的是请求体的数据,所以使用
POST
请求 - 在参数上添加该注解,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 传递的每个参数包含多个值
矩阵变量使用注意事项:
- 此功能默认关闭,需要开启
- 矩阵变量需要结合路径变量
@PathVariable
来使用 - 矩阵变量中的参数多个以分号隔开,每个参数的值可以是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
上面测试代码已经试验了Mode
和Map
会存储到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
方法,但是获取对应参数的值是调用子类RequestParamMethodArgumentResolver
的resolveName
方法,具体的代码如下:
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()
- 会判断该参数对象中是否存在这个字段,也就是是否需要赋值
- 把数据进行类型转换
- 调用方法设置值
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.注意点
- 自定义参数对象,将请求数据自动封装到参数中,请求需要是GET请求,如果是POST,请求体中是不会处理的
- 这里循环的内容是请求发送过来的参数,将接收的参数循环,然后赋值到参数对象中,那么当多个参数对象存在相同字段的时候,那么都会赋值
- 这里和普通参数获取不同,普通参数匹配是拿着参数名去请求中获取,而参数对象则相反,其是将接收的参数遍历,通过反射,调用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.注意点
- 自定义参数对象,将请求数据自动封装到参数中,请求需要是GET请求,如果是POST,请求体中是不会处理的
- 这里循环的内容是请求发送过来的参数,将接收的参数循环,然后赋值到参数对象中,那么当多个参数对象存在相同字段的时候,那么都会赋值
- 这里和普通参数获取不同,普通参数匹配是拿着参数名去请求中获取,而参数对象则相反,其是将接收的参数遍历,通过反射,调用setter方法,赋值到参数对象内