spring mvc工作机制

1. 配置

springMVC的核心是DispatcherServlet,它实现了HttpServlet类,在web.xml中引入配置:

 <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath*:springMVC.xml</param-value>
  </context-param>

  <servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

  </servlet>
  <servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>

Servlet容器tomcat在初始化DispatcherServlet时,会创建一个新的applicationContext(默认是XmlWebApplicationContext)去加载参数 contextConfigLocation指定的spring xml配置文件。

在基于注解的springMVC配置中,需要在spring配置文件中引入如下配置:

   <context:component-scan base-package="..."/>
   <mvc:annotation-driven/>

注解<mvc:annotation-driven/>默认会在spring上下文中引入以下这些bean:

  • RequestMappingHandlerMapping

  • RequestMappingHandlerAdapter
    两者配合处理基于注解的(@Controller@RequestMapping等等)spring mvc使用

    RequestMappingHandlerMapping会读取spring上下文中所有使用 @Controller或者@RequestMapping注解的bean并解析他的方法作为handler

  • BeanNameUrlHandlerMapping
    处理Struct风格的url,即将url作为bean name获取到bean,这种时候一个url请求对应一个bean(即你的controller类),这个时候你的controller类可以实现AbstractController抽象类

    BeanNameUrlHandlerMapping会读取spring上下文中所有名字以'/'开始的bean作为handler

  • SimpleControllerHandlerAdapter
    配合BeanNameUrlHandlerMapping一起使用。

2. SpringMVC工作流程

如下图:

springMVC流程.png
  1. DispatcherServlet作为前端控制器,请求会先经过它,进入到其doDispatch方法中,DispatcherServlet可以有多个HandlerMapping和HandlerAdapter,DispatcherServlet根据按序排列它们。

  2. DispatcherServlet会遍历所有HandlerMapping,并但返回第一个能够处理当前request的HandlerMapping对像,并调用其getHandler方法返回HandlerExecutionChain

  3. HandlerExecutionChain由一个handler和若干HandlerInterceptor组成,其中handler其实就是用户的业务逻辑实现,由创建这个HandlerExecutionChain的HandlerMapping设置,比如RequestMappingHandlerMapping的handler其实就是Contorller的某个映射当前request url的method的封装。

  4. DispatcherServlet调用所有HandlerInterceptor的preHandle处理request

  5. HandlerAdaptor是HandlerExecutionChain的handler的适配器,对于有多个HandlerAdaptor的情况,返回第一个能够适配handler的适配器。

    不同的适配器完成的功能不一样,一般都是在激活handler进入用户业务逻辑前做一些预处理,然后进入用户业务逻辑,最终返回ModelAnView对象。RequestMappingHandlerAdapter这种比较复杂的会处理@PathVariable,@ModelAttribute这个注解完成参数handler的参数的设置,然后调用handler的方法进入用户业务逻辑,

  6. 接下来调用HandlerExecutionChain中所有interceptor的postHandle

  7. 将ModelAndView交给ViewResolver处理

3. HandlerMapping

接口HandlerMapping只有一个接口方法:

HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;

getHandler里一般是判断能不能处理这个request,不能返回null,否则返回HandlerExecutionChain,类成员如下:

class HandlerExecutionChain{
    // handler一般是用户业务逻辑的封装
    private final Object handler;
    // 拦截器
    private HandlerInterceptor[] interceptors;
     ...
    // 上面第2节中步骤 4,它依次调用interceptors中每一个interceptor的preHandle
    // 只要有一个interceptor的preHandle返回false就不再走下去,直接返回false,本次对request处理结束,不再继续第2节中其他后续流程
    boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) 
    // 上面第2节中步骤 6
    void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {
}

