Spring 源码解析之HandlerAdapter源码解析(三)
来源:互联网 发布:淘宝卖家开通运费险 编辑:程序博客网 时间:2024/05/29 12:21
前言
这篇文章主要是解决上篇遗留的问题,主要是因为内容比较多
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源码解析(三)
- Spring 源码解析之HandlerAdapter源码解析(二)
- Spring 源码解析之HandlerAdapter源码解析(二)
- Spring 源码解析之HandlerAdapter源码解析(二)
- SpringMVC源码解析- HandlerAdapter初始化
- Spring源码之Resource加载源码解析(三)
- SpringMVC之HandlerAdapter解析
- SpringMVC之HandlerAdapter解析
- Spring源码解析之初始化
- Spring源码解析之DefaultListableBeanFactory
- Spring源码之XML解析
- 源码解析之路--spring解析(一)
- 【JDk源码解析之三】HashMap源码解析
- Spring源码解析之-Aop源码解析(2)
- Spring 源码解析之HandlerMapping源码解析(一)
- Spring 源码解析之ViewResolver源码解析(四)
- codeforces 894 A题 QAQ -mystical_curve
- WKWebView遇上UIScrollView,动态获取WKWebView的高度
- Python基础入门之函数二
- mysql 操作数据
- 数列求和-加强版(20 分) 给定某数字A(1≤A≤9)以及非负整数N(0≤N≤100000),求数列之和S=A+AA+AAA+⋯+AA⋯A(N个A)。例如A=1, N=3时,S=1+11+111=
- Spring 源码解析之HandlerAdapter源码解析(三)
- java 单列集合list及其子实现类
- @SessionAttributes(value="user")
- c++ 抽象基类、虚函数、纯虚函数 java抽象类、接口、android生命周期方法异同
- c++中函数前加~
- mysql explain执行计划详解
- 2016-nature-Natural speech reveals the semantic maps that tile human cerebral cortex
- PAT 1008. 数组元素循环右移问题 (20)
- jQuery的ajax表单提交,获取元素内容方法