前言
上一节我们看到了SpringMVC在初始化的时候做了大量的准备工作,本章节就重点跟踪下SpringMVC的实际调用过程。
1、DispatcherServlet
记不记得,在大学期间或者刚接触Java WEB开发的时候,前后端交互往往需要一个Servlet来接收请求,并返回信息。时至今日,Servlet仍不过时。
如果是一个SpringMVC的项目,在WEB.XML里面需要配置一个DispatcherServlet,它本质就是个Servlet。或许我们还有印象,在请求到达的时候,就会调用到Servlet的service方法。那么,说回到SpringMVC,就是FrameworkServlet类的service方法。
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String method = request.getMethod();
if (method.equalsIgnoreCase(RequestMethod.PATCH.name())) {
processRequest(request, response);
}
else {
super.service(request, response);
}
}
经过一系列的判断匹配,最后调用到DispatcherServlet类的doService方法。可以看到的是,doService方法向request里面添加了很多默认的属性,如果需要,我们就可以拿到。并在最后调用了实际的处理方法,doDispatch()。
protected void doService(HttpServletRequest request, HttpServletResponse response) {
// Make framework objects available to handlers and view objects.
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
if (inputFlashMap != null) {
request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
}
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
doDispatch(request, response);
}
2、doDispatch
实际处理的时候,大致分为几个步骤。
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) {
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 || mappedHandler.getHandler() == null) {
//URL没有找到匹配项,返回404
noHandlerFound(processedRequest, response);
return;
}
// 第二步 确定请求的处理适配器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
//第三步 调用拦截器 (方法调用前执行)
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 第四步 方法调用
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
//第五步 调用拦截器 (方法调用后执行)
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
//第六步 处理方法返回 (返回页面或者属性)
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
//第七步 调用拦截器(请求完成后执行)
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
不着急,下面我们一个一个的来看。
2.1、 获取处理器 getHandler
获取处理器,就是根据请求的uri,匹配到Method方法。具体做法,我们在上一节Spring源码分析(四)SpringMVC初始化做了预估,实际上Spring也确实是这样做的。
根据uri获取handlerMapping,再以handlerMapping为key,从handlerMethods容器中拿到相应的HandlerMethod对象。不过,它把HandlerMethod对象做了两次封装,源码来看。
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
List<Match> matches = new ArrayList<Match>();
//lookupPath就是uri,拿到mapping。以/user/index为例,mapping如下:
//[{[/user/index],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}]
List<T> directPathMatches = this.urlMap.get(lookupPath);
if (directPathMatches != null) {
//从handlerMethods容器中拿到Method对象,封装成Match对象
//Match对象其实就两个属性 mapping和handlerMethod对象
addMatchingMappings(directPathMatches, matches, request);
}
HandlerMethod handler = matches.get(0).handlerMethod;
//最后返回的是HandlerExecutionChain对象
//里面包含两个属性。handler>就是HandlerMethod对象
//还有个拦截器列表。interceptorList,在第三步调用拦截器就是循环这个List来调用
return getHandlerExecutionChain(handler, request);
}
2.2、获取适配器 getHandlerAdapter
这个比较简单。在初始化的时候,注册了几个适配器。判断上一步拿到的Handler是什么类型,就返回什么适配器,这里返回的是RequestMappingHandlerAdapter实例。
2.3、调用拦截器 (方法调用前执行)
拦截器是链式调用,因为可能会有多个拦截器。拦截器的第一个方法,也是预处理方法preHandle是有返回值的。如果返回false,整个请求就到此结束。在业务里,我们可以让这个拦截器做一些校验工作,不符合预期就返回false。需要注意的是interceptorIndex 这个变量,它记录当前调用到了第几个拦截器。为什么要记录这个呢?等看到后置拦截的时候我们就知道了。
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) {
if (getInterceptors() != null) {
for (int i = 0; i < getInterceptors().length; i++) {
HandlerInterceptor interceptor = getInterceptors()[i];
//preHandle方法如果返回false,接着就调用拦截器的后置方法。
//因为整个请求已经结束了
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
return false;
}
this.interceptorIndex = i;
}
}
return true;
}
2.4、方法调用
方法调用就是解析请求的参数,拿到Method对象直接invoke即可。调用之后根据返回值渲染视图。
2.4.1、参数解析
我们在上一章节已经看到,SpringMVC初始化的时候,加载注册了很多解析器的类,其中有参数解析器和返回值类型解析器,如今就派上用场。
一个HTTP请求的方法,不管有多少参数,有多少种参数的类型。最终,它们都是从哪来呢?没错,就是Request。一切从Request而来。参数解析的方法最终返回的是一个Object[] args,就是参数值的数组。
- 拿到方法上的参数列表,循环此列表
- 调用解析器来解析参数。它们的顶层接口是
HandlerMethodArgumentResolver
。它只有两个方法,supportsParameter、resolveArgument。解析过程全靠这两个方法,supports用来判断是否应该由此类解析,resolve才是真正解析。 - 返回参数值
private Object[] getMethodArgumentValues(NativeWebRequest request,
ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {
//获取方法参数列表
MethodParameter[] parameters = getMethodParameters();
//args就是解析后的参数值的数组
Object[] args = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
MethodParameter parameter = parameters[i];
if (args[i] != null) {
continue;
}
//判断parameter是否能被某一个解析器所解析
if (this.argumentResolvers.supportsParameter(parameter)) {
try {
//拿到上一步的解析器,解析拿到返回值放入args
args[i] = this.argumentResolvers.resolveArgument(
parameter, mavContainer, request, this.dataBinderFactory);
continue;
}
}
}
return args;
}
下面我们看一下HttpServletRequest在解析器里面具体的实现。这个参数会调用到ServletRequestMethodArgumentResolver解析器,里面其实是一些if else。
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
Class<?> paramType = parameter.getParameterType();
if (WebRequest.class.isAssignableFrom(paramType)) {
return webRequest;
}
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
if (ServletRequest.class.isAssignableFrom(paramType) ) {
Object nativeRequest = webRequest.getNativeRequest(paramType);
return nativeRequest;
}
else if (HttpSession.class.isAssignableFrom(paramType)) {
return request.getSession();
}
else if (HttpMethod.class.equals(paramType)) {
return ((ServletWebRequest) webRequest).getHttpMethod();
}
else if (Principal.class.isAssignableFrom(paramType)) {
return request.getUserPrincipal();
}
else if (Locale.class.equals(paramType)) {
return RequestContextUtils.getLocale(request);
}
else if (InputStream.class.isAssignableFrom(paramType)) {
return request.getInputStream();
}
else if (Reader.class.isAssignableFrom(paramType)) {
return request.getReader();
}
//未完......
}
2.4.2、invoke
上一步通过各种解析器之后,返回一个Object类型的参数数组。有了Method对象,参数,调用就变得简单了。
public Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
Object returnValue = doInvoke(args);
if (logger.isTraceEnabled()) {
logger.trace("Method [" + getMethod().getName() + "] returned [" + returnValue + "]");
}
return returnValue;
}
2.4.3、返回值的解析
invoke之后,方法返回Object 类型的returnValue。上面说了,解析器分为参数解析器和返回值解析器两种。So,返回值解析器就是在这里被调用。
它的解析和参数解析器套路基本一致,先判断是否该由此类解析,然后交由该类解析。记得刚工作的时候,只要是返回页面的,都是通过new ModelAndView().setName("xxx")
来返回,后来发现直接返回视图名字的字符串也可以,大感惊奇。原来,SpringMVC是在这里处理的。
我们来看这个类ViewNameMethodReturnValueHandler
的解析方法。
public void handleReturnValue(Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
//如果返回的值是字符串类型
//比如 /index,setViewName的工作它来搞
if (returnValue instanceof String) {
String viewName = (String) returnValue;
mavContainer.setViewName(viewName);
if (isRedirectViewName(viewName)) {
mavContainer.setRedirectModelScenario(true);
}
}
}
SpringMVC比较重要的一点是支持restful,是通过ResponseBody注解。它又是怎么处理的呢?在注册HandlerAdapter的时候,默认给它添加了消息转换器。里面有7种类型,其中有一个MappingJacksonHttpMessageConverter就是专门负责ResponseBody注解的。
来到RequestResponseBodyMethodProcessor
类,它来负责匹配解析ResponseBody注解。判断方法很简单
public boolean supportsReturnType(MethodParameter returnType) {
return (AnnotationUtils.findAnnotation(returnType.getContainingClass(),
ResponseBody.class) != null ||
returnType.getMethodAnnotation(ResponseBody.class) != null);
}
再来看它的handle方法。
public void handleReturnValue(Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws IOException, HttpMediaTypeNotAcceptableException {
//这个属性很重要,在此设置为true,先记住,下面再看。
mavContainer.setRequestHandled(true);
//具体用消息转换器来写入,这里的消息转换器就是
//MappingJacksonHttpMessageConverter
writeWithMessageConverters(returnValue, returnType, webRequest);
}
protected <T> void writeWithMessageConverters(T returnValue, MethodParameter returnType,
ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
throws IOException, HttpMediaTypeNotAcceptableException {
//...........省略大部分代码
//设置数据格式编码等 application/json;charset=UTF-8
if (selectedMediaType != null) {
selectedMediaType = selectedMediaType.removeQualityValue();
//messageConverters就是包含MappingJacksonHttpMessageConverter在内的多种转换器
for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
//先判断是否可写 判断方式也很简单,就是看mediaType是否是application/json
if (messageConverter.canWrite(returnValueClass, selectedMediaType)) {
//拿到返回值
returnValue = this.adviceChain.invoke(returnValue, returnType, selectedMediaType,
(Class<HttpMessageConverter<?>>) messageConverter.getClass(), inputMessage, outputMessage);
if (returnValue != null) {
//写的过程,先设置Response的头信息、编码,再调用jackson.databind包里的方法写入
((HttpMessageConverter<T>) messageConverter).write(returnValue, selectedMediaType, outputMessage);
//写完之后刷新
//outputMessage.getBody().flush();
}
return;
}
}
}
}
2.4.4、获取ModelAndView
方法调用完了,返回值也都解析了。视图需不需要返回,返回到哪里?ModelAndView来决定。
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
//在解析ResponseBody的时候,我们说有个属性很重要。mavContainer.setRequestHandled(true);
//在这里就用到了,说明不需要视图
if (mavContainer.isRequestHandled()) {
return null;
}
ModelMap model = mavContainer.getModel();
ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model);
if (!mavContainer.isViewReference()) {
mav.setView((View) mavContainer.getView());
}
return mav;
}
行文至此,关于方法调用算是都已经处理完毕了。我们可以看出来,其实调用很简单, 关键在于做好参数解析和返回值解析的工作。
2.5、调用拦截器 (方法调用后执行)
又到了一个拦截器的调用。这次它调用的是postHandle方法。
for (int i = getInterceptors().length - 1; i >= 0; i--) {
HandlerInterceptor interceptor = getInterceptors()[i];
interceptor.postHandle(request, response, this.handler, mv);
}
2.6、处理结果
这里是真正响应请求的地方。先是判断ModelAndView是否为空,如果为空说明返回的不是视图,就没必要往下执行。
if (mv != null && !mv.wasCleared()) {
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
重点在于render方法。它最终调用了renderMergedOutputModel方法,渲染输出。我们在配置文件中,都要配置一个视图解析器viewResolver,这里就是用到的它。解析出来完整的视图路径后,利用Servlet的RequestDispatcher直接做转发就完成了这一步的工作。
protected void renderMergedOutputModel(
Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) {
//获取返回的路径 视图解析器配置的prefix加上返回的viewName,再加上后缀p:suffix
String dispatcherPath = prepareForRendering(requestToExpose, response);
//获取RequestDispatcher,
RequestDispatcher rd = getRequestDispatcher(requestToExpose, dispatcherPath);
//直接转发
rd.forward(requestToExpose, response);
}
2.7、调用拦截器 (视图渲染后执行)
在调用拦截器的预处理方法时,提到了一个变量:interceptorIndex 。在这里就能看到它的作用。拦截器可能是多个的,它是链式调用的过程。比如有5个拦截器。如果在第3个拦截器的preHandler方法返回了false,后两个拦截器的After不应该再被执行。所以在后置拦截方法是从interceptorIndex 开始的。
void triggerAfterCompletion(HttpServletRequest request,
HttpServletResponse response, Exception ex)throws Exception {
for (int i = this.interceptorIndex; i >= 0; i--) {
HandlerInterceptor interceptor = getInterceptors()[i];
try {
interceptor.afterCompletion(request, response, this.handler, ex);
}
}
}
3、总结
以上就是SpringMVC处理一个请求的所有流程。DispatcherServlet其本质就是个Servlet,一切请求经过它来处理。它根据请求的uri找到对应的Method对象,然后从Request中拿到参数解析成我们想要的类型,调用具体方法。通过不同的返回值解析器来确定返回的数据的类型是什么,需不需要响应视图。然后调用RequestDispatcher 直接转发。最后通过HandlerInterceptor可以让我们有机会参与到SpringMVC处理环节中去,在具体方法执行的不同时机加入我们自定义的业务。
网友评论