深入分析Spring MVC

Spring MVC基本概念

顾名思义,需要了解m、v、c三个部分的概念,是熟悉和分析springmvc处理过程的首要条件。

M部分

M指的是model,也就是模型。在springmvc中的职责就是在后端进行页面的最后渲染时提供页面所需要的数据,这里所说的数据不仅仅是后端返回的业务数据,还包括模板引擎中的内置变量和工具类可以总结如下。

  • spring框架中的model(业务数据)
    • Model
    • ModelAndView中的Model
    • ModelMap
  • JSP模板引擎
    • JSP内置的九大对象
  • Thymeleaf模板引擎

    • strings
    • numbers
    • context上下文

    ……
    不同的模板引擎肯能内置不同变量,但是基本上model涉及的概念都是和以上的类似。

V部分

V部分指的是view,不能简单理解为html页面或者是jsp,应该是springmvc中,对view进行处理的一个完整的处理链 ,包括代表视图的视图对象,视图处理渲染等涉及对象。可以总结如下。

  • 一个视图抽象为后端的View实例对象,来进行处理。view对象具有渲染页面功能。

    • spring MVC中:InternalResourceView 和 RedirectView,一个表示转发请求一个表示重定向请求
    • Thymeleaf中:ThymeleafView
  • 构建抽象的View的对象

    • jsp中:InternalResourceViewResolver
    • Thymeleaf中:ThymeleafViewResolver

V部分处理过程在下面源码分析处讨论。

C部分

这是简单的部分,C指的是总控制器,就是我们熟悉的DispatcherServlet,所有的请求处理都需要经过这里。

依据源码分析springMVC工作过程

以Thymeleaf模板的mvc过程分析为例,基本上处理大同小异,基于spring-webmvc4.3.2源码进行分析。

在了解spring mvc对于请求处理的过程,需要先了解一些基本的请求处理过程中使用到的类以及其职能。

  • *HandlerMapping:主要负责处理映射request请求对应的HandlerMethod或者Handler。

  • 处理器(HandlerMethod和Object[Handler]):HandlerMethod一个聚合对象,主要属性包括被@Controller注解修饰的实例bean和这个bean内@RequestMapping修饰的方法对象,最终会通过反射方式调用这个方法;Object[Handler]是一个bean的name,最终会获取到这个bean然后利用这个bean做相应操作。这里处理器都是伴随对应的HandlerMapping一起的,在对应HandlerMapping中进行初始化,一个*HandlerMapping有多个处理器,*HandlerMapping通过请求的url映射为对应处理器做相应操作。

    处理器集合例如以下的

    • org.springframework.web.servlet.handler.AbstractUrlHandlerMapping#handlerMap
    • org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#mappingRegistry
  • *HandlerInterceptor:在通过反射执行HandlerMethod的方法之前,需要执行我们或者系统定义的HandlerInterceptor集合中每个HandlerInterceptor需要执行的org.springframework.web.servlet.HandlerInterceptor#preHandle方法;在反射调用执行结束时需要调用每个HandlerInterceptor的org.springframework.web.servlet.HandlerInterceptor#postHandle方法;

  • HandlerExecutionChain:拦截器(HandlerInterceptor)调用链持有对象,同时也持有处理方法(HandlerMethod),主要用于调度反射前调用HandlerInterceptor集合的org.springframework.web.servlet.HandlerInterceptor#preHandle和反射执行方法后的org.springframework.web.servlet.HandlerInterceptor#postHandle方法的调用。

  • *View:视图对象,有渲染最终视图的功能,不同的模板引擎有不同的View,例如ThymeleafView,InternalResourceView

  • *ViewResolver:视图对象构建器,同样对应不同模板引擎会有对应实现,例如InternalResourceViewResolver,ThymeleafViewResolver。

下面看源码分析。

DispatcherServlet

