SpringMVC入門筆記

SpringMVC 細節方面的東西很多,所以在這裏做一篇簡單的 SpringMVC 的筆記記錄,方便以後查看。

Spring MVC是當前最優秀的MVC框架,自從Spring 2.5版本發佈後,由於支持註解配置,易用性有了大幅度的提高。Spring 3.0更加完善,實現了對老牌的MVC框架Struts 2的超越,現在版本已經到了Spring5.x了。

一、工程創建

1.添加架包

創建Maven的web工程,添加依賴架包

Maven架包添加 spring-contextspring-webspring-webmvclog4j

2.web.xml配置

在web.xml中配置DispatcherServlet

<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>

注意:這裏配置的 <url-pattern>/</url-pattern> 攔截資源配置的是 /,攔截所有除其他 servlet 之外的資源訪問,包括 jsp、靜態網頁、圖片等等。與 /* 不一樣,/* 一般配在攔截器裏面,攔截所有資源訪問。

3.創建SpringMVC的配置文件

上面配置 DispatcherServlet 裏面用到了 contextConfigLocation 配置文件的地址,下面來創建配置文件。

<?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.xsd
       http://www.springframework.org/schema/mvc
       http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <!-- scan the package and the sub package -->
    <context:component-scan base-package="com.ogemray.springmvc"></context:component-scan>

    <!-- don't handle the static resource -->
    <mvc:default-servlet-handler />

    <!-- if you use annotation you must configure following setting -->
    <mvc:annotation-driven />

    <!-- configure the InternalResourceViewResolver -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/"></property>
        <property name="suffix" value=".jsp"></property>
    </bean>

    <!-- 配置首頁跳轉, 省略了在 Controller 裏的創建訪問方法 -->
    <mvc:view-controller path="index" view-name="../index"></mvc:view-controller>

</beans>

二、@RequestMapping 註解

在對 SpringMVC 進行的配置的時候, 需要我們指定請求與處理方法之間的映射關係。 指定映射關係,就需要我們用上 @RequestMapping 註解。
@RequestMapping 是 Spring Web 應用程序中最常被用到的註解之一,這個註解會將 HTTP 請求映射到控制器(Controller類)的處理方法上。

1.value和method屬性

簡單例子

@RequestMapping("rm")
@Controller
public class RequestMappingController {

    @RequestMapping(value = {"home", "/", ""}, method = RequestMethod.GET)
    public String goRMHome() {
        System.out.println("訪問了 Test RequestMapping 首頁");
        return "1-rm";
    }
}

最終訪問路徑是 .../rm/home,通過該方法返回視圖名字和SpringMVC視圖解析器加工,最終會轉發請求到 .../WEB-INF/jsp/1-rm.jsp 頁面。
如果沒有類名上面的 @RequestMapping("rm"),則訪問路徑爲 .../home
method 指定方法請求類型,取值有 GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE。
value 爲數組字符串,指定訪問路徑與方法對應,指定的地址可以是URI。URI值可以是中:普通的具體值、包含某變量、包含正則表達式。

下面以包含某一變量舉例:

@RequestMapping(value = "testPathVariable/{username}", method = RequestMethod.GET)
public String testPathVariable(@PathVariable(value = "username") String name) {
    //參數部分也可以直接寫成 @PathVariable String username, 省略value, 保證形參名與上面 {} 內的名字一致
    //不建議省略
    System.out.println("訪問了 Test PathVariable 方法 username: " + name);
    return "success";
}

2.consumes屬性

指定處理請求的提交內容類型(Content-Type)

clipboard.png

@RequestMapping(value = "testConsumes", method = RequestMethod.POST, consumes = "application/x-www-form-urlencoded")
public String testConsumes() {
    System.out.println("訪問了 Test Consumes 方法");
    return "success";
}

如果請求裏面的 Content-Type 對不上會報錯

clipboard.png

3.produces屬性

指定返回的內容類型,僅當request請求頭中的(Accept)類型中包含該指定類型才返回

clipboard.png

其中 */*;q=0.8 表明可以接收任何類型的,權重係數0.8表明如果前面幾種類型不能正常接收則使用該項進行自動分析。

@RequestMapping(value = "testProduces", method = RequestMethod.POST, produces = "text/html")
public String testProduces() {
    return "success";
}

4.params屬性

指定request中必須包含某些參數值,才讓該方法處理

JSP頁面請求

