SpringMVC-请求参数映射

目录

  • SpringMVC-请求参数映射
  • 1、简单介绍
  • 2、快速开始
  • 3、如何确定handler
  • 4、如果找到参数对应的值
  • 5、执行目标方法
  • 5.1、自定义类请求封装原理
  • Demo1
  • Demo2
  • 总结
  • 6、返回值处理
  • 总结

1、简单介绍

在controller层中经常需要在方法参数上写对应的参数来接收来自前端的请求,那么这些值是如何请求映射进来的?

下面将从这里的问题来开始进行分析:

2、快速开始

首先建立起来一个controller层,来接收参数,然后直接进行返回

@RestController
public class ParameterTestController {
    @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(value = "_ga",required = false) String _ga,
                                     @CookieValue(value = "_ga",required = false) Cookie cookie){
        Map<String,Object> map = new HashMap<>();
        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;
    }  
}

3、如何确定handler

首先发送请求,让请求到达这里。然后开始分析。

首先所有的请求必须要到达DispatcherServlet类中来,那么首先确定是哪个handler能够来对当前的请求来进行处理。

其实现在的开发中都是使用了注解@RequestMapping或者是其延伸注解来进行开发的。

那么我们也可以来看一下,是如何来确定对应的handler的,直接到DispatcherServlet中来找到下面这行方法:

mappedHandler = getHandler(processedRequest);

那么进去看下:

@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    if (this.handlerMappings != null) {
        // 首先遍历所有的处理器映射器,也就是HandlerMapping
        for (HandlerMapping mapping : this.handlerMappings) {
            HandlerExecutionChain handler = mapping.getHandler(request);
            if (handler != null) {
                return handler;
            }
        }
    }
    return null;
}

那么不妨去里面看一下这个接口:

@Nullable
HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;

这个接口中就只有一个接口,那么子类只需要去做不同的实现即可,那么看一下对应的子类:

axios 请求 后台spring mvc参数为null_spring

那么这里首先不需要来看所有的,我们断点进入到方法中来看:

axios 请求 后台spring mvc参数为null_子类_02

对于我们后台开发人员来说,最熟悉的莫过于是第一个注解:RequestMappingHandlerMapping。这个就是来处理我们在controller层中写的@RequestMapping注解及其衍生注解的。从上面可以看到这个类中重写了对应的getHandler,那么看一下,如何找到的:

先走父类中的方法:

@Override
@Nullable
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    Object handler = getHandlerInternal(request);
    .......
        return executionChain;
}

那么继续跟进,还是父类中的方法:

@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
    ....
    return super.getHandlerInternal(request);
    ....
}

那么继续跟进:

@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
    // 首先拿到对应的请求路径!从这里可以看到是想要通过request拿到请求的路径
    String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
    request.setAttribute(LOOKUP_PATH, lookupPath);
    this.mappingRegistry.acquireReadLock();
    try {
        // 通过名字就可以看到是找到处理方法的handler
        HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
        return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
    }
    finally {
        this.mappingRegistry.releaseReadLock();
    }
}

那么继续跟进:

@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
    List<Match> matches = new ArrayList<>();
    // 从映射注册中心中来找到映射的url
    List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
    // 这里如果为空,那么将不走这里
    if (directPathMatches != null) {
        addMatchingMappings(directPathMatches, matches, request);
    }
    
    // 下面这段注释注释的非常完美!别无选择,只能够去遍历所有的映射。也就是说,遍历
    // 所有的@RequestMapping中的注解中写的url路径。其实代码逻辑走到了这里,找到对应的handler就已经够用了
    // 其实自己也可以自己来做一个简单版本的springMVC了,加上注解来进行扫描即可。
    if (matches.isEmpty()) {
        // No choice but to go through all mappings...
        addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
    }
	// 当代码走到这里来的时候,已经都有值了。
    // 下面这里是利用算法来找到一个最合适的handler,一般来说,下面的就不用看了
    if (!matches.isEmpty()) {
        Match bestMatch = matches.get(0);
        if (matches.size() > 1) {
            Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
            matches.sort(comparator);
            bestMatch = matches.get(0);
            if (logger.isTraceEnabled()) {
                logger.trace(matches.size() + " matching mappings: " + matches);
            }
            if (CorsUtils.isPreFlightRequest(request)) {
                return PREFLIGHT_AMBIGUOUS_MATCH;
            }
            Match secondBestMatch = matches.get(1);
            if (comparator.compare(bestMatch, secondBestMatch) == 0) {
                Method m1 = bestMatch.handlerMethod.getMethod();
                Method m2 = secondBestMatch.handlerMethod.getMethod();
                String uri = request.getRequestURI();
                
                // 这段代码曾经是比较熟悉的代码了!!!!!也就是controller层中存在了多个相同的请求路径
                throw new IllegalStateException(
                    "Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
            }
        }
        request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
        handleMatch(bestMatch.mapping, lookupPath, request);
        return bestMatch.handlerMethod;
    }
    else {
        return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
    }
}

4、如果找到参数对应的值

上面的已经可以找到哪个controller中的哪个方法,也就是handler能够来对请求中的路径来进行处理了。那么对于handler中的方法中的值又是如何来确定的?

继续看向DispatcherServlet这个类,看到这段代码:

// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

通过方法注释,可以看到:使用处理器适配器来对当前的请求来做处理。

这里最终会使用反射来通过调用这个方法,这个Adapter其实是一个巨大的反射工具类。但是由于这一块的功能复杂,所以才单独抽取出来的。

那么首先看下这个接口:

public interface HandlerAdapter {

	// 首先判断是否支持这种类型的handler
	boolean supports(Object handler);

