Spring6:基於註解的Spring MVC(上篇)

五月的倉頡發表於2016-03-31

什麼是Spring MVC

Spring MVC框架是一個MVC框架,通過實現Model-View-Controller模式來很好地將資料、業務與展現進行分離。從這樣一個角度來說,Spring MVC和Structs、Structs2非常類似。Spring MVC的設計是圍繞DispatcherServlet展開的,DispatcherServlet負責將請求派發到特定的handler。通過可配置的hander mappings、view resolution、locale以及theme resolution來處理請求並且轉到對應的檢視。Spring MVC請求處理的整體流程如圖:

Spring MVC有基於註解版與基礎.xml版的兩種用法,不過現在的企業級開發基本都使用的是註解版,沒別的原因,就是方便而已。因此後面的程式碼示例,都是基於註解版本的,想了解基於.xml版本的Spring MVC的朋友可以自行上網查詢。

 

Spring MVC環境搭建

要開始本文後面的內容,自然要搭建一個Spring MVC的環境,那麼首先建立一個Java Web的工程,我建立的工程名字叫做SpringMVC,要搭建一個基礎功能的Spring MVC環境,必須引入的jar包是beans、context、core、expression、web、webmvc以及commons-logging。

然後,對web.xml新增一些內容:

<?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">
    
    <display-name></display-name>    
    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>
      
      <!-- 該監聽器將在Web容器啟動時啟用Spring -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    
    <!-- 處理由JavaBeans,Introspector的使用而引起的緩衝洩露,建議配置此監聽器 -->
    <listener>
        <listener-class>org.springframework.web.util.IntrospectorCleanupListener</listener-class>
    </listener>
  
      <!--configure the setting of springmvcDispatcherServlet and configure the mapping-->
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
              <param-name>contextConfigLocation</param-name>
              <param-value>classpath:springmvc-servlet.xml</param-value>
          </init-param>
          <load-on-startup>1</load-on-startup> 
    </servlet>
 
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

兩個listener不是必須的,但是servlet是必須的,url-pattern用於開發者選擇哪些路徑是需要讓Spring MVC來處理的。接著在classpath下按照我們約定的名字springmvc-servlet.xml寫一個xml檔案:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"   
    xmlns="http://www.springframework.org/schema/beans"  
    xmlns:mvc="http://www.springframework.org/schema/mvc"  
    xmlns:context="http://www.springframework.org/schema/context"  
    xmlns:aop="http://www.springframework.org/schema/aop"  
    xmlns:tx="http://www.springframework.org/schema/tx"  
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-4.2.xsd">    
    
    <context:annotation-config />    
    <context:component-scan base-package="com.xrq.controller"/>
    
    <!-- 配置檢視解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">  
        <!-- WebRoot到一指定資料夾檔案路徑 -->  
        <property name="prefix" value="/" />  
        <!-- 檢視名稱字尾  -->  
        <property name="suffix" value=".jsp" />  
    </bean>  
</beans> 

另外,由於使用了Spring,所以Tomcat啟動的時候預設會去WEB-INF下找applicationContext.xml,所以放一個空的applicationContext.xml到WEB-INF下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    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-4.2.xsd">
    
</beans>

寫一個Java POJO,用於處理具體的請求:

@Controller
@RequestMapping(value = "/test")
public class TestController
{
    @RequestMapping
    public String dispatchTest()
    {
        System.out.println("Enter TestController.dispatchTest");
        return "test";
    }
}

注意,這裡有一個@Controller,這個註解和@Service註解意思差不多,都表示該類是一個Spring Bean,這樣就不需要再在Spring檔案中為這個類定義Bean了。

另外,由於我前面在springmvc-servlet.xml中配置了prefix和suffix,因此return的時候就可以方便一些,不需要寫字首和字尾,Spring MVC預設會轉發到(請求轉發是Spring MVC預設的頁面跳轉方式)"/test.jsp"路徑下。

最後別忘了,因為在web.xml中設定了啟動啟用Spring,因此還需要寫一個applicationContext.xml(Spring檔案的預設名字),當然,裡面除了基本的宣告,什麼實際內容都不需要。最終,WebRoot資料夾應該是這麼一個結構:

最後,啟動容器,訪問"localhost:8080/SpringMVC/test",容器就會把該請求轉發到"localhost:8080/SpringMVC/test.jsp"頁面下了。

 

@RequestMapping註解

Spring MVC中最重要的註解應該就是@RequestMapping了,它是用於處理請求對映的。繼續看剛才的TestController:

@Controller
@RequestMapping(value = "/test")
public class TestController
{
    @RequestMapping
    public String dispatchTest()
    {
        System.out.println("Enter TestController.dispatchTest()");
        return "test";
    }
}

類上的RequestMapping是用於第一層匹配的。"localhost:8080/SpringMVC/test"和"localhost:8080/SpringMVC/add",value是test,自然走的是前者。

接著看,比如我在TestController中又定義了三個方法,此時類上不使用RequestMapping註解

@RequestMapping(value = "/add")
public String dispatchAddTest()
{
    System.out.println("Enter TestControll.dispatchAddTest()");
    return "test";
}
    
@RequestMapping(value = "/add/add")
public String dispatchAddAddTest()
{
    System.out.println("Enter TestControll.dispatchAddAddTest()");
    return "test";
}