DispatcherServlet是spring mvc的总控制器,从接受请求到视图渲染的调度工作统一负责。请求处理的源码入口就是doDispatch方法。这里也是整个spring mvc处理过程的骨架方法。摘取部分重要代码显示如下。

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;

        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

        try {
            ModelAndView mv = null;
            Exception dispatchException = null;

            try {
                processedRequest = checkMultipart(request);
                multipartRequestParsed = (processedRequest != request);

                // Determine handler for the current request.
                //1.映射request对应的处理器(HandlerMethod或者Handler),并且包装到HandlerExecutionChain中
                mappedHandler = getHandler(processedRequest);
                if (mappedHandler == null || mappedHandler.getHandler() == null) {
                    noHandlerFound(processedRequest, response);
                    return;
                }

                // Determine handler adapter for the current request.
                //2.获取最佳匹配的HandlerInterceptor
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

                // Process last-modified header, if supported by the handler.
                String method = request.getMethod();
                boolean isGet = "GET".equals(method);
                if (isGet || "HEAD".equals(method)) {
                    long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                    if (logger.isDebugEnabled()) {
                        logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
                    }
                    if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                        return;
                    }
                }
                //3.执行调用反射前HandlerInterceptor集合中每个元素的preHandle
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }

                // Actually invoke the handler.
                //4.反射执行处理
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }
                //5.设置viewName
                applyDefaultViewName(processedRequest, mv);
                //6.执行调用反射后HandlerInterceptor集合中每个元素的postHandle
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            }
            //......
            //7.渲染视图view,并输出
            processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
        }
        //......
        }
    }

下面针对骨架方法,逐个步骤分析。

1.映射request对应的处理器(HandlerMethod或者Handler),并且包装到HandlerExecutionChain中

先看这个方法的基本代码,org.springframework.web.servlet.DispatcherServlet#getHandler

    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;
    }

基本就是在DispatcherServlet的HandlerMapping列表中选择一个最佳匹配的HandlerMapping来映射url对应的处理器,这个处理器可能是HandlerMethod,也可能是Handler。获得处理器之后把处理器包装进HandlerExecutionChain。 那么由以下几个问题。
1. 怎么决定使用哪个HandlerMapping?
2. HandlerMapping怎么映射为HandlerMethod或者Handler?

1.1怎么决定使用哪个HandlerMapping

首先查看DispatcherServlet中持有的HandlerMapping列表到底是如何实例化的?查找发现是在org.springframework.web.servlet.DispatcherServlet#initHandlerMappings这个方法里面进行初始化的,关键代码如下。

private void initHandlerMappings(ApplicationContext context) {
        this.handlerMappings = null;

        if (this.detectAllHandlerMappings) {
            // Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
            Map<String, HandlerMapping> matchingBeans =
                    BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
            if (!matchingBeans.isEmpty()) {
                this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
                // We keep HandlerMappings in sorted order.
                AnnotationAwareOrderComparator.sort(this.handlerMappings);
            }
        }
        else {
            try {
                HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
                this.handlerMappings = Collections.singletonList(hm);
            }
            catch (NoSuchBeanDefinitionException ex) {
                // Ignore, we'll add a default HandlerMapping later.
            }
        }
        if (this.handlerMappings == null) {
            this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
        }
    }

大致含义就是在context中查找到所有的HandlerMapping.class的bean,然后添加到DispatchServlet中的HandlerMapping列表中。这个过程是在DispatcherServlet初始化时完成。
然后怎么决定使用哪一个HandlerMapping?其实每一个都可能,在org.springframework.web.servlet.DispatcherServlet#getHandler中可以看到其实是循环方式调用AbstractHandlerMapping#getHandler方法尝试匹配一个处理器,如果匹配出来就进行返回的,所以其实每个HandlerMapping都可能调用一下。
默认情况这个HandlerMapping列表有以下几个HandlerMapping:
这里写图片描述

在SpringMVC通常都是RequestMappingHandlerMapping来映射调用Controller的@RequestMapping修饰的方法。以下面的一个请求为例。

@Controller
public class ThymeleafController {
    @RequestMapping("/")
    public String indexPage(){
        return "index";
    }
}

请求这个链接时RequestMappingHandlerMapping就能匹配出一个处理器来。至于怎么匹配出来的?请看下面。

1.2. HandlerMapping怎么映射为HandlerMethod或者Handler?

在上面知道是循环调用HandlerMapping的getHandler方法来进行匹配处理器的,进一步跟踪其实是调用org.springframework.web.servlet.handler.AbstractHandlerMapping#getHandler方法,代码如下。

    public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    //映射出HandlerMethod或者Handler?
        Object handler = getHandlerInternal(request);
        if (handler == null) {
            handler = getDefaultHandler();
        }
        if (handler == null) {
            return null;
        }
        // Bean name or resolved handler?
        if (handler instanceof String) {
            String handlerName = (String) handler;
            handler = getApplicationContext().getBean(handlerName);
        }
        //构造HandlerExecutionChain
        HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
        if (CorsUtils.isCorsRequest(request)) {
            CorsConfiguration globalConfig = this.corsConfigSource.getCorsConfiguration(request);
            CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
            CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
            executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
        }
        return executionChain;
    }