<form action="${pageContext.request.contextPath}/rm/testParams" method="post">
    用戶名: <input type="text" name="username" value="Tom"><br>
    密  碼: <input type="text" name="password" value="123"><br>
    <input type="submit" value="測試 Test Params">
</form>

Controller 裏面對應的請求方法

@RequestMapping(value = "testParams", method = RequestMethod.POST, params = {"username!=Tom", "password"})
public String testParams() {
    return "success";
}

params = {"username!=Tom", "password"} 表示請求參數裏面 username !=Tom 且有包含 password,二者有一個不滿足則會報錯

clipboard.png

5.headers屬性

指定 request 中必須包含某些指定的 header 值,才能讓該方法處理請求

@RequestMapping(value = "testHeaders", method = RequestMethod.GET, headers = "Accept-Language=zh-CN,zh;q=0.9")
public String testHeaders() {
    return "success";
}

如果跟設定頭裏面對不上會報404錯誤

clipboard.png

三、@RequestParam註解

請求

<a href="${pageContext.request.contextPath}/rp/testGetOneParam?username=Tom">單參數 GET 請求方式</a>

1.省略註解

表單元素的name名字和控制器裏的方法的形參名一致,此時可以省略 @RequestParam 註解

@RequestMapping(value = "testGetOneParam", method = RequestMethod.GET)
public String testGetOneParam(String username) {
    System.out.println("訪問了 單參數 Get 請求方法 username: " + username);
    return "success";
}

2.不省略註解

示例

@RequestMapping(value = "testPostOneParam", method = RequestMethod.POST)
public String testPostOneParam(@RequestParam String username) {
    System.out.println("username: " + name);
    return "success";
}

參數名字不一致時

@RequestMapping(value = "testPostOneParam", method = RequestMethod.POST)
public String testPostOneParam(@RequestParam(value = "username", required = false, defaultValue = "") String name) {
    System.out.println("username: " + name);
    return "success";
}

value 屬性指定傳過來的參數名,跟方法裏的形參名字對應上
required 指定該參數是否是必須攜帶的
defaultValue 沒有或者爲 null 時,指定默認值

注:省略和不省略 @RequestParam 註解,最終SpringMVC內部都是使用 RequestParamMethodArgumentResolver 參數解析器進行參數解析的。如果省略 @RequestParam 註解或省略 @RequestParam 註解的 value 屬性則最終則以形參的名字作爲 keyHttpServletRequest 中取值。

四、@RequestHeader 和 @CookieValue 註解

@RequestHeader 註解:可以把 Request 請求 header 部分的值綁定到方法的參數上

@RequestMapping(value = "rh")
@Controller
public class RequestHeaderController {

    @RequestMapping(value = "testRHAccept", method = RequestMethod.GET)
    public String testRHAccept(@RequestHeader(value = "Accept") String accept) {
        System.out.println(accept);
        return "success";
    }

    @RequestMapping(value = "testRHAcceptEncoding", method = RequestMethod.GET)
    public String testRHAcceptEncoding(@RequestHeader(value = "Accept-Encoding") String acceptEncoding) {
        System.out.println(acceptEncoding);
        return "success";
    }
}

@CookieValue 註解:可以把Request header中關於cookie的值綁定到方法的參數上

clipboard.png

@RequestMapping(value = "cv")
@Controller
public class CookieValueController {
    @RequestMapping(value = "testGetCookieValue", method = RequestMethod.GET)
    public String testGetCookieValue(@CookieValue(value = "JSESSIONID") String cookie) {
        System.out.println("獲取到Cookie裏面 JSESSIONID 的值 " + cookie);
        return "success";
    }
}

五、數據結果封裝 ModelAndView & ModelMap & Map & Model

SpringMVC 爲了方便數據封裝和處理,提供了以下幾種方案,最終會將封裝到模型裏面的數據全都通過 request.setAttribute(name, value) 添加request請求域中。

1.ModelAndView

使用 ModelAndView 類用來存儲處理完後的結果數據,以及顯示該數據的視圖。從名字上看 ModelAndView 中的 Model 代表模型,View 代表視圖。model ModelMap 的類型,而 ModelMap 又是 LinkedHashMap 的子類,view 包含了一些視圖信息。

