《深入分析JavaWeb》---第14章 SpringMVC的时序图

1.序言

上一篇文章深入分析JavaWeb---第14章 SpringMVC的工作机制从思想层面,粗粒度的介绍了SpringMVC的几个核心关键点。这个文章从代码层面,细粒度介绍细节。

2.1 SpringMVC初始化时序图

《深入分析JavaWeb》---第14章 SpringMVC的时序图_第1张图片
SpringMVC初始化时序图

额外说明:
时序1.HttpServlet初始化调用子类HttpServletBean的init方法。这个方法的作用:获取Servlet中的init参数,并创建一个BeanWrapper对象,然后由子类真正执行BeanWrapper的初始化工作。
时序3.Spring容器的创建是在FrameworkServlet的initServletBean()方法中完成的,这个方法会创建WebApplicationContext对象。
时序6.Spring容器在加载时,会调用DispatcherServlet的initStrategies方法完成:SpringMVC框架需要的8个组件。具体细节可见上篇文章.

一句话总结:SpringMVC初始化工作,创建了WebApplicationContext对象。Spring容器创建时调用onApplicationEvent事件,调用onRefresh方法。在onRefresh方法之中,会去调用DispatcherServlet的initStrategies()方法:初始化HandlerMapping、HandlerAdapter、ViewResolver等8个组件。

2.2 HandlerMapping初始化

1.HandlerMapping是如何将URL映射到具体bean上,执行具体业务逻辑代码的?
答:HandlerMapping并没有规定这个URL与应用的处理类如何映射。在HandlerMapping接口中只定义了根据一个URL必须返回一个由HandlerExecutionChain代表的处理链,在这个处理链中可以添加任意的HandlerAdapters实例处理这个URL对应的请求。这种设计思路便是“职责链设计模式”的具体实现。
HandlerMapping中抽象方法,如下所示:

   /**
     * Return a handler and any interceptors for this request. 
     */
    HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;

2.SpringMVC提供的有关HandlerMapping的类结构图

HandlerMapping相关类结构图

SpringMVC提供了一个HandlerMapping的抽象类AbstractHandlerMapping,而1>AbstractHandlerMapping实现了Ordered接口:可以让HandlerMapping通过设置setOrder方法提高初始化优先级;

   /**
     * Specify the order value for this HandlerMapping bean.
     * 

Default value is {@code Integer.MAX_VALUE}, meaning that it's non-ordered. * @see org.springframework.core.Ordered#getOrder() */ public final void setOrder(int order) { this.order = order; }

2>AbstractHandlerMapping同时还继承了WebApplicationObjectSupport类:可以通过重写initApplicationContext方法实现初始化的一些工作。

   /**
     * Initializes the interceptors.
     * @see #extendInterceptors(java.util.List)
     * @see #initInterceptors()
     */
    @Override
    protected void initApplicationContext() throws BeansException {
        extendInterceptors(this.interceptors);
        detectMappedInterceptors(this.adaptedInterceptors);
        initInterceptors();
    }

3.以SimpleUrlHandlerMapping这个具体实现类,来探究初始化细节

《深入分析JavaWeb》---第14章 SpringMVC的时序图_第2张图片
HandlerMapping初始化时序图

额外说明:
时序3:将AbstractHandlerMapping中定义的List interceptors; 通过adaptInterceptor()方法包装成HandlerInterceptor对象保存在List adaptedInterceptors列表中。

扩充:AbstractHandlerMapping中定义的List interceptors; 这个拦截器列表的初始信息来自于,SpringMVC解析配置文件获得。

涉及到的源代码,如下所示:


    private final List interceptors = new ArrayList();

    private final List adaptedInterceptors = new ArrayList();


   /**
     * Initialize the specified interceptors, checking for {@link MappedInterceptor}s and
     * adapting {@link HandlerInterceptor}s and {@link WebRequestInterceptor}s if necessary.
     * @see #setInterceptors
     * @see #adaptInterceptor
     */
    protected void initInterceptors() {
        if (!this.interceptors.isEmpty()) {
            for (int i = 0; i < this.interceptors.size(); i++) {
                Object interceptor = this.interceptors.get(i);
                if (interceptor == null) {
                    throw new IllegalArgumentException("Entry number " + i + " in interceptors array is null");
                }
                this.adaptedInterceptors.add(adaptInterceptor(interceptor));
            }
        }
    }

   /**
     * Adapt the given interceptor object to the HandlerInterceptor interface.
     */
   protected HandlerInterceptor adaptInterceptor(Object interceptor) {
        if (interceptor instanceof HandlerInterceptor) {
            return (HandlerInterceptor) interceptor;
        }
        else if (interceptor instanceof WebRequestInterceptor) {
            return new WebRequestHandlerInterceptorAdapter((WebRequestInterceptor) interceptor);
        }
        else {
            throw new IllegalArgumentException("Interceptor type not supported: " + interceptor.getClass().getName());
        }
    }
 
 

