Spring 源码解析之HandlerAdapter
前言
这篇文章主要是解决上篇遗留的问题,主要是因为内容比较多
Spring 源码解析之HandlerAdapter源码解析(二)遗留问题
1. WebAsyncManager 和AsyncWebRequest 这些都是异步请求的管理?
先来看看使用的方式上有什么不同 ```java
@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源码解析(二)》](http://blog.csdn.net/king_is_everyone/article/details/51460735),`HandlerExecutionChain`包含了处理请求的handler,按照静态文件请求,这里处理静态文件请求的`HandlerMapping`是`org.springframework.web.servlet.handler.SimpleUrlHandlerMapping`,得到`HandlerExecutionChain`是包含了`org.springframework.web.servlet.resource.ResourceHttpRequestHandler`,的代码如下所示:
```java
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");
}
}
}
上面就是大概的流程
总结
这一篇保留两个问题:
- 如何根据viewname找到合适的ViewResolver,这里面有什么优化逻辑。
view.render(mv.getModelInternal(), request, response);
这部进行渲染的时候有什么不同(先漏出一些基础,jstlview委托tomcat进行渲染,也就是使用Servlet+jsp那种模式,FreeMarkerView 则使用response输出流对接FreeMarker的模板处理)