@RequestMapping(value = "testReturnModelAndView", method = RequestMethod.GET)
public ModelAndView testReturnModelAndView() {

    Student s1 = new Student(1, "Tom", 13, new Date());
    Student s2 = new Student(2, "Jerry", 14, new Date());

    List<Student> list = new ArrayList<>();
    list.add(s1); list.add(s2);

    HashMap<String, Student> map = new HashMap<>();
    map.put("s1", s1); map.put("s2", s2);

    ModelAndView mv = new ModelAndView();
    mv.addObject("s1", s1);
    mv.addObject("s2", s2);

    mv.addObject("list", list);
    mv.addObject("map", map);
    mv.setViewName("5-m&v-success");
    return mv;
}

2.ModelMap & Map & Model

最終也是將封裝的數據和返回視圖名字封裝成 ModelAndView對象

@RequestMapping(value = "testMapParam", method = RequestMethod.GET)
public String testMapParam(Map<String, Object> paramMap) {
    ...
    paramMap.put("s1", s1);
    paramMap.put("s2", s2);

    paramMap.put("list", list);
    paramMap.put("map", map);
    return "5-m&v-success";
}

@RequestMapping(value = "testModelParam", method = RequestMethod.GET)
public String testModelParam(Model model) {
    ...
    model.addAttribute("s1", s1);
    model.addAttribute("s2", s2);

    model.addAttribute("list", list);
    model.addAttribute("map", map);
    return "5-m&v-success";
}

@RequestMapping(value = "testModelMapParam", method = RequestMethod.GET)
public String testModelMapParam(ModelMap modelMap) {
    ...
    modelMap.addAttribute("s1", s1);
    modelMap.addAttribute("s2", s2);

    modelMap.addAttribute("list",list);
    modelMap.addAttribute("map", map);
    return "5-m&v-success";
}

3.JSP頁面提取數據

<%@ page contentType="text/html;charset=UTF-8" language="java" pageEncoding="UTF-8" isELIgnored="false" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<body>

    <c:if test="${s1 != null && s2 != null}">
        <h3 align="center">單個數據封裝</h3>
        <table border="1px solid black" style="border-collapse: collapse" align="center">
            <tr><td colspan="2" align="center">s1</td></tr>
            <tr><td>姓名</td><td>${s1.name}</td></tr>
            <tr><td>年齡</td><td>${s1.age}</td></tr>
            <tr><td>生日</td><td>${s1.birthday.toString()}</td></tr>

            <tr><td colspan="2" align="center">s2</td></tr>
            <tr><td>姓名</td><td>${s2.name}</td></tr>
            <tr><td>年齡</td><td>${s2.age}</td></tr>
            <tr><td>生日</td><td>${s2.birthday.toString()}</td></tr>
        </table>
    </c:if>

    <c:if test="${list != null}">
        <h3 align="center">List數據封裝</h3>
        <table border="1px solid black" style="border-collapse: collapse" align="center">
            <c:forEach items="${list}" var="s" varStatus="status">
                <tr><td colspan="2" align="center">${status.count}</td></tr>
                <tr><td>姓名</td><td>${s.name}</td></tr>
                <tr><td>年齡</td><td>${s.age}</td></tr>
                <tr><td>生日</td><td>${s.birthday.toString()}</td></tr>
            </c:forEach>
        </table>
    </c:if>

    <c:if test="${map != null}">
        <h3 align="center">Map數據封裝</h3>
        <table border="1px solid black" style="border-collapse: collapse" align="center">
            <c:forEach items="${map}" var="node">
                <tr><td colspan="2" align="center">${node.key}</td></tr>
                <tr><td>姓名</td><td>${node.value.name}</td></tr>
                <tr><td>年齡</td><td>${node.value.age}</td></tr>
                <tr><td>生日</td><td>${node.value.birthday.toString()}</td></tr>
            </c:forEach>
        </table>
    </c:if>
</body>
</html>

六、@SessionAttributes

如果我們希望在多個請求之間共用某個模型屬性數據,則可以在控制器類上標註一個 @SessionAttributes,SpringMVC 將把模型中對應的屬性暫存到 HttpSession 的域中。

使用方法:
@SessionAttributes(value={"xxx"}, types={xxxx.class})
value:是通過鍵來指定放入HttpSession 的域中的值;
types:是通過類型指定放入HttpSession 的域中的值;

@SessionAttributes(types=Student.class)
這個註解會將類中所有放入 Request 域中的 Student 對象同時放進 HttpSession 的域空間中。

可以添加多個屬性
@SessionAttributes(value={“s1”, “s2”})
@SessionAttributes(types={User.class, Grade.class})

可以混合使用
@SessionAttributes(value={“s1”, “s2”},types={Grade.class})

示例