	// 如果支持这种类型的handler,那么将会调用下面的handle方法来对这中类型的handler来做处理
	@Nullable
	ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;

	// 最后一次修改!相当于是缓存
	long getLastModified(HttpServletRequest request, Object handler);

}

那么看下它的子类:

axios 请求 后台spring mvc参数为null_ide_03

那么依然通过断点看到springmvc配置的handlerAdapter:

/**
* Return the HandlerAdapter for this handler object.
* @param handler the handler object to find an adapter for
* @throws ServletException if no HandlerAdapter can be found for the handler. This is a fatal error.
*/
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
    if (this.handlerAdapters != null) {
        for (HandlerAdapter adapter : this.handlerAdapters) {
            if (adapter.supports(handler)) {
                return adapter;
            }
        }
    }
    throw new ServletException("No adapter for handler [" + handler +
                               "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}

看一下springmvc默认配置的几个子类:

axios 请求 后台spring mvc参数为null_子类_04

对于第一个我们是最熟悉的了,支持方法上有@RequestMapping注解的方法!下面的几个在其他开发场景中可能会使用到,但是当前的场景下是原生servlet开发的,所以暂时不需要来做一个处理。

那么就看一下RequestMappingHandlerAdapter这个类中是如何判断支持的:

public final boolean supports(Object handler) {
    // 判断是否是HandlerMethod这个类型的。也就是说,判断是否是一个方法处理器,是不是这个类型的,然后是就直接返回为true
    return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
}

// 直接返回为true
protected boolean supportsInternal(HandlerMethod handlerMethod) {
    return true;
}

所以就相当于直接给我们找到了一个handler的适配器RequestMappingHandlerAdapter

5、执行目标方法

上面已经为handler找到了对应的adapter,那么下面就应该确定参数的值

那么继续走到DispatcherSerlvet中来,看到下面这段代码:

// Actually invoke the handler.  真正执行handler的方法
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

那么从这里开始就应该开始真正来开始执行handler了,那么跟进去:

public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
    throws Exception {

    return handleInternal(request, response, (HandlerMethod) handler);
}

那么继续跟进:

@Override
protected ModelAndView handleInternal(HttpServletRequest request,
                                      HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
    .........
    // Execute invokeHandlerMethod in synchronized block if required.
    if (this.synchronizeOnSession) {
	......
    }
    else {
        // 这段代码才是真正执行handler的方法
        mav = invokeHandlerMethod(request, response, handlerMethod);
    }
    .......

     return mav;
}

继续跟进:

RequestMappingHandlerAdapter中的invokeHandlerMethod方法:

@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
                                           HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

    ServletWebRequest webRequest = new ServletWebRequest(request, response);
    try {
        ..........
         // servlet可以调用的处理器方法。在这里来封装参数解析器和返回值处理器。这个对象非常重要
         ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
        // 确定参数解析器   
        if (this.argumentResolvers != null) {
            invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
        }
        // 确定返回值处理器
        if (this.returnValueHandlers != null) {
            invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
        }
        invocableMethod.setDataBinderFactory(binderFactory);
        invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);

        ModelAndViewContainer mavContainer = new ModelAndViewContainer();
        ...........
            if (asyncManager.hasConcurrentResult()) {
                Object result = asyncManager.getConcurrentResult();
                mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
                asyncManager.clearConcurrentResult();
                LogFormatUtils.traceDebug(logger, traceOn -> {
                    String formatted = LogFormatUtils.formatValue(result, !traceOn);
                    return "Resume with async result [" + formatted + "]";
                });
                invocableMethod = invocableMethod.wrapConcurrentResult(result);
            }
		// 这一步是核心!!!!!!!!!!!!
        invocableMethod.invokeAndHandle(webRequest, mavContainer);
        if (asyncManager.isConcurrentHandlingStarted()) {
            return null;
        }

        return getModelAndView(mavContainer, modelFactory, webRequest);
    }
    finally {
        webRequest.requestCompleted();
    }
}

那么看下springmvc提供的参数解析器和参数解析器的缓存:

axios 请求 后台spring mvc参数为null_子类_05

其实这里通过每个参数解析器的名字就可以判断出来对应的解析器是用来解析什么类型的了。

举个例子说明:第一个是用来解析标注了@RequestParam注解的,第二个是用来解析标注了@RequestParamMap注解的

第三个是用来解析标注了@PathVariable注解的等等

其实controller中的方法中的参数能够写多少种能够写什么,是根据这里来的,而不是随意写出来的。

那么来看一下参数解析器的接口:

public interface HandlerMethodArgumentResolver {
    // 能够参数解析器是否支持参数的解析
    boolean supportsParameter(MethodParameter var1);
	// 如果参数解析器支持,那么就来调用参数解析器来进行解析
    @Nullable
    Object resolveArgument(MethodParameter var1, @Nullable ModelAndViewContainer var2, NativeWebRequest var3, @Nullable WebDataBinderFactory var4) throws Exception;
}

那么看完这些,继续去看下核心方法的处理:

// 可执行的方法来执行方法
invocableMethod.invokeAndHandle(webRequest, mavContainer);

那么继续跟进:

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {
		// 执行当前请求,然后这里将会拿到所有的返回值!!那么这里又是如何来进行确定的???
		Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
		..........
	}

所以真正执行目标方法是在这一步执行的。

上面已经得到了参数解析器,这里应该来对参数进行解析,然后将解析之后的值进行返回。

那么继续跟进:

@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
    // 获取得到方法参数的值。这里就直接来进行确定了
    Object[] args = this.getMethodArgumentValues(request, mavContainer, providedArgs);
    if (this.logger.isTraceEnabled()) {
        this.logger.trace("Arguments: " + Arrays.toString(args));
    }

    return this.doInvoke(args);
}

