1. 程式人生 > 其它 >Spring MVC 學習總結(三)——請求處理方法Action詳解

Spring MVC 學習總結(三)——請求處理方法Action詳解

目錄

Spring MVC中每個控制器中可以定義多個請求處理方法,我們把這種請求處理方法簡稱為Action,每個請求處理方法可以有多個不同的引數,以及一個多種型別的返回結果。

一、Action引數型別

如果在請求處理方法中需要訪問HttpSession物件,則可以新增HttpSession作為引數,Spring會將物件正確的傳遞給方法,如:public String action(HttpSession session);若需要訪問客戶端語言環境和HttpServletRequest物件,則可以在方法簽名上包含這樣的引數,如:public String action(HttpServletRequest request,Locale locale)。可以在請求中出現的引數型別有:

org.springframework.web.context.request.WebRequest
org.springframework.web.context.request.NativeWebRequest
java.util.Locale 當前請求的語言環境
java.util.TimeZone 時區
java.io.InputStream或java.io.Reader
java.io.OutputStream或java.io.Writer
org.springframework.http.HttpMethod
java.security.Principal
HttpEntity <?>引數用於訪問Servlet的HTTP請求的標題和內容
java.util.Map / org.springframework.ui.Model / org.springframework.ui.ModelMap 檢視隱含模型
org.springframework.web.servlet.mvc.support.RedirectAttributes 重定向
命令或表單物件
基本資料型別,如int,String,double...
複雜資料型別,如自定義的POJO物件
HandlerAdapter
org.springframework.validation.Errors / org.springframework.validation.BindingResult 驗證結果
org.springframework.web.bind.support.SessionStatus 會話狀態
org.springframework.web.util.UriComponentsBuilder
@PathVariable 註解引數訪問URI模板變數。
@MatrixVariable 註釋引數用於訪問位於URI路徑段鍵值對對,矩陣變數。
@RequestParam 註解引數訪問特定的Servlet請求引數,請求引數繫結。
@RequestHeader 註解引數訪問特定的se​​rvlet請求HTTP標頭,對映請求頭。
@RequestBody 註解引數訪問HTTP請求主體,註解對映請求體
@RequestPart 註解引數訪問“的multipart / form-data的”請求部分的內容。處理客戶端上傳檔案,多部分檔案上傳的支援
@SessionAttribute 註解引數會話屬性
@RequestAttribute 註解引數訪問請求屬性

1.1、自動引數對映

1.1.1、基本資料型別

方法的引數可以是任意基本資料型別,如果方法引數名與http中請求的引數名稱相同時會進行自動對映,檢視foo目錄下的index.jsp與示例程式碼如下:

    // 自動引數對映
    @RequestMapping("/action0")
    public String action0(Model model, int id, String name) {
        model.addAttribute("message", "name=" + name + ",id=" + id);
        return "foo/index";
    }
<%@ 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>

執行結果如下:

包裝型別也一樣,但如果引數中沒有對應名稱與型別的資料則會異常。

1.1.2、自定義資料型別

除了基本資料型別,也可以自定義的資料型別,如一個自定義的POJO物件,Spring MVC會通過反射把請中的引數設定到物件中,轉換型別,示例程式碼如下:

package com.zhangguo.springmvc03.entities;

import java.io.Serializable;

/** * 產品 */
public class Product implements Serializable {
    private static final long serialVersionUID = 1L;
    private int id;
    private String name;
    private double price;

    public Product() {
    }

    public Product(String name, double price) {
        super();
        this.name = name;
        this.price = price;
    }

    public Product(int id, String name, double price) {
        super();
        this.id = id;
        this.name = name;
        this.price = price;
    }

    @Override
    public String toString() {
        return "編號(id):" + this.getId() + ",名稱(name):" + this.getName() + ",價格(price):" + this.getPrice();
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }
}
    // 自動引數對映自定義資料型別
    @RequestMapping("/action01")
    public String action01(Model model, Product product) {
        model.addAttribute("message", product);
        return "foo/index";
    }

執行結果如下:

示例中使用的是的URL中的引數,其實也可以是客戶端提交的任意引數,特別是表單中的資料。

1.1.3、複雜資料型別

這裡指的複雜資料型別指的是一個自定義型別中還包含另外一個物件型別,如使用者型別中包含產品物件:

package com.zhangguo.springmvc03.entities;

public class User {
    private String username;
    private Product product;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public Product getProduct() {
        return product;
    }

    public void setProduct(Product product) {
        this.product = product;
    }
}

示例程式碼如下:

    // 自動引數映射覆雜資料型別
    @RequestMapping("/action02")
    public String action02(Model model, User user) {
        model.addAttribute("message", user.getUsername() + "," + user.getProduct().getName());
        return "foo/index";
    }

測試執行結果:

為了方便這裡我使用的是url,這裡當然可以是一個表單,如下程式碼所示:

<form method="post" action="foo/action02">
     username:<input name="username" /><br/>
     pdctname:<input name="product.name" /><br/>
    <button>提交</button>
</form>

1.1.4、陣列

方法一:

提交時使用param1=aaa&param1=bbb&param1=3

接收時使用String param1[] 這種引數既可以獲取陣列的值

示例:

    //3.自動引數對映陣列資料型別
    @RequestMapping("/act03")
    public String act03(Model model,Integer[] id){
        model.addAttribute("msg",Arrays.toString(id));
        return "hi";
    }

結果:

方法二:

提交時使用param1=aaa&param1=bbb&param1=3

接收時使用List<String> param1 這種引數既可以獲取陣列的值

示例:

POJO Car.java

package com.zhangguo.springmvc01.entities;

import java.util.ArrayList;
import java.util.List;

/**車*/
public class Car {
    /**編號*/
    private int id;
    /**名稱*/
    private String name;
    /**價格*/
    private double price;
    /**尺寸*/
    private Size size;

    private List<Integer> ids;
    public List<Integer> getIds() {
        return ids;
    }

    public void setIds(List<Integer> ids) {
        this.ids = ids;
    }

    public Car(int id, String name, double price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }

    public Car() {
    }

    public Size getSize() {
        return size;
    }

    public void setSize(Size size) {
        this.size = size;
    }

    public static List<Car> getCars() {
        return cars;
    }