//@SessionAttributes(value = {"s1", "s2"})
@SessionAttributes(types = Student.class)
@RequestMapping(value = "sa")
@Controller
public class SessionAttributesController {

    @RequestMapping(value = "testSA", method = RequestMethod.GET)
    public String testSessionAttributes(Model model) {
        Student s1 = new Student(1, "Tom", 13, new Date());
        Student s2 = new Student(2, "Jerry", 13, new Date());

        model.addAttribute("s1", s1);
        model.addAttribute("s2", s2);
        return "6-sa-success";
    }
}

JSP 頁面提取數據

<%@ page contentType="text/html;charset=UTF-8" language="java" pageEncoding="UTF-8" isELIgnored="false" %>
<html>
<body>
    request s1 : ${requestScope.get("s1")}<br><br>
    request s2 : ${requestScope.get("s2")}<br><br>

    session s1 : ${sessionScope.get("s1")}<br><br>
    session s2 : ${sessionScope.get("s2")}<br><br>
</body>
</html>

七、@ModelAttribute

該註解平時使用的比較多,不僅可以寫在方法上面也可以寫在參數前面。

1.寫在方法上面

  • 在同一個控制器中,標註了@ModelAttribute 的方法實際上會在 @RequestMapping 註解方法之前被調用。
  • 標註了@ModelAttribute 的方法能接受與@RequestMapping 標註相同的參數類型,只不過不能直接被映射到具體的請求上。
  • 標註在方法上的 @ModelAttribute 說明方法一般是用於添加一個或多個屬性到 model 上。

模擬請求

<a href="${pageContext.request.contextPath}/testModelAttribute">模擬請求</a>

① 省略 value 屬性值手動加入屬性

@ModelAttribute
public void modelAttributeMethod1(ModelMap modelMap) {
    Person person = new Person("超哥哥 1 號", 12);
    modelMap.addAttribute("person1", person);
}

@RequestMapping(value = "testModelAttribute", method = RequestMethod.GET)
public String testModelAttribute(ModelMap modelMap) {
    modelMap.forEach((key, value) -> {
        System.out.println(key + " = " + value);
        //person1 = Person{name='超哥哥 1 號', age=12}
    });
    return "success";
}

可以看出手動加入 model 裏面屬性成功,key 爲自定義的字符串。

② 省略 value 屬性值自動加入屬性

@ModelAttribute
public Person modelAttributeMethod2() {
    return new Person("超哥哥 2 號", 12);
}

@RequestMapping(value = "testModelAttribute", method = RequestMethod.GET)
public String testModelAttribute(ModelMap modelMap) {
    modelMap.forEach((key, value) -> {
        System.out.println(key + " = " + value);
        //person = Person{name='超哥哥 2 號', age=12}
    });
    return "success";
}

可以看出 @ModelAttribute 修飾的方法沒有指定 value 屬性時,讓其自動加入的 key 是以添加類的類名首字母小寫。

③ 指明 value 屬性值自動加入屬性

@ModelAttribute(value = "person3")
public Person modelAttributeMethod3() {
    return new Person("超哥哥 3 號", 13);
}

@RequestMapping(value = "testModelAttribute", method = RequestMethod.GET)
public String testModelAttribute(ModelMap modelMap) {
    modelMap.forEach((key, value) -> {
        System.out.println(key + " = " + value);
        //person3 = Person{name='超哥哥 3 號', age=13}
    });
    return "success";
}

從上面可以看出 @ModelAttribute 修飾的方法有指定 value 屬性時,讓其自動加入的 key 就是自定的 value 屬性的值。

2.寫在參數前面

標註在方法參數前的 @ModelAttribute 說明了該方法參數的值將由 model 中取得,如果 model 中找不到,那麼該參數會先被實例化,然後被添加到 model 中。在 model 中存在以後,將請求中所有名稱匹配的參數都填充到該參數對象上。

模擬請求

<a href="${pageContext.request.contextPath}/testModelAttribute?age=13">模擬請求</a>

① 省略 value 屬性值自動匹配或創建

@RequestMapping(value = "testModelAttribute", method = RequestMethod.GET)
public String testModelAttribute(@ModelAttribute Person person) {
    System.out.println(person);
    //Person{name='null', age=13}
    return "success";
}

注:在執行 testModelAttribute(..) 方法時,因爲參數屬性是一個 Person 類對象,那麼他先從 model 裏面找(沒有指明 value 屬性值,則以該類名首字母小寫爲 key),發現找不到便創建一個,把請求裏面的參數賦值到該創建對象上,找到了則用請求裏面的參數更新該對象。