如何获取得到方法参数的值?

InvocableHandlerMethod中的getMethodArgumentValues方法

protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
    // 首先获取得到所有方法参数的声明
    MethodParameter[] parameters = this.getMethodParameters();
    // 如果方法中没有参数,那么就直接进行了返回。
    if (ObjectUtils.isEmpty(parameters)) {
        return EMPTY_ARGS;
    } else {
        // 如果有参数列表,那么在这里将会来进行封装。有多少个参数,这里存放的将会是这些参数的值
        // 可以看到最终返回的是这个args值
        Object[] args = new Object[parameters.length];
	   // 有多少个参数,将会来遍历多少次
        for(int i = 0; i < parameters.length; ++i) {
            // 拿到第i个参数的具体信息
            MethodParameter parameter = parameters[i];
            parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
            args[i] = findProvidedArgument(parameter, providedArgs);
            if (args[i] == null) {
                // 在解析之前,首先会来判断参数解析器是否是支持的?
                if (!this.resolvers.supportsParameter(parameter)) {
                    throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
                }

                try {
                    // 解析方法参数的值
                    args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
                } catch (Exception var10) {
                    if (this.logger.isDebugEnabled()) {
                        String exMsg = var10.getMessage();
                        if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
                            this.logger.debug(formatArgumentError(parameter, exMsg));
                        }
                    }

                    throw var10;
                }
            }
        }

        return args;
    }
}

从第一步中,可以获取得到方法参数中的所有信息:

axios 请求 后台spring mvc参数为null_子类_06

方法中加了什么注解?数据类型是什么?注解中的值是什么?这里都写的很清楚了

那么在判断参数解析器是否支持当前的方法参数的?我们可以看一下核心代码:

if (!this.resolvers.supportsParameter(parameter)) {
    throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
}

跟进去:

public boolean supportsParameter(MethodParameter parameter) {
    return this.getArgumentResolver(parameter) != null;
}

再跟进去:

@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
    // 缓存思想,首先从缓存中来进行获取!这种之前已经在截图中专门截图了一下
    HandlerMethodArgumentResolver result = (HandlerMethodArgumentResolver)this.argumentResolverCache.get(parameter);
    // 如果缓存中没有,那么将会来遍历所有的!这种思想在找handler的时候也是一致的。
    // 这里和在controller中从redis中获取也是一致的。
    if (result == null) {
        Iterator var3 = this.argumentResolvers.iterator();
		
        while(var3.hasNext()) {
            // 首先依次获取得到每一个参数解析器
            HandlerMethodArgumentResolver resolver = (HandlerMethodArgumentResolver)var3.next();
            // 来进行判断是否支持这种参数
            if (resolver.supportsParameter(parameter)) {
                // 如果支持,那么进行赋值操作
                result = resolver;
                // 并将这种参数类型支持的放入到参数解析器的缓存中去
                this.argumentResolverCache.put(parameter, resolver);
                break;
            }
        }
    }

    return result;
}

那么从这里,我们可以慢慢总结一下,哪些类型的由哪些参数处理器来进行处理:

参数解析器名称

类型

支持类型

RequestParamMethodArgumentResolver

1、@RequestParam且非Map;2、@RequestPart

RequestParamMapMethodArgumentResolver

@RequestParamMap

PathVariableMethodArgumentResolver

@PathVariable

那么既然说找到了对应的参数解析器之后,因为代码中的第一个参数中的使用了@PathVariable注解,所以看下对于这个的解析:

首先看一下核心方法:

args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);

那么看一下对应的方法:

@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
    // 获取得到参数解析器,因为在上一步已经确定好了这里的值了,所以这里直接获取得到就行了
    // 但是还是去看一下比较好
    HandlerMethodArgumentResolver resolver = this.getArgumentResolver(parameter);
    if (resolver == null) {
        throw new IllegalArgumentException("Unsupported parameter type [" + parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
    } else {
        // 这里开始来进行解析即可
        return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
    }
}


@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
    // 这里第一步,直接来从缓存中来进行获取即可
    HandlerMethodArgumentResolver result = (HandlerMethodArgumentResolver)this.argumentResolverCache.get(parameter);
    if (result == null) {
        Iterator var3 = this.argumentResolvers.iterator();

        while(var3.hasNext()) {
            HandlerMethodArgumentResolver resolver = (HandlerMethodArgumentResolver)var3.next();
            if (resolver.supportsParameter(parameter)) {
                result = resolver;
                this.argumentResolverCache.put(parameter, resolver);
                break;
            }
        }
    }

    return result;
}

那么看一下@PathVariable注解对应的解析方法:

