我们日常开发过程中,入参出参基本都是与后端对应的类结构完全一致,SpringMVC自动帮我们处理了参数转换的过程,GET 和 POST 的几种常见传参方式如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| @GetMapping("/listUser") public List<User> listUser(String userName) { return new ArrayList<>(); }
@GetMapping("/listUser")
public List<User> listUser1(@RequestParam("username") String userName) { return new ArrayList<>(); }
@GetMapping("/listUser")
public List<User> listUser2(SearchUserParam searchUserParam) { return new ArrayList<>(); }
@PostMapping("/save") public Long save(@RequestBody User user) { return 1L; }
|
上面的几种用法基本能满足我们大部分的需求,但是仍有一些特殊情况无法满足
使用
先来看下GET方法,入参太多时我们会定义类,但是类中的属性必须是和参数名一致,也不能进行嵌套,不然就无法转换,比如下面这种定义
1 2 3 4 5 6 7
| public class SearchUserParam { private Integer age; private String name; private Map<String, String> ext; }
|
对于上面的扩展参数,如果有一天我们需要支持根据地区进行查询,对于HTTP GET请求中的此次增加的入参如regionId=100这种情况,后端就会接收不到这个参数
一种解决方法当然是我们可以通过修改类结构增加参数来处理,这里主要来说说如果不修改类结构我们如何处理这种情况
这里就需要用到 SpringMVC 提供的 HandlerMethodArgumentResolver 类,所有的参数处理都是这个接口的实现,我们可以利用它来自定义我们的参数解析实现,如
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| @Component public class SearchUserHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
private Set<String> supportParams = new HashSet<>();
@PostConstruct public void init() { supportParams.add("name"); supportParams.add("age"); }
@Override public boolean supportsParameter(MethodParameter parameter) { return parameter.getParameterType() == SearchUserParam.class; }
@Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { Map<String, Object> result = new HashMap<>(); Iterator<String> parameterNames = webRequest.getParameterNames(); Map<String, Object> extMap = new HashMap<>(); while (parameterNames.hasNext()) { String paramName = parameterNames.next(); if (supportParams.contains(paramName)) { result.put(paramName, webRequest.getParameter(paramName)); } else { extMap.put(paramName, webRequest.getParameter(paramName)); } } result.put("ext", extMap); return JSON.parseObject(result, SearchUserParam.class); } }
|
Spring中的 PageableHandlerMethodArgumentResolver 也是利用了这个原理,会将请求参数中的分页参数填充到请求参数接收类 Pageable 中,如:
1 2 3
| public Page<User> pageUsers(Pageable pageable, QueryUser query) { }
|
我们可以利用这个特性在接收参数中定义一些需要用到的类,然后通过参数解析器进行添加,如用户信息等
上面说的是对于 GET 请求,接下来看下 POST 请求的处理,POST请求我们一般都是使用 json 这种格式作为入参和出参,而 SpringMVC 默认使用 jackson 进行响应的序列化和反序列化
所以 POST 请求的处理我们直接借助于 jackson 提供的方法即可,通过实现 JsonSerializer 或 JsonDeserializer 来实现对应属性的序列化与反序列化,在借由 SpringMVC 提供的 @JsonComponent 注解等方式进行设置即可,下面举一个简单的例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| @JsonComponent public class UserCodec {
public static class UserSerializer extends JsonSerializer<User> { @Override public void serialize(User user, JsonGenerator gen, SerializerProvider serializers) throws IOException { gen.writeStartObject(); gen.writeStringField("user-name", user.getName()); gen.writeNumberField("user-age", user.getAge()); gen.writeEndObject(); } }
public static class UserDeserializer extends JsonDeserializer<User> {
@Override public User deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { JsonNode node = p.getCodec().readTree(p); User user = new User(); user.setName(node.get("user-name").asText()); IntNode ageNode = (IntNode) node.get("user-age"); user.setAge(ageNode.intValue()); return user; } } }
|
之后在对应类上面添加注解
1 2 3 4 5 6 7 8
| @Data @JsonSerialize(using = UserSerializer.class) @JsonDeserialize(using = UserDeserializer.class) public class User { private String name; private Integer age; }
|
这样就实现了自定义的序列化与反序列化,当然我们也可以根据自己的实际需求实现更复杂的转换
原理
这个参数解析的原理比较简单,主要就是有一个参数解析器链,以类似责任链的方式进行处理,哪个解析器能处理就进行对应的处理,下面我们具体看下
SpringMVC主要依靠 HandlerMapping 和 HandlerAdapter 来实现路由到方法的映射和方法调用的实现, HandlerMapping 用来处理请求和 Controller 中的方法映射关系及匹配,而 HandlerAdapter 则是用来在匹配到方法后进行实际的方法调用,包括其中的参数处理(使用 HandlerMethodArgumentResolver)
这里我们主要看下最常用的 RequestMappingHandlerAdapter的处理过程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter implements BeanFactoryAware, InitializingBean {
@Override public void afterPropertiesSet() { initControllerAdviceCache();
if (this.argumentResolvers == null) { List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers(); this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers); } if (this.initBinderArgumentResolvers == null) { List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers(); this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers); } if (this.returnValueHandlers == null) { List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers(); this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers); } }
private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() { List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>(30);
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 ServletModelAttributeMethodProcessor(false)); resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice)); resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice)); resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory())); resolvers.add(new RequestHeaderMapMethodArgumentResolver()); resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory())); resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory())); resolvers.add(new SessionAttributeMethodArgumentResolver()); resolvers.add(new RequestAttributeMethodArgumentResolver());
resolvers.add(new ServletRequestMethodArgumentResolver()); resolvers.add(new ServletResponseMethodArgumentResolver()); resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice)); resolvers.add(new RedirectAttributesMethodArgumentResolver()); resolvers.add(new ModelMethodProcessor()); resolvers.add(new MapMethodProcessor()); resolvers.add(new ErrorsMethodArgumentResolver()); resolvers.add(new SessionStatusMethodArgumentResolver()); resolvers.add(new UriComponentsBuilderMethodArgumentResolver()); if (KotlinDetector.isKotlinPresent()) { resolvers.add(new ContinuationHandlerMethodArgumentResolver()); }
if (getCustomArgumentResolvers() != null) { resolvers.addAll(getCustomArgumentResolvers()); }
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true)); resolvers.add(new ServletModelAttributeMethodProcessor(true));
return resolvers; } }
|
上面主要是一些初始化注册的逻辑,接下来看一下实际调用过程的处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ServletWebRequest webRequest = new ServletWebRequest(request, response); try { ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
invocableMethod.invokeAndHandle(webRequest, mavContainer);
return getModelAndView(mavContainer, modelFactory, webRequest); } finally { webRequest.requestCompleted(); } }
protected ServletInvocableHandlerMethod createInvocableHandlerMethod(HandlerMethod handlerMethod) { return new ServletInvocableHandlerMethod(handlerMethod); }
|
调用方法处理在 ServletInvocableHandlerMethod 的父类 InvocableHandlerMethod 中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| public class InvocableHandlerMethod extends HandlerMethod { private HandlerMethodArgumentResolverComposite resolvers = new HandlerMethodArgumentResolverComposite();
public Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs); if (logger.isTraceEnabled()) { logger.trace("Arguments: " + Arrays.toString(args)); } return doInvoke(args); }
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
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); args[i] = findProvidedArgument(parameter, providedArgs); if (args[i] != null) { continue; } 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 ex) { throw ex; } } return args; }
protected Object doInvoke(Object... args) throws Exception { Method method = getBridgedMethod(); ReflectionUtils.makeAccessible(method); try { if (KotlinDetector.isSuspendingFunction(method)) { return CoroutinesUtils.invokeSuspendingFunction(method, getBean(), args); } return method.invoke(getBean(), args); } catch (IllegalArgumentException ex) { } } }
|
最后看下上面resolvers(HandlerMethodArgumentResolverComposite)中的处理,在GET方法中我们写的自定义参数解析器也是在这里进行处理的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver { private final List<HandlerMethodArgumentResolver> argumentResolvers = new ArrayList<>();
@Override public boolean supportsParameter(MethodParameter parameter) { return getArgumentResolver(parameter) != null; }
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { 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); }
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) { HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter); if (result == null) { for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) { if (resolver.supportsParameter(parameter)) { result = resolver; this.argumentResolverCache.put(parameter, result); break; } } } return result; } }
|
关于SpringMVC中自定义参数的转换内容基本就是这些,如有错误欢迎指正