② 指定 value 屬性值匹配或創建

@ModelAttribute(value = "p")
public Person modelAttributeMethod3(@RequestParam Integer age) {
    return new Person("超哥哥 3 號", age);
}

@RequestMapping(value = "testModelAttribute", method = RequestMethod.GET)
public String testModelAttribute(@ModelAttribute(value = "p") Person person) {
    System.out.println(person);
    //Person{name='超哥哥 3 號', age=13}
    return "success";
}

注:在執行 testModelAttribute(..) 方法時,因爲參數屬性是一個 Person 類對象,那麼他先從 model 裏面找(有指明 value 屬性值,則以 value 屬性值爲 key),發現找不到便創建一個,把請求裏面的參數賦值到該創建對象上,找到了則用請求裏面的參數更新該對象。

③ 省略 @ModelAttribute 註解的 POJO 參數

@ModelAttribute
public Person modelAttributeMethod3(@RequestParam Integer age) {
    return new Person("超哥哥 4 號", age);
}

@RequestMapping(value = "testModelAttribute", method = RequestMethod.GET)
public String testModelAttribute(Person person) {
    System.out.println(person);
    //Person{name='超哥哥 4 號', age=13}
    return "success";
}

注:@ModelAttribute 註解修飾的方法,沒有指定 value 屬性,則自動注入到 model 裏面的 value 以該對象類名首字母小寫爲 key。在下面 @RequestMapping 修飾的方法 testModelAttribute(..) 參數時一個 POJO 對象,雖前面沒有註解修飾,但默認也會去匹配 ModelAttributeMethodProcessor 參數解析器去解析該參數,說白了與上面的第一種情況 @ModelAttribute 註解修飾沒有設置 value 屬性值是一樣的。

八、在Controller中使用redirect方式處理請求

forword:表示轉發!
redirect:表示重定向!

@RequestMapping(value = "index")
public String index() {
    return "success";
}
@RequestMapping(value = "index")
public String index() {
    return "redirect:success";
}

九、RESTFul 風格的 SpringMVC

1.RESTFulController

@RequestMapping(value = "rest")
@Controller
public class RESTFulController {

    @RequestMapping(value = {"home", "/", ""}, method = RequestMethod.GET)
    public String goResetHome() {
        System.out.println("訪問了 Rest 風格測試首頁");
        return "8-rest";
    }

    @RequestMapping(value = "student/{id}", method = RequestMethod.GET)
    public String get(@PathVariable(value = "id") Integer id) {
        System.out.println("get " + id);
        return "success";
    }

    @RequestMapping(value = "student/{id}", method = RequestMethod.POST)
    public String post(@PathVariable(value = "id") Integer id) {
        System.out.println("post " + id);
        return "success";
    }

    @RequestMapping(value = "student/{id}", method = RequestMethod.PUT)
    public String put(@PathVariable(value = "id") Integer id) {
        System.out.println("put " + id);
        return "success";
    }

    @RequestMapping(value = "student/{id}", method = RequestMethod.DELETE)
    public String delete(@PathVariable(value = "id") Integer id) {
        System.out.println("delete " + id);
        return "success";
    }
}

2.web.xml中配置

form表單發送put和delete請求,需要在web.xml中進行如下配置

<!-- configure the HiddenHttpMethodFilter,convert the post method to put or delete -->
<filter>
  <filter-name>hiddenHttpMethodFilter</filter-name>
  <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
  <filter-name>hiddenHttpMethodFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

3.模擬請求

<form action="${pageContext.request.contextPath}/rest/student/1" method="get">
    <input type="submit" value="GET">
</form>

<form action="${pageContext.request.contextPath}/rest/student/1" method="post">
    <input type="submit" value="POST">
</form>

<form action="${pageContext.request.contextPath}/rest/student/1" method="post">
    <input type="hidden" name="_method" value="PUT">
    <input type="submit" value="PUT">
</form>

<form action="${pageContext.request.contextPath}/rest/student/1" method="post">
    <input type="hidden" name="_method" value="DELETE">
    <input type="submit" value="DELETE">
</form>

十、@RequestBody 和 @ResponseBody

在SpringMVC的 Controller 中經常會用到 @RequestBody@ResponseBody 這兩個註解,若想使用這兩個註解,前提要寫好 <mvc:annotation-driven /> 標籤,他會幫我們注入接下里解析需要的轉換器。

1.@RequestBody

