前言
SpringMVC目前已經(jīng)是主流使用的MVC框架之一,該框架將web開發(fā)中的重復(fù)性工作抽取封裝成各個組件,提高了我們開發(fā)的效率,更加專注于業(yè)務(wù)需求。了解和使用SpringMVC的作用流程,是每個JavaWeb開發(fā)人員都應(yīng)該掌握的技能。本篇文章將對SpringMVC融入Spring開發(fā)并簡化我們開發(fā)的過程以及SpringMVC調(diào)度的整個過程進(jìn)行講解,也希望能夠給各位讀者一個參考。
一、SpringMVC的簡介
SpringMVC 是一種基于 Java 的實現(xiàn) MVC 設(shè)計模型的請求驅(qū)動類型的輕量級 Web 框架,屬于SpringFrameWork 的后續(xù)產(chǎn)品,已經(jīng)融合在 Spring Web Flow 中。
當(dāng)前SpringMVC 已經(jīng)成為目前最主流的MVC框架之一,并且隨著Spring3.0 的發(fā)布,全面超越 Struts2,成為最優(yōu)秀的 MVC 框架。它通過一套注解,讓一個簡單的 Java 類成為處理請求的控制器,而無須實現(xiàn)任何接口。同時它還支持 RESTful 編程風(fēng)格的請求。
二、SpringMVC的作用
(一)Spring集成Web
在講SpringMVC之前,我們不妨先回顧一下,我們集成web環(huán)境需要做哪些工作?
首先我們需要引入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來響應(yīng)客戶端發(fā)來的網(wǎng)絡(luò)請求的,所以我們還需要定義我們的Servlet,從application-context.xml文件中獲取到交由spring托管的bean。
public class MyWebServlet extends HttpServlet {
/**
* 重寫doGet方法,在方法中調(diào)用service層的實現(xiàn)類
*/
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 獲取applicationContext.xml上下文對象
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
// 獲取userService的實現(xiàn)類
UserService userService = applicationContext.getBean(UserService.class);
// 調(diào)用實體類方法
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>
至此,我們常規(guī)的web部分代碼就寫完了。但是這里面是否存在有地方需要優(yōu)化呢?
比如說每次執(zhí)行doGet方法都會創(chuàng)建applicationContext對象,但實際上更好的做法是只創(chuàng)建一次,然后供所有servlet使用。同時我們通過字符串的方式定義了applicationContext的文件路徑,和代碼的耦合度很高,有沒有更優(yōu)雅的方法來實現(xiàn)呢?
基于上述問題,我們想到了更好的解決方案:通過監(jiān)聽器的方式將applicationContext上下文對象放到全局域中,使用配置文件的方式來實現(xiàn)配置和代碼之間的解耦合。
- 定義一個監(jiān)聽器
public class ContextLoadListen implements ServletContextListener {
/*
容器初始化時執(zhí)行的方法
*/
public void contextInitialized(ServletContextEvent servletContextEvent) {
// 獲取servletContext全局域?qū)ο? ServletContext servletContext = servletContextEvent.getServletContext();
// 讀取web.xml文件,獲取applicationContext的文件路徑
String applicationPath = servletContext.getInitParameter("contextConfigLocation");
// 創(chuàng)建applicationContext上下文對象
ApplicationContext app = new ClassPathXmlApplicationContext(applicationPath);
// 將上下文對象保存在servletContext全局域中
servletContext.setAttribute("app",app);
}
/**
* 容器銷毀時執(zhí)行的方法
* @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>
<!-- 定義自定義監(jiān)聽器 -->
<listener>
<listener-class>com.xiaoming.listen.ContextLoadListen</listener-class>
</listener>
封裝完成后,我們的servlet的內(nèi)容可以簡化為如下代碼:
public class MyWebServlet extends HttpServlet {
/**
* 重寫doGet方法,在方法中調(diào)用service層的實現(xiàn)類
*/
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 獲取applicationContext.xml上下文對象
ApplicationContext applicationContext = WebApplicationContextUtils.getApplicationContext(getServletContext());
// 獲取userService的實現(xiàn)類
UserService userService = applicationContext.getBean(UserService.class);
// 調(diào)用實體類方法
userService.saveUser();
}
}
我們可以發(fā)現(xiàn),現(xiàn)在的代碼和之前的相比,已經(jīng)優(yōu)雅了很多。實際上,我們上面所演示的代碼,正是Spring集成Web實現(xiàn)的內(nèi)容之一。Spring通過監(jiān)聽器初始化applicationContext對象,然后封裝到工具類中供我們使用。
所以上面的代碼我們可以通過直接引入springmvc的監(jiān)聽器來實現(xiàn)
<!-- 配置監(jiān)聽器-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
(二)SpringMVC的作用
我們可以看一下下面這張圖,在原先的web開發(fā)中,用戶發(fā)起請求后,由tomcat對原始請求封裝成req對象,再封裝響應(yīng)對象req,再根據(jù)用戶的請求路徑找到對應(yīng)的Servlet,Servlet處理后將結(jié)果封裝到req中,最后由tomcat核心返回最終的響應(yīng)體。
我們可以發(fā)現(xiàn)一旦項目往往需要定義很多個Servlet來處理不同模塊的請求,而每個Servlet中我們都有著一些共同的動作需要執(zhí)行:比如說將req中的參數(shù)封裝到pojo中、返回給用戶最終的視圖路徑,都繼承同一個抽象類HttpServlet等等,當(dāng)Servlet的數(shù)量變多的時候,我們往往需要花費(fèi)一定的精力來寫這些形式類似的代碼。