    public static void setCars(List<Car> cars) {
        Car.cars = cars;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Car{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", price=" + price +
                ", size=" + size +
                ", ids=" + ids +
                '}';
    }

    public static List<Car> cars=new ArrayList<>();
    static {
        cars.add(new Car(101,"朗逸",16.59));
        cars.add(new Car(102,"菲斯塔",15.50));
        cars.add(new Car(103,"雅閣",25.98));
        cars.add(new Car(104,"卡羅拉",17.58));
        cars.add(new Car(105,"軒逸",16.15));
    }
}

Action

    @RequestMapping("/act02")
    public String act02(Model model,Car car){
        model.addAttribute("msg",car);
        return "hi";
    }

結果:

1.1.5、List集合型別

不能直接在action的引數中指定List<T>型別,定義一個型別包裝List集合在其中,ProductList類如下所示:

package com.zhangguo.springmvc03.entities;

import java.util.List;

//產品集合
public class ProductList {
    private List<Product> items;

    public List<Product> getItems() {
        return items;
    }

    public void setItems(List<Product> items) {
        this.items = items;
    }
}

定義的action程式碼如下所示:

    // 集合型別
    @RequestMapping("/action03")
    public String action03(Model model, ProductList products) {
        model.addAttribute("message", products.getItems().get(0) + "<br/>" + products.getItems().get(1));
        return "foo/index";
    }

在url中模擬表單資料,提交後的結果如下所示:

這裡同樣可以使用一個表單向伺服器提交資料。

TomCat高版本可能會產生如下錯誤:

java.lang.IllegalArgumentException: Invalid character found in the request target. The valid characters are defined in RFC 7230 and RFC 3986
    at org.apache.coyote.http11.Http11InputBuffer.parseRequestLine(Http11InputBuffer.java:479)
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:684)
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:806)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1498)
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.lang.Thread.run(Thread.java:745)

Get請求新的安全規範要求URL中不能直接帶[],如下所示:

25-Oct-2018 14:12:09.277 警告 [http-nio-8080-exec-2] org.apache.tomcat.util.http.parser.HttpParser.<clinit> Character [,] is not allowed and will continue to be rejected

解決辦法(四種):

1、替換url請求。不用{}[]特殊字元! * ’( ) ; : @ & = + $ , / ? # [ ])

2、對請求編碼解碼。 UrlDecode、UrlEncode

3、配置Tomcat對字元的支援:

  3.1、更換Tomcat版本 (注,Tomcat從 7.0.73, 8.0.39, 8.5.7 版本後添加了對Url的限制。)

  3.2、配置tomcat支援|{}等字元的方法是:

在 conf/catalina.properties 中最後新增一行:

org.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH=true

tomcat.util.http.parser.HttpParser.requestTargetAllow=|{}[]

注3.2這種方法從8.5以後就被放棄的了,新的方法如下:

tomcat.util.http.parser.HttpParser.requestTargetAllowis deprecated since Tomcat 8.5:tomcat official doc.

You can userelaxedQueryChars / relaxedPathCharsin the connectors definition to allow these chars:tomcat official doc.

修改conf/server.xml檔案,如下所示:

    <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" URIEncoding="utf-8" relaxedQueryChars="],["/>

1.1.6、Map集合型別

Map與List的實現方式基本一樣,這裡先定義了一個包裝Map的型別ProductMap,程式碼如下所示:

package com.zhangguo.springmvc03.entities;

import java.util.Map;

/**
 * * 產品字典
 */
public class ProductMap {
    private Map<String, Product> items;

    public Map<String, Product> getItems() {
        return items;
    }

    public void setItems(Map<String, Product> items) {
        this.items = items;
    }
}

Action的定義如下:

    // Map型別
    @RequestMapping("/action04")
    public String action04(Model model, ProductMap map) {
        model.addAttribute("message", map.getItems().get("p1") + "<br/>" + map.getItems().get("p2"));
        return "foo/index";
    }

測試執行結果如下:

集合型別基本都一樣,set也差不多,問題是如果為了獲得一個集合需要刻意去包裝會很麻煩,可以通過@RequestParam結合@RequestBody等註解完成。

1.2、@RequestParam引數繫結

簡單的引數可以使用上一節中講過的自動引數對映,複雜一些的需使用@RequestParam完成,雖然自動引數對映很方便,但有些細節是不能處理的,如引數是否為必須引數,名稱沒有辦法指定,引數的預設值就沒有有辦法做到了。如果使用@RequestParam可以實現請求引數繫結,Spring MVC會自動查詢請求中的引數轉型別並將與引數進行繫結,示例程式碼如下:

1.2.1、基本資料型別繫結與註解屬性

package com.zhangguo.springmvc03.controllers;

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

@Controller
@RequestMapping("/foo")
public class FooController {
    @RequestMapping("/action1")
    public String action1(Model model, @RequestParam(required = false, defaultValue = "99") int id) {
        model.addAttribute("message", id);
        return "foo/index";
    }
}

@RequestParam共有4個註解屬性,required屬性表示是否為必須,預設值為true,如果請求中沒有指定的引數會報異常;defaultValue用於設定引數的預設值,如果不指定值則使用預設值,只能是String型別的。name與value互為別名關係用於指定引數名稱。

執行結果:

1.2.2、List與陣列繫結基本資料型別

在上一節中我們使用自動引數對映是不能直接完成List與陣列繫結的,結合@RequestParam可以輕鬆實現,示例程式碼如下所示:

    // List集合與陣列型別
    @RequestMapping("/action05")
    public String action05(Model model, @RequestParam("u") List<String> users) {
        model.addAttribute("message", users.get(0) + "," + users.get(1));
        return "foo/index";
    }

執行結果:

直接在URL中輸入測試資料可以繫結成功,使用表單同樣可行,頁面指令碼如下:

<form action="bar/action11" method="post">
    <p>
        <label>愛好:</label> 
        <input type="checkbox" value="15" name="id" />閱讀
         <input type="checkbox" value="20" name="id" />上網
         <input type="checkbox" value="73" name="id" />電遊
    </p>
    <button>提交</button>
</form>

請求處理方法action程式碼如下:

    // List與陣列繫結基本資料型別
    @RequestMapping("/action11")
    public String action11(Model model, @RequestParam("id") List<Integer> ids) {
        model.addAttribute("message", Arrays.deepToString(ids.toArray()));
        return "bar/index";
    }

執行結果:

@RequestParam("id")是必須的,因為頁面中的表單name的名稱為id,所有伺服器在收集資料時應該使用id頁非ids,如果同名則可以省去。

1.2.3、@RequestBody

@RequestBody 註解將HTTP請求正文插入方法中,使用適合的 HttpMessageConverter將請求體寫入某個物件。
作用:
1) 該註解用於讀取Request請求的body部分資料,使用系統預設配置的HttpMessageConverter進行解析,然後把相應的資料繫結到要返回的物件上;
2) 再把HttpMessageConverter返回的物件資料繫結到 controller中方法的引數上。

