Spring 源码解析之HandlerAdapter源码解析
Posted 夜宿山寺
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring 源码解析之HandlerAdapter源码解析相关的知识,希望对你有一定的参考价值。
Spring 源码解析之HandlerAdapter源码解析(三)
前言
这篇文章主要是解决上篇遗留的问题,主要是因为内容比较多
Spring 源码解析之HandlerAdapter源码解析(二)遗留问题
1. WebAsyncManager 和AsyncWebRequest 这些都是异步请求的管理?
先来看看使用的方式上有什么不同
@RequestMapping("/call")
@ResponseBody
public WebAsyncTask<String> asyncCall()
//借助mvcTaskExecutor在另外一个线程调用
//此时Servlet容器线程已经释放,可以处理其他的请求
Callable<String> callable = new Callable<String>()
@Override
public String call() throws Exception
Thread.sleep(5000);
return "Callable result";
;
logger.debug("asyncCall()");
return new WebAsyncTask<String>(5500, callable);//允许指定timeout时间
上面代码适合代码消耗时间长的业务处理,一开始看到这种代码的时候我也比较懵懂,大致想了这种适合这样的场景,可能是一些需要超时的情况下需要这样的场景,防止调用时间过长把系统的线程给耗尽。流程上并没有什么好讲的,这里就不在特殊去详细讲了,大家有时间可以看看
WebAsyncManager
其实逻辑差不多,只不过执行controller方法返回的时候把WebAsyncTask对象到这里去执行了而已。
2. Spring是如何知道请求对应Controller的方法的?
这个主要分两种情况处理,一种是静态请求,一种是动态请求。
2.1 静态文件处理
按照下面代码逻辑,咱们先讲静态文件处理流程,现在有一个静态请求
/static/bootstrap_module/css/bootstrap.min.css
,首先来看DispatcherServlet中的mappedHandler = getHandler(processedRequest);
方法,这里会按照顺序便利所有的HandlerMapping,通过request去查找到相应的HandlerExecutionChain
,这里流程可以看文章《Spring 源码解析之HandlerAdapter源码解析(二)》,HandlerExecutionChain
包含了处理请求的handler,按照静态文件请求,这里处理静态文件请求的HandlerMapping
是org.springframework.web.servlet.handler.SimpleUrlHandlerMapping
,得到HandlerExecutionChain
是包含了org.springframework.web.servlet.resource.ResourceHttpRequestHandler
,的代码如下所示:
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception
for (HandlerMapping hm : this.handlerMappings)
if (logger.isTraceEnabled())
logger.trace(
"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null)
return handler;
return null;
拿到
HandlerExecutionChain
后,会继续通过HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
去获取相应处理的HandlerAdapter,通过supports方法来判断是哪个HandlerAdapter
第一个支持,根据上面的静态请求来说这里得到的Adapter是org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter
具体代码如下所示:
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException
for (HandlerAdapter ha : this.handlerAdapters)
if (logger.isTraceEnabled())
logger.trace("Testing handler adapter [" + ha + "]");
if (ha.supports(handler))
return ha;
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
当通过路径找到了所有的处理类时,这个时候代码执行到了
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
ha是HttpRequestHandlerAdapter
这里的mappedHandler.getHandler()是ResourceHttpRequestHandler
,具体调用方法实现逻辑如下代码所示:
//HttpRequestHandlerAdapter 中 handle方法
@Override
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception
((HttpRequestHandler) handler).handleRequest(request, response);
return null;
//ResourceHttpRequestHandler 中handleRequest
@Override
public void handleRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
// Supported methods and required session
//校验请求支持的方法和session需要情况
checkRequest(request);
// Check whether a matching resource exists
//获取到resource
Resource resource = getResource(request);
//如果没有获取到就404
if (resource == null)
logger.trace("No matching resource found - returning 404");
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
// Header phase
if (new ServletWebRequest(request, response).checkNotModified(resource.lastModified()))
logger.trace("Resource not modified - returning 304");
return;
// Apply cache settings, if any
//设置缓存时间
prepareResponse(response);
// Check the resource's media type
//获取MediaType 比如我这里是css文件那么MediaType就是
MediaType mediaType = getMediaType(resource);
if (mediaType != null)
if (logger.isTraceEnabled())
logger.trace("Determined media type '" + mediaType + "' for " + resource);
else
if (logger.isTraceEnabled())
logger.trace("No media type found for " + resource + " - not sending a content-type header");
// Content phase
if (METHOD_HEAD.equals(request.getMethod()))
setHeaders(response, resource, mediaType);
logger.trace("HEAD request - skipping content");
return;
if (request.getHeader(HttpHeaders.RANGE) == null)
//设置头部
setHeaders(response, resource, mediaType);
//把图片写到response里面
writeContent(response, resource);
else
writePartialContent(request, response, resource, mediaType);
上面代码是spring处理静态文件的机制,先从技术上来说
writeContent(response, resource)
这里是直接把流写到了response里面,而且每次都是从硬盘去加载文件,spring默认是使用PathResourceResolver
去加载文件的,以前在使用spring的做项目的时候,jvm经常内存溢出,基本上每刷两到三次页面都会young gc,oldgc也会经常发生,通过这里就能够解释清楚了,Spring本身也有解决方案,Spring本身也提供了CachingResourceResolver
,但是总的来说最好还是把静态文件的处理分离出去,这样可以提升jvm的处理速度。
2.2动态请求处理
现在有一个动态请求http://localhost:8080/detail/8,根据上面代码来看,这里动态请求获取到的
HandlerMapping
是org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
,这里获取到的HandlerExecutionChain
是包含了HandlerMethod
,HandlerMethod
里面包含了Controller class和对应处理/detail/8
请求的Method.这个就是大概的逻辑,详细获取HandlerMethod
的流程可以看看这篇文章《Spring 源码解析之HandlerAdapter源码解析(二)》。
2.3问题总结
说到这里可能需要一个大致的流程图,说实话看了上面自己写的文字描述,感觉确实不太好理解,可以通过简单的流程图分别出两种请求的不同处理方式,具体流程如下图所示:
3. Spring模板的渲染机制?
这块逻辑是问题2的续篇,问题2和《Spring 源码解析之HandlerAdapter源码解析(二)》主要讲的是调用机制,但是调用完之后,是需要返回界面或者json数据,下面代码就是DispatcherServlet中处理这块逻辑的方法
/**
* Handle the result of handler selection and handler invocation, which is
* either a ModelAndView or an Exception to be resolved to a ModelAndView.
*/
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception
boolean errorView = false;
//判断是否有是异常view
if (exception != null)
if (exception instanceof ModelAndViewDefiningException)
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
else
//调用处理异常Handler
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
// Did the handler return a view to render?
if (mv != null && !mv.wasCleared())
//实际渲染view的地方
render(mv, request, response);
if (errorView)
WebUtils.clearErrorRequestAttributes(request);
else
if (logger.isDebugEnabled())
logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
"': assuming HandlerAdapter completed request handling");
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted())
// Concurrent handling started during a forward
return;
if (mappedHandler != null)
mappedHandler.triggerAfterCompletion(request, response, null);
protected View resolveViewName(String viewName, Map<String, Object> model, Locale locale,
HttpServletRequest request) throws Exception
for (ViewResolver viewResolver : this.viewResolvers)
//遍历所有的viewResolver 直到找到可以处理的View
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null)
return view;
return null;
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception
// Determine locale for request and apply it to the response.
//获取Locale对象
Locale locale = this.localeResolver.resolveLocale(request);
response.setLocale(locale);
View view;
//判断view是否是string
if (mv.isReference())
// We need to resolve the view name.
//查找到对应的ViewResolver 去渲染view
view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
if (view == null)
throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
"' in servlet with name '" + getServletName() + "'");
else
//直接获取到对应的view
// No need to lookup: the ModelAndView object contains the actual View object.
view = mv.getView();
if (view == null)
throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
"View object in servlet with name '" + getServletName() + "'");
// Delegate to the View object for rendering.
if (logger.isDebugEnabled())
logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'");
try
// 直接渲染数据
view.render(mv.getModelInternal(), request, response);
catch (Exception ex)
if (logger.isDebugEnabled())
logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" +
getServletName() + "'", ex);
throw ex;
来看看ViewResolver初始化的地方,按照这块逻辑来说,这里是获取所有ViewResolver的子类,所以Spring可以使用多种ViewResolver,只要定义了
FreeMarkerViewResolver
,InternalResourceViewResolver
等多种都会被使用,而且可以通过设置<property name="order" value="1"/>
这个属性决定使用的顺序。
private void initViewResolvers(ApplicationContext context)
this.viewResolvers = null;
if (this.detectAllViewResolvers)
// Find all ViewResolvers in the ApplicationContext, including ancestor contexts.
Map<String, ViewResolver> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ViewResolver.class, true, false);
if (!matchingBeans.isEmpty())
this.viewResolvers = new ArrayList<ViewResolver>(matchingBeans.values());
// We keep ViewResolvers in sorted order.
AnnotationAwareOrderComparator.sort(this.viewResolvers);
else
try
ViewResolver vr = context.getBean(VIEW_RESOLVER_BEAN_NAME, ViewResolver.class);
this.viewResolvers = Collections.singletonList(vr);
catch (NoSuchBeanDefinitionException ex)
// Ignore, we'll add a default ViewResolver later.
// Ensure we have at least one ViewResolver, by registering
// a default ViewResolver if no other resolvers are found.
if (this.viewResolvers == null)
this.viewResolvers = getDefaultStrategies(context, ViewResolver.class);
if (logger.isDebugEnabled())
logger.debug("No ViewResolvers found in servlet '" + getServletName() + "': using default");
上面就是大概的流程
总结
这一篇保留两个问题:
1. 如何根据viewname找到合适的ViewResolver,这里面有什么优化逻辑。
2.view.render(mv.getModelInternal(), request, response);
这部进行渲染的时候有什么不同(先漏出一些基础,jstlview委托tomcat进行渲染,也就是使用Servlet+jsp那种模式,FreeMarkerView 则使用response输出流对接FreeMarker的模板处理)
以上是关于Spring 源码解析之HandlerAdapter源码解析的主要内容,如果未能解决你的问题,请参考以下文章
Spring 源码解析之HandlerAdapter源码解析
Spring 源码解析之HandlerAdapter源码解析