上次那篇我们只分析了doDispatch中的getHandler
方法(获取执行链,执行链里包括当前请求URL对应的 handler 以及拦截器(Controller、method绑定关系))
,今儿继续向下看getHandlerAdapter
方法和handle
方法
public class DispatcherServlet{
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
mappedHandler = this.getHandler(processedRequest);
if (mappedHandler == null) {
this.noHandlerFound(processedRequest, response);
return;
}
//继续向下
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
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;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
//开始真正处理请求的方法
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
this.applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
}
一、getHandlerAdapter
有没有想过,为何我们加了那些注解,例如@PathVariable,为什么springmvc就能将其变量确定为对应的值呢?这就是HandlerAdapter的作用,在getHandler方法确定好控制器和对应的方法后(执行链),getHandlerAdapter就会来帮我们为当前的handler找一个adapter
然后我们通过该适配器,就能够将请求的链接所带的参数给适配上。
看一下DispatcherServlet的doService方法时序图:
直接进入getHandlerAdapter方法查看,debug -getHandlerAdapter
方法,可以看到,会在原生的4种handlerAdapter中选择一个匹配的适配器进行返回。获取代码:
public class DispatcherServlet{
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
for (HandlerAdapter adapter : this.handlerAdapters) {
if (adapter.supports(handler)) {
return adapter;
}
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
}
对应的处理如下:
注意:如果自己添加了Adapter就不会在加载springMVC默认的这些Adapter
getHandlerAdapter里调用了adapter.supports(handler)
- 通过supports方法来确定adapter,我们进入supports方法,发现不同的adapter有不同的判断方法,我们还是先以requestMapping请求的到的handler为例
- 可以发现他的判断方式很简单,就是判断handler是不是一个HandlerMethod(在上面匹配的时候会根据不同的情况获得不同的handler)
public class AbstractHandlerMethodAdapter{
public final boolean supports(Object handler) {
return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
}
}
我们可以通过debug其他类型的handler可以返现他们的判断方式和上面的类似都是instanceof来判断的,匹配后返回具体handleradapter
通过getHandler()和getHandlerAdapter()方法得到的执行链(得到controller中具体的执行方法)和适配器(可以解析请求所带的参数)后
,我们就可以来真正执行请求的方法(handle())了。
二、handle
执行
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
来到
进一步,进入到该抽象类的实现类RequestMappingHandlerAdapter
中(为何是RequestMappingHandlerAdapter),因为我的方法使用了@RequestMapping,所以就返回这个Adapter
),对一个请求方法的所有操作都会在这里进行。RequestMappingHandlerAdapter 部分源码如下:可以看到,handleInternal
执行后,会返回一个ModelAndView
protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
this.checkRequest(request);
ModelAndView mav;
if (this.synchronizeOnSession) {
HttpSession session = request.getSession(false);
if (session != null) {
Object mutex = WebUtils.getSessionMutex(session);
synchronized(mutex) {
mav = this.invokeHandlerMethod(request, response, handlerMethod);
}
} else {
mav = this.invokeHandlerMethod(request, response, handlerMethod);
}
} else {
//最终来到invokeHandlerMethod这里,才是真正的执行handler方法
mav = this.invokeHandlerMethod(request, response, handlerMethod);
}
if (!response.containsHeader("Cache-Control")) {
if (this.getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
this.applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
} else {
this.prepareResponse(response);
}
}
return mav;
}
invokeHandlerMethod方法源码
:
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ServletWebRequest webRequest = new ServletWebRequest(request, response);
Object result;
try {
WebDataBinderFactory binderFactory = this.getDataBinderFactory(handlerMethod);
ModelFactory modelFactory = this.getModelFactory(handlerMethod, binderFactory);
ServletInvocableHandlerMethod invocableMethod = this.createInvocableHandlerMethod(handlerMethod);
if (this.argumentResolvers != null) {
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
if (this.returnValueHandlers != null) {
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
invocableMethod.setDataBinderFactory(binderFactory);
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
modelFactory.initModel(webRequest, mavContainer, invocableMethod);
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()) {
result = asyncManager.getConcurrentResult();
mavContainer = (ModelAndViewContainer)asyncManager.getConcurrentResultContext()[0];
asyncManager.clearConcurrentResult();
LogFormatUtils.traceDebug(this.logger, (traceOn) -> {
String formatted = LogFormatUtils.formatValue(result, !traceOn);
return "Resume with async result [" + formatted + "]";
});
invocableMethod = invocableMethod.wrapConcurrentResult(result);
}
invocableMethod.invokeAndHandle(webRequest, mavContainer, new Object[0]);
if (!asyncManager.isConcurrentHandlingStarted()) {
ModelAndView var15 = this.getModelAndView(mavContainer, modelFactory, webRequest);
return var15;
}
result = null;
} finally {
webRequest.requestCompleted();
}
return (ModelAndView)result;
}
其中有两个变量值得我们研究:argumentResolvers(参数解析器 26种)
和returnValueHandlers(返回值处理器 15种)
,这两个东西就是这篇文章的主题:参数解析的核心。
- 参数解析器(其实底层是个接口)
看一下HandlerMethodArgumentResolver接口的定义:
public interface HandlerMethodArgumentResolver {
/** * 解析器是否支持当前参数 * * @param var1 需要被解析的Controller参数 * @return */
boolean supportsParameter(MethodParameter var1);
// 将request中的请求参数解析到当前Controller参数上,在这里进行类型转换
Object resolveArgument(MethodParameter var1, ModelAndViewContainer var2, NativeWebRequest var3, WebDataBinderFactory var4) throws Exception;
}
该接口作用:当前解析器是否支持解析这种参数,支持就调用 resolveArgument解析
最终确定将要执行的目标方法的每一个参数的值是什么SpringMVC目标方法能写多少种参数类型。取决于参数解析器
,默认26种:
- 返回值处理器
决定了目标方法到底能写多少种类型的返回值,默认15种
有一个值得注意的处理器就是RequestResponseBodyMethodHandler
,就是我们使用@ResponseBody时,使用的处理器,底层如下:
在将参数解析器和返回值处理器设置好后,进一步调用了invokeAndHandle
方法,跟踪该方法,我们来到:ServletInvocableHandlerMethod类
中的```invokeAndHandle方法
部分源码:
public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
Object returnValue = this.invokeForRequest(webRequest, mavContainer, providedArgs);
this.setResponseStatus(webRequest);
if (returnValue == null) {
if (this.isRequestNotModified(webRequest) || this.getResponseStatus() != null || mavContainer.isRequestHandled()) {
this.disableContentCachingIfNecessary(webRequest);
mavContainer.setRequestHandled(true);
return;
}
} else if (StringUtils.hasText(this.getResponseStatusReason())) {
mavContainer.setRequestHandled(true);
return;
}
}
跟踪invokeForRequest,来到InvocableHandlerMethod
类, invokeForRequest
及getMethodArgumentValues(开始解析参数了)
源码
public class InvocableHandlerMethod extends HandlerMethod {
@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
Object[] args = this.getMethodArgumentValues(request, mavContainer, providedArgs);
if (logger.isTraceEnabled()) {
logger.trace("Arguments: " + Arrays.toString(args));
}
return this.doInvoke(args);
}
//核心方法,获取参数值最底层的方法
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) {
//先判断当前解析器是否支持这种类型,不支持便对解析器遍历,直到找到支持的解析器
//具体调用链supportsParameter->HandlerMethodArgumentResolverComposite.getArgumentResolver->
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 (logger.isDebugEnabled()) {
String exMsg = var10.getMessage();
if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
logger.debug(formatArgumentError(parameter, exMsg));
}
}
throw var10;
}
}
}
return args;
}
}
}
获取到的参数声明:
HandlerMethodArgumentResolverComposite.getArgumentResolver
源码:
这里可以看到对我们上面提到的那26种解析器的遍历,最后会完全缓存在springboot的本地缓存中
拿到参数解析器后,我们就可以来获取参数的值了
HandlerMethodArgumentResolverComposite.ArgumentResolver
源码:
@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);
}
}
resolveArgument最终会调用AbstractNamedValueMethodArgumentResolver
的各种实现类如下:
再配合UrlPathHelper(会将url中的变量解析出来,放在request的请求域中),最终得到变量值。
三、对于传入的是Servlet API的参数的处理
WebRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId
这些都能找到对应的resolver进行解析。
以ServletRequestMethodArgumentResolver为例,它能解析以下的参数,总之,就是进行到resolvers.supportsParameter(parameter)
这个方法后,遍历那26个参数解析器,拿到对应的解析器去解析就好了
,原理都是一样的
@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);
}
四、复杂参数的处理
复杂参数如:Map、Model(map、model里面的数据会被放在request的请求域 request.setAttribute
)、Errors/BindingResult、RedirectAttributes( 重定向携带数据)
、ServletResponse(response)、SessionStatus、UriComponentsBuilder、ServletUriComponentsBuilder等
那么重点就是,他是怎么给request域放数据的呢:debug以下方法看一下
- 首先,解析参数都是跟上面一样的步骤,就是进行到
resolvers.supportsParameter(parameter)
这个方法后,遍历那26个参数解析器,拿到对应的解析器去解析
,而对于map这类型的数据来说,就是对应的
- 拿到解析器后,就得找对应的参数进行影射了,对于上面的使用@requestmapping注解的方法,它会去url缓存中获取参数值,那么map类型的呢?debug后我们发现,它来到了
mavContainer.getModel()
这个方法,准备获取模型数据。
而getModel()
这个方法他会返回一个ModeMap类型的数据,源码如下:
public ModelMap getModel() {
if (this.useDefaultModel()) {
return this.defaultModel;
} else {
if (this.redirectModel == null) {
this.redirectModel = new ModelMap();
}
return this.redirectModel;
}
}
最终,他是返回一个ModelMap的子类BindingAwareModelMap
,BindingAwareModelMap 是Model 也是Map
继承树如下:
Model参数类型就调用另一个解析器
debug后发现,居然跟解析Map类型调用的是一样的方法,也是来到了mavContainer.getModel()
这个方法,准备获取模型数据。我们可以发现,两者返回的是同一个BindingAwareModelMap
。同时,直接放心让request,和response对象也解析好。
然后我们放行方法,执行完invokeForRequest
方法,此时,我们知道,对于请求的处理已经完成了,接下来就是视图解析了,这里先不讨论视图解析的流程,就研究forward的时候,spring是如何将数据(model)放在请求域中给转发出去的。
跟踪进去,我们发现在处理返回结果的时候,也把mavContainer
传进去了:
mavContainer此时如下:
handleReturnValue方法:
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
//先获取返回类型
HandlerMethodReturnValueHandler handler = this.selectHandler(returnValue, returnType);
if (handler == null) {
throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
} else {
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
}
如果你放回的类型是个字符串,就把字符串设置成viewName
此时的mavContainer(view已经为“”forward:/success)
至此可以得出一个结论:方法执行完成后,springmvc会所有的数据都放在 ModelAndViewContainer;包含要去的页面地址View。还包含Model数据。
然后进一步对这些数据进行处理(渲染),会执行以下:
继续跟踪
仍然可以看到,还是围绕着处理mavContainer展开,ModelFactory里有一个
updateBindingResult方法,这是关键,它会遍历所有model的值,并根据绑定策略对数据进行封装
然后在执行:ModelAndView mav=new ModelAndView(....);
这一句,即把遍历到的model数据生成一个ModelAndView。然后再根据是不是重定向,转发,或者普通处理,再进一步对数据进行处理
此时,DispatchServlet的:
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
执行完成,开始执行DispatchServlet的另一个方法:
//完成业务处理后的后置处理
mappedHandler.applyPostHandle(processedRequest, response, mv);
开始执行render()
方法。
涉及两个主要方法:
//处理派发结果
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
//渲染合并输出模型(最关键的核心)
renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
@Override
protected void renderMergedOutputModel(
Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Expose the model object as request attributes.
exposeModelAsRequestAttributes(model, request);
// Expose helpers as request attributes, if any.
exposeHelpers(request);
// Determine the path for the request dispatcher.
String dispatcherPath = prepareForRendering(request, response);
// Obtain a RequestDispatcher for the target resource (typically a JSP).
RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
if (rd == null) {
throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
"]: Check that the corresponding file exists within your web application archive!");
}
// If already included or response already committed, perform include, else forward.
if (useInclude(request, response)) {
response.setContentType(getContentType());
if (logger.isDebugEnabled()) {
logger.debug("Including [" + getUrl() + "]");
}
rd.include(request, response);
}
else {
// Note: The forwarded resource is supposed to determine the content type itself.
if (logger.isDebugEnabled()) {
logger.debug("Forwarding to [" + getUrl() + "]");
}
rd.forward(request, response);
}
}
暴露模型作为请求域属性
// Expose the model object as request attributes.
exposeModelAsRequestAttributes(model, request);
//该方法可以看出,底层最终就是通过最普通的遍历,将model数据重新放入请求域中
protected void exposeModelAsRequestAttributes(Map<String, Object> model,
HttpServletRequest request) throws Exception {
//model中的所有数据遍历挨个放在请求域中
model.forEach((name, value) -> {
if (value != null) {
request.setAttribute(name, value);
}
else {
request.removeAttribute(name);
}
});
}
五、自定义POJO类型参数的处理
跟上面一样,来到resolvers.supportsParameter(parameter)
,处理POJO类型的有两个参数解析器,都是叫:ServletModelAttributeMethodProcessor
,但是一个是处理带注解的bean,一个是处理不带注解的bean。
判断时,先判断参数是不是简单类型
而自定义对象,自然就不是简单类型
然后便开始执行resolveArgument
方法。
- 先看model中有没有(getModel().containsAttribute(name);),没有就看get域中有没有HandlerMapping.uriTemplateVariables的map里面有没有,然后看域中有没有,没有就反射创建
- 反射创建一个没有初始化的bean
- 然后通过WebDatabinder来bind属性
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
核心方法,将请求参数的值绑定到指定的JavaBean里面
,WebDataBinder 利用它里面的Converters
将请求数据转成指定的数据类型。再次封装到JavaBean中- Converters :底层默认有124个,如下:
我们也可以自定义自己的Converters:
@FunctionalInterface
public interface Converter<S, T>