使用時機:

A) GET、POST方式提時, 根據request header Content-Type的值來判斷:

application/x-www-form-urlencoded, 可選(即非必須,因為這種情況的資料@RequestParam, @ModelAttribute也可以處理,當然@RequestBody也能處理);
multipart/form-data, 不能處理(即使用@RequestBody不能處理這種格式的資料);
其他格式, 必須(其他格式包括application/json, application/xml等。這些格式的資料,必須使用@RequestBody來處理);

B) PUT方式提交時, 根據request header Content-Type的值來判斷:

application/x-www-form-urlencoded, 必須;multipart/form-data, 不能處理;其他格式, 必須;

說明:request的body部分的資料編碼格式由header部分的Content-Type指定;

例如:

@RequestMapping(value = "user/login")
@ResponseBody
// 將ajax(datas)發出的請求寫入 User 物件中
public User login(@RequestBody User user) { 
// 這樣就不會再被解析為跳轉路徑,而是直接將user物件寫入 HTTP 響應正文中
return user; 
}

@requestBody註解常用來處理content-type不是預設的application/x-www-form-urlcoded編碼的內容,比如說:application/json或者是application/xml等。一般情況下來說常用其來處理application/json型別。

通過@requestBody可以將請求體中的JSON字串繫結到相應的bean上,當然,也可以將其分別繫結到對應的字串上。
例如:

    $.ajax({
        url:"/login",
        type:"POST",
        data:'{"userName":"admin","pwd","admin123"}',
        content-type:"application/json charset=utf-8",
        success:function(data){
          alert("request success ! ");
        }
    });

action:

    @requestMapping("/login")
    public void login(@requestBody String userName,@requestBody String pwd){
      System.out.println(userName+" :"+pwd);
    }

這種情況是將JSON字串中的兩個變數的值分別賦予了兩個字串,但是呢假如我有一個User類,擁有如下欄位:
String userName;
String pwd;
那麼上述引數可以改為以下形式:@requestBody User user 這種形式會將JSON字串中的值賦予user中對應的屬性上
需要注意的是,JSON字串中的key必須對應user中的屬性名,否則是請求不過去的。

1.2.4、List與陣列直接繫結自定義資料型別與AJAX

上一小節中我們繫結的集合中存放的只是基本資料型別,如果需要直接繫結更加複雜的資料型別則需要使用@RequestBody與@ResponseBody註解了,先解釋一下他們的作用:

@RequestBody 將HTTP請求正文轉換為適合的HttpMessageConverter物件。
@ResponseBody 將內容或物件作為 HTTP 響應正文返回,並呼叫適合HttpMessageConverter的Adapter轉換物件,寫入輸出流。

AnnotationMethodHandlerAdapter將會初始化7個轉換器,可以通過呼叫AnnotationMethodHandlerAdapter的getMessageConverts()方法來獲取轉換器的一個集合 List<HttpMessageConverter>,7個轉換器型別分別是

ByteArrayHttpMessageConverter
StringHttpMessageConverter
ResourceHttpMessageConverter
SourceHttpMessageConverter
XmlAwareFormHttpMessageConverter
Jaxb2RootElementHttpMessageConverter
MappingJacksonHttpMessageConverter

@RequestBody預設接收的Content-Type是application/json,因此傳送POST請求時需要設定請求報文頭資訊,否則Spring MVC在解析集合請求引數時不會自動的轉換成JSON資料再解析成相應的集合,Spring預設的json協議解析由Jackson完成。要完成這個功能還需要修改配置環境,具體要求如下:

a)、修改Spring MVC配置檔案,啟用mvc註解驅動功能,<mvc:annotation-driven />

b)、pom.xml,新增jackson依賴,新增依賴的配置內容如下:

    <!--jackson-->
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.7.4</version>
    </dependency>

    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-core</artifactId>
      <version>2.7.4</version>
    </dependency>

    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-annotations</artifactId>
      <version>2.7.4</version>
    </dependency>

c)、ajax請求時需要設定屬性dataType 為 json,contentType 為 'application/json;charse=UTF-8',data 轉換成JSON字串,如果條件不滿足有可能會出現415異常。

Action定義的示例程式碼如下:

    // List與陣列直接繫結自定義資料型別與AJAX
    @RequestMapping("/action21")
    public void action21(@RequestBody List<Product> products, HttpServletResponse response) throws IOException {
        response.setCharacterEncoding("UTF-8");
        System.out.println(Arrays.deepToString(products.toArray()));
        response.getWriter().write("新增成功");
    }