时序5:将SimpleUrlHandlerMapping类中定义的Map urlMap注册到 AbstractUrlHandlerMapping抽象类的Map handlerMap中。
直白来讲就是:将urlMap中url对应的handler类名,通过ApplicationContext().getBean(handlerName);实例化出来,同时保存handlerMap中。

涉及到的源代码,如下所示:
SimpleUrlHandlerMapping中的方法和属性:

    private final Map urlMap = new LinkedHashMap();


/**
     * Register all handlers specified in the URL map for the corresponding paths.
     * @param urlMap Map with URL paths as keys and handler beans or bean names as values
     * @throws BeansException if a handler couldn't be registered
     * @throws IllegalStateException if there is a conflicting handler registered
     */
    protected void registerHandlers(Map urlMap) throws BeansException {
        if (urlMap.isEmpty()) {
            logger.warn("Neither 'urlMap' nor 'mappings' set on SimpleUrlHandlerMapping");
        }
        else {
            for (Map.Entry entry : urlMap.entrySet()) {
                String url = entry.getKey();
                Object handler = entry.getValue();
                // Prepend with slash if not already present.
                if (!url.startsWith("/")) {
                    url = "/" + url;
                }
                // Remove whitespace from handler bean name.
                if (handler instanceof String) {
                    handler = ((String) handler).trim();
                }
                registerHandler(url, handler);  //-------核心点----------
            }
        }
    }

AbstractUrlHandlerMapping中的方法和属性:

   private final Map handlerMap = new LinkedHashMap();



   /**
     * Register the specified handler for the given URL path.
     * @param urlPath the URL the bean should be mapped to
     * @param handler the handler instance or handler bean name String
     * (a bean name will automatically be resolved into the corresponding handler bean)
     * @throws BeansException if the handler couldn't be registered
     * @throws IllegalStateException if there is a conflicting handler registered
     */
    protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
        Assert.notNull(urlPath, "URL path must not be null");
        Assert.notNull(handler, "Handler object must not be null");
        Object resolvedHandler = handler;

        // Eagerly resolve handler if referencing singleton via name.
        if (!this.lazyInitHandlers && handler instanceof String) {
            String handlerName = (String) handler;
            if (getApplicationContext().isSingleton(handlerName)) {
                resolvedHandler = getApplicationContext().getBean(handlerName);
            }
        }

        Object mappedHandler = this.handlerMap.get(urlPath);
        if (mappedHandler != null) {
            if (mappedHandler != resolvedHandler) {
                throw new IllegalStateException(
                        "Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath +
                        "]: There is already " + getHandlerDescription(mappedHandler) + " mapped.");
            }
        }
        else {
            if (urlPath.equals("/")) {
                if (logger.isInfoEnabled()) {
                    logger.info("Root mapping to " + getHandlerDescription(handler));
                }
                setRootHandler(resolvedHandler);
            }
            else if (urlPath.equals("/*")) {
                if (logger.isInfoEnabled()) {
                    logger.info("Default mapping to " + getHandlerDescription(handler));
                }
                setDefaultHandler(resolvedHandler);
            }
            else {
                this.handlerMap.put(urlPath, resolvedHandler); //-------核心点----------
                if (logger.isInfoEnabled()) {
                    logger.info("Mapped URL path [" + urlPath + "] onto " + getHandlerDescription(handler));
                }
            }
        }
    }

一句话总结:HandlerMapping初始化,将所有的interceptors包装成HandlerInterceptor对象,同时保存在adaptedInteceptors列表中;将urlMap集合中url对应的handler实例化出来,同时保存在handlerMap中。

2.3 HandlerAdapter初始化

HandlerMapping可以完成URL与Handler的映射关系。因为SpringMVC首先帮助我们把URL对应到一个Handler,那么这个Handler必定符合某种规则:最常见的办法就是我们所有的Handler都继承某个接口。

1.HanderAdapter接口源代码:
其中handle方法是处理业务逻辑的核心方法。

public interface HandlerAdapter {