@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
    // 从这里能够拿到注解中参数的所有的信息。比如说:name,required和defaultValue等值
    AbstractNamedValueMethodArgumentResolver.NamedValueInfo namedValueInfo = this.getNamedValueInfo(parameter);
    // 获取得到方法参数对应的值
    MethodParameter nestedParmeter = parameter.nestedIfOptional();
    // 通过注解来得到对应的参数的名字
    Object resolvedName = this.resolveStringValue(namedValueInfo.name);
    if (resolvedName == null) {
        throw new IllegalArgumentException("Specified name must not resolve to null: [" + namedValueInfo.name + "]");
    } else {
        // 确定方法的值!!!那么重点来了,看这里是怎么做的
        Object arg = this.resolveName(resolvedName.toString(), nestedParameter, webRequest);
        // 如果是空的,那么看看是否可以来使用对应的默认值来进行操作,可以看到这里也是很贴心的操作
        if (arg == null) {
            if (namedValueInfo.defaultValue != null) {
                arg = this.resolveStringValue(namedValueInfo.defaultValue);
            } else if (namedValueInfo.required && !nestedParameter.isOptional()) {
                this.handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
            }

            arg = this.handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
        } else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
            arg = this.resolveStringValue(namedValueInfo.defaultValue);
        }

        if (binderFactory != null) {
            WebDataBinder binder = binderFactory.createBinder(webRequest, (Object)null, namedValueInfo.name);

            try {
                arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
            } catch (ConversionNotSupportedException var11) {
                throw new MethodArgumentConversionNotSupportedException(arg, var11.getRequiredType(), namedValueInfo.name, parameter, var11.getCause());
            } catch (TypeMismatchException var12) {
                throw new MethodArgumentTypeMismatchException(arg, var12.getRequiredType(), namedValueInfo.name, parameter, var12.getCause());
            }
        }
		// 将解析之后的值放入到处理器解析之后的值
        this.handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
        return arg;
    }
}

进入到获取得到方法注解的值得地方:

@Override
@SuppressWarnings("unchecked")
@Nullable
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
    // 获取得到属性域的值
    // 其实这里后面做的步骤就是UrlPathHelper在请求一进来的时候就进行了解析和封装,然后保存到request域对象中,还是一个map对象
    Map<String, String> uriTemplateVars = (Map<String, String>) request.getAttribute(
        HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
    // 直接从缓存好了值中来进行获取得到对应的值即可
    return (uriTemplateVars != null ? uriTemplateVars.get(name) : null);
}

5.1、自定义类请求封装原理

定义表单:

<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="pet.name" value="阿猫"/><br/>
    宠物年龄:<input name="pet.age" value="5"/>
    <!--宠物: <input name="pet" value="啊猫,3"/>-->
    <input type="submit" value="保存"/>
</form>

定义类:

@Data
public class Person {
    private String userName;
    private Integer age;
    private Date birth;
    private Pet pet;
}


@Data
public class Pet {
    private String name;
    private Integer age;
}

写一个请求处理类:

@PostMapping("/saveuser")
public Person saveuser(Person person){
    return person;
}

只要发送请求,那么从表单中提交过去的参数就会来进行封装到对象中去。那么为什么可以这样子来进行操作?

下面就是数据绑定的原理解析。

下面来跟踪一下全过程:

首先所有的请求都将会到达DispatcherServlet类中来,然后调用doDispatch方法:

// 首先会为当前请求找到handler
mappedHandler = getHandler(processedRequest);
// 再为找到的handler来找到对应的处理器适配器
// 因为需要确定参数名称和参数值,最终通过反射来进行调用方法,这里是做了一个大的封装
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

// 真正调用方法,并返回模型和数据
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

那么参数的封装、映射和处理就在这个方法中,那么来分析一下:

// 调用handler来处理
mav = invokeHandlerMethod(request, response, handlerMethod);

// 可执行方法来调用并执行
invocableMethod.invokeAndHandle(webRequest, mavContainer);

// 得到所有的返回值,如何来进行获取的?请求数据封装也是在这里来做的
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);

// 得到方法方法的所有值。那么数据绑定肯定也是在这里来做的
Object[] args = this.getMethodArgumentValues(request, mavContainer, providedArgs);

重点方法另外起一行:

protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
    // 获取得到方法参数的所有信息,将其封装称为一个数组
    MethodParameter[] parameters = this.getMethodParameters();
    if (ObjectUtils.isEmpty(parameters)) {
        return EMPTY_ARGS;
    } else {
        Object[] args = new Object[parameters.length];

        for(int i = 0; i < parameters.length; ++i) {
            MethodParameter parameter = parameters[i];
            parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
            args[i] = findProvidedArgument(parameter, providedArgs);
            if (args[i] == null) {
                // 判断哪种参数解析器支持解析
                if (!this.resolvers.supportsParameter(parameter)) {
                    throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
                }

                try {
                    // 如果支持,那么利用这种参数解析器来进行解析
                    args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
                } catch (Exception var10) {
                    if (this.logger.isDebugEnabled()) {
                        String exMsg = var10.getMessage();
                        if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
                            this.logger.debug(formatArgumentError(parameter, exMsg));
                        }
                    }

                    throw var10;
                }
            }
        }

        return args;
    }
}

所以对于我们自定义的数据类型Person来说,首先需要找到是哪种参数解析器来进行解析的?

通过断点,可以知道是:ServletModelAttributeMethodProcessor

那么看下这个参数解析器是如何来对我们自定义的数据类型来进行解析的:

// 首先判断是否是支持的?
public boolean supportsParameter(MethodParameter parameter) {
    // 没有标注ModelAttribute注解,那么走下一个判断
    return parameter.hasParameterAnnotation(ModelAttribute.class) || this.annotationNotRequired && 
        // 方法返回false,那么这里就是true,判断就支持了
        !BeanUtils.isSimpleProperty(parameter.getParameterType());
}

// 判断是否是简单类型的
public static boolean isSimpleProperty(Class<?> type) {
    Assert.notNull(type, "'type' must not be null");
    return isSimpleValueType(type) || type.isArray() && isSimpleValueType(type.getComponentType());
}

// 判断是否是以下类型的,当然我们自定义的不可能是这些数据类型,所以这里返回的是false
public static boolean isSimpleValueType(Class<?> type) {
    return Void.class != type && Void.TYPE != 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);
}

判断支持之后,那么看下是如何来进行处理的?