所以关键方法是getHandlerInternal,跟踪发现有以下两个实现,分别对应返回Handler和HandlerMethod。

  • org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#getHandlerInternal
  • org.springframework.web.servlet.handler.AbstractUrlHandlerMapping#getHandlerInternal

上面两个对应的继承子类分别是。

  • RequestMappingHandlerMapping
  • SimpleUrlHandlerMapping

正好对应两种不同映射处理器结果,大多数是这两种情况。

先看映射出HandlerMethod的org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#getHandlerInternal方法,代码如下。

    protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
       //获得request请求路径
        String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
        //......
        this.mappingRegistry.acquireReadLock();
        try {
        //根据请求路径映射HandlerMethod
            HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
            //......
            return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
        }
        //......
    }

继续看lookupHandlerMethod,代码如下。

    protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
        List<Match> matches = new ArrayList<Match>();
        //根据request请求url匹配对应的处理器
        List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
        if (directPathMatches != null) {
            addMatchingMappings(directPathMatches, matches, request);
        }
        if (matches.isEmpty()) {
            // No choice but to go through all mappings...
            addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
        }
        if (!matches.isEmpty()) {
            Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
            Collections.sort(matches, comparator);
            //......
            Match bestMatch = matches.get(0);
            if (matches.size() > 1) {
                if (CorsUtils.isPreFlightRequest(request)) {
                    return PREFLIGHT_AMBIGUOUS_MATCH;
                }
                Match secondBestMatch = matches.get(1);
                if (comparator.compare(bestMatch, secondBestMatch) == 0) {
                    Method m1 = bestMatch.handlerMethod.getMethod();
                    Method m2 = secondBestMatch.handlerMethod.getMethod();
                    //匹配多个,报错
                    throw new IllegalStateException("Ambiguous handler methods mapped for HTTP path '" +
                            request.getRequestURL() + "': {" + m1 + ", " + m2 + "}");
                }
            }
            handleMatch(bestMatch.mapping, lookupPath, request);
            //返回最佳匹配
            return bestMatch.handlerMethod;
        }
        //......
    }

上面的主要过程就是根据request的请求url去mappingRegistry中获取匹配的HandlerMethod,类似于key-val形式去匹配,最后返回经过判断获取最佳的匹配返回。

同样地,其实org.springframework.web.servlet.handler.AbstractUrlHandlerMapping#getHandlerInternal返回Handler的匹配方式也是一样的,不过这里的Handler就是一个Bean或者bean的StringName。

现在有个问题就是mappingRegistry里面维护的HandlerMethod或者Handler都是怎么初始化的?或者说是从哪里来的?

1.3. mappingRegistry的初始化(Handlermethod和Handler)

可以先跟踪HandlerMethod的mappingRegistry初始化过程。

直接在AcstrackMethodHandlerMapping搜索看有没有初始化代码,有,在org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.MappingRegistry#register中,代码如下。

public void register(T mapping, Object handler, Method method) {
            this.readWriteLock.writeLock().lock();
            try {
                //利用handler和method构建一个HandlerMethod对象
                HandlerMethod handlerMethod = createHandlerMethod(handler, method);
                assertUniqueMethodMapping(handlerMethod, mapping);

                if (logger.isInfoEnabled()) {
                    logger.info("Mapped \"" + mapping + "\" onto " + handlerMethod);
                }
                this.mappingLookup.put(mapping, handlerMethod);

                List<String> directUrls = getDirectUrls(mapping);
                for (String url : directUrls) {
                    this.urlLookup.add(url, mapping);
                }

                String name = null;
                if (getNamingStrategy() != null) {
                    name = getNamingStrategy().getName(handlerMethod, mapping);
                    addMappingName(name, handlerMethod);
                }

                CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
                if (corsConfig != null) {
                    this.corsLookup.put(handlerMethod, corsConfig);
                }
                //利用传入的mapping作为key存储Handlermethod
                this.registry.put(mapping, new MappingRegistration<T>(mapping, handlerMethod, directUrls, name));
            }
            finally {
                this.readWriteLock.writeLock().unlock();
            }
        }