簡介:
@RequestBody 註解用於修飾 Controller 的方法參數,根據 HTTP Request Header 的 content-Type 的內容,通過適當的 HttpMessageConverter 轉換爲 Java 類。

使用時機:
當提交的數據不是普通表單的形式(application/x-www-form-urlcodedmultipart/form-data),而是 JSON 格式(application/json) 或 XML 格式(application/xml)。

使用示例:XML格式數據提交

POJO 模型類

@XmlRootElement(name = "person")
public class Person {
    private String name;
    private Integer age;

    public String getName() { return name; }
    @XmlElement
    public void setName(String name) { this.name = name; }
    public Integer getAge() { return age; }
    @XmlElement
    public void setAge(Integer age) { this.age = age; }
}

AJAX 請求

<a id="tag" href="${pageContext.request.contextPath}/testRequestBody">點擊事件</a>

<script type="text/javascript">
    $("#tag").click(function () {
        var arg =
            "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>" +
                "<person>" +
                    "<name>Tom</name>" +
                    "<age>13</age>" +
                "</person>";
        $.ajax({
            url: this.href,
            type: "POST",
            data: arg,
            contentType: "application/xml;charset=utf-8",
            success: function (data, textStatus) {  },
            error: function (data, textStatus, errorThrown) {  }
        });
        return false;
    });
</script>

Controller 裏對應的方法

@RequestMapping(value = "testRequestBody", method = RequestMethod.POST)
public String testRequestBody(@RequestBody Person person) {
    System.out.println(person);
    //Person{name='Tom', age=13}
    return "success";
}

注:@RequestBody 註解對於XML請求數據的解析,請求方要指定 Content-Type = application/xml;charset=utf-8,服務器如果要將接收數據封裝成 POJO 類,需要在該 POJO 類裏面用 @XmlRootElement@XmlElement 註解指明跟標籤和子標籤,SpringMVC 內部最終用到的是自帶的 Jaxb2RootElementHttpMessageConverter 轉換器(其實現了 HttpMessageConverter 接口)。

2.@ResponseBody

簡介:
@ResponseBody 註解用於修飾 Controller 的方法,根據 HTTP Request Header 的 Accept 的內容,通過適當的 HttpMessageConverter 轉換爲客戶端需要格式的數據並且寫入到 Responsebody 數據區,從而不通過視圖解析器直接將數據響應給客戶端。

使用時機:
返回的數據不是html標籤的頁面,而是其他某種格式的數據時(如json、xml等)使用。

使用示例:XML格式數據響應

POJO 模型類

@XmlRootElement(name = "person")
public class Person {
    private String name;
    private Integer age;

    public String getName() { return name; }
    @XmlElement
    public void setName(String name) { this.name = name; }
    public Integer getAge() { return age; }
    @XmlElement
    public void setAge(Integer age) { this.age = age; }
}

Controller 裏對應的方法

@ResponseBody
@RequestMapping(value = "testRequestBody", method = RequestMethod.POST)
public Person testRequestBody() {
    Person person = new Person("Tom",13);
    return person;
}

AJAX 請求

<a id="tag" href="${pageContext.request.contextPath}/testRequestBody">點擊事件</a>

<script type="text/javascript">
    $("#tag").click(function () {
        $.ajax({
            url: this.href,
            type: "POST",
            data: null,
            headers: { Accept: "application/xml;charset=utf-8" },
            success: function (data, textStatus) {
                console.log(textStatus);
                console.log(data);
            },
            error: function (data, textStatus, errorThrown) {
                console.log(textStatus + "   " + data + "  " + errorThrown);
            }
        });
        return false;
    });
</script>

最終瀏覽器控制檯輸出

clipboard.png

注:@ResponseBody 註解對於響應XML格式數據的解析,請求方要指定 Accept = application/xml;charset=utf-8,服務器如果想將 POJO 類轉換成XML格式數據,需要在該 POJO 類裏面用 @XmlRootElement@XmlElement 註解指明跟標籤和子標籤,SpringMVC 內部最終用到的是自帶的 Jaxb2RootElementHttpMessageConverter 轉換器(其實現了 HttpMessageConverter 接口)。

3.原理簡介

@RequestBody@ResponseBody 註解最終匹配到的參數解析器和返回值解析器都是 RequestResponseBodyMethodProcessor 對象,所以該對象分別實現了 HandlerMethodArgumentResolverHandlerMethodReturnValueHandler 接口。
在該解析器中有一個 messageConverters 屬性,該屬性是用來記錄轉換器的 List,這些轉換器都是在該解析器初始化的時候 <mvc:annotation-driven /> 標籤幫我們注入的。並且這些解析器都實現了 HttpMessageConverter 接口,在 HttpMessageConverter 接口中有四個最爲主要的接口方法。

