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的数量变多的时候,我们往往需要花费一定的精力来写这些形式类似的代码。
那么,有没有什么方法可以简化或者说将这些方法抽取出来呢?
答案是有的,SringMvc其实做的就是这件事情,它将Servlet的共有部分动作给抽离出来,开发者不再需要写Servlet,只需要写一个POJO类就可以,可以更加专注于业务逻辑的开发。现在,相比你可以知道SpringMVC到底做了什么吧。
简单来说,SpringMVC就是抽取了我们web开发共有部分,简化了我们的开发。
三、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的各个组件实现的。
我们可以看一下上面这幅图,每个人总结的流程图虽有差异,但流程上总是大同小异的。
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的源码或者官方文档,相信可以收获更多。