那么,有沒有什么方法可以簡化或者說將這些方法抽取出來呢?
答案是有的,SringMvc其實做的就是這件事情,它將Servlet的共有部分動作給抽離出來,開發(fā)者不再需要寫Servlet,只需要寫一個POJO類就可以,可以更加專注于業(yè)務(wù)邏輯的開發(fā)?,F(xiàn)在,相比你可以知道SpringMVC到底做了什么吧。
簡單來說,SpringMVC就是抽取了我們web開發(fā)共有部分,簡化了我們的開發(fā)。

三、SpringMVC的簡單使用
接下來,我們就來使用SpringMVC來做一個簡單的案例吧。
步驟一:導(dǎo)入依賴
首先,先導(dǎo)入對應(yīng)的依賴(依賴的版本根據(jù)實際而定,不需要和我的一樣)
<!--SpringMVC坐標(biāo)-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<!--Spring坐標(biāo)-->
<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>
這里講解一下參數(shù),url-pattern為/,表名所有的請求都會經(jīng)過springmvc的前端(核心)控制器。同時init-param標(biāo)識了springmvc的配置文件路徑
步驟三:編寫Controller
@Controller
public class MyWebController {
@RequestMapping("/webTest")
public String save(){
System.out.println();
return "/jsp/success.jsp";
}
}
我們需要在我們的類中做兩件事情,第一是聲明我們這個controller映射的路徑,第二是將我們的實體類交由spring托管。而@Controller生效的前提是開啟了注冊發(fā)現(xiàn),所以我們還需要在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>
這里有一個小細(xì)節(jié)需要注意,我們是在spring-mvc中開啟的包掃描,但是spring怎么知道我們這個文件呢?所以就需要我們在配置前端控制器的時候,配置上spring-mvc的路徑上去。(也就是步驟二中init-param的部分)。因為前端控制器初始化需要時間,所以我們一般會讓它在項目啟動的時候一起加載。
至此,springmvc的案例我們就已經(jīng)完成了。
四、SpringMVC的工作過程
我們在前面的介紹中講到,我們依靠SpringMVC的前端控制器DispatcherServlet實現(xiàn)了Servlet共有部分的提取,但實際上類似于根據(jù)請求路徑查找執(zhí)行的Controller對象,調(diào)用具體的執(zhí)行類等動作,都不是DispatcherServlet直接完成的,它只是充當(dāng)了調(diào)度者的角色,真正完成這些動作的是由SpringMVC的各個組件實現(xiàn)的。

