一、SpringMVC主要流程源码解析

605 阅读5分钟

一、spring mvc 功能特性


1、回顾servlet 与jsp 执行过程

图片

流程说明:

  1. 请求Servlet
  2. 处理业务逻辑
  3. 设置业务Model
  4. forward jsp Servlet
  5. jsp Servlet 解析封装html 返回

2、spring mvc功能特性

spring mvc本质上还是在使用Servlet处理,并在其基础上进行了封装简化了开发流程,提高易用性、并使用程序逻辑结构变得更清晰

  1. 基于注解的URL映谢
  2. 表单参数映射
  3. 缓存处理
  4. 全局统一异常处理
  5. 拦截器的实现

3、请求处理流程

图片

4、spring mvc示例

为便于理解,这里给出一个最简单,配置最少的spring mvc示例:

web.xml servlet配置:

<servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            classpath:/spring-mvc.xml
        </param-value>
    </init-param>
</servlet>

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

编写Controller方法:

public class SimpleController implements Controller {
    @Override
    public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
        ModelAndView modelAndView = new ModelAndView("/WEB-INF/page/userView.jsp");
        modelAndView.addObject("name","cyan");
        return modelAndView;
    }
}

配置spring-mvc.xml文件

<bean name="/hello.do" class="com.cyan.controller.SimpleController"></bean>

整个过程是如何实现的?

  1. dispatchServlet 如何找到对应的Control?
  2. 如何执行调用Control 当中的业务方法?

在面试中要回答好上述问题,就必须得弄清楚spring mvc 的体系组成。

二、mvc 体系结构详解


1、spring mvc框架解决的问题

从技术角度去思考,任何一个现存的框架都有其存在的理由,而这个理由就是解决实际的问题。或者提供更好的解决问题的方案。spring mvc它解决了什么问题呢?

  1. URL映射
  2. 表单参数映射
  3. 调用目标Control
  4. 数据模型映射
  5. 视图解析
  6. 异常处理

上述问题的解决都在体现在spring mvc中的如下组件当中:

  • HandlerMapping
    • url与控制器的映谢
  • HandlerAdapter
    • 控制器执行适配器
  • ViewResolver
    • 视图仓库
  • view
    • 具体解析视图
  • HandlerExceptionResolver
    • 异常捕捉器
  • HandlerInterceptor
    • 拦截器

其对应具体uml如下图:

图片

mvc各组件执行流程:

图片

2、HandlerMapping 详解

解决mvc中url路径与Controller对像的映射问题,DispatcherServlet就是基于此组件来寻找对应的Controller,如果找不到就会报No mapping found for HTTP request with URI的异常。

HandlerMapping 接口结构分析:

图片

HandlerMapping的作用是通过url找到对应的Handler ,但其HandlerMapping.getHandler()方法并不会直接返回Handler对像,而是返回HandlerExecutionChain对像,在通过HandlerExecutionChain.getHandler()返回最终的handler

图片

常用实现类:

图片

目前主流的三种mapping 如下:

  1. SimpleUrlHandlerMapping:基于手动配置 url 与control 映谢
  2. BeanNameUrlHandlerMapping:基于ioc name 中已 "/" 开头的Bean时行 注册至映谢.
  3. RequestMappingHandlerMapping:基于@RequestMapping注解配置对应映谢

SimpleUrlHandlerMapping

编写Controller方法:

public class SimpleHandler implements HttpRequestHandler {

    @Override
    public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setAttribute("name","tangtang");
        request.getRequestDispatcher("/WEB-INF/page/userView.jsp").forward(request,response);
    }
}

编写spring-mvc.xml配置文件:

<bean id="simpleHandler" class="com.cyan.handler.SimpleHandler"></bean>

<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
    <property name="order" value="100"></property>
    <property name="mappings">
        <props>
            <prop key="hello2.do">simpleHandler</prop>
        </props>
    </property>
</bean>

SimpleUrlHandlerMapping体系结构:

图片

初始化SimpleUrlHandlerMapping流程关键源码:

> org.springframework.web.servlet.handler.SimpleUrlHandlerMapping#setMappings()
> org.springframework.web.servlet.handler.SimpleUrlHandlerMapping#initApplicationContext()
> org.springframework.web.servlet.handler.SimpleUrlHandlerMapping#registerHandlers()
> org.springframework.web.servlet.handler.AbstractUrlHandlerMapping#registerHandler()

主要流程为:将映射路径与handler对象(如果为懒加载则为bean的名字)放入LinkedHashMap

获取 Handler流程关键源码:

> org.springframework.web.servlet.DispatcherServlet#getHandler()
> org.springframework.web.servlet.handler.AbstractHandlerMapping#getHandler()
> org.springframework.web.servlet.handler.AbstractUrlHandlerMapping#getHandlerInternal()
> org.springframework.web.util.UrlPathHelper#getLookupPathForRequest()
> org.springframework.web.util.UrlPathHelper#getPathWithinApplication()