action21的引數@RequestBody List<Product> products是接收從客戶端傳送到伺服器的產品集合,預設的請求內容並非是application/json,而是:application/x-www-form-urlencoded,在引數前增加@RequestBody的作用是讓Spring MVC在收到客戶端請求時將選擇合適的轉換器將引數轉換成相應的物件。action22的返回值為List<Product>,且在方法上有一個註解@ResponseBody,系統會使用jackson將該物件自動序列化成json字元;在客戶端請求時設定內容型別為application/json,定義一個myform21.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>List與陣列直接繫結自定義資料型別與AJAX</title>
</head>
<body>
    <button type="button" onclick="addPdts_click1();">向伺服器傳送json</button>
    <button type="button" onclick="addPdts_click2();">接收伺服器返回的json</button>
    <p id="msg"></p>
    <script type="text/javascript"
        src="<c:url value="/scripts/jQuery1.11.3/jquery-1.11.3.min.js"/>"></script>
    <script type="text/javascript">
        var products = new Array();
        products.push({
            id : 1,
            name : "iPhone 6 Plus",
            price : 4987.5
        });
        products.push({
            id : 2,
            name : "iPhone 7 Plus",
            price : 5987.5
        });
        products.push({
            id : 3,
            name : "iPhone 8 Plus",
            price : 6987.5
        });
        function addPdts_click1() {
            $.ajax({
                type : "POST",
                //請求謂詞型別
                url : "bar/action21",
                data : JSON.stringify(products), //將products物件轉換成json字串
                contentType : "application/json;charset=UTF-8",
                //傳送資訊至伺服器時內容編碼型別,(預設: "application/x-www-form-urlencoded")
                dataType : "text", //預期伺服器返回的資料型別
                success : function(result) {
                    $("#msg").html(result);
                }
            });
        }
        function addPdts_click2() {
            $.ajax({
                type : "POST",
                //請求謂詞型別
                url : "bar/action22",
                data : JSON.stringify(products), //將products物件轉換成json字串
                contentType : "application/json;charset=UTF-8",
                //傳送資訊至伺服器時內容編碼型別,(預設: "application/x-www-form-urlencoded")
                dataType : "json", //預期伺服器返回的資料型別
                success : function(result) {
                    var str = "";
                    $.each(result, function(i, obj) {
                        str += "編號:" + obj.id + ",名稱:" + obj.name + ",價格:"+ obj.price + "<br/>";
                    });
                    $("#msg").html(str);
                }
            });
        }
    </script>
</body>
</html>

頁面中有兩個方法,第一個方法是實現將一個json集合傳送到伺服器並對映成一個List集合;第二個方法是實現接收伺服器返回的json物件。

點選按鈕1時的執行結果如下:

控制檯輸出:

[編號(id):1,名稱(name):iPhone 6 Plus,價格(price):4987.5, 編號(id):2,名稱(name):iPhone 7 Plus,價格(price):5987.5, 編號(id):3,名稱(name):iPhone 8 Plus,價格(price):6987.5]

點選按鈕2時的執行結果如下:

1.3、重定向與Flash屬性

在一個請求處理方法Action中如果返回結果為“index”字元則表示轉發到檢視index,有時候我們需要重定向,則可以在返回的結果前加上一個字首“redirect:”,可以重定向到一個指定的頁面也可以是另一個action,示例程式碼如下:

    // 重定向
    @RequestMapping("/action2")
    public String action2(Model model) {
        return "foo/index";
    }

    @RequestMapping("/action3")
    public String action3(Model model) {
        model.addAttribute("message", "action3Message");
        return "redirect:action2";
    }

當請求http://localhost:8087/SpringMVC02/foo/action3時執行結果如下:

在action3中返回的結果為redirect:action2,則表示重定向到action2這個請求處理方法,所有重定向都是以當前路徑為起點的,請注意路徑。在action3向model中添加了名稱message的資料,因為重定向到action2中會發起2次請求,為了保持action3中的資料Spring MVC自動將資料重寫到了url中。為了實現重定向時傳遞複雜資料,可以使用Flash屬性,示例程式碼如下:

    // 接收重定向引數
    @RequestMapping("/action2")
    public String action2(Model model, Product product) {
        model.addAttribute("message", product);
        System.out.println(model.containsAttribute("product")); // true
        return "foo/index";
    } 

//重定向屬性 @RequestMapping("/action3") public String action3(Model model, RedirectAttributes redirectAttributes) { Product product = new Product(2, "iPhone7 Plus", 6989.5); redirectAttributes.addFlashAttribute("product", product); return "redirect:action2"; }

當訪問action3時,首先建立了一個product產口物件,將該物件新增到了Flash屬性中,在重定向後取出,個人猜測應該暫時將物件存入了Session中。當請求foo/action3時執行結果如下:

url地址已經發生了變化,product物件其實也已經被存入了model中,在action的檢視中可以直接拿到。

1.4、轉發

str=”forward : 路徑”        請求轉發到一個頁面中
str=”forward : controller的對映”  請求轉發到一個controller方法中

示例:

    //11.轉發
    @RequestMapping("/act11")
    public String act11(Model model){
        return "hi";
    }

    //12.轉發
    @RequestMapping("/act12")
    public String act12(Model model){
        model.addAttribute("msg","act12");
        return "forward:act11";
    }

結果:

URL沒有變化,資料存在可以直接使用。

1.5、@ModelAttribute模型特性

@ModelAttribute可以應用在方法引數上或方法上,他的作用主要是當註解在方法中時會將註解的引數物件新增到Model中;當註解在請求處理方法Action上時會將該方法變成一個非請求處理的方法,但其它Action被呼叫時會首先呼叫該方法。

1.5.1、註解在引數上

當註解在引數上時會將被註解的引數新增到Model中,並預設完成自動資料繫結,示例程式碼如下:

    @RequestMapping("/action6")
    public String action6(Model model, @ModelAttribute(name = "product", binding = true) Product entity) {
        model.addAttribute("message", model.containsAttribute("product") + "<br/>" + entity);
        return "foo/index";
    }

執行結果:

其實不使用@ModelAttribute我也樣可以完成引數與物件間的自動對映,但使用註解可以設定更多詳細內容,如名稱,是否繫結等。

1.5.2、註解在方法上

用於標註一個非請求處理方法,通俗說就是一個非Action,普通方法。如果一個控制器類有多個請求處理方法,以及一個有@ModelAttribute註解的方法,則在呼叫其它Action時會先呼叫非請求處理的Action,示例程式碼如下:

    @RequestMapping("/action7")
    public String action7(Model model) {
        Map<String, Object> map = model.asMap();
        for (String key : map.keySet()) {
            System.out.println(key + ":" + map.get(key));
        }
        return "foo/index";
    }

    @ModelAttribute
    public String noaction() {
        System.out.println("noaction 方法被呼叫!");
        String message = "來自noaction方法的資訊";
        return message;
    }

當訪問http://localhost:8087/SpringMVC03/foo/action7時,控制檯顯示結果如下:

非請求處理方法可以返回void,也可以返回一個任意物件,該物件會被自動新增到每一個要被訪問的Action的Model中,key從示例中可以看出為型別名稱。

