SpringMVC的作用流程

前言

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)一定的精力來寫這些形式類似的代碼。

傳統(tǒng)web項目流程圖

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

SpringMVC抽取共有部分

三、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)的。

springmvc執(zhí)行流程圖

我們可以看一下上面這幅圖,每個人總結(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的源碼或者官方文檔,相信可以收獲更多。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容