可以看到这里利用handler和method构建一个HandlerMethod对象,然后利用传入的mapping作为key存储Handlermethod。我们可以推测传入的mapping是基于@RequestMapping配置的url构造的;而handler则是Controller对应的bean;method则是对应需要调用的方法。为了验证推测还需要继续往上层调用链跟踪代码。

  • AbstractHandlerMethodMapping.MappingRegistry#register
    • AbstractHandlerMethodMapping#registerHandlerMethod
    • AbstractHandlerMethodMapping#detectHandlerMethods
      • AbstractHandlerMethodMapping#initHandlerMethods
      • AbstractHandlerMethodMapping#afterPropertiesSet

来到detectHandlerMethods,代码如下。

    protected void detectHandlerMethods(final Object handler) {
        //handler推测是Controller对象实例
        Class<?> handlerType = (handler instanceof String ?
                getApplicationContext().getType((String) handler) : handler.getClass());
        final Class<?> userType = ClassUtils.getUserClass(handlerType);
        //获取被@RequestMapping修饰的方法
        Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
                new MethodIntrospector.MetadataLookup<T>() {
                    @Override
                    public T inspect(Method method) {
                        try {
                            return getMappingForMethod(method, userType);
                        }
                        catch (Throwable ex) {
                            throw new IllegalStateException("Invalid mapping on handler class [" +
                                    userType.getName() + "]: " + method, ex);
                        }
                    }
                });

        if (logger.isDebugEnabled()) {
            logger.debug(methods.size() + " request handler methods found on " + userType + ": " + methods);
        }
        //遍历Method对象,注册初始化HandlerMethod
        for (Map.Entry<Method, T> entry : methods.entrySet()) {
            Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType);
            T mapping = entry.getValue();
            registerHandlerMethod(handler, invocableMethod, mapping);
        }
    }

再往上initHandlerMethods,代码如下。

    protected void initHandlerMethods() {
        if (logger.isDebugEnabled()) {
            logger.debug("Looking for request mappings in application context: " + getApplicationContext());
        }
        String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
                BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
                getApplicationContext().getBeanNamesForType(Object.class));
        //取出上下文所有的bean
        for (String beanName : beanNames) {
            if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
                Class<?> beanType = null;
                try {
                    beanType = getApplicationContext().getType(beanName);
                }
                catch (Throwable ex) {
                    // An unresolvable bean type, probably from a lazy bean - let's ignore it.
                    if (logger.isDebugEnabled()) {
                        logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex);
                    }
                }
                //判断bean是不是handler
                if (beanType != null && isHandler(beanType)) {
                    detectHandlerMethods(beanName);
                }
            }
        }
        handlerMethodsInitialized(getHandlerMethods());
    }

主要逻辑就和注释写的一样,所以我们只需要再看isHandler方法逻辑就可以知道到底哪些bean会注册HandlerMethod,isHandler代码如下。

    protected boolean isHandler(Class<?> beanType) {
        return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
                AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
    }

根据上面的代码结果很明显,就是Controller注解修饰或者RequestMapping修饰的bean就会进行HandlerMethod注册的过程。符合我们了解的点。

最后可以看到整个HandlerMethod初始化的过程起点是AbstractHandlerMethodMapping#afterPropertiesSet,也就是熟知在bean初始化完成后调用的方法里面开始执行HandlerMethod初始化过程。

这里完全可以用相同的方式跟踪Handler处理器的注册过程,基本大同小异。Handler判断是否需要注册到Map的逻辑是这个Handler在spring 容器里面name属性是否是以”/”开始的,如果是,那么就是可注册,以key-val形式存储,key同样是请求url,val是beanName,请求到达时就可以获取到这个bean实例作为Handler处理器,当然还需要搭配对应的HandlerAdapter才能正常工作。

1.4. 构造HandlerExecutionChain

获得处理器HandlerMethod之后就要构造一个HandlerExecutionChain对象返回给DispatcherServlet了,代码如下。

    protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
        HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
                (HandlerExecutionChain) handler : new HandlerExecutionChain(handler));

        String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
        for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
            if (interceptor instanceof MappedInterceptor) {
                MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
                if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
                    chain.addInterceptor(mappedInterceptor.getInterceptor());
                }
            }
            else {
                chain.addInterceptor(interceptor);
            }
        }
        return chain;
    }

