Spring—spring mvc框架入門 – JAVA編程語言程序開發技術文章

   Spring mvc在Spring特征裡面處於滿核心的地位,在官網上的對Spring 特征(FEATURES)羅列中,對應這是“MODERN WEB”(現代web),也就是Spring特征的第一項。也反映瞭其重要作用,另一方面也是因為mvc在Spring項目中誕生比較早。

       隻要是同網絡應用相關的,無論是有同用戶互動的(帶UI的)或者沒有互動的情況,spring mvc都是種成熟、功能齊全的架構。閱讀下面的內容需要對java、spring(依賴註入dependency injection)、servlet需要有些基本的瞭解。

       Spring的MVC框架如下圖所示:

 

在Spring MVC中前端的控制器就是DispatcherServlet這個Servlet來掌管著用戶的請求及最後的系統回應。這個DispatcherServlet同具體的業務邏輯一點都不著邊,而是把所有的事情委派給控制器去做(Controller),當然DispatcherServlet是知道該把當前的事情交個那個控制器去做,這個後面會講;然後當控制器把事情都做完瞭後,這個時候輪到視圖(View)上場瞭,簡單的理解好比我們做PPT,那麼這裡的視圖好比PPT裡面的模板,它可以把數據以不同的展現形式交給客戶,可以是jsp、xml、json等等。下面來看看如何具體實現的:

  1、Servlet部分,上面說到前端控制器是DispatcherServlet這個Servlet,那很顯然需要在web.xml中加入一個servlet,然後把用戶的請求都讓DispatcherServlet去處理,下面是web.xml文件:

[html] 
<?xml version="1.0" encoding="UTF-8"?> 
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> 
  
    <!– Processes application requests –> 
    <servlet> 
        <servlet-name>appServlet</servlet-name> 
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> 
        <init-param> 
            <param-name>contextConfigLocation</param-name> 
            <param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value> 
        </init-param> 
        <load-on-startup>1</load-on-startup> 
    </servlet>        
  
    <servlet-mapping> 
        <servlet-name>appServlet</servlet-name> 
        <url-pattern>/</url-pattern> 
    </servlet-mapping> 
</web-app> 
這裡有個地方就是contextConfigLocation,這個是個初始化參數(init-param),在servlet進行初始化的時候可以設置它的值,而這個值定義瞭spring應用上下文(context—上下文,指的是一種環境,主要是各種bean,可以理解為各種component)的配置文件(XML格式)的位置,這個上下文會被DispatcherServlet加載進來,這樣Dispatcher工作起來時會依照這個上下文的內容進行分配任務。具體原理可以參考下面這個圖:

        關於上下文的配置,這裡使用的是xml的配置方式,上面代碼指定瞭配置文件是/WEB-INF/spring/appServlet/servlet-context.xml,這個也可以使用java的配置方式,這裡需要在DispatcherServlet的初始參數中加入AnnotationConfigWebApplicationContext。另外在下面的例子中我們會看到組件即可以在XML中配置但也可以使用java代碼來做。接下來看看第一個controller

2、Controller類,下面是一個最mini的控制類瞭:

[html] 
package xyz.sample.baremvc; 
  
import org.springframework.stereotype.Controller; 
import org.springframework.web.bind.annotation.RequestMapping; 
  
/** 
 * Handles requests for the application home page. 
 */ 
@Controller 
public class HomeController { 
  
    @RequestMapping(value = "/") 
    public String home() { 
        System.out.println("HomeController: Passing through…"); 
        return "WEB-INF/views/home.jsp"; 
    } 

上面這個類中有幾個註意的地方:
        1、使用瞭@Controller這個annotation(這個可以翻譯成“註釋”,表示下面的類的作用),來表示HomeController這個類是作為Spring MVC中的Controller(控制器),根據上面的那個圖也就是表示這個類具有處理用戶請求的能力。另外@Controller是@Component這個annotation中的特定annotation(有點類似@Component是@Controller的父類一樣,@Component相當於抽象程度比較高,@Controller是屬於一種特定的@Component),所以這裡HomeController也是作為一個組件(Component),在Spring初始化掃描的時候它會被自動檢測到並且加入到Spring container中(Spring容器或者叫Spring上下文,都是類似的概念),然後生成對應的類實例,最後像其他任何Spring組件一樣允許註入到系統中。

        2、home這個方法使用瞭@RequestMapping這個註釋,表示home這個方法可以用來處理對應於路徑“/”(根路徑)的用戶請求