二、Action返回值型別

ModelAndView
Model
Map 包含模型的屬性
View
String 檢視名稱
void
HttpServletResponse
HttpEntity<?>或ResponseEntity<?>
HttpHeaders
Callable<?>
DeferredResult<?>
ListenableFuture<?>
ResponseBodyEmitter
SseEmitter
StreamingResponseBody
其它任意型別,Spring將其視作輸出給View的物件模型

2.1、檢視中url問題

新增一個action5,程式碼如下:

    @RequestMapping("/action5")
    public String action5(Model model) {
        return "foo/action5";
    }

在foo目錄下新增檢視action5.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>action5的檢視</title>
</head>
<body>
    <img alt="風景" src="../../images/3.jpg">
</body>
</html>

目標結構如下:

訪問結果:

這裡圖片訪問不到的原因是因為:action5.jsp檢視此時並非以它所在的目錄為實際路徑,他是以當前action所在的控制器為起始目錄的,當前控制器的url為:http://localhost:8087/SpringMVC02/foo/,而圖片的src為:../../images/3.jpg,向上2級後變成:http://localhost:8087/images/3.jpg,但我們的專案實際路徑中並沒有存放3.jpg這張圖片,解決的辦法是在檢視中使用“絕對”路徑;另外一個問題是我們將靜態資源存放到WEB-INF下不太合理,因為該目錄禁止客戶端訪問,修改後的檢視如下:

<%@ 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>action5的檢視</title>
</head>
<body>
    <img alt="風景" src="<c:url value="/images/3.jpg"></c:url>">
</body>
</html>

目錄結構變化後如下所示:

執行結果:

小結:主要是藉助了標籤<c:url value="/images/3.jpg"></c:url>,將路徑轉換成“絕對路徑”;建議在引用外部資源如js、css、圖片資訊時都使用該標籤解析路徑。

2.2、返回值為String

2.2.1、String作為檢視名稱

預設如果action返回String,此時的String為檢視名稱,會去檢視解析器的設定的目錄下查詢,查詢的規則是:URL= prefix字首+檢視名稱+suffix字尾組成,示例程式碼如下:

    //返回值為String
    @RequestMapping("/action31")
    public String action31(Model model)
    {
        model.addAttribute("message","action31");
        return "bar/action31";
    }

Spring MVC的配置檔案內容如下:

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

實際url=/WEB-INF/views/bar/action31.jsp

2.2.2、String作為內容輸出

如果方法聲明瞭註解@ResponseBody ,將內容或物件作為 HTTP 響應正文返回,並呼叫適合HttpMessageConverter的Adapter轉換物件,寫入輸出流。些時的String不再是路徑而是內容,示例指令碼如下:

    @RequestMapping("/action32")
    @ResponseBody
    public String action32()
    {
        return "not <b>path</b>,but <b>content</b>";
    }

測試執行結果:

2.3、返回值為void

void在普通方法中是沒有返回值的意思,但作為請求處理方法並非這樣,存在如下兩種情況:

2.3.1、方法名預設作為檢視名

當方法沒有返回值時,方法中並未指定檢視的名稱,則預設檢視的名稱為方法名,如下程式碼所示:

    @RequestMapping("/action33")
    public void action33()
    {
    }

直接會去訪問的路徑是:url=/WEB-INF/views/bar/action33.jsp,bar是當前控制器對映的路徑,action33是方法名,上面的程式碼等同於:

    @RequestMapping("/action33")
    public String action33()
    {
        return "bar/action33";  //bar是控制器的路徑
    }

可見URL= prefix字首+控制器路徑+方法名稱+suffix字尾組成。

2.3.2、直接響應輸出結果

當方法的返回值為void,但輸出流中存在輸出內容時,則不會去查詢檢視,而是將輸入流中的內容直接響應到客戶端,響應的內容型別是純文字,如下程式碼所示:

    @RequestMapping("/action34")
    public void action34(HttpServletResponse response) throws IOException
    {
        response.getWriter().write("<h2>void method</h2>");
    }

執行結果如下:

可以看到h2標籤並未渲染成標題。

2.4、返回值為ModelAndView

在舊的Spring MVC中ModelAndView使用頻率非常高,它可以同時指定須返回的模型與檢視物件或名稱,示例程式碼如下:

    @RequestMapping("/action35")
    public ModelAndView action35() 
    {
        //1只指定檢視
        //return new ModelAndView("/bar/index");
        
        //2分別指定檢視與模型
        //Map<String, Object> model=new HashMap<String,Object>();
        //model.put("message", "ModelAndView action35");
        //return new ModelAndView("/bar/index",model);
        
        //3同時指定檢視與模型
        //return new ModelAndView("/bar/index","message","action35 ModelAndView ");
        
        //4分開指定檢視與模型
        ModelAndView modelAndView=new ModelAndView();
        //指定檢視名稱
        modelAndView.setViewName("/bar/index");
        //新增模型中的物件
        modelAndView.addObject("message", "<h2>Hello ModelAndView</h2>");
        return modelAndView;
    }

ModelAndView有個多構造方法過載,單獨設定屬性也很方便,執行結果如下:

2.5、返回值為Map

當返回結果為Map時,相當於只是返回了Model,並未指定具體的檢視,返回檢視的辦法與void是一樣的,即URL= prefix字首+控制器路徑+方法名稱+suffix字尾組成,示例程式碼如下:

    @RequestMapping("/action36")
    public Map<String, Object> action36()
    {
        Map<String, Object> model=new HashMap<String,Object>();
        model.put("message", "Hello Map");
        model.put("other", "more item");
        return model;
    }

實際訪問的路徑是:/SpringMVC03/WEB-INF/views/bar/action36.jsp,返回給客戶端的map相當於模型,在檢視中可以取出。

2.6、返回值為任意型別

2.6.1、返回值為基本資料型別

當返回結果直接為int,double,boolean等基本資料型別時的狀態,測試程式碼如下:

    @RequestMapping("/action37")
    public Integer action37()
    {
        return 9527;
    }

測試執行的結果是:exception is java.lang.IllegalArgumentException: Unknown return value type異常。

如果確實需要直接將基本資料型別返回,則可以使用註解@ReponseBody。

    @RequestMapping("/action38")
    @ResponseBody
    public int action38()
    {
        return 9527;
    }

