1. 程式人生 > >Spring MVC 學習總結(二)——控制器定義與@RequestMapping詳解

Spring MVC 學習總結(二)——控制器定義與@RequestMapping詳解

一、控制器定義

控制器提供訪問應用程式的行為,通常通過服務介面定義或註解定義兩種方法實現。 控制器解析使用者的請求並將其轉換為一個模型。在Spring MVC中一個控制器可以包含多個Action(動作、方法)。

1.1、實現介面Controller定義控制器

Controller是一個介面,處在包org.springframework.web.servlet.mvc下,介面中只有一個未實現的方法,具體的介面如下所示:

複製程式碼
package org.springframework.web.servlet.mvc;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import org.springframework.web.servlet.ModelAndView; //實現該介面的類獲得控制器功能與型別, 解析使用者的請求並將其轉換為一個模型 public interface Controller { //處理請求且返回一個模型與檢視物件 ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception; }
複製程式碼

在自定義控制器前先建立一個基於maven的web專案,新增包的依賴,pom.xml檔案如下:

複製程式碼
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId
>com.zhangguo</groupId> <artifactId>SpringMVC02</artifactId> <version>0.0.1</version> <packaging>war</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <spring.version>4.3.0.RELEASE</spring.version> </properties> <dependencies> <!--Spring框架核心庫 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <!-- Spring MVC --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <!-- JSTL --> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <!-- Servlet核心包 --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.0.1</version> <scope>provided</scope> </dependency> <!--JSP應用程式介面 --> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>2.1</version> <scope>provided</scope> </dependency> </dependencies> </project>
複製程式碼 View Code

如果不配置scope,會把jar包釋出,會跟容器裡的jar包衝突、scope要設定為provided,由容器提供,不會發布(或者不配這兩個依賴,在專案的Java BuildPath的Libraries裡新增Server Runtime)目前scope可以使用5個值:

compile:預設值,適用於所有階段,會隨著專案一起釋出。

provided:類似compile,期望JDK、容器或使用者會提供這個依賴。如servlet.jar。

runtime:只在執行時使用,如JDBC驅動,適用執行和測試階段。test,只在測試時使用,用於編譯和執行測試程式碼。不會隨專案釋出。

system:類似provided,需要顯式提供包含依賴的jar,Maven不會在Repository中查詢它。

建立一個名為Foo的類,實現介面Controller,重寫handleRequest方法,程式碼如下:

複製程式碼
package com.zhangguo.springmvc02.controllers;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

/*
 * 定義控制器
 */
public class FooController implements Controller {

    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        //返回一個模型檢視物件,指定路徑,指定模型的名稱為message,值為一段字串
        return new ModelAndView("foo/index", "message", "Hello,我是通過實現介面定義的一個控制器");
    }

}
複製程式碼

在WEB-INF/views/foo目錄下建立一個名為index.jsp的檢視,內容如下:

複製程式碼
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Foo</title>
</head>
<body>
${message}
</body>
</html>
複製程式碼

修改springmvc-servlet.xml配置檔案,增加一個控制器bean的宣告,具體內容如下:

複製程式碼
<?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:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
         http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context-4.3.xsd
        http://www.springframework.org/schema/mvc 
        http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd">

    <!-- 自動掃描包,實現支援註解的IOC -->
    <context:component-scan base-package="com.zhangguo.springmvc02" />

    <!-- Spring MVC不處理靜態資源 -->
    <mvc:default-servlet-handler />

    <!-- 支援mvc註解驅動 -->
    <mvc:annotation-driven />

    <!-- 檢視解析器 -->
    <bean
        class="org.springframework.web.servlet.view.InternalResourceViewResolver"
        id="internalResourceViewResolver">
        <!-- 字首 -->
        <property name="prefix" value="/WEB-INF/views/" />
        <!-- 字尾 -->
        <property name="suffix" value=".jsp" />
    </bean>

    <bean name="/foo" class="com.zhangguo.springmvc02.controllers.FooController"></bean>
</beans>
複製程式碼

基中name是訪問路徑,class是自定義的控制器的全名稱。執行後的結果如下:

