SpringMVC的作用流程

前言

SpringMVC目前已经是主流使用的MVC框架之一,该框架将web开发中的重复性工作抽取封装成各个组件,提高了我们开发的效率,更加专注于业务需求。了解和使用SpringMVC的作用流程,是每个JavaWeb开发人员都应该掌握的技能。本篇文章将对SpringMVC融入Spring开发并简化我们开发的过程以及SpringMVC调度的整个过程进行讲解,也希望能够给各位读者一个参考。

一、SpringMVC的简介

SpringMVC 是一种基于 Java 的实现 MVC 设计模型的请求驱动类型的轻量级 Web 框架,属于SpringFrameWork 的后续产品,已经融合在 Spring Web Flow 中。
当前SpringMVC 已经成为目前最主流的MVC框架之一,并且随着Spring3.0 的发布,全面超越 Struts2,成为最优秀的 MVC 框架。它通过一套注解,让一个简单的 Java 类成为处理请求的控制器,而无须实现任何接口。同时它还支持 RESTful 编程风格的请求。

二、SpringMVC的作用

(一)Spring集成Web

在讲SpringMVC之前,我们不妨先回顾一下,我们集成web环境需要做哪些工作?
首先我们需要引入web、servlet和jsp依赖

 //  web依赖
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>5.0.5.RELEASE</version>
        </dependency>
// servlet和jsp依赖
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.0.1</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet.jsp</groupId>
            <artifactId>javax.servlet.jsp-api</artifactId>
            <version>2.2.1</version>
            <scope>provided</scope>
        </dependency>

我们知道JavaWeb是通过Servlet来响应客户端发来的网络请求的,所以我们还需要定义我们的Servlet,从application-context.xml文件中获取到交由spring托管的bean。

public class MyWebServlet extends HttpServlet {
    /**
     * 重写doGet方法,在方法中调用service层的实现类
     */
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 获取applicationContext.xml上下文对象
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 获取userService的实现类
        UserService userService = applicationContext.getBean(UserService.class);
        // 调用实体类方法
        userService.saveUser();
    }
}

同时,在web.xml文件中我们需要配置自定义servlet映射路径

    <!-- MyWebServlet -->
    <servlet>
        <servlet-name>MyWebServlet</servlet-name>
        <servlet-class>com.xiaoming.web.MyWebServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>MyWebServlet</servlet-name>
        <url-pattern>/webTest</url-pattern>
    </servlet-mapping>
至此,我们常规的web部分代码就写完了。但是这里面是否存在有地方需要优化呢?

比如说每次执行doGet方法都会创建applicationContext对象,但实际上更好的做法是只创建一次,然后供所有servlet使用。同时我们通过字符串的方式定义了applicationContext的文件路径,和代码的耦合度很高,有没有更优雅的方法来实现呢?
基于上述问题,我们想到了更好的解决方案:通过监听器的方式将applicationContext上下文对象放到全局域中,使用配置文件的方式来实现配置和代码之间的解耦合。

  • 定义一个监听器
public class ContextLoadListen implements ServletContextListener {
    /*
     容器初始化时执行的方法
     */
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        // 获取servletContext全局域对象
        ServletContext servletContext = servletContextEvent.getServletContext();
        // 读取web.xml文件,获取applicationContext的文件路径
        String applicationPath =  servletContext.getInitParameter("contextConfigLocation");
        // 创建applicationContext上下文对象
        ApplicationContext app = new ClassPathXmlApplicationContext(applicationPath);
        // 将上下文对象保存在servletContext全局域中
        servletContext.setAttribute("app",app);
    }

    /**
     * 容器销毁时执行的方法
     * @param servletContextEvent
     */
    public void contextDestroyed(ServletContextEvent servletContextEvent) {

    }
}

将从servletContext全局域获取applicationContext上下文对象的步骤封装成一个工具类

public class WebApplicationContextUtils {
    public static ApplicationContext getApplicationContext(ServletContext servletContext){
        return (ApplicationContext) servletContext.getAttribute("app");
    }
}

配置web.xml文件

    <!-- 定义全局初始化变量-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext.xml</param-value>
    </context-param>
<!-- 定义自定义监听器 -->
     <listener>
         <listener-class>com.xiaoming.listen.ContextLoadListen</listener-class>
     </listener> 

封装完成后,我们的servlet的内容可以简化为如下代码:

public class MyWebServlet extends HttpServlet {
    /**
     * 重写doGet方法,在方法中调用service层的实现类
     */
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 获取applicationContext.xml上下文对象
        ApplicationContext applicationContext = WebApplicationContextUtils.getApplicationContext(getServletContext());
        // 获取userService的实现类
        UserService userService = applicationContext.getBean(UserService.class);
        // 调用实体类方法
        userService.saveUser();
    }
}

我们可以发现,现在的代码和之前的相比,已经优雅了很多。实际上,我们上面所演示的代码,正是Spring集成Web实现的内容之一。Spring通过监听器初始化applicationContext对象,然后封装到工具类中供我们使用。
所以上面的代码我们可以通过直接引入springmvc的监听器来实现

    <!-- 配置监听器-->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
(二)SpringMVC的作用

我们可以看一下下面这张图,在原先的web开发中,用户发起请求后,由tomcat对原始请求封装成req对象,再封装响应对象req,再根据用户的请求路径找到对应的Servlet,Servlet处理后将结果封装到req中,最后由tomcat核心返回最终的响应体。
我们可以发现一旦项目往往需要定义很多个Servlet来处理不同??榈那肭?,而每个Servlet中我们都有着一些共同的动作需要执行:比如说将req中的参数封装到pojo中、返回给用户最终的视图路径,都继承同一个抽象类HttpServlet等等,当Servlet的数量变多的时候,我们往往需要花费一定的精力来写这些形式类似的代码。