@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 = (ModelAttribute)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 {
        try {
            // 创建属性。这里也就是开始来进行初始化。也就是说一个person对象,但是属性全部是空的
            attribute = this.createAttribute(name, parameter, binderFactory, webRequest);
        } catch (BindException var10) {
            if (this.isBindExceptionRequired(parameter)) {
                throw var10;
            }

            if (parameter.getParameterType() == Optional.class) {
                attribute = Optional.empty();
            }

            bindingResult = var10.getBindingResult();
        }
    }
	// 获取得到上面的空的person对象,然后将表单中的属性和person对象来进行绑定,那么需要看下如何来进行绑定的?
    if (bindingResult == null) {
        // 创建一个web数据绑定器。作用是将请求携带的数据绑定到person对象中来。
        WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
        if (binder.getTarget() != null) {
            if (!mavContainer.isBindingDisabled(name)) {
                this.bindRequestParameters(binder, webRequest);
            }

            this.validateIfApplicable(binder, parameter);
            if (binder.getBindingResult().hasErrors() && this.isBindExceptionRequired(binder, parameter)) {
                throw new BindException(binder.getBindingResult());
            }
        }

        if (!parameter.getParameterType().isInstance(attribute)) {
            attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
        }

        bindingResult = binder.getBindingResult();
    }

    Map<String, Object> bindingResultModel = bindingResult.getModel();
    mavContainer.removeAttributes(bindingResultModel);
    mavContainer.addAllAttributes(bindingResultModel);
    return attribute;
}

下面通过截图来看一下:

axios 请求 后台spring mvc参数为null_spring_07

从上面的图中可以看到有我们的目标类型以及对应的转换器。转换器就是将从浏览器发送过来的数据(都是字符串类型),而我们自定义的类类型不可能都是字符串类型,这里需要有转换器来将字符串转换成为指定的类型。

那么说明这里来可以自己来进行定义。这就是后面提到的定制化操作。

这里就说明了从表单中提交的数据绑定到最终的自定义的类上:

对比一下:

<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="pet.name" value="阿猫"/><br/>
    宠物年龄:<input name="pet.age" value="5"/>
    <!--宠物: <input name="pet" value="啊猫,3"/>-->
    <input type="submit" value="保存"/>
</form>
userName	zhangsan  		string			String userName
age			18			   string		    Integer age
birth		2019/12/10 		string			Date birth
pet.name	阿猫			  string		  Pet pet
pet.age		5			    string		    Pet pet

从这里可以看到因为提交的参数都是和javabean中属性一致的。而中间这一步的转换就是转换器需要做的事情。

也就是说WebDataBinder会创建一个对象,其属性全为空的对象。然后利用转换器进行赋值操作。

if (bindingResult == null) {
        // 创建一个web数据绑定器。作用是将请求携带的数据绑定到person对象中来。
        WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
    	// 拿到属性为空的对象来进行操作
        if (binder.getTarget() != null) {
            if (!mavContainer.isBindingDisabled(name)) {
                // 绑定请求参数
                this.bindRequestParameters(binder, webRequest);
            }
		............

那么看一下绑定请求参数原理:

protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) {
    // 获取得到原生请求
    ServletRequest servletRequest = request.getNativeRequest(ServletRequest.class);
    Assert.state(servletRequest != null, "No ServletRequest");
    ServletRequestDataBinder servletBinder = (ServletRequestDataBinder) binder;
    // 开始来进行绑定
    servletBinder.bind(servletRequest);
}

开始绑定:

public void bind(ServletRequest request) {
    // 获取得到属性的值
    MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request);
    // 判断是否是文件上传请求
    MultipartRequest multipartRequest = (MultipartRequest)WebUtils.getNativeRequest(request, MultipartRequest.class);
    if (multipartRequest != null) {
        this.bindMultipart(multipartRequest.getMultiFileMap(), mpvs);
    }

    this.addBindValues(mpvs, request);
    this.doBind(mpvs);
}

axios 请求 后台spring mvc参数为null_ide_08

从可以获取得到所有的请求参数的名字和对应的值,然后接下来的操作就是要将k和person的属性来进行比较。

看下:

this.doBind(mpvs);

然后看下这个方法:

super.doBind(mpvs);

进去看下对应的方法:

protected void doBind(MutablePropertyValues mpvs) {
    this.checkAllowedFields(mpvs);
    this.checkRequiredFields(mpvs);
    // 添加属性的值
    this.applyPropertyValues(mpvs);
}

那么看下这个方法:

protected void applyPropertyValues(MutablePropertyValues mpvs) {
    try {
        // 开始对属性设置值,看下具体的方法
        this.getPropertyAccessor().setPropertyValues(mpvs, this.isIgnoreUnknownFields(), this.isIgnoreInvalidFields());
    } catch (PropertyBatchUpdateException var7) {
        PropertyAccessException[] var3 = var7.getPropertyAccessExceptions();
        int var4 = var3.length;
        for(int var5 = 0; var5 < var4; ++var5) {
            PropertyAccessException pae = var3[var5];
            this.getBindingErrorProcessor().processPropertyAccessException(pae, this.getInternalBindingResult());
        }
    }
}

看一下设置值的方法:

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());
    Iterator var6 = propertyValues.iterator();

    while(var6.hasNext()) {
        PropertyValue pv = (PropertyValue)var6.next();

        try {
            // 循环遍历对应的属性来进行赋值
            this.setPropertyValue(pv);
        } catch (NotWritablePropertyException var9) {
            if (!ignoreUnknown) {
                throw var9;
            }
        } catch (NullValueInNestedPathException var10) {
            if (!ignoreInvalid) {
                throw var10;
            }
        } catch (PropertyAccessException var11) {
            if (propertyAccessExceptions == null) {
                propertyAccessExceptions = new ArrayList();
            }

            propertyAccessExceptions.add(var11);
        }
    }

    if (propertyAccessExceptions != null) {
        PropertyAccessException[] paeArray = (PropertyAccessException[])propertyAccessExceptions.toArray(new PropertyAccessException[0]);
        throw new PropertyBatchUpdateException(paeArray);
    }
}