小結:實現介面Controller定義控制器是較老的辦法,缺點是:一個控制器中只有一個Action,如果要多個Action則需要定義多個Controller;定義的方式比較麻煩;Spring 2.5以後採用註解的方式定義解決這引起問題。

1.2、使用註解@Controller定義控制器

org.springframework.stereotype.Controller註解型別用於宣告Spring類的例項是一個控制器(在講IOC時還提到了另外3個註解);Spring可以使用掃描機制來找到應用程式中所有基於註解的控制器類,為了保證Spring能找到你的控制器,需要在配置檔案中宣告元件掃描。

建立一個名了Bar的類,定義為一個控制器,類的具體實現如下:

複製程式碼
package com.zhangguo.springmvc02.controllers;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * 定義控制器
 */
//BarController類的例項是一個控制器,會自動新增到Spring上下文中
@Controller
public class BarController {
    //對映訪問路徑
    @RequestMapping("/bar")
    public String index(Model model){
        //Spring MVC會自動例項化一個Model物件用於向檢視中傳值
        model.addAttribute("message", "這是通過註解定義的一個控制器中的Action");
        //返回檢視位置
        return "foo/index";
    }
}
複製程式碼

還要需要修改Spring mvc配置檔案,啟用自動元件掃描功能,在beans中增加如下配置:

    <!-- 自動掃描包,實現支援註解的IOC -->
    <context:component-scan base-package="com.zhangguo.springmvc02" />

base-package屬性用於指定掃描的基礎包,可以縮小掃描的範圍。執行結果如下:

小結:從程式碼與執行結果可以看出BarController與FooController同時都指定了一個檢視foo/index.jsp,但是頁面結果的結果是不一樣的,從這裡可以看出檢視是被複用的,而控制器與檢視之間是弱偶合關係。

 二、@RequestMapping詳解

@RequestMapping註釋用於對映url到控制器類或一個特定的處理程式方法。可用於類或方法上。用於類上,表示類中的所有響應請求的方法都是以該地址作為父路徑。該註解共有8個屬性,註解原始碼如下:

複製程式碼
package org.springframework.web.bind.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.Callable;

import org.springframework.core.annotation.AliasFor;

/**
 * 用於對映url到控制器類或一個特定的處理程式方法.
 */
//該註解只能用於方法或型別上
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {

    /**
     * 指定對映的名稱
     */
    String name() default "";

    /**
     * 指定請求的路徑對映,指定的地址可以是uri模板,別名為path
     */
    @AliasFor("path")
    String[] value() default {};

    /** 別名為value,使用path更加形象
     * 只有用在一個Servlet環境:路徑對映URI(例如“/myPath.do”)。
     * Ant風格的路徑模式,同時也支援(例如,“/myPath/*.do”)。在方法層面,在主要的對映在型別級別表示相對路徑(例如,“edit.do”)
     * 的支援。路徑對映的URI可能包含佔位符(例如“/$ {}連線”)
     */
    @AliasFor("value")
    String[] path() default {};

    /**
     * 指定請求謂詞的型別如GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE. 收窄請求範圍 The
     * HTTP request methods to map to, narrowing the primary mapping: GET, POST,
     * HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE.
     */
    RequestMethod[] method() default {};

    /**
     * 對映請求的引數,收窄請求範圍 The parameters of the mapped request, narrowing the
     * primary mapping.
     */
    String[]params() default {};

    /**
     * 對映請求頭部,收窄請求範圍 The headers of the mapped request, narrowing the primary
     * mapping. RequestMapping(value = "/something", headers =
     * "content-type=text/*")
     */
    String[] headers() default {};

    /**
     * 指定處理請求的提交內容型別(Content-Type),例如application/json, text/html,收窄請求範圍 The
     * consumable media types of the mapped request, narrowing the primary
     * mapping.
     */
    String[] consumes() default {};

    /**
     * 指定返回的內容型別,僅當request請求頭中的(Accept)型別中包含該指定型別才返回 The producible media types
     * of the mapped request, narrowing the primary mapping. produces =
     * "text/plain" produces = {"text/plain", "application/*"} produces =
     * "application/json; charset=UTF-8"
     */
    String[] produces() default {};
}
複製程式碼

從上面的原始碼可以發現除了name基本都是陣列型別,在設定時我們可以指定單個值,如@RequestMapping(value="/foo");也可以同時指定多個值如:@RequestMapping(value={"/foo","/bar"})。