public interface HttpMessageConverter<T> {
    boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);
    T read(Class<? extends T> clazz, HttpInputMessage inputMessage);

    boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);
    void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage);
}

read 對應請求輸入的轉換解析,write 對應響應輸出的轉換解析。
canRead 根據 Request Header 的 content-Type 的內容查看該 HttpMessageConverter 換器是否支持轉換,支持則轉換爲對應的 Java 類綁定到修飾的方法入參上。
canWrite 根據 Request Headers 裏面的 Accept 的內容查看該 HttpMessageConverter 換器是否支持轉換,支持則轉換成指定格式後,寫入到 Response 對象的 body 數據區。

對應流程圖如下

clipboard.png

十一、解析和返回 Json 數據

1.支持架包導入

首先需要導入 JSON 支持架包並且注入轉換器

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

jackson-databind-2.9.6.jar 架包依賴於 jackson-annotations-2.9.0.jarjackson-core-2.9.6.jar,所以省略了依賴架包的手動導入。

同時要寫好 <mvc:annotation-driven /> 標籤,其會幫我們注入對應的JSON數據轉換器。

2.代碼示例

需要封裝的 POJO

public class Person {
    private String name;
    private Integer age;
}

Controller中對應的請求方法

@ResponseBody
@RequestMapping(value = "testRequestBody", method = RequestMethod.POST)
public Person testRequestBody(@RequestBody Person person) {
    System.out.println(person);
    return person;
}

注:參數用 @RequestBody 修飾意思是將請求的JSON數據用合適的轉換器,轉換成 Java 類。@ResponseBody 註解是將返回的數據通過合適的轉換器轉換成客戶端想要的樣子並返回,在這裏是將請求解析的 Person 對象轉換成JOSN格式數據並返回。

AJAX 請求

<a id="tag" href="${pageContext.request.contextPath}/testRequestBody">點擊事件</a>

<script type="text/javascript">
    $("#tag").click(function () {
        var arg = {name : "Tom", age : "10"};
        $.ajax({
            url: this.href,
            type: "POST",
            data: JSON.stringify(arg),
            contentType: "application/json;charset=utf-8",
            headers: { Accept: "application/json;charset=utf-8" },
            success: function (data, textStatus) {
                console.log(textStatus);
                console.log(data);
            },
            error: function (data, textStatus, errorThrown) {
                console.log(textStatus + "   " + data + "  " + errorThrown);
            },
        });
        return false;
    });
</script>

注:① 發送的數據要是JSON格式(也就是 data 屬性的數據是JSON格式);② 指明請求數據爲JSON格式(contentType: "application/json;charset=utf-8");③ 指明接收數據爲JSON格式(headers: { Accept: "application/json;charset=utf-8" })。

3. 原理簡介

最終使用到的轉換器是 jackson 提供的 MappingJackson2HttpMessageConverter,也是在解析器初始化的時候 <mvc:annotation-driven /> 標籤幫我們注入的。

十二、文件上傳

1.支持架包導入

爲了實現文件上傳,需要導入 commons-fileupload 架包,導入如下

<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.3.3</version>
</dependency>

2.配置MultipartResolver

SpringMVC 上下文中默認沒有裝配 MultipartResolver,因此默認情況下其不能處理文件上傳工作。如果想使用SpringMVC的文件上傳功能,則需要在上下文中配置 MultipartResolver。在SpringMVC配置文件中進行如下配置

<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <!-- 請求的編碼格式,必須和 jsp 的 pageEncoding 屬性一致,默認爲ISO-8859-1 -->
    <property name="defaultEncoding" value="utf-8"></property>
    <!-- 上傳最大限制 1M = 1M * 1024 * 1024 = 1048576 byte-->
    <property name="maxUploadSize" value="1048576"></property>
</bean>

注:這裏一定要設置 id,並且值必須是 multipartResolver,下面的簡單原理會解釋。

3.代碼示例

Controller 中對應的方法