@RequestMapping(value = "/del")
public String dispatchDelTest()
{
    System.out.println("Enter TestControll.dispatchDelTest()");
    return "test";
}

那麼這三個方法分別匹配的路徑是:

"localhost:8080/SpringMVC/add"
"localhost:8080/SpringMVC/add/add"
"localhost:8080/SpringMVC/del"

關於路徑匹配,再提一點,假如在類上和方法上都加了RequestMapping,那麼將會以類路徑為基準,再向方法路徑做匹配,比如:

@Controller
@RequestMapping(value = "/test/")
public class TestController
{
    @RequestMapping(value = "common")
    public String root()
    {
        System.out.println("Enter TestController.root()!");
        return "result";
    }
}

這種寫法,匹配的應當是:

"localhost:8080/SpringMVC/test/common"
"localhost:8080/SpringMVC/test/common/"
"localhost:8080/SpringMVC/test/common.html"
"localhost:8080/SpringMVC/test/common.jsp"
"localhost:8080/SpringMVC/test/common.vm"

類似這種的路徑,如果還想往"localhost:8080/SpringMVC/test/common/"再新增內容,那麼root()這個方法就無法匹配到了,必須再新增方法。多說一句,"/"一直是一個容易弄混的東西,我自己試驗的時候發現,RequestMapping裡面的value屬性,只要路徑不存在多級的關係,加不加"/"是沒有什麼影響的

另外,@RequestMapping還可以匹配請求型別,到底是GET還是POST還是其他的,這麼做:

@RequestMapping(method = RequestMethod.POST)
public String dispatchTest()
{
    System.out.println("Enter TestController.dispatchTest()");
    return "test";
}

這樣就指定了該方法只匹配"localhost:8080/SpringMVC/test"且請求方式為POST的請求。

前面頁面跳轉的方式都是轉發(dispatch)的方式,轉發在我看來未必是一種很好的方式,典型的就是處理表單的時候會有表單重複提交的問題,那麼如何使用重定向(redirect)的方式進行頁面跳轉?可以這麼寫Controller的方法,差別在於return部分:

@RequestMapping
public String dispatchTest(Test test)
{
    System.out.println("Enter TestController.dispatchTest(), test: " + test);
    return "redirect:/test.jsp";
}

最後,@RequestMapping中還有params、headers、consumes等幾個屬性,不過這幾個都不重要,也不常用,就不講了。

 

引數匹配

處理url也好、表單提交也好,引數匹配是非常重要的一個內容,萬幸,Spring MVC對引數請求的支援做得非常好----它會自動根據url或者表單中引數的名字和方法中同名形參進行匹配並賦值

舉一個例子:

@Controller
@RequestMapping(value = "/test")
public class TestController
{
    @RequestMapping
    public String dispatchTest(String testId, String ttestId)
    {
        System.out.println("Enter TestController.dispatchTest(), testId = " + testId + 
                ", ttestId = " + ttestId);
        return "test";
    }
}

此時,我訪問"localhost:8080/SpringMVC/test?testId=1&ttestId=2",控制檯列印出:

Enter TestController.dispatchTest(), testId = 1, ttestId = 2

不僅如此,方法中還可以放入一個實體類物件:

public class Test
{
    private String tid;
    private String nid;
    private String bid;
    
    public void setTid(String tid)
    {
        this.tid = tid;
    }
    
    public void setNid(String nid)
    {
        this.nid = nid;
    }
    
    public void setBid(String bid)
    {
        this.bid = bid;
    }
    
    public String toString()
    {
        return "tid = " + tid + ", nid = " + nid + ", bid = " + bid;
    }
}

注意,實體類物件中如果私有屬性不打算對外提供,getter可以沒有,但是為了Spring MVC可以將對應的屬性根據屬性名稱進行匹配並賦值,setter必須要有。把TestController稍作改造,傳入一個物件:

@RequestMapping
public String dispatchTest(Test test)
{
    System.out.println("Enter TestController.dispatchTest(), test: " + test);
    return "test";
}

此時我訪問"http://localhost:8080/SpringMVC/test?tid=0&bid=1&nid=2",控制檯上列印出:

Enter TestController.dispatchTest(), test: tid = 0, nid = 2, bid = 1

看到,引數完全匹配。

不僅如此,再多試驗一點:

@RequestMapping
public String dispatchTest(Test test1, Test test2, String tid, String nid)
{
    System.out.println("Enter TestController.dispatchTest(), test1:" + test1 + 
            "; test2:" + test2 + "; tid:" + tid + "; nid:" + nid);
    return "test";
}

訪問一樣地址"http://localhost:8080/SpringMVC/test?tid=0&bid=1&nid=2",結果是:

Enter TestController.dispatchTest(), test1:tid = 0, nid = 2, bid = 1; test2:tid = 0, nid = 2, bid = 1; tid:0; nid:2

結論就是:

  • 假如方法的引數是普通的字串,只要字串名字有和請求引數中的key完全匹配的,Spring MVC就會將完全匹配的自動賦值
  • 假如方法的引數是實體類,只要實體類中的引數有和請求引數中的key完全匹配的,Spring MVC就會將完全匹配的自動賦值

對於url如此,應用到表單中也是一樣的,有興趣的可以自己試驗一下。

相關文章