开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 21 天,点击查看活动详情
前言
在详解SpringMVC-RequestMappingHandlerMapping中对web请求到来时如何获取处理该请求的handler(实际是HandlerMethod)进行了分析,那么获取到web请求对应的handler之后,SpringMVC框架还会根据handler获取其对应的HandlerAdapter,handler的实际执行就是由HandlerAdapter完成。
HandlerAdapter一共会做三件事情:入参解析
,执行处理逻辑
,返回值处理
。
通俗的说就是执行方法前要把方法需要的参数准备好,然后执行方法,最后方法执行后要对方法返回值进行处理。
如果handler是由RequestMappingHandlerMapping获得,那么执行该handler的HandlerAdapter应该为RequestMappingHandlerAdapter,本篇文章将对RequestMappingHandlerAdapter的入参解析,执行处理逻辑,返回值处理三部分进行学习。
SpringBoot版本:2.4.1
正文
一. 初识RequestMappingHandlerAdapter
在正式分析源码前,先通过两张示意图,认识RequestMappingHandlerAdapter的作用。
首先是RequestMappingHandlerAdapter的初始化,如下所示。
在RequestMappingHandlerAdapter初始化阶段,会加载一系列的方法和bean,用于后续的入参解析和返回结果处理。
然后是RequestMappingHandlerAdapter正式执行一个处理器方法,示意图如下。
也就是会先从Request中拿到处理器方法需要的参数,然后通过反射的方式执行处理器方法,最后执行结果会经过一系列处理最终按照预期的媒体类型写入到Response中。
二. RequestMappingHandlerAdapter初始化
首先通过类图认识一下RequestMappingHandlerAdapter。
由类图可知RequestMappingHandlerAdapter实现了InitializingBean接口,因此在容器加载RequestMappingHandlerAdapter时会执行其实现的afterPropertiesSet() 方法。该方法如下所示。
public void afterPropertiesSet() {
initControllerAdviceCache();
if (this.argumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
if (this.initBinderArgumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
if (this.returnValueHandlers == null) {
List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
}
}
afterPropertiesSet() 方法依次为RequestMappingHandlerAdapter加载:
- ControllerAdviceBean相关内容;
- 加载参数解析器HandlerMethodArgumentResolver;
- 加载返回结果处理器HandlerMethodReturnValueHandler。
其中参数解析器和返回结果处理器默认情况下会将SpringMVC框架提供的和用户自定义的一并进行加载,而加载ControllerAdviceBean相关内容实际就是先获取容器中所有由@ControllerAdvice注解修饰的bean,然后将这些bean的由@ModelAttribute注解和@InitBinder注解修饰的方法加载到RequestMappingHandlerAdapter中,最后再判断由@ControllerAdvice注解修饰的bean是否实现了RequestBodyAdvice或ResponseBodyAdvice接口,如果是,则将该bean也加载到RequestMappingHandlerAdapter中。
initControllerAdviceCache() 方法如下所示。
private void initControllerAdviceCache() {
if (getApplicationContext() == null) {
return;
}
// 将容器中所有带@ControllerAdvice注解的bean获取出来并包装成ControllerAdviceBean对象
List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
List<Object> requestResponseBodyAdviceBeans = new ArrayList<>();
// 遍历ControllerAdviceBean对象集合
for (ControllerAdviceBean adviceBean : adviceBeans) {
Class<?> beanType = adviceBean.getBeanType();
if (beanType == null) {
throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
}
// 获取ControllerAdviceBean对象(包括父对象)和其实现的接口的不由@RequestMapping注解修饰且由@ModelAttribute注解修饰的方法集合
Set<Method> attrMethods = MethodIntrospector.selectMethods(beanType, MODEL_ATTRIBUTE_METHODS);
if (!attrMethods.isEmpty()) {
this.modelAttributeAdviceCache.put(adviceBean, attrMethods);
}
// 获取ControllerAdviceBean对象(包括父对象)和其实现的接口的由@InitBinder注解修饰的方法集合
Set<Method> binderMethods = MethodIntrospector.selectMethods(beanType, INIT_BINDER_METHODS);
if (!binderMethods.isEmpty()) {
this.initBinderAdviceCache.put(adviceBean, binderMethods);
}
// 判断ControllerAdviceBean对象是否实现了RequestBodyAdvice或ResponseBodyAdvice接口
if (RequestBodyAdvice.class.isAssignableFrom(beanType) || ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
requestResponseBodyAdviceBeans.add(adviceBean);
}
}
if (!requestResponseBodyAdviceBeans.isEmpty()) {
this.requestResponseBodyAdvice.addAll(0, requestResponseBodyAdviceBeans);
}
if (logger.isDebugEnabled()) {
int modelSize = this.modelAttributeAdviceCache.size();
int binderSize = this.initBinderAdviceCache.size();
int reqCount = getBodyAdviceCount(RequestBodyAdvice.class);
int resCount = getBodyAdviceCount(ResponseBodyAdvice.class);
if (modelSize == 0 && binderSize == 0 && reqCount == 0 && resCount == 0) {
logger.debug("ControllerAdvice beans: none");
}
else {
logger.debug("ControllerAdvice beans: " + modelSize + " @ModelAttribute, " + binderSize +
" @InitBinder, " + reqCount + " RequestBodyAdvice, " + resCount + " ResponseBodyAdvice");
}
}
}
由@ControllerAdvice注解修饰的bean是功能增强型Controller,通常搭配@ModelAttribute注解和@InitBinder注解使用,可以实现:全局数据绑定;全局数据预处理;全局异常处理。
小节:RequestMappingHandlerAdapter
初始化时会将解析参数,参数预处理,执行结果处理等步骤需要用到的bean进行加载。
三. RequestMappingHandlerAdapter参数解析
先通过DispatcherServlet中的doDispatch() 方法观察handler执行的入口。
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// 根据handler获取HandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 如果是GET或者HEAD请求,则获取LastModified时间戳并判断请求的资源是否改变
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
// 执行拦截器的preHandler方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 执行handler
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
// ......
}
catch (Throwable err) {
// ......
}
// ......
}
catch (Exception ex) {
// ......
}
catch (Throwable err) {
// ......
}
finally {
// ......
}
}
handler的执行发生在HandlerAdapter接口声明的handle() 方法中,由前面类图可知,RequestMappingHandlerAdapter继承于抽象类AbstractHandlerMethodAdapter,该抽象类实现了HandlerAdapter接口,并在其实现的handle() 方法中调用了其声明的抽象方法handleInternal(),因此handler的执行入口是RequestMappingHandlerAdapter的handleInternal() 方法,该方法如下所示。
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ModelAndView mav;
// 如果对支持的HTTP请求方法和会话进行了设置,则校验request是否符合设置,校验失败抛出异常
checkRequest(request);
// 如果synchronizeOnSession为true且能从request获取会话对象,则以同步方式执行handler
if (this.synchronizeOnSession) {
HttpSession session = request.getSession(false);
if (session != null) {
Object mutex = WebUtils.getSessionMutex(session);
synchronized (mutex) {
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// 以非同步方式执行handler
mav = invokeHandlerMethod(request, response, handlerMethod);
}
// response如果没有包含Cache-Control响应头,则在这里为response添加Cache-Control响应头
if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
// handler如果由@SessionAttributes注解修饰且声明了会话属性,则将response响应头的Cache-Control设置为no-store
if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
}
else {
// 否则根据WebContentGenerator的设置来丰富response
prepareResponse(response);
}
}
return mav;
}
继续看invokeHandlerMethod() 的实现。
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
// 获取WebDataBinderFactory,顾名思义,该工厂对象用于获取WebDataBinder,以完成request参数与JavaBean的绑定
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
// 获取ModelFactory,用于在handler执行前协助初始化Model,并在执行后更新Model
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
// InvocableHandlerMethod继承于HandlerMethod并对其进行了扩展,能够使用参数解析器从request中获取参数并将获取到的参数作为入参调用handler方法
// ServletInvocableHandlerMethod继承于InvocableHandlerMethod并对其进行了扩展,能够使用返回值处理器处理handler方法的返回值以及基于@ResponseStatus注解设置响应状态
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
// 为invocableMethod设置参数解析器
if (this.argumentResolvers != null) {
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
// 为invocableMethod设置返回值处理器
if (this.returnValueHandlers != null) {
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
// 为invocableMethod设置WebDataBinderFactory和ModelFactory
invocableMethod.setDataBinderFactory(binderFactory);
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
// 创建ModelAndViewContainer对象
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
// 从request中获取Input Flash Attributes并将其加载到ModelAndViewContainer使用的模型Model中
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
// 初始化模型Model
modelFactory.initModel(webRequest, mavContainer, invocableMethod);
// 设置是否在重定向时不使用默认模型defaultModel,默认值为false,即重定向时也要使用defaultModel
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
asyncWebRequest.setTimeout(this.asyncRequestTimeout);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.setTaskExecutor(this.taskExecutor);
asyncManager.setAsyncWebRequest(asyncWebRequest);
asyncManager.registerCallableInterceptors(this.callableInterceptors);
asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
if (asyncManager.hasConcurrentResult()) {
Object result = asyncManager.getConcurrentResult();
mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
asyncManager.clearConcurrentResult();
LogFormatUtils.traceDebug(logger, traceOn -> {
String formatted = LogFormatUtils.formatValue(result, !traceOn);
return "Resume with async result [" + formatted + "]";
});
invocableMethod = invocableMethod.wrapConcurrentResult(result);
}
// 执行handler
invocableMethod.invokeAndHandle(webRequest, mavContainer);
if (asyncManager.isConcurrentHandlingStarted()) {
return null;
}
// 更新模型Model,然后创建ModelAndView对象并返回
return getModelAndView(mavContainer, modelFactory, webRequest);
}
finally {
webRequest.requestCompleted();
}
}
invokeHandlerMethod() 方法有点长,所做的事情可以概括为:
- 首先为执行handler准备各种必要条件(参数解析器,返回值处理器等);
- 然后执行handler;
- 最后更新模型Model并生成ModelAndView对象。
这里特别说明一下上面源码中的ModelAndViewContainer对象,该对象的源码的注释翻译如下。
记录与模型Model和视图View相关的由HandlerMethodArgumentResolvers和HandlerMethodReturnValueHandlers在handler方法执行中做出的相关决策。
要理解上面的话,先看一下ModelAndViewContainer的成员变量。
public class ModelAndViewContainer {
// 重定向时不使用defaultModel标志位,默认为false(表示重定向时也要使用defaultModel)
private boolean ignoreDefaultModelOnRedirect = false;
// 视图
@Nullable
private Object view;
// 默认模型
private final ModelMap defaultModel = new BindingAwareModelMap();
// 重定向模型,重定向场景时可以使用该模型
@Nullable
private ModelMap redirectModel;
// 重定向场景的标志位,表示handler是否返回重定向指令
private boolean redirectModelScenario = false;
// http状态码
@Nullable
private HttpStatus status;
// 不应该进行数据绑定的模型属性集合
private final Set<String> noBinding = new HashSet<>(4);
private final Set<String> bindingDisabled = new HashSet<>(4);
// 标识会话处理是否完成
private final SessionStatus sessionStatus = new SimpleSessionStatus();
// 标识handler是否执行完成
private boolean requestHandled = false;
}
即ModelAndViewContainer主要功能如下。
- 维护模型Model:非重定向场景下使用defaultModel,重定向场景下且ignoreDefaultModelOnRedirect为true时使用redirectModel;
- 维护视图View:如果是字符串类型则是逻辑视图;
- 维护是否是重定向场景:通过redirectModelScenario属性字段进行标识;
- 维护handler是否执行完:通过requestHandled属性字段进行标识。
因为在后面参数解析和返回结果处理的过程中会将ModelAndViewContainer对象传递来传递去,其实这个对象做的事情大部分是存储数据和记录状态以方便最后获取ModelAndView对象,所以在这里先对其进行简要说明。
回到invokeHandlerMethod() 方法,handler的实际执行是发生在ServletInvocableHandlerMethod对象的invokeAndHandle() 方法中,继续看这个方法的实现。
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// 参数解析,执行handler
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
setResponseStatus(webRequest);
if (returnValue == null) {
if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
disableContentCachingIfNecessary(webRequest);
mavContainer.setRequestHandled(true);
return;
}
}
else if (StringUtils.hasText(getResponseStatusReason())) {
mavContainer.setRequestHandled(true);
return;
}
mavContainer.setRequestHandled(false);
Assert.state(this.returnValueHandlers != null, "No return value handlers");
try {
// 返回值处理
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(formatErrorForReturnValue(returnValue), ex);
}
throw ex;
}
}
invokeAndHandle() 方法中,调用了ServletInvocableHandlerMethod的父类InvocableHandlerMethod的invokeForRequest() 来完成参数解析和执行handler。其实现如下。
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// 参数解析
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
if (logger.isTraceEnabled()) {
logger.trace("Arguments: " + Arrays.toString(args));
}
// 执行handler
return doInvoke(args);
}
到这里,终于看到了参数解析和执行handler的方法。本小节主要学习参数解析过程,本小节正文开始。
getMethodArgumentValues() 方法是InvocableHandlerMethod提供的方法,该方法完成两个事情:
- 获取需要执行的handler方法的参数信息,包括:参数名,参数类型,参数注解(如果有的话)等,得到的是一个MethodParameter[] 数组,每一个handler方法的参数信息都会封装成一个MethodParameter对象并加入到MethodParameter[] 数组中;
- 将handler方法的参数信息与request送入参数解析器,通过参数解析器将需要的参数从request中获取出来。
getMethodArgumentValues() 的实现如下所示。
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// 获取需要执行的handler方法的参数信息
MethodParameter[] parameters = getMethodParameters();
if (ObjectUtils.isEmpty(parameters)) {
return EMPTY_ARGS;
}
Object[] args = new Object[parameters.length];
// 遍历所有handler方法的参数信息
for (int i = 0; i < parameters.length; i++) {
MethodParameter parameter = parameters[i];
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
args[i] = findProvidedArgument(parameter, providedArgs);
if (args[i] != null) {
continue;
}
// 遍历所有参数解析器,寻找能够解析当前handler方法参数的参数解析器,并将其缓存
if (!this.resolvers.supportsParameter(parameter)) {
throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
}
try {
// 将当前handler方法参数信息与request送入参数解析器,通过参数解析器将需要的参数从request中获取出来
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
String exMsg = ex.getMessage();
if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
logger.debug(formatArgumentError(parameter, exMsg));
}
}
throw ex;
}
}
return args;
}
InvocableHandlerMethod的resolvers成员变量是一个HandlerMethodArgumentResolverComposite对象,该对象和其它参数解析器一样实现了HandlerMethodArgumentResolver接口,下面看一下其实现的resolveArgument() 方法。
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
// 获取handler方法参数对应的参数解析器(一般这里不用遍历全部参数解析器,可以直接从缓存中获取)
HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
if (resolver == null) {
throw new IllegalArgumentException("Unsupported parameter type [" +
parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
}
// 调用参数解析器从request中获取参数
return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}
在上面resolveArgument() 方法中调用了参数解析器的resolveArgument() 方法,而不同参数解析器的resolveArgument() 方法实现存在一定差别。
在日常的web开发中,使用@RequestParam,@PathVariable,@RequestBody注解较多,不同注解修饰的参数对应的参数解析器不尽相同,首先看一下这三种注解对应的参数解析器的类图。
由类图可以知道,@RequestParam注解和@PathVariable注解对应的参数解析器继承于AbstractNamedValueMethodArgumentResolver抽象类,该抽象类实现了HandlerMethodArgumentResolver接口,因此这两个注解对应的参数解析器调用resolveArgument() 方法实际是调用其父类实现的resolveArgument() 方法。而@RequestBody注解对应的参数解析器RequestResponseBodyMethodProcessor对其父类实现的resolveArgument() 方法进行了重写,故该注解对应的参数解析器调用resolveArgument() 方法时实际是调用其本身重写的方法。
先看一下AbstractNamedValueMethodArgumentResolver对resolveArgument() 方法的实现。
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
// 根据handler方法的参数信息创建NamedValueInfo对象
// 例如@RequestParam(name = "password", required = true, defaultValue = "admin")
// 创建的NamedValueInfo对象为NamedValueInfo(name=password, required=true, defaultValue=admin)
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
MethodParameter nestedParameter = parameter.nestedIfOptional();
Object resolvedName = resolveEmbeddedValuesAndExpressions(namedValueInfo.name);
if (resolvedName == null) {
throw new IllegalArgumentException(
"Specified name must not resolve to null: [" + namedValueInfo.name + "]");
}
// 从request中解析获取参数arg
Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
if (arg == null) {
if (namedValueInfo.defaultValue != null) {
arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
}
else if (namedValueInfo.required && !nestedParameter.isOptional()) {
handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
}
arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
}
else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
}
if (binderFactory != null) {
// 创建WebDataBinder,并执行由@InitBinder注解修饰的方法来初始化WebDataBinder
WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
try {
// 使用WebDataBinder完成arg参数的类型转换和绑定
arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
}
catch (ConversionNotSupportedException ex) {
throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),
namedValueInfo.name, parameter, ex.getCause());
}
catch (TypeMismatchException ex) {
throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),
namedValueInfo.name, parameter, ex.getCause());
}
if (arg == null && namedValueInfo.defaultValue == null &&
namedValueInfo.required && !nestedParameter.isOptional()) {
handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
}
}
handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
return arg;
}
上面源码中的resolveName() 方法是AbstractNamedValueMethodArgumentResolver声明的一个抽象方法,@RequestParam注解和@PathVariable注解对应的参数解析器均对其进行了实现,具体的实现逻辑比较简单,这里不再赘述,总之该方法可以从request中获取指定名称的参数。参数获取到之后,还会通过WebDataBinderFactory创建WebDataBinder对象,在创建WebDataBinder对象的同时还会调用所有由@InitBinder注解修饰的方法来初始化WebDataBinder对象,最后使用WebDataBinder来对刚刚从request中获取到的参数进行类型转换和绑定(即先将arg参数按照我们的预期进行类型转换,然后再将arg指向转换后的值)。关于WebDataBinder如何完成类型转换和绑定,将会在后面的一篇文章中专门进行学习,这里不再进行深入。
下面再看一下RequestResponseBodyMethodProcessor对resolveArgument() 方法的实现。
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
parameter = parameter.nestedIfOptional();
// 从request中获取参数并调用消息转换器将参数转换为期望的类型
Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
String name = Conventions.getVariableNameForParameter(parameter);
if (binderFactory != null) {
// 创建WebDataBinder,并执行由@InitBinder注解修饰的方法来初始化WebDataBinder
WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
if (arg != null) {
// 如果handler方法的参数由@Valid或@Validated注解修饰,则对刚刚获取到的参数进行参数校验
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);
}
readWithMessageConverters() 方法实际就是调用SpringMVC框架提供的(或用户实现的)消息转换器来完成请求体到参数对象的绑定,并且如果使用了@Valid或@Validated注解来进行参数校验,则会在readWithMessageConverters() 方法获取到参数对象后创建WebDataBinder来完成参数校验,如果校验不通过则会抛出MethodArgumentNotValidException全局异常,通过处理该异常可以对参数校验不过的情况进行统一处理。下面再看一下readWithMessageConverters() 的实现。
protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter,
Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
Assert.state(servletRequest != null, "No HttpServletRequest");
ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(servletRequest);
// 调用父类的readWithMessageConverters()方法
Object arg = readWithMessageConverters(inputMessage, parameter, paramType);
if (arg == null && checkRequired(parameter)) {
throw new HttpMessageNotReadableException("Required request body is missing: " +
parameter.getExecutable().toGenericString(), inputMessage);
}
return arg;
}
在RequestResponseBodyMethodProcessor的readWithMessageConverters() 方法中调用了其父类AbstractMessageConverterMethodArgumentResolver的readWithMessageConverters() 方法,其实现如下所示。
protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
MediaType contentType;
boolean noContentType = false;
try {
// 获取媒体类型,例如application/json;charset=UTF-8
contentType = inputMessage.getHeaders().getContentType();
}
catch (InvalidMediaTypeException ex) {
throw new HttpMediaTypeNotSupportedException(ex.getMessage());
}
if (contentType == null) {
noContentType = true;
contentType = MediaType.APPLICATION_OCTET_STREAM;
}
// 获取handler的Class对象
Class<?> contextClass = parameter.getContainingClass();
// targetClass表示handler方法参数类型的Class对象
Class<T> targetClass = (targetType instanceof Class ? (Class<T>) targetType : null);
if (targetClass == null) {
ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);
targetClass = (Class<T>) resolvableType.resolve();
}
// 获取http请求方法,例如POST
HttpMethod httpMethod = (inputMessage instanceof HttpRequest ? ((HttpRequest) inputMessage).getMethod() : null);
Object body = NO_VALUE;
// EmptyBodyCheckingHttpInputMessage实现了HttpInputMessage接口,增强提供了一个hasBody()方法用于判断request是否有请求体
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()) {
// 在消息转换器读取请求体前调用所有RequestBodyAdvice接口的beforeBodyRead()方法,以完成一些定制操作
HttpInputMessage msgToUse =
getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
// 消息转换器读取请求体并将其转换为参数对象
body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
// 在消息转换器完成转换后调用所有RequestBodyAdvice接口的afterBodyRead()方法,以完成一些定制操作
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;
}
在AbstractMessageConverterMethodArgumentResolver的readWithMessageConverters() 方法中,会循环遍历所有消息转换器并判断其是否可以处理当前请求,同一个请求只会有一个消息转换器可以处理。消息转换器读取请求体前,会先获取所有适配当前handler的RequestBodyAdvice接口,然后循环遍历这些接口并执行其beforeBodyRead() 方法,以完成一些定制处理,同理在消息转换器完成转换后还会循环遍历这些接口并执行其afterBodyRead() 方法。
至此,从request中解析@RequestParam,@PathVariable和@RequestBody注解修饰的参数的流程基本分析完毕。
小节:RequestMappingHandlerAdapter
在解析参数前会根据需要执行的handler方法生成一个ServletInvocableHandlerMethod
对象,然后为该对象加载参数解析器,在进行参数解析时,会遍历所有加载的参数解析器并判断遍历到的参数解析器能否解析当前参数,如果能,则使用该参数解析器完成参数解析。
四. RequestMappingHandlerAdapter执行handler方法
在上一小节中已经知道,handler方法的参数获取和执行发生在InvocableHandlerMethod的invokeForRequest() 方法中,再给出该方法的源码如下所示。
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// 解析参数
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
if (logger.isTraceEnabled()) {
logger.trace("Arguments: " + Arrays.toString(args));
}
// 执行handler
return doInvoke(args);
}
上一小节主要是学习getMethodArgumentValues() 方法做了什么事情,本小节开始学习doInvoke() 方法如何执行handler。doInvoke() 方法实现如下所示。
protected Object doInvoke(Object... args) throws Exception {
// 如果handler方法是桥接方法则获取其桥接方法,否则获取其本身
Method method = getBridgedMethod();
ReflectionUtils.makeAccessible(method);
try {
if (KotlinDetector.isSuspendingFunction(method)) {
return CoroutinesUtils.invokeSuspendingFunction(method, getBean(), args);
}
// 反射方式调用handler方法,getBean()获取handler对象,args是解析得到的参数
return method.invoke(getBean(), args);
}
catch (IllegalArgumentException ex) {
assertTargetBean(method, getBean(), args);
String text = (ex.getMessage() != null ? ex.getMessage() : "Illegal argument");
throw new IllegalStateException(formatInvokeError(text, args), ex);
}
catch (InvocationTargetException ex) {
Throwable targetException = ex.getTargetException();
if (targetException instanceof RuntimeException) {
throw (RuntimeException) targetException;
}
else if (targetException instanceof Error) {
throw (Error) targetException;
}
else if (targetException instanceof Exception) {
throw (Exception) targetException;
}
else {
throw new IllegalStateException(formatInvokeError("Invocation failure", args), targetException);
}
}
}
小节:handler方法的执行是通过反射的方式实现的。
五. RequestMappingHandlerAdapter处理返回结果
在第二小节中已经知道,返回值处理是发生在ServletInvocableHandlerMethod对象的invokeAndHandle() 方法中,再给出其实现如下所示。
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// 参数解析,执行handler
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
setResponseStatus(webRequest);
if (returnValue == null) {
if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
disableContentCachingIfNecessary(webRequest);
mavContainer.setRequestHandled(true);
return;
}
}
else if (StringUtils.hasText(getResponseStatusReason())) {
mavContainer.setRequestHandled(true);
return;
}
mavContainer.setRequestHandled(false);
Assert.state(this.returnValueHandlers != null, "No return value handlers");
try {
// 返回值处理
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(formatErrorForReturnValue(returnValue), ex);
}
throw ex;
}
}
ServletInvocableHandlerMethod对象的成员变量returnValueHandlers是一个HandlerMethodReturnValueHandlerComposite对象,下面看一下该对象的handleReturnValue() 方法。
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
// 根据返回值类型匹配返回值处理器
HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
if (handler == null) {
throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
}
// 返回值处理器对返回值进行处理
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
在handleReturnValue() 方法中先是根据返回值类型匹配对应的返回值处理器,然后用匹配到的返回值处理器对返回值进行处理。
基于SpringMVC框架进行web开发时,比较标准的返回值类型应该为ResponseEntity<T>,该类是由Spring提供的一个继承于HttpEntity<T>的响应实体类,ResponseEntity<T>相较于HttpEntity<T>增加了响应状态码的成员变量,SpringMVC框架专门提供了一个ResponseEntity<T>对应的返回值处理器HttpEntityMethodProcessor。
同时,如果返回值类型是一般类型或者自定义类型,但是handler方法或者handler由@ResponseBody注解修饰,那么这种情况对应的返回值处理器是第二小节中出现过的RequestResponseBodyMethodProcessor。那么现在以HttpEntityMethodProcessor和RequestResponseBodyMethodProcessor这两个返回值处理器为例,对返回值处理器如何处理返回值进行学习。
首先看一下这两个返回值处理器的类图。
HttpEntityMethodProcessor和RequestResponseBodyMethodProcessor均实现了HandlerMethodReturnValueHandler接口,该接口声明了一个用于判断返回值处理器是否可以处理当前返回值的方法supportsReturnType()。HttpEntityMethodProcessor对该方法的实现如下。
public boolean supportsReturnType(MethodParameter returnType) {
// 可以处理类型是HttpEntity且不是RequestEntity的返回值
return (HttpEntity.class.isAssignableFrom(returnType.getParameterType()) &&
!RequestEntity.class.isAssignableFrom(returnType.getParameterType()));
}
RequestResponseBodyMethodProcessor对该方法的实现如下。
public boolean supportsReturnType(MethodParameter returnType) {
// 如果返回值所在方法或类由@ResponseBody注解修饰,则可以处理
return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
returnType.hasMethodAnnotation(ResponseBody.class));
}
特别说明一下,每次循环遍历返回值处理器时时,HttpEntityMethodProcessor会比RequestResponseBodyMethodProcessor先被遍历到,因此如果handler类或handler方法既由@ResponseBody注解修饰同时返回值类型还是ResponseEntity,那么还是会使用HttpEntityMethodProcessor来处理该返回值。
HandlerMethodReturnValueHandler接口还声明了一个方法为handleReturnValue(),该方法会实际的执行处理返回值的逻辑。HttpEntityMethodProcessor对该方法的实现如下。
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
mavContainer.setRequestHandled(true);
if (returnValue == null) {
return;
}
// 将HttpServletRequest封装成ServletServerHttpRequest对象
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
// 将HttpServletResponse封装成ServletServerHttpResponse对象
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
Assert.isInstanceOf(HttpEntity.class, returnValue);
HttpEntity<?> responseEntity = (HttpEntity<?>) returnValue;
HttpHeaders outputHeaders = outputMessage.getHeaders();
HttpHeaders entityHeaders = responseEntity.getHeaders();
if (!entityHeaders.isEmpty()) {
// 设置响应头字段
entityHeaders.forEach((key, value) -> {
if (HttpHeaders.VARY.equals(key) && outputHeaders.containsKey(HttpHeaders.VARY)) {
List<String> values = getVaryRequestHeadersToAdd(outputHeaders, entityHeaders);
if (!values.isEmpty()) {
outputHeaders.setVary(values);
}
}
else {
outputHeaders.put(key, value);
}
});
}
if (responseEntity instanceof ResponseEntity) {
// 从ResponseEntity中获取响应码
int returnStatus = ((ResponseEntity<?>) responseEntity).getStatusCodeValue();
// 设置HttpServletResponse的响应码
outputMessage.getServletResponse().setStatus(returnStatus);
if (returnStatus == 200) {
HttpMethod method = inputMessage.getMethod();
// 如果响应码为200,且http请求方法为GET或者HEAD,且请求的资源没有改变,此时立即调用HttpServletResponse的flushBuffer()方法将缓冲区的内容写入客户端
if ((HttpMethod.GET.equals(method) || HttpMethod.HEAD.equals(method))
&& isResourceNotModified(inputMessage, outputMessage)) {
outputMessage.flush();
return;
}
}
else if (returnStatus / 100 == 3) {
String location = outputHeaders.getFirst("location");
if (location != null) {
// 如果是重定向场景,将flash attributes添加到session中
saveFlashAttributes(mavContainer, webRequest, location);
}
}
}
// 调用消息转换器将返回值写入响应
writeWithMessageConverters(responseEntity.getBody(), returnType, inputMessage, outputMessage);
outputMessage.flush();
}
RequestResponseBodyMethodProcessor对该方法的实现如下。
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
mavContainer.setRequestHandled(true);
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
可以发现,HttpEntityMethodProcessor和RequestResponseBodyMethodProcessor在handleReturnValue() 方法中做了同样的事情:
- 先将ModelAndViewContainer对象的handler方法是否执行标志设置为true;
- 然后基于HttpServletRequest和HttpServletResponse对象创建ServletServerHttpRequest和ServletServerHttpResponse对象;
- 最后调用公共父类AbstractMessageConverterMethodProcessor的writeWithMessageConverters() 方法处理返回值。
其中HttpEntityMethodProcessor多做了两件事情:设置响应头和设置响应码。
最后看一下writeWithMessageConverters() 的实现。
protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
Object body;
// 返回值类型的Class对象
Class<?> valueType;
// 返回值的泛型类型
Type targetType;
if (value instanceof CharSequence) {
body = value.toString();
valueType = String.class;
targetType = String.class;
}
else {
body = value;
valueType = getReturnValueType(body, returnType);
targetType = GenericTypeResolver.resolveType(getGenericType(returnType), returnType.getContainingClass());
}
// 判断返回值类型是否是InputStreamResource或者Resource
if (isResourceType(value, returnType)) {
outputMessage.getHeaders().set(HttpHeaders.ACCEPT_RANGES, "bytes");
if (value != null && inputMessage.getHeaders().getFirst(HttpHeaders.RANGE) != null &&
outputMessage.getServletResponse().getStatus() == 200) {
Resource resource = (Resource) value;
try {
List<HttpRange> httpRanges = inputMessage.getHeaders().getRange();
outputMessage.getServletResponse().setStatus(HttpStatus.PARTIAL_CONTENT.value());
body = HttpRange.toResourceRegions(httpRanges, resource);
valueType = body.getClass();
targetType = RESOURCE_REGION_LIST_TYPE;
}
catch (IllegalArgumentException ex) {
outputMessage.getHeaders().set(HttpHeaders.CONTENT_RANGE, "bytes */" + resource.contentLength());
outputMessage.getServletResponse().setStatus(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE.value());
}
}
}
// selectedMediaType表示响应体的媒体类型
MediaType selectedMediaType = null;
// 从响应头中获取媒体类型,如果在handler方法中没有手动设置响应体的媒体类型那么这里的contentType为null
MediaType contentType = outputMessage.getHeaders().getContentType();
boolean isContentTypePreset = contentType != null && contentType.isConcrete();
// 如果在handler方法中设置了响应体媒体类型,则消息转换器使用设置的媒体类型对返回值进行类型转换
// 如果在handler方法中没有设置响应体媒体类型,则从请求中获取客户端接受的媒体类型并选取最合适的媒体类型
if (isContentTypePreset) {
if (logger.isDebugEnabled()) {
logger.debug("Found 'Content-Type:" + contentType + "' in response");
}
selectedMediaType = contentType;
}
else {
HttpServletRequest request = inputMessage.getServletRequest();
List<MediaType> acceptableTypes = getAcceptableMediaTypes(request);
List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);
if (body != null && producibleTypes.isEmpty()) {
throw new HttpMessageNotWritableException(
"No converter found for return value of type: " + valueType);
}
List<MediaType> mediaTypesToUse = new ArrayList<>();
for (MediaType requestedType : acceptableTypes) {
for (MediaType producibleType : producibleTypes) {
if (requestedType.isCompatibleWith(producibleType)) {
mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));
}
}
}
if (mediaTypesToUse.isEmpty()) {
if (body != null) {
throw new HttpMediaTypeNotAcceptableException(producibleTypes);
}
if (logger.isDebugEnabled()) {
logger.debug("No match for " + acceptableTypes + ", supported: " + producibleTypes);
}
return;
}
MediaType.sortBySpecificityAndQuality(mediaTypesToUse);
for (MediaType mediaType : mediaTypesToUse) {
if (mediaType.isConcrete()) {
selectedMediaType = mediaType;
break;
}
else if (mediaType.isPresentIn(ALL_APPLICATION_MEDIA_TYPES)) {
selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
break;
}
}
if (logger.isDebugEnabled()) {
logger.debug("Using '" + selectedMediaType + "', given " +
acceptableTypes + " and supported " + producibleTypes);
}
}
if (selectedMediaType != null) {
selectedMediaType = selectedMediaType.removeQualityValue();
// 遍历所有消息转换器
for (HttpMessageConverter<?> converter : this.messageConverters) {
GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?
(GenericHttpMessageConverter<?>) converter : null);
// 判断消息转换器是否支持转换当前返回值为期望的媒体类型
if (genericConverter != null ?
((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
converter.canWrite(valueType, selectedMediaType)) {
// 在消息转换器转换返回值前调用所有ResponseBodyAdvice接口的beforeBodyWrite()方法,以完成一些定制操作
body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
(Class<? extends HttpMessageConverter<?>>) converter.getClass(),
inputMessage, outputMessage);
if (body != null) {
Object theBody = body;
LogFormatUtils.traceDebug(logger, traceOn ->
"Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]");
// Content-Disposition响应头字段处理
addContentDispositionHeader(inputMessage, outputMessage);
// 调用消息处理器将返回值按照期望的媒体类型写入响应体
if (genericConverter != null) {
genericConverter.write(body, targetType, selectedMediaType, outputMessage);
}
else {
((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Nothing to write: null body");
}
}
return;
}
}
}
if (body != null) {
Set<MediaType> producibleMediaTypes =
(Set<MediaType>) inputMessage.getServletRequest()
.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
if (isContentTypePreset || !CollectionUtils.isEmpty(producibleMediaTypes)) {
throw new HttpMessageNotWritableException(
"No converter for [" + valueType + "] with preset Content-Type '" + contentType + "'");
}
throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
}
}
在writeWithMessageConverters() 方法中,主要是获取响应体媒体类型,然后调用消息转换器将返回值按照期望的媒体类型写入响应体,在写入前还会执行ResponseBodyAdvice对返回值实现一些定制操作。
小节:RequestMappingHandlerAdapter
处理返回值首先会根据返回值类型匹配相应的返回值处理器来处理返回值,在返回值处理器中通常是先获取响应体的媒体类型,然后调用消息转换器将返回值按照期望的媒体类型写入响应体。
总结
RequestMappingHandlerAdapter是基于SpringMVC框架进行web开发使用频次最高的组件之一,RequestMappingHandlerAdapter在handler方法执行前会使用SpringMVC框架提供的和用户实现的参数解析器从request中获取handler方法需要的参数,然后基于反射方式调用handler方法,最后会使用SpringMVC框架提供的和用户实现的返回值处理器将handler方法的返回值写入响应体中。
开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 21 天,点击查看活动详情