        3、這裡home的處理就是在log中打印一個字符串,然後返回WEB-INF/views/home.jsp,這個是交給View去處理的,這裡就是個jsp文件,默認情況下,如果我們沒有設置特定的View的話,Spring會使用默認的View來處理WEB-INF/views/home.jsp這個Response(回應);對應View在後面將詳細講,這裡隻要知道View會把系統的home.jsp這個文件呈現給客戶就好

下面是對應的home.jsp文件,文件很簡單,隻是個Hello:

[html] 
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> 
<%@ page session="false" %> 
<html> 
    <head> 
        <title>Home</title> 
    </head> 
    <body> 
        <h1>Hello world!</h1> 
    </body> 
</html> 
3、配置Spring上下文,上面在Servlet中提到的Spring上下文還沒有創建(/WEB-INF/spring/appServlet/servlet-context.xml),看看下面的代碼:
[html] 
<?xml version="1.0" encoding="UTF-8"?> 
<beans xmlns="http://www.springframework.org/schema/beans" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:mvc="http://www.springframework.org/schema/mvc" 
    xmlns:context="http://www.springframework.org/schema/context" 
    xsi:schemaLocation=" 
        http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd 
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> 
  
    <!– DispatcherServlet Context: defines this servlet's request-processing infrastructure –> 
  
    <!– Scans within the base package of the application for @Components to configure as beans –> 
    <!– @Controller, @Service, @Configuration, etc. –> 
    <context:component-scan base-package="xyz.sample.baremvc" /> 
  
    <!– Enables the Spring MVC @Controller programming model –> 
    <mvc:annotation-driven /> 
  
</beans> 
       1、看到這裡xml的根元素是<beans>,所以這裡主要涉及創建各種bean,這裡的bean其實就是component瞭(組件)。
       2、這裡一共有三個名字空間,第一個是默認的beans、然後是mvc和context。

       3、<context:component-scan/>表示要求Spring 容器(Spring container)對類進行掃描、檢查,這樣所有代碼中包含有屬於@Component子類型的註釋(例如上面的@Controller就是屬於@Component的子類型)都會被檢查到,然後為瞭提高效率,這裡自動檢查的范圍隻在xyz..sample.baremvc這個包下面進行。

       4、<mvc:annotation-driven />,表示可以使用annotation來進行工作,也就是支持Spring MVC將請求轉給@Controller去處理;當然還可以涉及到如轉換、格式化、有效性

通過上面的幾個步驟後,啟動服務器,在地址欄輸入http://localhost:8080/baremvc,可以看到下面的效果:

 

上面的這個例子十分簡單吧,但是卻涉及到瞭Spring MVC應用的關鍵部分,來看看這個應用的調用流程:

        A、當Tomcat啟動,我們的Spring MVC應用被啟動起來,DispatcherServlet被加載進來,這是因為我們在Web.xml中定瞭這個Servlet。

        B、DispatcherServlet去加載基於註釋(annotation)的Spring應用上下文,這裡是通過對指定的包(指定的包可以使用正則表達式)進行scan的方式來獲得帶註釋的組件。

        C、這些帶註釋的組件被Spring 的 container檢測到。

        D、http://localhost:8080/baremvc的HTTP請求匹配瞭我們DispatcherServlet中的servlet-mapping,所以DispatcherServlet將去處理這個HTTP請求

        E、在上面的HTTP請求中暗示著用戶的請求路徑為“/”,然後DispatcherServlet發現他裡面有相應該路徑請求的映射(這個其實是我們第二張圖裡面的那個Handler Mapping)

        F、Dispatcher進行決定要如何處理請求,它使用到的策略叫做HandlerAdapter,這個可以這麼理解,對於request(請求)來說必須要有對應的handler(處理辦法)去處理,而這些handler可能根據當前所處的不同情況,而使用不同類別的handler,所以這裡還需要一個HandlerAdapter來作為不同情況下的來轉換不同類別的handler去處理request。這個HandlerAdapter(或者多個,可以將它們鏈接起來)我們可以去指定,但是沒有指定的情況下將使用系統默認的HandlerAdapter,這裡就是使用註釋驅動(annotation-based)的策略瞭。這裡就是把請求轉給@Controller,然後找到匹配對應路徑的@RequestMapping方法,將輸入的參數給該方法,並讓該方法處理請求。使用其他HandlerAdapter的例子還沒做,但是暫時我們不去關心這些,隻要有個瞭解就ok瞭