執行結果:

2.6.2、當返值為自定義型別

當返回值為自定義型別時Spring會把方法認為是檢視名稱,與返回值為void的類似辦法處理URL,但頁面中獲得資料比較麻煩,示例程式碼如下:

    @RequestMapping("/action39")
    public Product action39()
    {
        return new Product(1,"iPhone",1980.5);
    }

如果存在action39對應的檢視,頁面還是可以正常顯示。

如果在action上新增@ResponseBody註解則返回的是Product本身,而非檢視,Spring會選擇一個合適的方式解析物件,預設是json。示例程式碼如下:

    @RequestMapping("/action39")
    @ResponseBody
    public Product action39()
    {
        return new Product(1,"iPhone",1980.5);
    }

執行結果:

如果是接收json值,則需要使用註解@RequestBody指定在相應引數上。

2.7、返回值為Model型別

該介面Model定義在包org.springframework.ui下,model物件會用於頁面渲染,檢視路徑使用方法名,與void類似。示例程式碼如下:

    @RequestMapping("/action40")
    public Model action40(Model model)
    {
        model.addAttribute("message", "返回型別為org.springframework.ui.Model");
        return model;
    }

執行結果:

2.8、自定義輸出內容

2.8.1、輸出Excel

返回的型別還有許多如view等,通過view可指定一個具體的檢視,如下載Excel、Pdf文件,其實它們也修改http的頭部資訊,手動同樣可以實現,如下程式碼所示:

    @RequestMapping("/action41")
    @ResponseBody
    public String action41(HttpServletResponse response)
    {
        response.setHeader("Content-type","application/octet-stream");         
        response.setHeader("Content-Disposition","attachment; filename=table.xls");
        return "<table><tr><td>Hello</td><td>Excel</td></tr></table>";
    }

執行結果:

Content-disposition解釋:

Content-disposition 是 MIME 協議的擴充套件,MIME 協議指示 MIME 使用者代理如何顯示附加的檔案。當 Internet Explorer 接收到頭時,它會啟用檔案下載對話方塊,它的檔名框自動填充了頭中指定的檔名。(請注意,這是設計導致的;無法使用此功能將文件儲存到使用者的計算機上,而不向使用者詢問儲存位置。)

服務端向客戶端遊覽器傳送檔案時,如果是瀏覽器支援的檔案型別,一般會預設使用瀏覽器開啟,比如txt、jpg等,會直接在瀏覽器中顯示,如果需要提示使用者儲存,就要利用Content-Disposition進行一下處理,關鍵在於一定要加上attachment:

Response.AppendHeader("Content-Disposition","attachment;filename=FileName.txt");

Content-Type解釋:

MediaType,即是Internet Media Type,網際網路媒體型別;也叫做MIME型別,在Http協議訊息頭中,使用Content-Type來表示具體請求中的媒體型別資訊。

例如: Content-Type: text/html;charset:utf-8;

常見的MIME:

 常見的媒體格式型別如下:

    text/html : HTML格式
    text/plain :純文字格式      
    text/xml :  XML格式
    image/gif :gif圖片格式    
    image/jpeg :jpg圖片格式 
    image/png:png圖片格式
   以application開頭的媒體格式型別:

   application/xhtml+xml :XHTML格式
   application/xml     : XML資料格式
   application/atom+xml  :Atom XML聚合格式    
   application/json    : JSON資料格式
   application/pdf       :pdf格式  
   application/msword  : Word文件格式
   application/octet-stream : 二進位制流資料(如常見的檔案下載)
   application/x-www-form-urlencoded : <form encType=””>中預設的encType,form表單資料被編碼為key/value格式傳送到伺服器(表單預設的提交資料的格式)
   另外一種常見的媒體格式是上傳檔案之時使用的:

    multipart/form-data : 需要在表單中進行檔案上傳時,就需要使用該格式

2.8.2、匯出XLS時增加BOM頭部解決亂碼問題

action程式碼如下:

//21.下載附件,匯出Excel,xls
    @RequestMapping("/act21")
    @ResponseBody
    public void act21(HttpServletResponse response) throws IOException {

        //POI
        //response.setContentType("text/html;charset=utf-8");
        //response.setCharacterEncoding("utf-8");

        //問題:下載xls問題用excel開啟亂碼,用notepad++等工具轉成UTF-8格式(帶BOM)可以正常開啟。
        //解決:嚴格來說這並不是xls檔案的問題,而是Excel處理檔案編碼方式問題,Excel預設並不是以UTF-8來開啟檔案,所以在xls開頭加入BOM,告訴Excel檔案使用utf-8的編碼方式。
        response.setHeader("Content-Type","application/octet-stream;charset=utf-8");
        response.setHeader("Content-Disposition","attachment;filename=Cars.xls");
        PrintWriter out = response.getWriter();
        //加上bom頭,解決excel開啟亂碼問題
        byte[] bomStrByteArr = new byte[] { (byte) 0xef, (byte) 0xbb, (byte) 0xbf };
        String bomStr = new String(bomStrByteArr, "UTF-8");
        out.write(bomStr);

        StringBuffer str=new StringBuffer("");
        str.append("<table border=1 width=100%>");
        str.append("<tr><th>編號</th><th>名稱</th><th>價格</th></tr>");

        for (Car car: Car.cars) {
            str.append("<tr><td>"+car.getId()+"</td><td>"+car.getName()+"</td><td>"+car.getPrice()+"</td></tr>");
        }

        str.append("</table>");
        out.write(str.toString());
    }

匯出結果:

2.8.3、匯出CSV格式

上面的方式並非Excel原生支援的,只是轉換HTML的結果,轉換成csv更好,佔用空間更少。

逗號分隔值CSV

逗號分隔值(Comma-Separated Values,CSV,有時也稱為字元分隔值,因為分隔字元也可以不是逗號),其檔案以純文字形式儲存表格資料(數字和文字)。純文字意味著該檔案是一個字元序列,不含必須像二進位制數字那樣被解讀的資料。CSV檔案由任意數目的記錄組成,記錄間以某種換行符分隔;每條記錄由欄位組成,欄位間的分隔符是其它字元或字串,最常見的是逗號或製表符。通常,所有記錄都有完全相同的欄位序列。通常都是純文字檔案。建議使用WORDPAD或是記事本(NOTE)來開啟,再則先另存新檔後用EXCEL開啟,也是方法之一。

