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,按照静态文件请求,这里处理静态文件请求的HandlerMappingorg.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,根据上面代码来看,这里动态请求获取到的HandlerMappingorg.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,这里获取到的HandlerExecutionChain是包含了HandlerMethod,HandlerMethod里面包含了Controller class和对应处理/detail/8请求的Method.这个就是大概的逻辑,详细获取HandlerMethod的流程可以看看这篇文章《Spring 源码解析之HandlerAdapter源码解析(二)》。

2.3问题总结

说到这里可能需要一个大致的流程图,说实话看了上面自己写的文字描述,感觉确实不太好理解,可以通过简单的流程图分别出两种请求的不同处理方式,具体流程如下图所示:

Renderings

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,只要定义了FreeMarkerViewResolverInternalResourceViewResolver等多种都会被使用,而且可以通过设置<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的模板处理)

阅读全文
0 0
原创粉丝点击