SpringMVC 可以用@PathVariable、@RequestParam等来接收不同的参数类型。下面研究其参数解析过程,不管是何种参数方式,都是从Request 中拿的参数。
1. 测试Controller
代码如下:
User 类:
package com.xm.ggn.test; import lombok.Data; @Data public class User { private String username; private String fullname; private String createDate; }
Controller 接口:
@PostMapping("/user/add/{userId}") public User addUser(@RequestBody User user, @PathVariable String userId, @RequestParam String username, User user2) { System.out.println(user); System.out.println(userId); System.out.println(username); System.out.println(user2); return user; }
测试访问:
curl -X POST --header 'Content-Type: application/json' -d '{"username": "lisi", "fullname": "lisi"}' 'http://localhost:8088/user/add/3?username=zs' % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 0 0 0 0 0 0 0 0 --:--:-- --:100 150 0 110 100 40 2340 851 --:--:-- --:--:-- --:--:-- 6875{"success":true,"data":{"username":"lisi","fullname":"lisi","createDate":null},"msg":"成功","errorCode":"0"}
2. 分析其解析过程
1. SpringMVC 入口是在方法:org.springframework.web.servlet.DispatcherServlet#doDispatch, 其大致流程如下:
(1) 获取一个处理器执行器链: HandlerExecutionChain (包含Handler 和Inteceptors)。 org.springframework.web.servlet.handler.AbstractHandlerMapping#getHandler
(2) 遍历处理器链获取一个HandlerAdapter 处理器适配器, 适配的类型是org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter。
(3) 调用到方法开始处理:org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#invokeHandlerMethod
2. 接下来研究处理过程的参数解析环节
1. org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter#handle
2. 调用到 org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#handleInternal
@Override protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { ModelAndView mav; checkRequest(request); // Execute invokeHandlerMethod in synchronized block if required. if (this.synchronizeOnSession) { HttpSession session = request.getSession(false); if (session != null) { Object mutex = WebUtils.getSessionMutex(session); synchronized (mutex) { mav = invokeHandlerMethod(request, response, handlerMethod); } } else { // No HttpSession available -> no mutex necessary mav = invokeHandlerMethod(request, response, handlerMethod); } } else { // No synchronization on session demanded at all... mav = invokeHandlerMethod(request, response, handlerMethod); } if (!response.containsHeader(HEADER_CACHE_CONTROL)) { if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) { applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers); } else { prepareResponse(response); } } return mav; }
3. org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#invokeHandlerMethod
4. org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#invokeAndHandle
5. org.springframework.web.method.support.InvocableHandlerMethod#invokeForRequest
@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); }
6. 参数解析:org.springframework.web.method.support.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 { 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; } }
可以看到参数解析是在这里,根据参数的个数,然后循环遍历获取参数信息。
1》单个参数的解析过程如下:org.springframework.web.method.support.HandlerMethodArgumentResolverComposite#resolveArgument
@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); } }
获取到参数解析器,然后进行参数的解析,获取参数解析器的方法如下:
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; }
可以看到先从缓存里面拿,如果缓存里面没有拿到,遍历MVC内置的一些解析器,然后判断是否支持该参数的解析,如果支持解析完存入缓存map,下次直接从缓存中拿。这个类内置了大概30个参数解析器,如下:
主要的参数解析器为:
RequestResponseBodyMethodProcessor - 解析@RequestBody和@ResponseBody 的参数
PathVariableMethodArgumentResolver- 解析@PathVariable 注解的
RequestParamMethodArgumentResolver - 解析@RequestParam 注解修饰的参数
ServletModelAttributeMethodProcessor - 解析默认不加任何注解的参数以及解析@ModelAttribute 注解修饰的参数
2》 然后开始解析参数调用的方法是参数解析器的resolveArgument 方法
分析四个解析过程:
(1)如果是RequestBody 修饰的参数:
org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor#resolveArgument
@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()); } } return adaptArgumentIfNecessary(arg, parameter); }
请求转交给父类:org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver#readWithMessageConverters(org.springframework.http.HttpInputMessage, org.springframework.core.MethodParameter, java.lang.reflect.Type)
protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException { MediaType contentType; boolean noContentType = false; try { contentType = inputMessage.getHeaders().getContentType(); } catch (InvalidMediaTypeException ex) { throw new HttpMediaTypeNotSupportedException(ex.getMessage()); } if (contentType == null) { noContentType = true; contentType = MediaType.APPLICATION_OCTET_STREAM; } Class<?> contextClass = parameter.getContainingClass(); Class<T> targetClass = (targetType instanceof Class ? (Class<T>) targetType : null); if (targetClass == null) { ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter); targetClass = (Class<T>) resolvableType.resolve(); } HttpMethod httpMethod = (inputMessage instanceof HttpRequest ? ((HttpRequest) inputMessage).getMethod() : null); Object body = NO_VALUE; EmptyBodyCheckingHttpInputMessage message; try { message = new EmptyBodyCheckingHttpInputMessage(inputMessage); for (HttpMessageConverter<?> converter : this.messageConverters) { Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass(); GenericHttpMessageConverter<?> genericConverter = (converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null); if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) : (targetClass != null && converter.canRead(targetClass, contentType))) { if (message.hasBody()) { HttpInputMessage msgToUse = getAdvice().beforeBodyRead(message, parameter, targetType, converterType); body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) : ((HttpMessageConverter<T>) converter).read(targetClass, msgToUse)); body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType); } else { body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType); } break; } } } catch (IOException ex) { throw new HttpMessageNotReadableException("I/O error while reading input message", ex, inputMessage); } if (body == NO_VALUE) { if (httpMethod == null || !SUPPORTED_METHODS.contains(httpMethod) || (noContentType && !message.hasBody())) { return null; } throw new HttpMediaTypeNotSupportedException(contentType, this.allSupportedMediaTypes); } MediaType selectedContentType = contentType; Object theBody = body; LogFormatUtils.traceDebug(logger, traceOn -> { String formatted = LogFormatUtils.formatValue(theBody, !traceOn); return "Read "" + selectedContentType + "" to [" + formatted + "]"; }); return body; }
这里实际就是读取请求体中的数据,然后转换成JSON对象。
判断messageConverter 消息转换器是否能读取消息,对于普通的Bean 支持的转换器是org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; 然后处理消息,也就是读取请求体重的数据,然后转换成对应的java Bean。 注意这里会交给JackSON工具包的com.fasterxml.jackson.databind.ObjectMapper#_readMapAndClose 方法。
(2) 如果是@PathVariable 修饰的参数: 请求交给org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver#resolveArgument
父类会调用子类的org.springframework.web.servlet.mvc.method.annotation.PathVariableMethodArgumentResolver#resolveName 方法进行解析参数:
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception { Map<String, String> uriTemplateVars = (Map<String, String>) request.getAttribute( HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST); return (uriTemplateVars != null ? uriTemplateVars.get(name) : null); }
实际就是从参数模板的Map 中根据名称获取到一个参数的值
(3) 如果是@RequestParam 注解修饰的参数:
最后也是会打到org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver#resolveArgument
请求打到:org.springframework.web.method.annotation.RequestParamMethodArgumentResolver#resolveName。
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception { HttpServletRequest servletRequest = (HttpServletRequest)request.getNativeRequest(HttpServletRequest.class); Object arg; if (servletRequest != null) { arg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest); if (arg != MultipartResolutionDelegate.UNRESOLVABLE) { return arg; } } arg = null; MultipartRequest multipartRequest = (MultipartRequest)request.getNativeRequest(MultipartRequest.class); if (multipartRequest != null) { List<MultipartFile> files = multipartRequest.getFiles(name); if (!files.isEmpty()) { arg = files.size() == 1 ? files.get(0) : files; } } if (arg == null) { String[] paramValues = request.getParameterValues(name); if (paramValues != null) { arg = paramValues.length == 1 ? paramValues[0] : paramValues; } } return arg; }
实际就是调用request.getParameterValues 获取到参数的值
(4) 如果没有注解修饰或者@ModelAttribute 修饰的参数:
org.springframework.web.method.annotation.ModelAttributeMethodProcessor#resolveArgument
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 { 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(); } } if (bindingResult == null) { 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; }
3. 自定义自己的参数解析器以及DataBinder
1. 声明一个注解用于从标记使用自定义的参数处理器
package com.xm.ggn.test.paramresolver; import java.lang.annotation.*; /** * 标记Head中取值的注解 */ @Target({ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface HeaderParam { }
2. 编写一个参数处理器,模拟从Header 中获取到参数之后设置值
package com.xm.ggn.test; import com.xm.ggn.test.paramresolver.HeaderParam; import org.apache.commons.lang3.ArrayUtils; import org.springframework.core.MethodParameter; import org.springframework.objenesis.instantiator.util.ClassUtils; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.ModelAndViewContainer; import sun.reflect.misc.FieldUtil; import javax.servlet.http.HttpServletRequest; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class HeaderParamMethodArgumentResolver implements HandlerMethodArgumentResolver { /** * 查看是否有 * * @param parameter * @return */ @Override public boolean supportsParameter(MethodParameter parameter) { return parameter.hasParameterAnnotation(HeaderParam.class); } /** * 解析参数: 从Header 中拿取属性 * * @param parameter * @param mavContainer * @param webRequest * @param binderFactory * @return * @throws Exception */ @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { HttpServletRequest servletRequest = (HttpServletRequest) webRequest.getNativeRequest(HttpServletRequest.class); Class<?> parameterType = parameter.getParameterType(); String parameterName = parameter.getParameterName(); // 如果是基本数据类型设置值之后返回 if (isPrimitive(parameterType)) { return getArg(parameterType, servletRequest.getHeader(parameterName)); } // 如果是String获取之后返回 if (String.class.equals(parameterType)) { return servletRequest.getHeader(parameterName); } // 包装类型的话直接创造对象,然后遍历属性进行设置 Field[] fields = FieldUtil.getDeclaredFields(parameterType); Object result = ClassUtils.newInstance(parameterType); Object arg = null; for (Field field : fields) { arg = servletRequest.getHeader(field.getName()); if (arg == null) { continue; } if (isPrimitive(field.getType())) { Class<?> parType = field.getType(); arg = getArg(parType, arg); } if (arg == null) { continue; } Method setter = getSetterMethod(parameterType, field); if (setter != null) { setter.invoke(result, arg); } } return result; } /** * 获取到Set 方法 * * @param clazz * @param field * @return * @throws NoSuchMethodException */ private Method getSetterMethod(Class<?> clazz, Field field) throws NoSuchMethodException { return clazz.getDeclaredMethod("set" + toUpperCaseFirstOne(field.getName()), field.getType()); } /** * 字段第一个转为大写 * * @param fieldName * @return */ private String toUpperCaseFirstOne(String fieldName) { if (Character.isUpperCase(fieldName.charAt(0))) { return fieldName; } return String.valueOf(Character.toUpperCase(fieldName.charAt(0))) + fieldName.substring(1); } private static final Class[] PRIMITIVE_CLAZZ = {Byte.class, Short.class, Integer.class, Long.class, Character.class, Boolean.class, Float.class, Double.class}; /** * 是否是8种基本类型的包装类型 * * @param clazz * @return */ private boolean isPrimitive(Class<?> clazz) { return ArrayUtils.contains(PRIMITIVE_CLAZZ, clazz); } private Object getArg(Class<?> primitiveClass, Object value) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException { if (value == null) { return null; } // 获取到类型名称 if (isPrimitive(primitiveClass)) { // 还可以用 String methodName = "parse" + firstUpperprimitiveName 构造方法名称; 然后用 clazz.getDeclaredMethod(methodName, String.class) 获取方法 String primitiveName = primitiveClass.getSimpleName(); switch (primitiveName) { case "Byte": return Byte.parseByte(primitiveName); case "Short": return Short.parseShort(primitiveName); case "Integer": return Integer.parseInt(primitiveName); case "Long": return Long.parseLong(primitiveName); case "Character": value.toString().charAt(0); case "Boolean": return Boolean.parseBoolean(primitiveName); case "Float": return Float.parseFloat(primitiveName); case "Double": return Double.parseDouble(primitiveName); } } return null; } }
3. 注册到SpringMVC 的参数解析器列表中
package com.xm.ggn.config; import com.xm.ggn.test.HeaderParamMethodArgumentResolver; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer; import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import java.util.List; /** * 1.JSON返回实体类报错2.设置页面的默认页面 * * @author Administrator */ @Configuration public class MVCConfig extends WebMvcConfigurerAdapter { /** * 解决JSON返回实体类报错 */ @Override public void configureContentNegotiation(ContentNegotiationConfigurer configurer) { configurer.favorPathExtension(false); } /** * 设置页面的默认页面 */ @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/").setViewName("forward:/index.html"); registry.setOrder(Ordered.HIGHEST_PRECEDENCE); super.addViewControllers(registry); } /** * 将UserArgumentResolver将入到处理器队列中来 * * @param argumentResolvers */ @Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) { if (argumentResolvers.isEmpty()) { argumentResolvers.add(0, new HeaderParamMethodArgumentResolver()); } else { argumentResolvers.set(0, new HeaderParamMethodArgumentResolver()); } } }
4. 编写测试Controller
@PostMapping("/user/add/{userId}") public User addUser(@RequestBody User user, @PathVariable String userId, @RequestParam String username, @HeaderParam User user2, @HeaderParam String headKey, User user3) { System.out.println(user); System.out.println(userId); System.out.println(username); System.out.println(user2); System.out.println(headKey); System.out.println(user3); return user; }
上面据完成了一个简单的自定义参数解析器,从header 中读取属性。