規則

1、開頭是不留空,以行為單位。
2、可含或不含列名,含列名則居檔案第一行。
3、一行資料不跨行,無空行。
4、以半形逗號(即,)作分隔符,列為空也要表達其存在。
5、列內容如存在半形引號(即"),替換成半形雙引號("")轉義,即用半形引號(即"")將該欄位值包含起來。
6、檔案讀寫時引號,逗號操作規則互逆。
7、內碼格式不限,可為 ASCII、Unicode 或者其他。
8、不支援數字
9、不支援特殊字元

示例

1997,Ford,E350,"ac, abs, moon",3000.00
1999,Chevy,"Venture ""Extended Edition""","",4900.00
1999,Chevy,"Venture ""Extended Edition, Very Large""","",5000.00
1996,Jeep,Grand Cherokee,"MUST SELL!

action程式碼如下:

//22.下載附件,匯出Excel,csv
    @RequestMapping("/act22")
    @ResponseBody
    public void act22(HttpServletResponse response) throws IOException {

        //POI
        //response.setContentType("text/html;charset=utf-8");
        //response.setCharacterEncoding("utf-8");
        response.setHeader("Content-Type","application/octet-stream;charset=utf-8");
        response.setHeader("Content-Disposition","attachment;filename=Cars.csv");
        PrintWriter out = response.getWriter();
        //加上bom頭,解決excel開啟亂碼問題
        byte[] bomStrByteArr = new byte[] { (byte) 0xef, (byte) 0xbb, (byte) 0xbf };
        String bomStr = new String(bomStrByteArr, "UTF-8");
        out.write(bomStr);

        StringBuffer str=new StringBuffer("");
        str.append("編號,名稱,價格\r\n");
        for (Car car: Car.cars) {
            str.append(car.getId()+","+car.getName()+","+car.getPrice()+"\r\n");
        }
        response.getWriter().write(str.toString());
    }

結果:

2.8、@ResponseBody

預設情況下面Spring MVC會使用如下流程處理請求與響應結果:

@ResponseBody是作用在方法上的,@ResponseBody 表示該方法的返回結果直接寫入 HTTP response body 中,一般在非同步獲取資料時使用【也就是AJAX】,在使用 @RequestMapping後,返回值通常解析為跳轉路徑,但是加上 @ResponseBody 後返回結果不會被解析為跳轉路徑,而是直接寫入 HTTP response body 中。 比如非同步獲取 json 資料,加上 @ResponseBody 後,會直接返回 json 資料。@RequestBody 將 HTTP 請求正文插入方法中,使用適合的 HttpMessageConverter 將請求體寫入某個物件。

如下面的原始碼所示,ResponseBody可以同時註解在方法與控制器上面:

/*
 * Copyright 2002-2014 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

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;

/**
 * Annotation that indicates a method return value should be bound to the web
 * response body. Supported for annotated handler methods in Servlet environments.
 *
 * <p>As of version 4.0 this annotation can also be added on the type level in
 * which case it is inherited and does not need to be added on the method level.
 *
 * @author Arjen Poutsma
 * @since 3.0
 * @see RequestBody
 * @see RestController
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResponseBody {

}

如果註解在控制器上面將作用與每一個方法上,每個方法都將受到影響。

2.9、@RestController

Spring 4 MVC中提供的@RestController,使用最少的程式碼來構建一個Restful Web Service,支援返回xml或json資料,這個可以讓使用者選擇,通過URL字尾.xml或.json來完成。

REST即表述性狀態傳遞(英文:Representational State Transfer,簡稱REST)是Roy Fielding博士在2000年他的博士論文中提出來的一種軟體架構風格。它是一種針對網路應用的設計和開發方式,可以降低開發的複雜性,提高系統的可伸縮性。
目前在三種主流的Web服務實現方案中,因為REST模式的Web服務與複雜的SOAP和XML-RPC對比來講明顯的更加簡潔,越來越多的web服務開始採用REST風格設計和實現。例如,Amazon.com提供接近REST風格的Web服務進行圖書查詢;雅虎提供的Web服務也是REST風格的。

Rest風格的URL:

新增: http://www.zhangguo.com/order      POST 
修改: http://www.zhangguo.com/order/1   PUT update?id=1 
獲取:http://www.zhangguo.com/order/1     GET get?id=1 
刪除: http://www.zhangguo.com/order/1   DELETE delete?id=1

實現方法一:

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
@RequestMapping(value="/restservice")
public class RestService {
    
    public final static String SUCCEEDD="show";
    
    
    /**
     * get請求
     * url:  http://localhost:8080/springmvc/restservice/testRestGet/12
     * @param id  
     *         查詢的引數
     * @return
     */
    @RequestMapping(value="/testRestGet/{id}",method=RequestMethod.GET)
    public String testRestGet(@PathVariable("id") Integer id){
        System.out.println("rest 風格的GET請求..........id=" +id);
        return SUCCEEDD;
    }
    /**
     * post新增 
     * url:  http://localhost:8080/springmvc/restservice/testRestPost
     * @return
     */
    @RequestMapping(value="/testRestPost",method=RequestMethod.POST)
    public String testRestPost(){
        System.out.println("rest 風格的POST請求.......... ");
        return SUCCEEDD;
    }
    /**
     * PUT 修改操作
     * url:  http://localhost:8080/springmvc/restservice/testRestPut/put123
     * @param name
     * @return
     */
    @RequestMapping(value="/testRestPut/{name}",method=RequestMethod.PUT)
    public String testRestPut(@PathVariable("name") String name){
        System.out.println("rest 風格的PUT請求..........name="+name);
        return SUCCEEDD;
    }
    /**
     *   DELETE刪除操作
     *   url: http://localhost:8080/springmvc/restservice/testRestDelete/11
     * @param id
     * @return
     */
    @RequestMapping(value="/testRestDelete/{id}",method=RequestMethod.DELETE)
    public String testRestDelete(@PathVariable Integer id){
        System.out.println("rest 風格的DELETE請求..........id="+id);
        return SUCCEEDD;
    }
    
       
}