-------拦截器HandlerInterceptor----------
public interface HandlerInterceptor {
    // 返回false就不会调用后续拦截器,意味着本次request处理结束
    boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler);
    void postHandle(
            HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
            throws Exception;
    // preHandler返回false,或者出现异常,或者正常返回时调用
    void afterCompletion(
            HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception;

3.1 HandlerMapping的一些实现类

1. RequestMappingHandlerMapping
它是用来处理使用了@Controller或者@RequestMapping注解的用户controller层面的业务逻辑类的。
像下面这样:

@Controller
@RequestMapping("/api/echo")
public class EchoTime {
    @RequestMapping(value = "/currentDate", method = RequestMethod.GET, produces = "application/json")
    public Date currentDate(){
        Date d = new Date();
        System.out.println("in currentDate");
        return d;
    }
}

RequestMappingHandlerMapping获取当前applicationContext中所有使用了@Controller或者@RequestMapping注解的bean,然后解析这些bean中使用了@RequestMapping注解的方法,将这些方法封装成HandlerMethod,然后建立url到这个HandlerMethod的映射。

  • RequestMappingHandlerMapping # getHandler返回的就是包装了HandlerMethod对象HandlerExecutionChain
  • RequestMappingHandlerMapping返回的HandlerExecutionChain中interceptor处理用户在spring的配置文件中显示指定以外,应该默认会包含当前上下文中所有实现了MappedInterceptor的bean

2. BeanNameUrlHandlerMapping
它将url映射到bean。和在RequestMappingHandlerMapping中,一个url映射到一个contorller的方法不同。
同时BeanNameUrlHandlerMapping要求bean name必须是以'/'开始的。BeanNameUrlHandlerMapping默认会加载当前applicationContext中所有bean name以‘/’开始bean,然后以bean name作为url建立到bean的映射。 这种方式中一般要求用户的controller类实现Controller接口,或者AbstractController抽象类。如下所示:

// bean name要以‘/’开始,
@Controller("/test.do")
public class EchoController extends AbstractController {
    protected ModelAndView handleRequestInternal(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws Exception {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("date", "date");
        return modelAndView;
    }
}

BeanNameUrlHandlerMapping返回的HandlerExecutionChain的handler就是匹配url的bean的实例。

3. SimpleUrlHandlerMapping
这是一种很灵活的Handler Mapping,能够直接指定url到bean的映射, 可以在配置文件中指定:

<bean id="echoContorller" class="me.eric.springmvc.controller.EchoController"/>

        <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
            <property name="urlMap">
                <props>
                   <!--将url:/test.do映射到echoController处理-->
                    <prop key="/test.do">echoContorller</prop>
                </props>
            </property>
        </bean>

4 HandlerAdaptor

接口HandlerAdaptor如下:

// 参数handler即HandlerExecutionChain中的handler, supports判断当前adaptor是否适配handler
boolean supports(Object handler);

// 使用handler处理请求
ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;

4.1 HandlerAdaptor实现类

1. RequestMappingHandlerAdapter
它能适配RequestMappingHandlerMapping返回的handler,即MethodHandler的实例(它封装了处理当前请求url的bean以及更加具体method的信息)

RequestMappingHandlerAdapter会处理@PathVariable,@ModelAttribute等注解。

它会先完成使用了@ModelAttribute注解的方法的调用。然后调用url映射的具体方法的调用(调用前完成方法参数的绑定)。

2. SimpleControllerHandlerAdapter
它能够适配BeanNameUrlHandlerMappingSimpleUrlHandlerMapping返回的handler(也就是controller bean的实例)

5. HandlerInterceptor

在HandlerAdaptor # handle前后调用,也就是在用户业务逻辑代码前后提供预处理和后处理的能力。

自定义interceptor需要实现接口HandlerInterceptor,然后在spring的xml文件中配置:

<bean id="myInterceptor" class="me.eric.springmvc.interceptors.MyInterceptor"/>
<mvc:interceptors>
            <mvc:interceptor>
                // 拦截这个地址
                <mvc:mapping path="/test.do"/>
               //不拦截这个地址
                <mvc:exclude-mapping path="/test2.do"
                <ref bean="myInterceptor"/>
            </mvc:interceptor>
</mvc:interceptors>

上面这种写法,自定义拦截器myInterceptor会被包装成MappedInterceptor,看看MappedInterceptor的成员就明白了:

// url匹配这些模式的拦截
private final String[] includePatterns;

// url匹配这些模式的不拦截
private final String[] excludePatterns;

// 真正的拦截器
private final HandlerInterceptor interceptor;

所以如果你不使用<mvc:interceptors>的形式定义拦截器,也可以直接使用MappedInterceptor作为bean,如下:

<bean class="org.springframework.web.servlet.handler.MappedInterceptor">
                <constructor-arg index="0">
                    <array>
                        <value>/test.do</value>
                    </array>
                </constructor-arg>
                <constructor-arg index="1">
                    <ref bean="timerInterceptor"/>
                </constructor-arg>
</bean>

上面不管哪种定义拦截器的方式,都会被RequestMappingHandlerMapping,BeanNameUrlHandlerMapping,SimpleUrlHandlerMapping加载。

要想单独只被某个HandlerMapping加载,应该做如下配置:

<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping">
       <property name="interceptors">
           <list>
                 <ref bean="myInterceptor"/>
           </list>
       </property>
</bean>

这样myInterceptor这个拦截器只会被这个BeanNameUrlHandlerMapping使用到。

注:如果按上面配置,同时xml文件里又存在<mvc:annotation-driven/>,那么可能发现不生效,原因是因为<mvc:annotation-driven/>会默认加载一个BeanNameUrlHandlerMapping,而且它的优先级是2(越小越高),而你在配置文件里的BeanNameUrlHandlerMapping默认优先级很低,前面说到DispatcherServlet会对HandlerMapping排序,所以它默认加载的总是会先使用。

一些参考

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

推荐阅读更多精彩内容

  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,724评论 6 342
  • 引言 一直以来都在使用Spring mvc,能够熟练使用它的各种组件。但是,它一直像个黑盒一样,我并不知道它内部是...
    yoqu阅读 905评论 0 24
  • 什么是Spring Spring是一个开源的Java EE开发框架。Spring框架的核心功能可以应用在任何Jav...
    jemmm阅读 16,438评论 1 133
  • Spring MVC一、什么是 Spring MVCSpring MVC 属于 SpringFrameWork 的...
    任任任任师艳阅读 3,371评论 0 32
  • 1、Spring MVC请求流程 (1)初始化:(对DispatcherServlet和ContextLoderL...
    拾壹北阅读 1,944评论 0 12