       G、然後home這個方法做它的事情,打印一個log,然後返回一個字符串,這個字符串暗示瞭要呈現給用戶的東西,也幫助View選擇合適呈現方式。

       H、然後從第一個圖中我們知道,Controller做完事情後,任務又交到DispatcherServlet手上瞭,對於如何選擇合適的視圖,同樣DispatcherServlet需要策略,這次的策略叫做ViewResolver,通過這個ViewResolver(視圖仲裁員)去仲裁那個視圖(View)將回應呈現給用戶,這個ViewResolver也是可以自己定義的。如果默認沒有配置的情況下系統將使用InternalResourceViewResolver,這個ViewResolver將創建一個JSTLView,而這個View隻是簡單的把任務委托給Servlet內部的RequestDispatcher去呈現資源,所以這種View很適合於使用JSP或者HTML頁面的情況

       I、最後Servlet將回應通過制定的jsp呈現給用戶

4、對View進行抽象,讓Controller返回一個純粹的邏輯性的名稱。

      上面的home函數中我們看到返回的是個純粹的物理地址,為瞭減少耦合,讓應用有更好的彈性,當然是不能使用這種硬編碼的方式;要讓Controller返回一個邏輯的名稱,而最終的物理路徑由View來決定,這樣讓Controller隻關心邏輯上的處理,把展示留給View去做,真正做到C和V分離。看下改造瞭得Controller:

[java] 
@Controller 
public class HomeController { 
  
    @RequestMapping(value = "/") 
    public String home() { 
        System.out.println("HomeController: Passing through…"); 
        return "home"; 
    } 

上面home函數裡面返回一個邏輯名稱“home”,也就是好像告訴系統需要調用一個叫做“home“的view給用戶,但是具體這個”home“的view具體是什麼樣子完全由系統自己去決定,當然我們知道這裡是有ViewResolver去決定的。當然我們可以把這些View的名稱統一到一起,上面的這種直接”home“還是有點”生硬“。
接下來我們要做的就是把”home“這個名稱映射到”WEB-INF/views/home.jsp“這個文件上面。上面我們說過默認情況下這種映射是通過InternalResourceViewResolver類來處理的。我們可以使用自己的ViewResolver來處理這種邏輯到物理的映射,但是這個例子裡面我們隻需要對InternalResourceViewResolver做一點點調整就可以達到我們的目的瞭。
InternalResourceViewResolver對Controller返回過來(其實最後是DispatcherServlet發過來的)的名稱,隻是在其前後加上可選的前後綴(默認情況為空),然後把這個字符串交給由它自己創建的JSTLView,然後JSTLView再委托Servlet引擎中的RequestDispatcher去幹真正的活,呈現視圖模板,這裡是jsp做視圖的模板。這個例子裡比較簡單,隻要對View的名稱返回增加相應的前後綴就可以瞭,前綴”WEB-INF/views/“,後綴".jsp"。一種簡單的配置ViewResolver的方式是使用以java為基本的容器配置(另外就是以XML為基礎的容器配置/上下文配置),把我們的Resolver定義為一個Bean,下面是對應的代碼:

[java] 
package xyz.sample.baremvc; 
  
import org.springframework.context.annotation.Bean; 
import org.springframework.context.annotation.Configuration; 
import org.springframework.web.servlet.ViewResolver; 
import org.springframework.web.servlet.view.InternalResourceViewResolver; 
  
@Configuration 
public class AppConfig { 
  