@RequestMapping(value = "upload", method = RequestMethod.POST)
public String testUpload(@RequestParam(value = "file") MultipartFile multipartFile, HttpServletRequest request) throws Exception {
    if (multipartFile.isEmpty() == false) {
        //multipartFile.getName()   標籤名字
        //multipartFile.getOriginalFilename()  上傳文件名字
        //multipartFile.getSize()   上傳文件大小
        //multipartFile.getContentType()    上傳文件類型

        //在 webapp 目錄下面(項目目錄下面) 建立一個 resources 資源文件夾, 用來存儲上傳的資源文件
        String parent = request.getServletContext().getRealPath("/resources");
        String filename = UUID.randomUUID() + multipartFile.getOriginalFilename();

        File file = new File(parent, filename);
        multipartFile.transferTo(file);
    }
    return "success";
}

JSP頁面的可變表單請求

<form action="${pageContext.request.contextPath}/upload" 
      enctype="multipart/form-data" 
      method="post">
    <input type="file" name="file" value="請選擇需要上傳的文件" /><br>
    <input type="submit" value="提交">
</form>

4.原理簡介

DispatcherServlet 初始化的時候,會從容器中加載 MultipartResolver 可變表單解析器,從下面源碼中可以看出加載條件就是 idname 爲 multipartResolver 的 bean

clipboard.png

接着簡單瞭解下解析,在 DispatcherServletdoDispatch(..) 方法中檢查該請求是否是可變表單請求,如果是則用加載到緩存的 MultipartResolver 解析器 (這裏用到的是注入容器中的 CommonsMultipartResolver 可變表單解析器,其實現了 MultipartResolver 接口) 將可變請求解析成 MultipartFile 對象 (這裏是 CommonsMultipartFile,其實現了MultipartFile 接口),放在 HttpServletRequest 對象中,最終通過合適的參數解析器綁定到對應方法的參數上。

clipboard.png

十三、文件下載

SpringMVC提供了一個 ResponseEntity 類型,使用它可以很方便地定義返回的 HttpHeadersHttpStatus
以下代碼演示文件的下載功能

@RequestMapping(value = "download", method = RequestMethod.GET)
public ResponseEntity<byte[]> testDownload(HttpServletRequest request, @RequestParam String filename) throws Exception {

    String parent = request.getServletContext().getRealPath("/resources");
    File file = new File(parent, filename);

    byte[] body = FileUtils.readFileToByteArray(file);

    String downloadFilename = new String(file.getName().getBytes("utf-8"), "iso-8859-1");

    HttpHeaders headers = new HttpHeaders();
    //設置文件類型
    headers.add("Content-Disposition", "attchement;filename=" + downloadFilename);

    ResponseEntity responseEntity = new ResponseEntity(body, headers, HttpStatus.OK);
    return responseEntity;
}

十四、整合SpringIOC和SpringMVC

  1. 在 web.xml 中配置 contextLoaderListener,並且加入spring的配置文件 applicationContext.xml

這樣可以把 service、dao、事務、緩存、以及和其它框架的整合放到 spring 的配置文件裏面
web.xml 文件配置如下

<!DOCTYPE web-app PUBLIC
        "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
        "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>

    <!-- configure the spring -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    
    <!-- configure the spring mvc -->
    <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>

</web-app>
  1. 在 web.xml 中配置 SpringMVC 的 Servlet 和加入 springmvc.xml,這時兩個配置文件中掃描的包有重合的時候出現某些bean會被初始化2次的問題。

解決:在掃描包的子節點下配置 exclude-filterinclude-filter

SpringMVC 只掃描 @Controller@ControllerAdvice

<context:component-scan base-package="com.ogemray.springmvc">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
    <context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice" />
</context:component-scan>

Spring排除掃描 @Controller@ControllerAdvice

<context:component-scan base-package="com.ogemray.springmvc">
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" />
    <context:exclude-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice" />
</context:component-scan>

注意:Spring 和 SpringMVC 都有一個 IOC 容器,並且Controller 類的 bean 在 SpringMVC 的 IOC 容器中,但是它可以引用 Spring 的 IOC 容器中的 bean 如 service 和 dao 層的 bean,反之則不行,因爲 Spring IOC 容器和 SpringMVC IOC 容器是父子關係,相當於全局變量和局部變量的關係!

十五、SpringMVC運行流程

clipboard.png

其他相關文章

SpringMVC入門筆記
SpringMVC工作原理之處理映射HandlerMapping
SpringMVC工作原理之適配器HandlerAdapter
SpringMVC工作原理之參數解析
SpringMVC之自定義參數解析
SpringMVC工作原理之視圖解析及自定義
SpingMVC之<mvc:annotation-driven/>標籤

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章