非常简单,就是把DispatchServlet持有的HandlerInterceptor加入到HandlerExecutionChain持有的HandlerInterceptor集合里面就行了。那么这里的DispatcherServlet持有的HandlerInterceptor集合是哪里来的?跟踪调用处,可以看到初始化代码如下。

    protected void initApplicationContext() throws BeansException {
        extendInterceptors(this.interceptors);
        detectMappedInterceptors(this.adaptedInterceptors);
        initInterceptors();
    }

    protected void detectMappedInterceptors(List<HandlerInterceptor> mappedInterceptors) {
        mappedInterceptors.addAll(
                BeanFactoryUtils.beansOfTypeIncludingAncestors(
                        getApplicationContext(), MappedInterceptor.class, true, false).values());
    }

基本上就是获得了MappedInterceptor以及MappedInterceptor实现的接口的HandlerInterceptor所有的实现类bean加入到列表中。

2.获取最佳匹配的HandlerInterceptor

如何获取映射获得的处理器的最佳HandlerInterceptor?先看代码如下。

    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");
    }

跟HandlerMapping一样,遍历DispatcherServlet#handlerAdapters,看哪个合适就用哪个,但是这里唯一不一样的是HandlerInterceptor的实现类都实现了Ordered接口,也就是说DispatcherServlet#handlerAdapters想必是有序的,分优先级。可以看一下DispatcherServlet#handlerAdapters初始化代码。

    private void initHandlerAdapters(ApplicationContext context) {
        this.handlerAdapters = null;
        if (this.detectAllHandlerAdapters) {
            // Find all HandlerAdapters in the ApplicationContext, including ancestor contexts.
            //找出上下文中所有实现HandlerAdapter接口的类的bean然后加入到DispatcherServlet#handlerAdapters列表中
            Map<String, HandlerAdapter> matchingBeans =
                    BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
            if (!matchingBeans.isEmpty()) {
                this.handlerAdapters = new ArrayList<HandlerAdapter>(matchingBeans.values());
                // We keep HandlerAdapters in sorted order.
                //但是,需要经过依次排序
                AnnotationAwareOrderComparator.sort(this.handlerAdapters);
            }
        }
        else {
            try {
                HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);
                this.handlerAdapters = Collections.singletonList(ha);
            }
            catch (NoSuchBeanDefinitionException ex) {
                // Ignore, we'll add a default HandlerAdapter later.
            }
        }
        //.......
    }
abstract class AbstractHandlerMethodAdapter extends WebContentGenerator implements HandlerAdapter, Ordered 

所以结论是,ordered优先级高的且适配的HandlerAdapter优先使用。

然后看一下怎么判断是否适配的?org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter#supports的实现如下

    public final boolean supports(Object handler) {
        return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
    }

接着是org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#supportsInternal的实现如下。

    protected boolean supportsInternal(HandlerMethod handlerMethod) {
        return true;
    }

所以RequestMappingHandlerAdapter适用于所有处理器是HandlerMethod的情况。同样可以判断处理器是Handler的情况是使用哪个适配器。或者也许我们可以扩展自定义的适配器。

3.执行调用反射前HandlerInterceptor集合中每个元素的preHandle

这一步也是非常简单,代码如下。

    boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HandlerInterceptor[] interceptors = getInterceptors();
        if (!ObjectUtils.isEmpty(interceptors)) {
            for (int i = 0; i < interceptors.length; i++) {
                HandlerInterceptor interceptor = interceptors[i];
                if (!interceptor.preHandle(request, response, this.handler)) {
                    triggerAfterCompletion(request, response, null);
                    return false;
                }
                this.interceptorIndex = i;
            }
        }
        return true;
    }

值得注意的是这里的编码方式并不是意义上的责任链模式,这里的链式调用依赖外部的循环调用,同时终止是由元素的返回值决定。

4.反射执行处理

