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 等参数解析过程
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
2.org.springframework.web.servlet.mvc.method.annotation.ServletRequestMethodArgumentResolver 源码如下:
public class ServletRequestMethodArgumentResolver implements HandlerMethodArgumentResolver { @Nullable private static Class<?> pushBuilder; static { try { pushBuilder = ClassUtils.forName("javax.servlet.http.PushBuilder", ServletRequestMethodArgumentResolver.class.getClassLoader()); } catch (ClassNotFoundException ex) { // Servlet 4.0 PushBuilder not found - not supported for injection pushBuilder = null; } } @Override public boolean supportsParameter(MethodParameter parameter) { Class<?> paramType = parameter.getParameterType(); return (WebRequest.class.isAssignableFrom(paramType) || ServletRequest.class.isAssignableFrom(paramType) || MultipartRequest.class.isAssignableFrom(paramType) || HttpSession.class.isAssignableFrom(paramType) || (pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) || Principal.class.isAssignableFrom(paramType) || InputStream.class.isAssignableFrom(paramType) || Reader.class.isAssignableFrom(paramType) || HttpMethod.class == paramType || Locale.class == paramType || TimeZone.class == paramType || ZoneId.class == paramType); } @Override public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { Class<?> paramType = parameter.getParameterType(); // WebRequest / NativeWebRequest / ServletWebRequest if (WebRequest.class.isAssignableFrom(paramType)) { if (!paramType.isInstance(webRequest)) { throw new IllegalStateException( "Current request is not of type [" + paramType.getName() + "]: " + webRequest); } return webRequest; } // ServletRequest / HttpServletRequest / MultipartRequest / MultipartHttpServletRequest if (ServletRequest.class.isAssignableFrom(paramType) || MultipartRequest.class.isAssignableFrom(paramType)) { return resolveNativeRequest(webRequest, paramType); } // HttpServletRequest required for all further argument types return resolveArgument(paramType, resolveNativeRequest(webRequest, HttpServletRequest.class)); } private <T> T resolveNativeRequest(NativeWebRequest webRequest, Class<T> requiredType) { T nativeRequest = webRequest.getNativeRequest(requiredType); if (nativeRequest == null) { throw new IllegalStateException( "Current request is not of type [" + requiredType.getName() + "]: " + webRequest); } return nativeRequest; } @Nullable private Object resolveArgument(Class<?> paramType, HttpServletRequest request) throws IOException { 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 (pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) { return PushBuilderDelegate.resolvePushBuilder(request, paramType); } 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 (ZoneId.class == paramType) { TimeZone timeZone = RequestContextUtils.getTimeZone(request); return (timeZone != null ? timeZone.toZoneId() : ZoneId.systemDefault()); } // Should never happen... throw new UnsupportedOperationException("Unknown parameter type: " + paramType.getName()); } /** * Inner class to avoid a hard dependency on Servlet API 4.0 at runtime. */ private static class PushBuilderDelegate { @Nullable public static Object resolvePushBuilder(HttpServletRequest request, Class<?> paramType) { PushBuilder pushBuilder = request.newPushBuilder(); if (pushBuilder != null && !paramType.isInstance(pushBuilder)) { throw new IllegalStateException( "Current push builder is not of type [" + paramType.getName() + "]: " + pushBuilder); } return pushBuilder; } } }
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. 对于ServletRequest 实际是从org.springframework.web.context.request.NativeWebRequest 获取的, org.springframework.web.context.request.NativeWebRequest 对象包装了ServletRequest 和 ServletResponse 对象。
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#invokeHandlerMethod 这一步封装成 ServletWebRequest 对象:(也就是将request和response 封装起来进行后续参数的解析)
ServletWebRequest webRequest = new ServletWebRequest(request, response);