看一下如何来进行封装的:

public void setPropertyValue(PropertyValue pv) throws BeansException {
    AbstractNestablePropertyAccessor.PropertyTokenHolder tokens = (AbstractNestablePropertyAccessor.PropertyTokenHolder)pv.resolvedTokens;
    if (tokens == null) {
        String propertyName = pv.getName();

        AbstractNestablePropertyAccessor nestedPa;
        try {
            nestedPa = this.getPropertyAccessorForPropertyPath(propertyName);
        } catch (NotReadablePropertyException var6) {
            throw new NotWritablePropertyException(this.getRootClass(), this.nestedPath + propertyName, "Nested property in path '" + propertyName + "' does not exist", var6);
        }

        tokens = this.getPropertyNameTokens(this.getFinalPath(nestedPa, propertyName));
        if (nestedPa == this) {
            pv.getOriginalPropertyValue().resolvedTokens = tokens;
        }
	   // 这里来进行set属性值
        nestedPa.setPropertyValue(tokens, pv);
    } else {
        this.setPropertyValue(tokens, pv);
    }

}

// 可以看到里面的属性
protected void setPropertyValue(AbstractNestablePropertyAccessor.PropertyTokenHolder tokens, PropertyValue pv) throws BeansException {
    if (tokens.keys != null) {
        this.processKeyedProperty(tokens, pv);
    } else {
        this.processLocalProperty(tokens, pv);
    }

}

axios 请求 后台spring mvc参数为null_spring_09

参数在属性中的名字,然后获取得到最简洁的名字.看一下核心方法:

private void processLocalProperty(AbstractNestablePropertyAccessor.PropertyTokenHolder tokens, PropertyValue pv) {
    // 拿到实际参数
    AbstractNestablePropertyAccessor.PropertyHandler ph = this.getLocalPropertyHandler(tokens.actualName);
    if (ph != null && ph.isWritable()) {
        Object oldValue = null;

        PropertyChangeEvent propertyChangeEvent;
        try {
            // 原始值
            Object originalValue = pv.getValue();
            // 开始准备将值来进行赋值
            Object valueToApply = originalValue;
            if (!Boolean.FALSE.equals(pv.conversionNecessary)) {
                if (pv.isConverted()) {
                    valueToApply = pv.getConvertedValue();
                } else {
                    if (this.isExtractOldValueForEditor() && ph.isReadable()) {
                        try {
                            oldValue = ph.getValue();
                        } catch (Exception var8) {
                            Exception ex = var8;
                            if (var8 instanceof PrivilegedActionException) {
                                ex = ((PrivilegedActionException)var8).getException();
                            }

                            if (logger.isDebugEnabled()) {
                                logger.debug("Could not read previous value of property '" + this.nestedPath + tokens.canonicalName + "'", ex);
                            }
                        }
                    }
				  // 为属性转换值。也就是表单中提交过来的数据要转换到类中来
                    valueToApply = this.convertForProperty(tokens.canonicalName, oldValue, originalValue, ph.toTypeDescriptor());
                }

                pv.getOriginalPropertyValue().conversionNecessary = valueToApply != originalValue;
            }

            ph.setValue(valueToApply);
        } catch (TypeMismatchException var9) {
            throw var9;
        } catch (InvocationTargetException var10) {
            propertyChangeEvent = new PropertyChangeEvent(this.getRootInstance(), this.nestedPath + tokens.canonicalName, oldValue, pv.getValue());
            if (var10.getTargetException() instanceof ClassCastException) {
                throw new TypeMismatchException(propertyChangeEvent, ph.getPropertyType(), var10.getTargetException());
            } else {
                Throwable cause = var10.getTargetException();
                if (cause instanceof UndeclaredThrowableException) {
                    cause = cause.getCause();
                }

                throw new MethodInvocationException(propertyChangeEvent, cause);
            }
        } catch (Exception var11) {
            propertyChangeEvent = new PropertyChangeEvent(this.getRootInstance(), this.nestedPath + tokens.canonicalName, oldValue, pv.getValue());
            throw new MethodInvocationException(propertyChangeEvent, var11);
        }
    } else if (pv.isOptional()) {
        if (logger.isDebugEnabled()) {
            logger.debug("Ignoring optional value for property '" + tokens.actualName + "' - property not found on bean class [" + this.getRootClass().getName() + "]");
        }

    } else {
        throw this.createNotWritablePropertyException(tokens.canonicalName);
    }
}

看一下这里的值转换:

@Nullable
protected Object convertForProperty(String propertyName, @Nullable Object oldValue, @Nullable Object newValue, TypeDescriptor td) throws TypeMismatchException {
    return this.convertIfNecessary(propertyName, oldValue, newValue, td.getType(), td);
}



private Object convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue, @Nullable Object newValue, @Nullable Class<?> requiredType, @Nullable TypeDescriptor td) throws TypeMismatchException {
    Assert.state(this.typeConverterDelegate != null, "No TypeConverterDelegate");

    PropertyChangeEvent pce;
    try {
        // 看一下这里的转换方法
        return this.typeConverterDelegate.convertIfNecessary(propertyName, oldValue, newValue, requiredType, td);
    } catch (IllegalStateException | ConverterNotFoundException var8) {
        pce = new PropertyChangeEvent(this.getRootInstance(), this.nestedPath + propertyName, oldValue, newValue);
        throw new ConversionNotSupportedException(pce, requiredType, var8);
    } catch (IllegalArgumentException | ConversionException var9) {
        pce = new PropertyChangeEvent(this.getRootInstance(), this.nestedPath + propertyName, oldValue, newValue);
        throw new TypeMismatchException(pce, requiredType, var9);
    }
}