传统web项目流程图

那么,有没有什么方法可以简化或者说将这些方法抽取出来呢?
答案是有的,SringMvc其实做的就是这件事情,它将Servlet的共有部分动作给抽离出来,开发者不再需要写Servlet,只需要写一个POJO类就可以,可以更加专注于业务逻辑的开发。现在,相比你可以知道SpringMVC到底做了什么吧。
简单来说,SpringMVC就是抽取了我们web开发共有部分,简化了我们的开发。

SpringMVC抽取共有部分

三、SpringMVC的简单使用

接下来,我们就来使用SpringMVC来做一个简单的案例吧。

步骤一:导入依赖

首先,先导入对应的依赖(依赖的版本根据实际而定,不需要和我的一样)

  <!--SpringMVC坐标-->
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.0.5.RELEASE</version>
  </dependency>
 <!--Spring坐标-->
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.0.5.RELEASE</version>
  </dependency>
步骤二:配置核心控制器
    <!-- 配置SpringMVC的核心控制器 -->
    <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>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>DispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

这里讲解一下参数,url-pattern/,表名所有的请求都会经过springmvc的前端(核心)控制器。同时init-param标识了springmvc的配置文件路径

步骤三:编写Controller
@Controller
public class MyWebController {

    @RequestMapping("/webTest")
    public String save(){
        System.out.println();
        return "/jsp/success.jsp";
    }
}

我们需要在我们的类中做两件事情,第一是声明我们这个controller映射的路径,第二是将我们的实体类交由spring托管。而@Controller生效的前提是开启了注册发现,所以我们还需要在spring-mvc.xml的配置文件中开启包扫描。

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/mvc
       http://www.springframework.org/schema/mvc/spring-mvc.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd">
    <!--配置注解扫描-->
    <context:component-scan base-package="com.要扫描的包名"/>
</beans>

这里有一个小细节需要注意,我们是在spring-mvc中开启的包扫描,但是spring怎么知道我们这个文件呢?所以就需要我们在配置前端控制器的时候,配置上spring-mvc的路径上去。(也就是步骤二中init-param的部分)。因为前端控制器初始化需要时间,所以我们一般会让它在项目启动的时候一起加载。
至此,springmvc的案例我们就已经完成了。

四、SpringMVC的工作过程

我们在前面的介绍中讲到,我们依靠SpringMVC的前端控制器DispatcherServlet实现了Servlet共有部分的提取,但实际上类似于根据请求路径查找执行的Controller对象,调用具体的执行类等动作,都不是DispatcherServlet直接完成的,它只是充当了调度者的角色,真正完成这些动作的是由SpringMVC的各个组件实现的。

springmvc执行流程图

我们可以看一下上面这幅图,每个人总结的流程图虽有差异,但流程上总是大同小异的。

1. 首先是客户端浏览器发出请求,由于前端控制器的映射路径为 / ,所以请求会先到达前端控制器
2. 前端控制器接收到请求后需要调用处理器映射器,返回处理器执行链HandlerExecutionChain,这里的执行链可以理解为是请求到达对应controller需要经历的所有调用链,比如说可能要先经过拦截器才能到达controller。
3. 前端控制器得到执行链后不自己执行,而是会交由处理器适配器HandlerAdaper处理,由适配器找到每个调用链节点上执行的具体对象后,由处理器Handler执行。(这里的Handler其实就是Controller、拦截器等对象)。执行完controller方法后,返回视图模型给前端控制器
4. 前端控制器得到视图模型后,会交由视图解析器ViewResolver解析出对应的视图,即对应的jsp页面
5. 前端控制器进行视图的渲染(也就是把数据渲染上去)
6. 一切就绪后,前端控制器将jsp页面返回给客户端浏览器。

五、SpringMVC的组件介绍

前端控制器:DispatcherServlet

用户请求到达前端控制器,它就相当于 MVC 模式中的 C,DispatcherServlet 是整个流程控制的中心,由
它调用其它组件处理用户的请求,DispatcherServlet 的存在降低了组件之间的耦合性。

处理器映射器:HandlerMapping

HandlerMapping 负责根据用户请求找到 Handler 即处理器,SpringMVC 提供了不同的映射器实现不同的
映射方式,例如:配置文件方式,实现接口方式,注解方式等。

处理器适配器:HandlerAdapter

通过 HandlerAdapter 对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。

处理器:Handler

它就是我们开发中要编写的具体业务控制器。由 DispatcherServlet 把用户请求转发到 Handler。由
Handler 对具体的用户请求进行处理。

视图解析器:View Resolver

View Resolver 负责将处理结果生成 View 视图,View Resolver 首先根据逻辑视图名解析成物理视图名,即具体的页面地址,再生成 View 视图对象,最后对 View 进行渲染将处理结果通过页面展示给用户。

视图 View

SpringMVC 框架提供了很多的 View 视图类型的支持,包括:jstlView、freemarkerView、pdfView等。最常用的视图就是 jsp。一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要由程序员根据业务需求开发具体的页面

小结

本篇文章从springmvc如何集成web环境出发,讲述了其在项目开发中的实际作用——抽取Servlet共有部分,简化开发,然后进行了案例演示,最后分析了springmvc的工作流程和介绍了其各个组件的作用??吹秸饫?,想必各位读者对于springmvc已经有了一个大概的印象,要想了解更多细节,可以看一下springmvc的源码或者官方文档,相信可以收获更多。

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

推荐阅读更多精彩内容