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,按照静态文件请求,这里处理静态文件请求的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问题总结

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

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的模板处理)

以上是关于Spring 源码解析之HandlerAdapter源码解析的主要内容,如果未能解决你的问题,请参考以下文章

Spring 源码解析之ViewResolver源码解析

Spring 源码解析之ViewResolver源码解析

Spring 源码解析之HandlerAdapter源码解析

Spring 源码解析之HandlerAdapter源码解析

Spring 源码解析之HandlerAdapter源码解析

Spring 源码解析之HandlerAdapter源码解析