// 开始来进行转换
public <T> T convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue, @Nullable Object newValue, @Nullable Class<T> requiredType, @Nullable TypeDescriptor typeDescriptor) throws IllegalArgumentException {
    PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName);
    ConversionFailedException conversionAttemptEx = null;
    // 从这里可以拿到所有的conversionService,这里面有很多的转换服务
    ConversionService conversionService = this.propertyEditorRegistry.getConversionService();
    if (editor == null && conversionService != null && newValue != null && typeDescriptor != null) {
        TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);
        // 首先判断是否可以来进行转换
        if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {
            try {
                // 可以转换,那么就来进行转换
                return conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);
            } catch (ConversionFailedException var14) {
                conversionAttemptEx = var14;
            }
        }
    }

那么看一下如何来进行判断的:

public boolean canConvert(@Nullable TypeDescriptor sourceType, TypeDescriptor targetType) {
    Assert.notNull(targetType, "Target type to convert to cannot be null");
    if (sourceType == null) {
        return true;
    } else {
        // 看一下这里的转换方法
        GenericConverter converter = this.getConverter(sourceType, targetType);
        return converter != null;
    }
}


@Nullable
protected GenericConverter getConverter(TypeDescriptor sourceType, TypeDescriptor targetType) {
    GenericConversionService.ConverterCacheKey key = new GenericConversionService.ConverterCacheKey(sourceType, targetType);
    GenericConverter converter = (GenericConverter)this.converterCache.get(key);
    if (converter != null) {
        return converter != NO_MATCH ? converter : null;
    } else {
        // 重点来了!目标类型和自定义类类型中的属性的类型,这里是用来找到
        converter = this.converters.find(sourceType, targetType);
        if (converter == null) {
            converter = this.getDefaultConverter(sourceType, targetType);
        }
		// 找到了就放入到缓存中去
        if (converter != null) {
            this.converterCache.put(key, converter);
            return converter;
        // 如果是为空的,那么将getDefaultConverter放入到缓存中去
        } else {
            this.converterCache.put(key, NO_MATCH);
            return null;
        }
    }
}

如何找到的:

public GenericConverter find(TypeDescriptor sourceType, TypeDescriptor targetType) {
    List<Class<?>> sourceCandidates = this.getClassHierarchy(sourceType.getType());
    List<Class<?>> targetCandidates = this.getClassHierarchy(targetType.getType());
    Iterator var5 = sourceCandidates.iterator();

    while(var5.hasNext()) {
        Class<?> sourceCandidate = (Class)var5.next();
        Iterator var7 = targetCandidates.iterator();
		// 循环遍历136个converter
        while(var7.hasNext()) {
            Class<?> targetCandidate = (Class)var7.next();
            ConvertiblePair convertiblePair = new ConvertiblePair(sourceCandidate, targetCandidate);
            GenericConverter converter = this.getRegisteredConverter(sourceType, targetType, convertiblePair);
            if (converter != null) {
                return converter;
            }
        }
    }
    return null;
}

所以上面的步骤是为了找到对应的converter,找到了之后,下面将会来进行转换:

@Nullable
public Object convert(@Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType) {
    Assert.notNull(targetType, "Target type to convert to cannot be null");
    if (sourceType == null) {
        Assert.isTrue(source == null, "Source must be [null] if source type == [null]");
        return this.handleResult((TypeDescriptor)null, targetType, this.convertNullSource((TypeDescriptor)null, targetType));
    } else if (source != null && !sourceType.getObjectType().isInstance(source)) {
        throw new IllegalArgumentException("Source to convert from must be an instance of [" + sourceType + "]; instead it was a [" + source.getClass().getName() + "]");
    } else {
        GenericConverter converter = this.getConverter(sourceType, targetType);
        if (converter != null) {
            // 开始来进行转换
            Object result = ConversionUtils.invokeConverter(converter, source, sourceType, targetType);
            return this.handleResult(sourceType, targetType, result);
        } else {
            return this.handleConverterNotFound(source, sourceType, targetType);
        }
    }
}

@Nullable
public static Object invokeConverter(GenericConverter converter, @Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
    try {
        // 开始来进行转换
        return converter.convert(source, sourceType, targetType);
    } catch (ConversionFailedException var5) {
        throw var5;
    } catch (Throwable var6) {
        throw new ConversionFailedException(sourceType, targetType, source, var6);
    }
}


@Nullable
public Object convert(@Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType) {
    Assert.notNull(targetType, "Target type to convert to cannot be null");
    if (sourceType == null) {
        Assert.isTrue(source == null, "Source must be [null] if source type == [null]");
        return this.handleResult((TypeDescriptor)null, targetType, this.convertNullSource((TypeDescriptor)null, targetType));
    } else if (source != null && !sourceType.getObjectType().isInstance(source)) {
        throw new IllegalArgumentException("Source to convert from must be an instance of [" + sourceType + "]; instead it was a [" + source.getClass().getName() + "]");
    } else {
        GenericConverter converter = this.getConverter(sourceType, targetType);
        if (converter != null) {
            // 开始转换
            Object result = ConversionUtils.invokeConverter(converter, source, sourceType, targetType);
            return this.handleResult(sourceType, targetType, result);
        } else {
            return this.handleConverterNotFound(source, sourceType, targetType);
        }
    }
}