2.1、value 屬性指定對映路徑或URL模板

指定請求的實際地址,指定的地址可以是URL模板,正則表示式或路徑佔位,該屬性與path互為別名關係,@RequestMapping("/foo")} 與 @RequestMapping(path="/foo")相同。該屬性是使用最頻繁,最重要的一個屬性,如果只指定該屬性時可以把value略去。Spring Framework 4.2引入了一流的支援宣告和查詢註釋屬性的別名。@AliasFor註解可用於宣告一雙別名屬性,來給註解的屬性起別名, 讓使用註解時, 更加的容易理解(比如給value屬性起別名, 更容易讓人理解)。先看一個官網的示例:

複製程式碼
@Controller
@RequestMapping("/appointments")
public class AppointmentsController {
    private final AppointmentBook appointmentBook;

    @Autowired
    public AppointmentsController(AppointmentBook appointmentBook) {
        this.appointmentBook = appointmentBook;
    }

    @RequestMapping(method = RequestMethod.GET)
    public Map<String, Appointment> get() {
        return appointmentBook.getAppointmentsForToday();
    }

    @RequestMapping(value = "/{day}", method = RequestMethod.GET)
    public Map<String, Appointment> getForDay(@PathVariable @DateTimeFormat(iso = ISO.DATE) Date day, Model model) {
        return appointmentBook.getAppointmentsForDay(day);
    }

    @RequestMapping(value = "/new", method = RequestMethod.GET)
    public AppointmentForm getNewForm() {
        return new AppointmentForm();
    }

    @RequestMapping(method = RequestMethod.POST)
    public String add(@Valid AppointmentForm appointment, BindingResult result) {
        if (result.hasErrors()) {
            return "appointments/new";
        }
        appointmentBook.addAppointment(appointment);
        return "redirect:/appointments";
    }
}
複製程式碼

2.1.1、指定具體路徑字元

2.1.1.1 只註解方法

複製程式碼
@Controller
public class FooBarController {
    @RequestMapping("/action1")
    public String action1(){
        return "foo/index";
    }
}
複製程式碼

訪問路徑:http://localhost:8087/SpringMVC02/action1

2.1.1.2 同時註解類與方法

複製程式碼
@Controller
@RequestMapping("/foobar")
public class FooBarController {
    @RequestMapping("/action1")
    public String action1(){
        return "foo/index";
    }
}
複製程式碼

訪問路徑:http://localhost:8087/SpringMVC02/foobar/action1

需要先指定類的路徑再指定方法的路徑

2.1.1.3 當value為空值

註解在方法上時,如果value為空則表示該方法為類下預設的Action。

複製程式碼
@Controller
@RequestMapping("/foobar")
public class FooBarController {
    @RequestMapping("/action1")
    public String action1(Model model){
        //在模型中新增屬性message值為action1,渲染頁面時使用
        model.addAttribute("message", "action1");
        return "foo/index";
    }
    
    @RequestMapping
    public String action2(Model model){
        //在模型中新增屬性message值為action2,渲染頁面時使用
        model.addAttribute("message", "action2");
        return "foo/index";
    }
}
複製程式碼

訪問action2的路徑是:http://localhost:8087/SpringMVC02/foobar,如果加上action2就錯誤了。

註解在類上時,當value為空值則為預設的控制器,可以用於設定專案的起始頁。

複製程式碼
@Controller
@RequestMapping
public class FooBarController {
    @RequestMapping("/action1")
    public String action1(Model model){
        //在模型中新增屬性message值為action1,渲染頁面時使用
        model.addAttribute("message", "action1");
        return "foo/index";
    }
    
    @RequestMapping
    public String action2(Model model){
        //在模型中新增屬性message值為action2,渲染頁面時使用
        model.addAttribute("message", "action2");
        return "foo/index";
    }
}
複製程式碼

訪問路徑:http://localhost:8087/SpringMVC02/,同時省去了控制器名與Action名稱,可用於歡迎頁。

訪問action1的路徑是:http://localhost:8087/SpringMVC02/action1

2.1.2、路徑變數佔位,URI模板模式

在Spring MVC可以使用@PathVariable 註釋方法引數的值繫結到一個URI模板變數。