    // Resolve logical view names to .jsp resources in the /WEB-INF/views directory 
    @Bean 
    ViewResolver viewResolver() { 
        InternalResourceViewResolver resolver = new InternalResourceViewResolver(); 
        resolver.setPrefix("WEB-INF/views/"); 
        resolver.setSuffix(".jsp"); 
        return resolver; 
    } 

上面的代碼需要cglib庫的支持,這個可以網上下載也可以到我的資源裡去下cglib。
由於@Configuration也是一個@Component,所以掃描的時候也會被掃描到,然後這個配置組件(Configuration)會被加入到Spring容器中,Spring容器還會掃描所有的Bean並發現ViewResolver(也就是我們的容器裡多瞭個由viewResolver函數裡面創建的resolver,作為一個bean),然後使用這個ViewResolver來處理邏輯的View名稱。

上面的使用的是java的方式,有些人喜歡xml的方式,如下面:

[html]
<!– Resolve logical view names to .jsp resources in the /WEB-INF/views directory –> 
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> 
    <property name="prefix" value="/WEB-INF/views/" /> 
    <property name="suffix" value=".jsp" /> 
</bean> 
這個效果也是一樣的創建一個InternalResourceViewResolver類型的bean,設置其bean的屬性用來處理View名稱。
5、處理用戶輸入

我們總是要和用戶互動的,讓我們看看如何處理些簡單的用戶輸入,然後再輸出結果給用戶。在Spring MVC中有多種輸入、輸出的方式,讓我們看看最簡單的一種。這裡我們把上面的HomeController中增加一個的處理請求的方法,讓它能獲得兩個用戶輸入的字符,然後進行比較,並且把結果通過jsp輸出給用戶,看看增加的方法:

[java]
package xyz.sample.baremvc; 
  
import java.util.Comparator; 
  
import org.springframework.beans.factory.annotation.Autowired; 
import org.springframework.stereotype.Controller; 
import org.springframework.ui.Model; 
import org.springframework.web.bind.annotation.RequestMapping; 
import org.springframework.web.bind.annotation.RequestMethod; 
import org.springframework.web.bind.annotation.RequestParam; 
  
/**
 * Handles requests for the application home page.
 */ 
@Controller 
public class HomeController { 
  
    @Autowired 
    Comparator<String> comparator; 
  
    @RequestMapping(value = "/") 
    public String home() { 
        System.out.println("HomeController: Passing through…"); 
        return "home"; 
    } 
  
    @RequestMapping(value = "/compare", method = RequestMethod.GET) 
    public String compare(@RequestParam("input1") String input1, 
            @RequestParam("input2") String input2, Model model) { 
  
        int result = comparator.compare(input1, input2); 
        String inEnglish = (result < 0) ? "less than" : (result > 0 ? "greater than" : "equal to"); 
  
        String output = "According to our Comparator, '" + input1 + "' is " + inEnglish + "'" + input2 + "'"; 
  
        model.addAttribute("output", output); 
        return "compareResult"; 
    } 

        1、增加瞭一個compare函數,當用戶請求”/compare“這個路徑的時候,這個函數會用來處理用戶請求
        2、這個函數希望用戶通過GET請求的方式傳入兩個字符作為其參數,如果用戶沒有傳兩個指定名稱(input1和input2)的字符串來,則會提示HTTP 400的錯誤,然後這裡compare函數獲得用戶GET請求中的參數是通過@RequestParam這個註釋來實現的。

        3、我們使用自己定義的比較器來比較這兩個字符串。這裡註意到@Autowired,也就Spring通過在container中尋找類型同comparator相同(Comparator<String>)的組件,然後wired(連接)到comparator上面(可以理解為調用setComparator這樣)

        4、最後我們使用瞭Model來放置比較的結果,這樣View將可以獲得放在Model中的結果,這裡隻要把Model看做一個HashMap就可以瞭,名稱->數據的一個對應。放到Model中的數據主要為瞭View能夠訪問這些有用的數據。上面的例子中隻是個字符串,將來呈現到jsp上面。

我們創建一個對應的jsp文件來顯示結果,WEB-INF/views/compareResult.jsp:

[html] 
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> 
<%@ page session="false" %> 
<html> 
    <head> 
        <title>Result</title> 
    </head> 
    <body> 
        <h1><c:out value="${output}"></c:out></h1> 
    </body> 
</html> 
現在我們還需要創建一個給Controller中comparator註入的組件,下面是這個組件的代碼:
[java]
package xyz.sample.baremvc; 
  
import java.util.Comparator; 
import org.springframework.stereotype.Component; 
  
@Service 
public class CaseInsensitiveComparator implements Comparator<String> { 
    public int compare(String s1, String s2) { 
        assert s1 != null && s2 != null; 
        return String.CASE_INSENSITIVE_ORDER.compare(s1, s2); 
    } 

這個比較器是個很簡單的比較器,實現瞭Comparator<String>這個接口,這樣就可以註入到Controller中comparator去瞭。因為@Service也是@Component的一種,因此也是可以被自動掃描到的。當然我們也註意到瞭,有人要問這個可以不可以通過上面的@Configuration中的@Bean定義來做,當然答案是肯定得,同樣也是可以在XML中進行定義的。不同的定義方式讓這些類提供的控制層次會有高低不同之分。這個應該參考對應的annotation。

 
最後想瞭解更多Spring MVC的特性的話,可以看看這個 show case

發佈留言