> org.springframework.web.servlet.handler.AbstractUrlHandlerMapping#lookupHandler()

> org.springframework.web.servlet.handler.AbstractHandlerMapping#getHandlerExecutionChain

主要流程为:获取url路径、查找handler、封装执行链

BeanNameUrlHandlerMapping

BeanNameUrlHandlerMapping实现上与SimpleUrlHandlerMapping一至,唯一区别在于继承自AbstractDetectingUrlHandlerMapping,通过对应detectHandlers 可以在无配置的情况下发现url与handler映射。

结构图:

图片

RequestMappingHandlerMapping

基于注解实现,在后续章节讲解注解映谢的时候在详细讲。

Handler类型

在AbstractUrlHandlerMapping中,我们可以看到存储handler的Map值类型是Object,是否意味着所有的类都可以做来Handler来使用?

图片

Handler对应类型如下:

图片

  • Controller 接口:
  • HttpRequestHandler 接口:
  • HttpServlet 接口:
  • @RequestMapping方法注解

可以看出Handler没有统一的接口,当dispatchServlet获取当前对应的Handler之后如何调用呢?调用其哪个方法?这里有两种解决办法:一是用instanceof 判断Handler 类型然后调用相关方法 。二是通过引入适配器实现,每个适配器实现对指定Handler的调用(spring 采用后者)

3、HandlerAdapter详解

这里spring mvc采用适配器模式来适配调用指定的Handler,根据Handler的不同种类采用不同的Adapter,其Handler与HandlerAdapter对应关系如下:

Handler类别 对应适配器 描述
Controller SimpleControllerHandlerAdapter 标准控制器,返回ModelAndView
HttpRequestHandler HttpRequestHandlerAdapter 业务自行处理请求,不需要通过modelAndView 转到视图
Servlet SimpleServletHandlerAdapter 基于标准的servlet 处理
HandlerMethod RequestMappingHandlerAdapter 基于@requestMapping对应方法处理

HandlerAdapter接口方法

图片

HandlerAdapter接口结构图

图片

  • 演示基于Servlet 处理 SimpleServletHandlerAdapter

编写Controller方法:

public class HelloServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            response.getWriter().println("hello lingqi");
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request,response);
    }
}

编写spring-mvc.xml配置文件:

<bean id="/helloServlet" class="com.cyan.servlet.HelloServlet"></bean>
<bean class="org.springframework.web.servlet.handler.SimpleServletHandlerAdapter"></bean>

上述例子中当IOC中实例化这些类之后,DispatcherServlet就会通过org.springframework.web.servlet.DispatcherServlet#getHandlerAdapter()方法查找对应handler的适配器,如果找不到就会报 如下异常:javax.servlet.ServletException: No adapter for handler ......

获取Adapter流程关键源码:

> org.springframework.web.servlet.DispatcherServlet#getHandlerAdapter()
> 

4、ViewResolver与View详解

找到对应的Adapter之后就会基于适配器调用业务处理,处理完之后业务方会返回一个ModelAndView,在去查找对应的视图进行处理。在org.springframework.web.servlet.DispatcherServlet#resolveViewName()中遍历viewResolvers列表查找,如果找不到就会报一个Could not resolve view with name 异常。

图片

BeanNameViewResolver示例:

编写自定义视图:

public class MyView implements View {
    @Override
    public void render(Map<String, ?> map, HttpServletRequest request, HttpServletResponse response) throws Exception {
        response.getWriter().println("hello "+map.get("name"));
    }

    @Override
    public String getContentType() {
        return null;
    }
}

编写spring-mvc.xml配置文件:

<bean name="/myController" class="com.cyan.controller.MyController"></bean>
<bean class="org.springframework.web.servlet.view.BeanNameViewResolver"></bean>
<bean name="myView" class="com.cyan.view.MyView"></bean>

编写Controller方法:

public class MyController implements Controller {
    @Override
    public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
        ModelAndView modelAndView = new ModelAndView("myView");
        modelAndView.addObject("name","qingzi");
        return modelAndView;
    }
}

InternalResourceViewResolver示例:

编写spring-mvc.xml配置文件:

<bean name="/hello1.do" class="com.cyan.controller.SimpleController"></bean>

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/page/"/>
    <property name="suffix" value=".jsp"/>
    <property name="viewClass" value="org.springframework.web.servlet.view.InternalResourceView"/>
</bean>

编写Controller方法:

public class SimpleController implements Controller {

    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        ModelAndView modelAndView = new ModelAndView("userView");
        modelAndView.addObject("name","cyan");
        return modelAndView;
    }
}

在下一步就是基于ViewResolver.resolveViewName() 获取对应View来解析生成Html并返回。对应VIEW结构如下:

图片