不難發現@RestController是繼承自@Controller與@ResponseBody的,所以它同時擁有他們的特性:

/*
 * Copyright 2002-2016 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

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 org.springframework.stereotype.Controller;

/**
 * A convenience annotation that is itself annotated with
 * {@link Controller @Controller} and {@link ResponseBody @ResponseBody}.
 * <p>
 * Types that carry this annotation are treated as controllers where
 * {@link RequestMapping @RequestMapping} methods assume
 * {@link ResponseBody @ResponseBody} semantics by default.
 *
 * <p><b>NOTE:</b> {@code @RestController} is processed if an appropriate
 * {@code HandlerMapping}-{@code HandlerAdapter} pair is configured such as the
 * {@code RequestMappingHandlerMapping}-{@code RequestMappingHandlerAdapter}
 * pair which are the default in the MVC Java config and the MVC namespace.
 * In particular {@code @RestController} is not supported with the
 * {@code DefaultAnnotationHandlerMapping}-{@code AnnotationMethodHandlerAdapter}
 * pair both of which are also deprecated.
 *
 * @author Rossen Stoyanchev
 * @author Sam Brannen
 * @since 4.0
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {

    /**
     * The value may indicate a suggestion for a logical component name,
     * to be turned into a Spring bean in case of an autodetected component.
     * @return the suggested component name, if any
     * @since 4.0.1
     */
    String value() default "";

}

可以輕鬆的將上面的程式碼修改為@RestController註解實現。

package com.zhangguo.springmvc01.controller;

import com.zhangguo.springmvc01.entities.Car;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/car")
public class CarController {
    
    @GetMapping("/{id}")
    public Car getCar(@PathVariable int id){
        return null;
    }

    @DeleteMapping("/{id}")
    public Car deleteCar(@PathVariable int id){
        return null;
    }
    
    @PostMapping
    public Car addCar(Car car){
        return null;
    }

    @PutMapping
    public Car updateCar(Car car){
        return null;
    }
    
}

2.10、小結

使用 String 作為請求處理方法的返回值型別是比較通用的方法,這樣返回的邏輯檢視名不會和請求 URL 繫結,具有很高的靈活性,而模型資料又可以通過Model控制。

使用void,map,Model時,返回對應的邏輯檢視名稱真實url為:prefix字首+控制器路徑+方法名 +suffix字尾組成。

使用String,ModelAndView返回檢視名稱可以不受請求的url繫結,ModelAndView可以設定返回的檢視名稱。

另外在非MVC中使用的許多辦法在Action也可以使用。

三、Spring MVC亂碼解決方法

3.1、頁面編碼

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>

3.2、URL中的亂碼

  改tomcat中server.xml中Connector的port=“8080”,加上一個 URIEncoding=”utf-8”

3.3、配置過濾器,指定所有請求的編碼

  (1)配置spring的編碼過濾器,為了防止spring中post方式提交的時候中文亂碼,方法:修改web.xml檔案,新增spring的編碼過濾器

<!-- 配置編碼方式過濾器,注意一點:要配置在所有過濾器的前面 -->
  <filter>
    <filter-name>CharacterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
      <param-name>encoding</param-name>
      <param-value>utf-8</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>CharacterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

  (2)配置編碼過濾器,方法:先建立filter類,再修改web.xml檔案,注意的是放在spring的編碼過濾器之後

  filter類:

package com.qiyuan.filter;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class EncoidingFilter implements Filter {

    private String encoding="";
    @Override
    public void destroy() {
        // TODO Auto-generated method stub

    }

    
    //過濾方法  是否往下執行
    @Override
    public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest request=(HttpServletRequest)arg0;
        HttpServletResponse response=(HttpServletResponse)arg1;
        
        request.setCharacterEncoding(encoding);
        response.setCharacterEncoding(encoding);

        //過濾通行證
        chain.doFilter(request, response);
    }

    //根據web.xml檔案的配置進行初始化  
    @Override
    public void init(FilterConfig arg0) throws ServletException {
        this.encoding = arg0.getInitParameter("Encoding");
        
    }

}

  修改web.xml,新增如下配置:

<!-- 配置編碼過濾 -->
   <filter>
    <filter-name>EncoidingFilter</filter-name>
    <filter-class>com.qiyuan.filter.EncoidingFilter</filter-class>
    
    <init-param>
       <param-name>Encoding</param-name>
       <param-value>utf-8</param-value>
    </init-param>
  </filter>
  
  <filter-mapping>
       <filter-name>EncoidingFilter</filter-name>
       <url-pattern>/*</url-pattern>
  </filter-mapping> 

3.4、檔案編碼

  將檔案另存為utf-8格式

3.5、資料庫編碼

  連線字串指定編碼格式

public static String URL="jdbc:mysql://127.0.0.1:3306/mvcdb?useUnicode=true&characterEncoding=UTF-8"

  建立資料庫的時候指定utf-8編碼格式

3.6、IDE中檔案與工程的編碼

工程編碼(最後一開始建立工程就設定整個工程的編碼,如UTF-8)

四、示例

點選下載示例

https://zhangguo5.coding.net/public/SpringMVCDemo/SpringMVCDemo/git

五、視訊

https://www.bilibili.com/video/av16991874/

六、作業

1、重現文中所有示例

2、定義一個員工實體(Employee),實現批量新增員工功能,在表單中可以一次新增多個員工,資料可以不持久化,使用JSTL渲染頁面,資料要發到伺服器後再響應到頁面中

3、繼續完善個人專案的前後臺頁面

4、定義一個員工實體(Employee),實現批量新增員工功能,在表單中可以一次新增多個員工,資料可以不持久化,使用AJAX渲染頁面,資料要發到伺服器後再響應到頁面中

5、個人專案後臺至少3個頁面(登入,主頁,二級頁面),前端至少6個頁面

6、將第4題修改為CRUD,使用JSTL實現,使用集合,可以不持久化到資料庫。

7、升級員工管理功能,實現如下功能:

7.1、新增

7.2、修改

7.3、刪除

7.4、批量刪除

7.5、查詢

7.6、匯出(XLS、CSV、DOC)

7.7、匯入(上傳,POI解析,組員選作)

嘗試使用Rest風格