複製程式碼
    @RequestMapping("/action3/{p1}/{p2}")
    public String action3(@PathVariable int p1,@PathVariable int p2,Model model){
        model.addAttribute("message", p1+p2);
        return "foo/index";
    }
複製程式碼

執行結果:

使用路徑變數的好處:使路徑變得更加簡潔;獲得引數更加方便,框架會自動進行型別轉換。通過路徑變數的型別可以約束訪問引數,如果型別不一樣,則訪問不到action,如這裡訪問是的路徑是/action3/1/a,則路徑與方法不匹配,而不會是引數轉換失敗。

2.1.3、正則表示式模式的URI模板

複製程式碼
    @RequestMapping(value="/action4/{id:\\d{6}}-{name:[a-z]{3}}")
    public String action4(@PathVariable int id,@PathVariable String name,Model model){
        model.addAttribute("message", "id:"+id+" name:"+name);
        return "foo/index";
    }
複製程式碼

正則要求id必須為6位的數字,而name必須為3位小寫字母,訪問結果如下:

2.1.4、矩陣變數@MatrixVariable

矩陣變數可以出現在任何路徑段,每個矩陣變數用“;”分隔。例如:“/汽車;顏色=紅;年=2012”。多個值可以是“,”分隔“顏色=紅、綠、藍”或變數名稱可以重複“顏色=紅;顏色=綠色;顏色=藍”,如下所示:

複製程式碼
    // GET /pets/42;q=11;r=22
    @RequestMapping(value = "/pets/{petId}")
    public void findPet(@PathVariable String petId, @MatrixVariable int q) {
        // petId == 42
        // q == 11
    }
複製程式碼 複製程式碼
    // 矩陣變數
    @RequestMapping(value = "/action5/{name}")
    public String action5(Model model, 
            @PathVariable String name,   //路徑變數,用於獲得路徑中的變數name的值
            @MatrixVariable String r,
            @MatrixVariable(required = true) String g,  //引數g是必須的
            @MatrixVariable(defaultValue = "99", required = false) String b) {  //引數b不是必須的,預設值是99
        model.addAttribute("message", name + " is #" + r + g + b);
        return "foo/index";
    }
    //Get http://localhost:8087/SpringMVC02/action5/the%20book%20color;r=33;g=66
    //the book color is #336699
複製程式碼

預設是不允許使用矩陣變數的,需要設定配置文中的RequestMappingHandlerMapping的屬性removeSemicolonContent為false;在annotation-driven中增加屬性enable-matrix-variables="true",修改後的springmvc-servlet.xml檔案如下:

複製程式碼
    <!-- 支援mvc註解驅動 -->
    <mvc:annotation-driven enable-matrix-variables="true" />

    <!-- 配置對映媒體型別的策略 -->
    <bean
        class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
        <property name="removeSemicolonContent" value="false" />
    </bean>
複製程式碼

訪問結果如下:

2.1.5、Ant風格路徑模式

@RequestMapping註解也支援ant風格的路徑模式,如/myPath/*.do,/owners/*/pets/{petId},示例程式碼如下:

複製程式碼
    //Ant風格路徑模式
    @RequestMapping(value = "/action6/*.do")
    public String action6(Model model){ 
        model.addAttribute("message","Ant風格路徑模式");
        return "foo/index";
    }
複製程式碼

執行結果:

當然還有關於路徑匹配的規則,特殊的優先順序高過一般的,更多規則可以參考官方幫助。

2.2、method屬性指定謂詞型別

用於約束請求的謂詞型別,可以收窄請求範圍。指定請求謂詞的型別如GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE,如下程式碼所示:

複製程式碼
    //謂詞型別
    @RequestMapping(value = "/action6",method={RequestMethod.POST,RequestMethod.DELETE})
    public String action6(Model model) {
        model.addAttribute("message", "請求謂詞只能是POST與DELETE");
        return "foo/index";
    }
複製程式碼

要訪問action7請求謂詞型別必須是POST或者為DELETE,當我們從瀏覽器的URL欄中直接請求時為一個GET請求,則結果是405,如下所示:

如果將POST修改為GET則正常了,如下所示:

複製程式碼
    //謂詞型別