    /**
     * Given a handler instance, return whether or not this {@code HandlerAdapter}
     * can support it. Typical HandlerAdapters will base the decision on the handler
     * type. HandlerAdapters will usually only support one handler type each.
     * 

A typical implementation: *

{@code * return (handler instanceof MyHandler); * } * @param handler handler object to check * @return whether or not this object can use the given handler */ boolean supports(Object handler); /** * Use the given handler to handle this request. * The workflow that is required may vary widely. * @param request current HTTP request * @param response current HTTP response * @param handler handler to use. This object must have previously been passed * to the {@code supports} method of this interface, which must have * returned {@code true}. * @throws Exception in case of errors * @return ModelAndView object with the name of the view and the required * model data, or {@code null} if the request has been handled directly */ ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception; /** * Same contract as for HttpServlet's {@code getLastModified} method. * Can simply return -1 if there's no support in the handler class. * @param request current HTTP request * @param handler handler to use * @return the lastModified value for the given handler * @see javax.servlet.http.HttpServlet#getLastModified * @see org.springframework.web.servlet.mvc.LastModified#getLastModified */ long getLastModified(HttpServletRequest request, Object handler); }

2.SpringMVC中提供了如下三个典型简单HandlerAdapter接口实现类:
1》.HttpRequestHandlerAdapter:可以实现HttpRequestHandler接口,所有的Handler可以实现其如下的方法,方法没有返回值

void handleRequest(HttpServlet request, HttpServletResponse response)

2》.SimpleControllerHandlerAdapter:可以实现Controller接口,所有的Handler可以实现如下方法,该方法返回ModelAndView对象,用于后续的模板渲染

public ModelAndView handleRequest(HttpServlet request, HttpServletResponse response, Object handler)

3》.SimpleServletHandlerAdapter:可直接实现Servlet接口,可以将一个Servlet座位一个Handler来处理请求

3.HandlerAdapter初始化 和 匹配URL细节
HandlerAdapter初始化并没有什么特别之处,只是简单地创建一个HandlerAdapter,将这个HandlerAdapter保存在DispatcherServlet的handlerAdapters集合中。

/** List of HandlerAdapters used by this servlet */
    private List handlerAdapters;

匹配URL细节:
当SpringMVC将某个URL,通过HandlerMapping接口的getHander方法,对应到某个HandlerExecutionChain对象。这个HandlerExecutionChain对象包含一个Object handler。Spring会根据这个Object handler在DispatcherServlet的handlerAdapters集合中,通过HandlerAdapter接口中的

boolean supports(Object handler);

方法找到某个具体的handlerAdapter对象。然后调用这个具体的handlerAdapter对象中方法处理请求。

2.4 Control调用逻辑时序图

1.整个SpringMVC的调用,都是从DispatcherServlet的doService方法开始的。
这个doService方法,官方源码方法描述信息是:

     /**
     * Exposes the DispatcherServlet-specific request attributes and delegates to {@link #doDispatch}
     * for the actual dispatching.
     */
    @Override
    protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {}

根据英文备注可知,doService方法做了两项重要工作:
工作1:
Exposes the DispatcherServlet-specific request attributes(暴露Spring容器相关的属性)。即首先将ApplicationContext、localResolver、themeResolver等对象,通过request.setAttribute()方法设置到到request中,以便后面的处理流程使用。
工作2:
and delegates to doDispatch for the actual dispatching.(将真正的处理逻辑代理给doDispatch方法处理)。即:doDispatch这个方法才是主要处理用户请求的地方。

2.处理细节:
Control处理的关键就是在DispatcherServlet的handlerMapping集合中,根据请求的URL通过getHandler方法匹配成功后,将会返回一个处理链HandlerExecutionChain对象。而在这个HandlerExecutionChain对象中将会包含用户自定义的多个HandlerInterceptor对象。在HandlerInterceptor接口中定义的3个方法中:preHandler和postHandler分别在handler执行前和执行后运行, afterCompletion在view渲染完成、DispatcherServlet返回之前执行。这里需要注意的地方是,当preHandler返回false时,当前的请求将在执行完afterCompletion后直接返回,Handler也将不再执行。

3.联想问题:
在HandlerMapping初始化的过程中,有一个很重要的步骤是registerHandlers:注册处理器,实例化出具体的Handler对象。
但是在HandlerAdapter初始化的时候,又会再一次创建HandlerAdapter,同时将这个HandlerAdapter保存在DispatcherServlet的handlerAdapters集合中。
问这两个初始化过程,实例化出来的Handler有什么区别吗?
答:有区别,前者HandlerMapping初始化时,注册的Handler类型是Object;而HandlerAdapter初始化,保存在DispatcherServlet的handlerAdapters集合中的类型是HandlerAdapter

将Handler类型从HandlerExecutionChain中的Object类型,转变为HandlerAdapter类型。需要遍历DispatcherServlet的handlerAdapters集合中的HandlerAdapter,调用boolean supports(Object handler);方法。找寻到第一个匹配的即可。

4.Control调用逻辑时序图:

Control调用逻辑时序图

常有欲以观其徼!!!

你可能感兴趣的:(《深入分析JavaWeb》---第14章 SpringMVC的时序图)