这里的反射执行处理指的是处理器是Handlermethod类型,适配器是RequestMappingHandlerAdapter的。代码如下。AbstractHandlerMethodAdapter#handle->RequestMappingHandlerAdapter#handleInternal,

    protected ModelAndView handleInternal(HttpServletRequest request,
            HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

        ModelAndView mav;
        checkRequest(request);

        // Execute invokeHandlerMethod in synchronized block if required.
        if (this.synchronizeOnSession) {
            HttpSession session = request.getSession(false);
            if (session != null) {
                Object mutex = WebUtils.getSessionMutex(session);
                synchronized (mutex) {
                    //反射调用方法,返回ModelAndView,具体不细看
                    mav = invokeHandlerMethod(request, response, handlerMethod);
                }
            }
            else {
                // No HttpSession available -> no mutex necessary
                mav = invokeHandlerMethod(request, response, handlerMethod);
            }
        }
        else {
            // No synchronization on session demanded at all...
            mav = invokeHandlerMethod(request, response, handlerMethod);
        }

        if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
            if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
                applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
            }
            else {
                prepareResponse(response);
            }
        }

        return mav;
    }

5.设置viewName

    private void applyDefaultViewName(HttpServletRequest request, ModelAndView mv) throws Exception {
        if (mv != null && !mv.hasView()) {
            mv.setViewName(getDefaultViewName(request));
        }
    }

    protected String getDefaultViewName(HttpServletRequest request) throws Exception {
        return this.viewNameTranslator.getViewName(request);
    }

    public String org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator#getViewName(HttpServletRequest request) {
        String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
        //前后缀拼接视图名称,后续用于寻址模板资源(html)
        return (this.prefix + transformPath(lookupPath) + this.suffix);
    }

6.执行调用反射后HandlerInterceptor集合中每个元素的postHandle

void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {
   HandlerInterceptor[] interceptors = getInterceptors();
   if (!ObjectUtils.isEmpty(interceptors)) {
      for (int i = interceptors.length - 1; i >= 0; i--) {
         HandlerInterceptor interceptor = interceptors[i];
         interceptor.postHandle(request, response, this.handler, mv);
      }
   }
}

7.渲染视图view,并输出

  • DispatcherServlet#processDispatchResult
    • DispatcherServlet#render
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
   // Determine locale for request and apply it to the response.
   Locale locale = this.localeResolver.resolveLocale(request);
   response.setLocale(locale);
   View view;
   if (mv.isReference()) {
      // 1.调用ViewResolver#resolveViewName 创建一个新的View对象
      view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
      if (view == null) {
        //........ex
      }
   }
   else {
      // No need to lookup: the ModelAndView object contains the actual View object.
      view = mv.getView();
      if (view == null) {
       //........ex
   }
//......
   try {
      if (mv.getStatus() != null) {
         response.setStatus(mv.getStatus().value());
      }
       //2.利用新产生的view对象渲染视图
      view.render(mv.getModelInternal(), request, response);
   }
   catch (Exception ex) {
     //........
      throw ex;
   }
}

7.1.调用ViewResolver#resolveViewName 创建一个新的View对象

上面是结果视图渲染的基本骨架,进一步先看resolveViewName方法,代码如下。

protected View resolveViewName(String viewName, Map<String, Object> model, Locale locale,
      HttpServletRequest request) throws Exception {

   for (ViewResolver viewResolver : this.viewResolvers) {
      View view = viewResolver.resolveViewName(viewName, locale);
      if (view != null) {
         return view;
      }
   }
   return null;
}

这里DispatcherServlet#viewResolvers的初始化方式跟HandlerInterceptor一模一样,同样根据Ordered排序,viewResolvers是查找所有实现ViewResolver的类的bean加入到列表。然后是具体的ViewResolver调用resolveViewName。

下面这个图的使用Thymeleaf模板时默认的viewResolvers,可以看到优先级最高的是一个叫做ContentNegotiatingViewResolver

这里写图片描述

经过测试,ContentNegotiatingViewResolver#resolveViewName里面实际上也是遍历ContentNegotiatingViewResolver里面持有的ViewResolver#resolveViewName,所有能够适配的ViewResolver都能创建一个view,然后ContentNegotiatingViewResolver选出最佳适配。ContentNegotiatingViewResolver持有是ViewResolver如下所示。主要代码如下。