@Nullable
public static Object invokeConverter(GenericConverter converter, @Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
    try {
        // 这里开始来进行转换
        return converter.convert(source, sourceType, targetType);
    } catch (ConversionFailedException var5) {
        throw var5;
    } catch (Throwable var6) {
        throw new ConversionFailedException(sourceType, targetType, source, var6);
    }
}

@Nullable
public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
    return source == null ? GenericConversionService.this.convertNullSource(sourceType, targetType) : this.converterFactory.getConverter(targetType.getObjectType()).convert(source);
}

StringToNumberConverterFactory中的
// 看下这个里面的方法    
public <T extends Number> Converter<String, T> getConverter(Class<T> targetType) {
    // 利用这个方法来进行转换
    return new StringToNumberConverterFactory.StringToNumber(targetType);
}

那么来看一下这个类:

// 看一下这里的接口中的泛型转换。将String类型的转换成T类型的
private static final class StringToNumber<T extends Number> implements Converter<String, T> {
    private final Class<T> targetType;

    public StringToNumber(Class<T> targetType) {
        this.targetType = targetType;
    }

    public T convert(String source) {
        // 看看这里的转换方法。将String转换成T类型
        return source.isEmpty() ? null : NumberUtils.parseNumber(source, this.targetType);
    }
}

@FunctionalInterface
public interface Converter<S, T> {
    @Nullable
    T convert(S var1);
}

上面的请求过程中只是设置了一个属性。那么其他属性都将会走这个过程。

ph.setValue(valueToApply);

最终会把值来进行设置。最终都会将值来进行封装好

Demo1

那么下面来做一下小小的案例来进行分析:

<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="pet.age" value="5"/>-->
    宠物: <input name="pet" value="啊猫,3"/>
    <input type="submit" value="保存"/>
</form>

将宠物类型的属性通过value直接绑定到属性pet上,下面开始来进行测试:

axios 请求 后台spring mvc参数为null_ide_10

从这里可以看到person中的属性pet在进行赋值的时候,无法继续拿给你转换,原因是因为没有对应的转换策略。

因为springmvc不知道我们要将“啊猫,3”将其封装到pet对象中来。

那么根据上面的converter接口来自定义一个,因为springmcv中所有的定制化都将在webconfigure接口中来进行定义,所以来进行一个实现。

@Bean
public WebMvcConfigurer webMvcConfigurer(){
    return new WebMvcConfigurer() {

        /**
             * 自定义类型转换
             * @param registry
             */
        @Override
        public void addFormatters(FormatterRegistry registry) {
            registry.addConverter(new Converter<String,Pet>(){
                @Override
                public Pet convert(String s) {
                    if (!StringUtils.isEmpty(s)){
                        Pet pet = new Pet();
                        String[] split = s.split(",");
                        pet.setName(split[0]);
                        pet.setAge(Integer.parseInt(split[1]));
                        return pet;
                    }
                    return null;
                }
            });
        }
    }

在此请求,发现请求响应正常。

因为最终的设置值是在转换器处理完成之后,这里会将其来进行直接set进去。所以这个请求封装完成。

Demo2

发送请求:http://localhost:8080/date?date=2021/11/17

@GetMapping("date")
public String testDate(@RequestParam(value = "date") Date date){
    System.out.println("对应的值是"+date);
    return "hello,date";
}

但是现在不要求发送这种格式的。要求http://localhost:8080/date?date=20211117这种格式的

那么在webconfigure接口中来改造一下:

registry.addConverter(new Converter<String, Date>() {
    @Override
    public Date convert(String s) {
        if (!StringUtils.isEmpty(s)) {
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd");
            try {
                Date date = simpleDateFormat.parse(s);
                return date;
            } catch (ParseException e) {
                e.printStackTrace();
                System.out.println("转换异常");
            }finally {
                System.out.println("执行完毕了...........");
            }
        }
        return null;
    }
});

再次来进行请求,可以看到请求响应是成功的,而不是失败了的。

总结

关于在方法参数中自定义的时候,也就是进行进行数据绑定的时候,首先会来判断哪种类型的转换器可以来支持,如果说不支持,那么将会发生数据绑定异常。所以出现了这种问题应该重写WebDataBinder中的converter中的方法来进行绑定。

6、返回值处理

看一下返回值处理器能够支持哪些,因为从上面可以知道返回值也不是能够随意写的:

axios 请求 后台spring mvc参数为null_子类_11

第一种支持返回值是ModelAndView类型的,第二种支持Model类型的,第三种支持返回值类型是View类型的,第四种支持@ResposeBody注解的等等。

那么看一下返回值处理器的接口:

public interface HandlerMethodReturnValueHandler {
    // 当前处理器是否支持这种类型的,如果支持将会调用handleReturnValue
    boolean supportsReturnType(MethodParameter var1);

    void handleReturnValue(@Nullable Object var1, MethodParameter var2, ModelAndViewContainer var3, NativeWebRequest var4) throws Exception;
}

一般来说,我们都是会写这种json数据格式类型的。那么最终会将json格式的数据保存到response的buffer中去。而此时的modelandview是没有值得。

这个之后会来进行补充。

总结

1、首先通过请求路径来找到对应的handler来进行处理;

2、找到对应的handler之后,再为当前的handler找到对应的适配器;此时,适配器已经能够得到了所有的参数的值;

3、知道了方法和参数的值,最终通过反射来执行方法;

axios 请求 后台spring mvc参数为null_子类_12

从理论中来,到实践中去,最终回归理论