SpingMVC参数解析过程(一)
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 中读取属性。
补充: 对于HttpServletRequest、HttpSession、ServletResponse 等参数解析过程
1. 对于这样的参数解析是在org.springframework.web.method.support.HandlerMethodArgumentResolverComposite#resolveArgument:
@Override @Nullable 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); }
获取到的 resolver 是:org.springframework.web.servlet.mvc.method.annotation.ServletRequestMethodArgumentResolver 或者 ServletResponseMethodArgumentResolver
2.org.springframework.web.servlet.mvc.method.annotation.ServletRequestMethodArgumentResolver 源码如下:
package org.springframework.web.servlet.mvc.method.annotation; import java.io.InputStream; import java.io.Reader; import java.security.Principal; import java.time.ZoneId; import java.util.Locale; import java.util.TimeZone; import javax.servlet.ServletRequest; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import org.springframework.core.MethodParameter; import org.springframework.http.HttpMethod; import org.springframework.lang.UsesJava8; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.WebRequest; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.ModelAndViewContainer; import org.springframework.web.multipart.MultipartRequest; import org.springframework.web.servlet.support.RequestContextUtils; /** * Resolves request-related method argument values of the following types: * <ul> * <li>{@link WebRequest} * <li>{@link ServletRequest} * <li>{@link MultipartRequest} * <li>{@link HttpSession} * <li>{@link Principal} * <li>{@link InputStream} * <li>{@link Reader} * <li>{@link HttpMethod} (as of Spring 4.0) * <li>{@link Locale} * <li>{@link TimeZone} (as of Spring 4.0) * <li>{@link java.time.ZoneId} (as of Spring 4.0 and Java 8) * </ul> * * @author Arjen Poutsma * @author Rossen Stoyanchev * @author Juergen Hoeller * @since 3.1 */ public class ServletRequestMethodArgumentResolver implements HandlerMethodArgumentResolver { @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) || Principal.class.isAssignableFrom(paramType) || InputStream.class.isAssignableFrom(paramType) || Reader.class.isAssignableFrom(paramType) || HttpMethod.class == paramType || Locale.class == paramType || TimeZone.class == paramType || "java.time.ZoneId".equals(paramType.getName())); } @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { Class<?> paramType = parameter.getParameterType(); if (WebRequest.class.isAssignableFrom(paramType)) { if (!paramType.isInstance(webRequest)) { throw new IllegalStateException( "Current request is not of type [" + paramType.getName() + "]: " + webRequest); } return webRequest; } HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class); if (ServletRequest.class.isAssignableFrom(paramType) || MultipartRequest.class.isAssignableFrom(paramType)) { Object nativeRequest = webRequest.getNativeRequest(paramType); if (nativeRequest == null) { throw new IllegalStateException( "Current request is not of type [" + paramType.getName() + "]: " + request); } return nativeRequest; } else if (HttpSession.class.isAssignableFrom(paramType)) { HttpSession session = request.getSession(); if (session != null && !paramType.isInstance(session)) { throw new IllegalStateException( "Current session is not of type [" + paramType.getName() + "]: " + session); } return session; } else if (InputStream.class.isAssignableFrom(paramType)) { InputStream inputStream = request.getInputStream(); if (inputStream != null && !paramType.isInstance(inputStream)) { throw new IllegalStateException( "Request input stream is not of type [" + paramType.getName() + "]: " + inputStream); } return inputStream; } else if (Reader.class.isAssignableFrom(paramType)) { Reader reader = request.getReader(); if (reader != null && !paramType.isInstance(reader)) { throw new IllegalStateException( "Request body reader is not of type [" + paramType.getName() + "]: " + reader); } return reader; } else if (Principal.class.isAssignableFrom(paramType)) { Principal userPrincipal = request.getUserPrincipal(); if (userPrincipal != null && !paramType.isInstance(userPrincipal)) { throw new IllegalStateException( "Current user principal is not of type [" + paramType.getName() + "]: " + userPrincipal); } return userPrincipal; } else if (HttpMethod.class == paramType) { return HttpMethod.resolve(request.getMethod()); } else if (Locale.class == paramType) { return RequestContextUtils.getLocale(request); } else if (TimeZone.class == paramType) { TimeZone timeZone = RequestContextUtils.getTimeZone(request); return (timeZone != null ? timeZone : TimeZone.getDefault()); } else if ("java.time.ZoneId".equals(paramType.getName())) { return ZoneIdResolver.resolveZoneId(request); } else { // Should never happen... throw new UnsupportedOperationException( "Unknown parameter type [" + paramType.getName() + "] in " + parameter.getMethod()); } } /** * Inner class to avoid a hard-coded dependency on Java 8's {@link java.time.ZoneId}. */ @UsesJava8 private static class ZoneIdResolver { public static Object resolveZoneId(HttpServletRequest request) { TimeZone timeZone = RequestContextUtils.getTimeZone(request); return (timeZone != null ? timeZone.toZoneId() : ZoneId.systemDefault()); } } }
supportsParameter 方法可以看到其支持的类型。
org.springframework.web.servlet.mvc.method.annotation.ServletRequestMethodArgumentResolver#resolveArgument(java.lang.Class<?>, javax.servlet.http.HttpServletRequest) 方法可以看到其获取对应类型的参数的具体策略。
3. 对于HttpSession 实际是调用org.apache.catalina.connector.RequestFacade#getSession(): (如果被过滤器或者监听器重写了Request 是调用自己Request的相关方法)
@Override public HttpSession getSession() { if (request == null) { throw new IllegalStateException( sm.getString("requestFacade.nullRequest")); } return getSession(true); } @Override public HttpSession getSession(boolean create) { if (request == null) { throw new IllegalStateException( sm.getString("requestFacade.nullRequest")); } if (SecurityUtil.isPackageProtectionEnabled()){ return AccessController. doPrivileged(new GetSessionPrivilegedAction(create)); } else { return request.getSession(create); } }
继续调用到:org.apache.catalina.connector.Request#getSession(boolean)
public HttpSession getSession(boolean create) { Session session = doGetSession(create); if (session == null) { return null; } return session.getSession(); }
继续调用:
protected Session doGetSession(boolean create) { // There cannot be a session if no context has been assigned yet Context context = getContext(); if (context == null) { return null; } // Return the current session if it exists and is valid if ((session != null) && !session.isValid()) { session = null; } if (session != null) { return session; } // Return the requested session if it exists and is valid Manager manager = context.getManager(); if (manager == null) { return null; // Sessions are not supported } if (requestedSessionId != null) { try { session = manager.findSession(requestedSessionId); } catch (IOException e) { if (log.isDebugEnabled()) { log.debug(sm.getString("request.session.failed", requestedSessionId, e.getMessage()), e); } else { log.info(sm.getString("request.session.failed", requestedSessionId, e.getMessage())); } session = null; } if ((session != null) && !session.isValid()) { session = null; } if (session != null) { session.access(); return session; } } // Create a new session if requested and the response is not committed if (!create) { return null; } boolean trackModesIncludesCookie = context.getServletContext().getEffectiveSessionTrackingModes().contains(SessionTrackingMode.COOKIE); if (trackModesIncludesCookie && response.getResponse().isCommitted()) { throw new IllegalStateException(sm.getString("coyoteRequest.sessionCreateCommitted")); } // Re-use session IDs provided by the client in very limited // circumstances. String sessionId = getRequestedSessionId(); if (requestedSessionSSL) { // If the session ID has been obtained from the SSL handshake then // use it. } else if (("/".equals(context.getSessionCookiePath()) && isRequestedSessionIdFromCookie())) { /* This is the common(ish) use case: using the same session ID with * multiple web applications on the same host. Typically this is * used by Portlet implementations. It only works if sessions are * tracked via cookies. The cookie must have a path of "/" else it * won't be provided for requests to all web applications. * * Any session ID provided by the client should be for a session * that already exists somewhere on the host. Check if the context * is configured for this to be confirmed. */ if (context.getValidateClientProvidedNewSessionId()) { boolean found = false; for (Container container : getHost().findChildren()) { Manager m = ((Context) container).getManager(); if (m != null) { try { if (m.findSession(sessionId) != null) { found = true; break; } } catch (IOException e) { // Ignore. Problems with this manager will be // handled elsewhere. } } } if (!found) { sessionId = null; } } } else { sessionId = null; } session = manager.createSession(sessionId); // Creating a new session cookie based on that session if (session != null && trackModesIncludesCookie) { Cookie cookie = ApplicationSessionCookieConfig.createSessionCookie( context, session.getIdInternal(), isSecure()); response.addSessionCookieInternal(cookie); } if (session == null) { return null; } session.access(); return session; }
4. 对于ServletResponse 参数的解析是在: org.springframework.web.servlet.mvc.method.annotation.ServletResponseMethodArgumentResolver
package org.springframework.web.servlet.mvc.method.annotation; import java.io.OutputStream; import java.io.Writer; import java.lang.reflect.Method; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletResponse; import org.springframework.core.MethodParameter; 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; /** * Resolves response-related method argument values of types: * <ul> * <li>{@link ServletResponse} * <li>{@link OutputStream} * <li>{@link Writer} * </ul> * * @author Arjen Poutsma * @author Rossen Stoyanchev * @since 3.1 */ public class ServletResponseMethodArgumentResolver implements HandlerMethodArgumentResolver { @Override public boolean supportsParameter(MethodParameter parameter) { Class<?> paramType = parameter.getParameterType(); return (ServletResponse.class.isAssignableFrom(paramType) || OutputStream.class.isAssignableFrom(paramType) || Writer.class.isAssignableFrom(paramType)); } /** * Set {@link ModelAndViewContainer#setRequestHandled(boolean)} to * {@code false} to indicate that the method signature provides access * to the response. If subsequently the underlying method returns * {@code null}, the request is considered directly handled. */ @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { if (mavContainer != null) { mavContainer.setRequestHandled(true); } HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class); Class<?> paramType = parameter.getParameterType(); if (ServletResponse.class.isAssignableFrom(paramType)) { Object nativeResponse = webRequest.getNativeResponse(paramType); if (nativeResponse == null) { throw new IllegalStateException( "Current response is not of type [" + paramType.getName() + "]: " + response); } return nativeResponse; } else if (OutputStream.class.isAssignableFrom(paramType)) { return response.getOutputStream(); } else if (Writer.class.isAssignableFrom(paramType)) { return response.getWriter(); } else { // should not happen Method method = parameter.getMethod(); throw new UnsupportedOperationException("Unknown parameter type: " + paramType + " in method: " + method); } } }
5. 总结: 对于ServletRequest、ServletResponse 实际是从org.springframework.web.context.request.NativeWebRequest 获取的。org.springframework.web.context.request.NativeWebRequest 对象包装了ServletRequest 和 ServletResponse 对象。
(1). org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#invokeHandlerMethod 这一步封装成 ServletWebRequest 对象:(也就是将request和response 封装起来进行后续参数的解析)
ServletWebRequest webRequest = new ServletWebRequest(request, response);
(2). org.springframework.web.context.request.ServletWebRequest 实际继承自ServletRequestAttributes, 实际ServletRequest、ServletResponse 也是维护在 ServletRequestAttributes

package org.springframework.web.context.request; import java.security.Principal; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Enumeration; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.TimeZone; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.util.ClassUtils; import org.springframework.util.CollectionUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; import org.springframework.web.util.WebUtils; /** * {@link WebRequest} adapter for an {@link javax.servlet.http.HttpServletRequest}. * * @author Juergen Hoeller * @author Brian Clozel * @author Markus Malkusch * @since 2.0 */ public class ServletWebRequest extends ServletRequestAttributes implements NativeWebRequest { private static final String ETAG = "ETag"; private static final String IF_MODIFIED_SINCE = "If-Modified-Since"; private static final String IF_UNMODIFIED_SINCE = "If-Unmodified-Since"; private static final String IF_NONE_MATCH = "If-None-Match"; private static final String LAST_MODIFIED = "Last-Modified"; private static final List<String> SAFE_METHODS = Arrays.asList("GET", "HEAD"); /** * Pattern matching ETag multiple field values in headers such as "If-Match", "If-None-Match" * @see <a href="https://tools.ietf.org/html/rfc7232#section-2.3">Section 2.3 of RFC 7232</a> */ private static final Pattern ETAG_HEADER_VALUE_PATTERN = Pattern.compile("\\*|\\s*((W\\/)?(\"[^\"]*\"))\\s*,?"); /** * Date formats as specified in the HTTP RFC * @see <a href="https://tools.ietf.org/html/rfc7231#section-7.1.1.1">Section 7.1.1.1 of RFC 7231</a> */ private static final String[] DATE_FORMATS = new String[] { "EEE, dd MMM yyyy HH:mm:ss zzz", "EEE, dd-MMM-yy HH:mm:ss zzz", "EEE MMM dd HH:mm:ss yyyy" }; private static TimeZone GMT = TimeZone.getTimeZone("GMT"); /** Checking for Servlet 3.0+ HttpServletResponse.getHeader(String) */ private static final boolean servlet3Present = ClassUtils.hasMethod(HttpServletResponse.class, "getHeader", String.class); private boolean notModified = false; /** * Create a new ServletWebRequest instance for the given request. * @param request current HTTP request */ public ServletWebRequest(HttpServletRequest request) { super(request); } /** * Create a new ServletWebRequest instance for the given request/response pair. * @param request current HTTP request * @param response current HTTP response (for automatic last-modified handling) */ public ServletWebRequest(HttpServletRequest request, HttpServletResponse response) { super(request, response); } @Override public Object getNativeRequest() { return getRequest(); } @Override public Object getNativeResponse() { return getResponse(); } @Override public <T> T getNativeRequest(Class<T> requiredType) { return WebUtils.getNativeRequest(getRequest(), requiredType); } @Override public <T> T getNativeResponse(Class<T> requiredType) { return WebUtils.getNativeResponse(getResponse(), requiredType); } /** * Return the HTTP method of the request. * @since 4.0.2 */ public HttpMethod getHttpMethod() { return HttpMethod.resolve(getRequest().getMethod()); } @Override public String getHeader(String headerName) { return getRequest().getHeader(headerName); } @Override public String[] getHeaderValues(String headerName) { String[] headerValues = StringUtils.toStringArray(getRequest().getHeaders(headerName)); return (!ObjectUtils.isEmpty(headerValues) ? headerValues : null); } @Override public Iterator<String> getHeaderNames() { return CollectionUtils.toIterator(getRequest().getHeaderNames()); } @Override public String getParameter(String paramName) { return getRequest().getParameter(paramName); } @Override public String[] getParameterValues(String paramName) { return getRequest().getParameterValues(paramName); } @Override public Iterator<String> getParameterNames() { return CollectionUtils.toIterator(getRequest().getParameterNames()); } @Override public Map<String, String[]> getParameterMap() { return getRequest().getParameterMap(); } @Override public Locale getLocale() { return getRequest().getLocale(); } @Override public String getContextPath() { return getRequest().getContextPath(); } @Override public String getRemoteUser() { return getRequest().getRemoteUser(); } @Override public Principal getUserPrincipal() { return getRequest().getUserPrincipal(); } @Override public boolean isUserInRole(String role) { return getRequest().isUserInRole(role); } @Override public boolean isSecure() { return getRequest().isSecure(); } @Override public boolean checkNotModified(long lastModifiedTimestamp) { return checkNotModified(null, lastModifiedTimestamp); } @Override public boolean checkNotModified(String etag) { return checkNotModified(etag, -1); } @Override public boolean checkNotModified(String etag, long lastModifiedTimestamp) { HttpServletResponse response = getResponse(); if (this.notModified || !isStatusOK(response)) { return this.notModified; } // Evaluate conditions in order of precedence. // See https://tools.ietf.org/html/rfc7232#section-6 if (validateIfUnmodifiedSince(lastModifiedTimestamp)) { if (this.notModified) { response.setStatus(HttpStatus.PRECONDITION_FAILED.value()); } return this.notModified; } boolean validated = validateIfNoneMatch(etag); if (!validated) { validateIfModifiedSince(lastModifiedTimestamp); } // Update response boolean isHttpGetOrHead = SAFE_METHODS.contains(getRequest().getMethod()); if (this.notModified) { response.setStatus(isHttpGetOrHead ? HttpStatus.NOT_MODIFIED.value() : HttpStatus.PRECONDITION_FAILED.value()); } if (isHttpGetOrHead) { if(lastModifiedTimestamp > 0 && isHeaderAbsent(response, LAST_MODIFIED)) { response.setDateHeader(LAST_MODIFIED, lastModifiedTimestamp); } if (StringUtils.hasLength(etag) && isHeaderAbsent(response, ETAG)) { response.setHeader(ETAG, padEtagIfNecessary(etag)); } } return this.notModified; } private boolean isStatusOK(HttpServletResponse response) { if (response == null || !servlet3Present) { // Can't check response.getStatus() - let's assume we're good return true; } return response.getStatus() == 200; } private boolean isHeaderAbsent(HttpServletResponse response, String header) { if (response == null || !servlet3Present) { // Can't check response.getHeader(header) - let's assume it's not set return true; } return (response.getHeader(header) == null); } private boolean validateIfUnmodifiedSince(long lastModifiedTimestamp) { if (lastModifiedTimestamp < 0) { return false; } long ifUnmodifiedSince = parseDateHeader(IF_UNMODIFIED_SINCE); if (ifUnmodifiedSince == -1) { return false; } // We will perform this validation... this.notModified = (ifUnmodifiedSince < (lastModifiedTimestamp / 1000 * 1000)); return true; } private boolean validateIfNoneMatch(String etag) { if (!StringUtils.hasLength(etag)) { return false; } Enumeration<String> ifNoneMatch; try { ifNoneMatch = getRequest().getHeaders(IF_NONE_MATCH); } catch (IllegalArgumentException ex) { return false; } if (!ifNoneMatch.hasMoreElements()) { return false; } // We will perform this validation... etag = padEtagIfNecessary(etag); while (ifNoneMatch.hasMoreElements()) { String clientETags = ifNoneMatch.nextElement(); Matcher eTagMatcher = ETAG_HEADER_VALUE_PATTERN.matcher(clientETags); // Compare weak/strong ETags as per https://tools.ietf.org/html/rfc7232#section-2.3 while (eTagMatcher.find()) { if (StringUtils.hasLength(eTagMatcher.group()) && etag.replaceFirst("^W/", "").equals(eTagMatcher.group(3))) { this.notModified = true; break; } } } return true; } private String padEtagIfNecessary(String etag) { if (!StringUtils.hasLength(etag)) { return etag; } if ((etag.startsWith("\"") || etag.startsWith("W/\"")) && etag.endsWith("\"")) { return etag; } return "\"" + etag + "\""; } private boolean validateIfModifiedSince(long lastModifiedTimestamp) { if (lastModifiedTimestamp < 0) { return false; } long ifModifiedSince = parseDateHeader(IF_MODIFIED_SINCE); if (ifModifiedSince == -1) { return false; } // We will perform this validation... this.notModified = ifModifiedSince >= (lastModifiedTimestamp / 1000 * 1000); return true; } public boolean isNotModified() { return this.notModified; } private long parseDateHeader(String headerName) { long dateValue = -1; try { dateValue = getRequest().getDateHeader(headerName); } catch (IllegalArgumentException ex) { String headerValue = getHeader(headerName); // Possibly an IE 10 style value: "Wed, 09 Apr 2014 09:57:42 GMT; length=13774" int separatorIndex = headerValue.indexOf(';'); if (separatorIndex != -1) { String datePart = headerValue.substring(0, separatorIndex); dateValue = parseDateValue(datePart); } } return dateValue; } private long parseDateValue(String headerValue) { if (headerValue == null) { // No header value sent at all return -1; } if (headerValue.length() >= 3) { // Short "0" or "-1" like values are never valid HTTP date headers... // Let's only bother with SimpleDateFormat parsing for long enough values. for (String dateFormat : DATE_FORMATS) { SimpleDateFormat simpleDateFormat = new SimpleDateFormat(dateFormat, Locale.US); simpleDateFormat.setTimeZone(GMT); try { return simpleDateFormat.parse(headerValue).getTime(); } catch (ParseException ex) { // ignore } } } return -1; } @Override public String getDescription(boolean includeClientInfo) { HttpServletRequest request = getRequest(); StringBuilder sb = new StringBuilder(); sb.append("uri=").append(request.getRequestURI()); if (includeClientInfo) { String client = request.getRemoteAddr(); if (StringUtils.hasLength(client)) { sb.append(";client=").append(client); } HttpSession session = request.getSession(false); if (session != null) { sb.append(";session=").append(session.getId()); } String user = request.getRemoteUser(); if (StringUtils.hasLength(user)) { sb.append(";user=").append(user); } } return sb.toString(); } @Override public String toString() { return "ServletWebRequest: " + getDescription(true); } }
可以看到org.springframework.web.context.request.ServletWebRequest#getNativeRequest(java.lang.Class<T>) 方法实际会调用到WebUtils 相关方法,org.springframework.web.util.WebUtils#getNativeRequest 如下: 可以看到是如果是需要的类型就直接返回,否则判断是否是包装类,如果是包装类就调用包装类内部属性然后继续调用。
public static <T> T getNativeRequest(ServletRequest request, Class<T> requiredType) { if (requiredType != null) { if (requiredType.isInstance(request)) { return (T) request; } else if (request instanceof ServletRequestWrapper) { return getNativeRequest(((ServletRequestWrapper) request).getRequest(), requiredType); } } return null; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· Obsidian + DeepSeek:免费 AI 助力你的知识管理,让你的笔记飞起来!
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了