public View resolveViewName(String viewName, Locale locale) throws Exception {
    RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
    Assert.isInstanceOf(ServletRequestAttributes.class, attrs);
    List<MediaType> requestedMediaTypes = getMediaTypes(((ServletRequestAttributes) attrs).getRequest());
    if (requestedMediaTypes != null) {
        //遍历ViewResolver获取候选View
        List<View> candidateViews = getCandidateViews(viewName, locale, requestedMediaTypes);
        //筛选最佳匹配View
        View bestView = getBestView(candidateViews, requestedMediaTypes, attrs);
        if (bestView != null) {
            return bestView;
        }
    }
    if (this.useNotAcceptableStatusCode) {
        if (logger.isDebugEnabled()) {
            logger.debug("No acceptable view found; returning 406 (Not Acceptable) status code");
        }
        return NOT_ACCEPTABLE_VIEW;
    }
    else {
        logger.debug("No acceptable view found; returning null");
        return null;
    }
}

这里写图片描述

所以说其实如果在ContentNegotiatingViewResolver中都找不到适配的View的话,其实在后面再跑一次viewResolver.resolveViewName(viewName, locale)也是没意义的。

然后就是假如我们的模板引擎是Thymeleaf,那么就会自动配置一个ThymeleafViewResolver,它的resolveViewName方法是来自AbstractCachingViewResolver#resolveViewName。这里的大概逻辑是如果View对应Viewname和locale有缓存对象那么就读取缓存,没有就新建一个ViewObject。

  • AbstractCachingViewResolver#resolveViewName
    • AbstractCachingViewResolver#createView
    • ThymeleafViewResolver#createView(看项目具体使用的模板引擎)
      • ThymeleafViewResolver#loadView(看项目具体使用的模板引擎)

假如使用Thymeleaf,那么就是使用ThymeleafViewResolver#loadView新建一个ThymeleafView.class的实例。假如是jsp,那么就是使用InternalResourceViewResolver#loadView新建一个View,然后经过ContentNegotiatingViewResolver进行赛选匹配最佳的View。匹配方式下次在分析。

7.2.利用新产生的view对象渲染视图

这里假设使用的是Thymeleaf模板,那么调用的是ThymeleafView#render方法,跟踪代码调用链如下。

  • ThymeleafView#render

    • ThymeleafView#renderFragment(主要准备渲染页面所需Model)

    • TemplateEngine#process(String, IContext,IFragmentSpec, Writer)

      • TemplateEngine#process(String, IProcessingContext, IFragmentSpec, Writer)

      • TemplateEngine#process(TemplateProcessingParameters, IFragmentSpec, Writer)

最终TemplateEngine#process(TemplateProcessingParameters, IFragmentSpec, Writer)代码如下

private void process(final TemplateProcessingParameters templateProcessingParameters,
        final IFragmentSpec fragmentSpec, final Writer writer) {

    final String templateName = templateProcessingParameters.getTemplateName();

    //使用ResourceResolver,设置templateResolution中模板文件Resource路径
    final Template template = this.templateRepository.getTemplate(templateProcessingParameters);
    final TemplateResolution templateResolution = template.getTemplateResolution();
    final String templateMode = templateResolution.getTemplateMode(); 

    Document document = template.getDocument();
    //......

    final Arguments arguments = 
            new Arguments(this, 
                    templateProcessingParameters, templateResolution, 
                    this.templateRepository, document);



    if (document != null) {
        document.process(arguments);
    }

    if (logger.isDebugEnabled()) {
        logger.debug("[THYMELEAF][{}] Finished process on template \"{}\" using mode \"{}\"", 
                new Object[] { TemplateEngine.threadIndex(), templateName, templateMode });
    }

    final ITemplateModeHandler templateModeHandler =
            this.configuration.getTemplateModeHandler(templateMode);
    final ITemplateWriter templateWriter = templateModeHandler.getTemplateWriter();

    if (templateWriter == null) {

    }

    try {
        // It depends on the ITemplateWriter implementation to allow nulls or not.
        // Standard writer will simply not write anything for null.
        //渲染模板字符
        templateWriter.write(arguments, writer, document);
    } catch (IOException e) {
        throw new TemplateOutputException("Error during creation of output", e);
    }

    /*
     * Finally, flush the writer in order to make sure that everything has been written to output
     */
    try {
        //输出渲染完成的字符
        writer.flush();
    } catch (final IOException e) {
        throw new TemplateOutputException("An error happened while flushing output writer", e);
    }

}

可以看writer.flush();输出结果如下图。

这里写图片描述

猜你喜欢

转载自blog.csdn.net/qq_20597727/article/details/82292481