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

1.序言

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

2.1 SpringMVC初始化时序图

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.
     * <p>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这个具体实现类,来探究初始化细节

HandlerMapping初始化时序图

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

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

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


    private final List<Object> interceptors = new ArrayList<Object>();

    private final List<HandlerInterceptor> adaptedInterceptors = new ArrayList<HandlerInterceptor>();


   /**
     * 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<String, Object> urlMap注册到 AbstractUrlHandlerMapping抽象类的Map<String, Object> handlerMap中。
直白来讲就是:将urlMap中url对应的handler类名,通过ApplicationContext().getBean(handlerName);实例化出来,同时保存handlerMap中。

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

    private final Map<String, Object> urlMap = new LinkedHashMap<String, Object>();


/**
     * 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<String, Object> urlMap) throws BeansException {
        if (urlMap.isEmpty()) {
            logger.warn("Neither 'urlMap' nor 'mappings' set on SimpleUrlHandlerMapping");
        }
        else {
            for (Map.Entry<String, Object> 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<String, Object> handlerMap = new LinkedHashMap<String, Object>();



   /**
     * 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.
     * <p>A typical implementation:
     * <p>{@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<HandlerAdapter> 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调用逻辑时序图

常有欲以观其徼!!!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容

  • Spring Web MVC Spring Web MVC 是包含在 Spring 框架中的 Web 框架,建立于...
    Hsinwong阅读 22,304评论 1 92
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,596评论 18 139
  • 1.SpringMVC的总体设计 Part1:通过web.xml将SpringMVC集成项目中。项目要使用Spri...
    ___TheOne___阅读 207评论 0 1
  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,178评论 11 349
  • 仍记得高考结束后的那个半黄昏 无比轻松的去见那心心念念的人 我走过的那条路 可能是我的早半生记忆里最美好的路 时隔...
    Raise1206阅读 214评论 0 0