美文网首页
spring mvc工作机制

spring mvc工作机制

作者: aaron1993 | 来源:发表于2017-10-22 21:32 被阅读0次

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实践

相关文章

网友评论

      本文标题:spring mvc工作机制

      本文链接:https://www.haomeiwen.com/subject/trbzjxtx.html