我們可以看一下上面這幅圖,每個人總結(jié)的流程圖雖有差異,但流程上總是大同小異的。
1. 首先是客戶端瀏覽器發(fā)出請求,由于前端控制器的映射路徑為 / ,所以請求會先到達(dá)前端控制器
2. 前端控制器接收到請求后需要調(diào)用處理器映射器,返回處理器執(zhí)行鏈HandlerExecutionChain,這里的執(zhí)行鏈可以理解為是請求到達(dá)對應(yīng)controller需要經(jīng)歷的所有調(diào)用鏈,比如說可能要先經(jīng)過攔截器才能到達(dá)controller。
3. 前端控制器得到執(zhí)行鏈后不自己執(zhí)行,而是會交由處理器適配器HandlerAdaper處理,由適配器找到每個調(diào)用鏈節(jié)點上執(zhí)行的具體對象后,由處理器Handler執(zhí)行。(這里的Handler其實就是Controller、攔截器等對象)。執(zhí)行完controller方法后,返回視圖模型給前端控制器
4. 前端控制器得到視圖模型后,會交由視圖解析器ViewResolver解析出對應(yīng)的視圖,即對應(yīng)的jsp頁面
5. 前端控制器進(jìn)行視圖的渲染(也就是把數(shù)據(jù)渲染上去)
6. 一切就緒后,前端控制器將jsp頁面返回給客戶端瀏覽器。
五、SpringMVC的組件介紹
前端控制器:DispatcherServlet
用戶請求到達(dá)前端控制器,它就相當(dāng)于 MVC 模式中的 C,DispatcherServlet 是整個流程控制的中心,由
它調(diào)用其它組件處理用戶的請求,DispatcherServlet 的存在降低了組件之間的耦合性。
處理器映射器:HandlerMapping
HandlerMapping 負(fù)責(zé)根據(jù)用戶請求找到 Handler 即處理器,SpringMVC 提供了不同的映射器實現(xiàn)不同的
映射方式,例如:配置文件方式,實現(xiàn)接口方式,注解方式等。
處理器適配器:HandlerAdapter
通過 HandlerAdapter 對處理器進(jìn)行執(zhí)行,這是適配器模式的應(yīng)用,通過擴(kuò)展適配器可以對更多類型的處理器進(jìn)行執(zhí)行。
處理器:Handler
它就是我們開發(fā)中要編寫的具體業(yè)務(wù)控制器。由 DispatcherServlet 把用戶請求轉(zhuǎn)發(fā)到 Handler。由
Handler 對具體的用戶請求進(jìn)行處理。
視圖解析器:View Resolver
View Resolver 負(fù)責(zé)將處理結(jié)果生成 View 視圖,View Resolver 首先根據(jù)邏輯視圖名解析成物理視圖名,即具體的頁面地址,再生成 View 視圖對象,最后對 View 進(jìn)行渲染將處理結(jié)果通過頁面展示給用戶。
視圖 View
SpringMVC 框架提供了很多的 View 視圖類型的支持,包括:jstlView、freemarkerView、pdfView等。最常用的視圖就是 jsp。一般情況下需要通過頁面標(biāo)簽或頁面模版技術(shù)將模型數(shù)據(jù)通過頁面展示給用戶,需要由程序員根據(jù)業(yè)務(wù)需求開發(fā)具體的頁面
小結(jié)
本篇文章從springmvc如何集成web環(huán)境出發(fā),講述了其在項目開發(fā)中的實際作用——抽取Servlet共有部分,簡化開發(fā),然后進(jìn)行了案例演示,最后分析了springmvc的工作流程和介紹了其各個組件的作用。看到這里,想必各位讀者對于springmvc已經(jīng)有了一個大概的印象,要想了解更多細(xì)節(jié),可以看一下springmvc的源碼或者官方文檔,相信可以收獲更多。