溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Spring MVC 入門

發(fā)布時(shí)間:2020-07-10 16:20:20 來源:網(wǎng)絡(luò) 閱讀:706 作者:灰白世界 欄目:編程語言

Spring MVC 概述

Spring MVC 介紹

Spring 為表現(xiàn)層提供的基于 MVC 設(shè)計(jì)理念的 Web 框架。

Spring MVC 通過一套 MVC 注解,讓 POJO 成為處理器請(qǐng)求的控制器,而無需實(shí)現(xiàn)任何接口。

支持 REST 風(fēng)格的 url 請(qǐng)求。

采用松散耦合可插拔組件結(jié)構(gòu),比其他 MVC 框架更具有擴(kuò)展性和靈活性。
Spring MVC 入門

永遠(yuǎn)的 Hello World

首先,是要導(dǎo)入 jar 包

  • servlet-api-x.y.z.jar
  • commons-logging-x.y.z.jar
  • spring-aop-x.y.z.jar
  • spring-beans-x.y.z.jar
  • spring-context-x.y.z.jar
  • spring-core-x.y.z.jar
  • spring-expression-x.y.z.jar
  • spring-webmvc-x.y.z.jar
  • spring-web-x.y.z.jar

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <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:application.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>

application.xml

<?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"
       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">
    <context:component-scan base-package="com.kernel.spring.web"/>
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/views"/>
        <property name="suffix" value=".jsp"/>
    </bean>
</beans>

HelloController.java

package com.kernel.spring.web.controller;

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

@Controller
public class HelloController {
    @RequestMapping("/hello")
    public String hello() {
        return "/success";
    }
}

success.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <h2>SUCCESS!</h2>
</body>
</html>

HelloWorld 深度解析

  1. 當(dāng)用戶請(qǐng)求 /hello 這個(gè)路徑時(shí),首先經(jīng)過 DispatcherServlet 的攔截。
  2. 然后通過 /hello 找到對(duì)應(yīng)的 Controller。
  3. DispatcherServlet 將請(qǐng)求提交給 Controller。
  4. Controller 調(diào)用業(yè)務(wù)處理邏輯后,返回 ModelAndView。
  5. DispatcherServlet 查詢一個(gè)或多個(gè) ViewResoler 視圖解析器,找到 ModelAndView 指定的視圖。
  6. 將結(jié)果返回給客戶端。

Spring MVC 入門

@RequestMapping

@RequestMapping 映射請(qǐng)求注解

在控制器的類定義及方法定義上都可以標(biāo)注 @RequestMapping。

標(biāo)記在類上:提供初步的映射信息,相當(dāng)于 Web 應(yīng)用的根目錄。

標(biāo)記在方法上:提供細(xì)分的映射信息,如果類上未標(biāo)注該注解,那么方法處標(biāo)記的 URL 相對(duì)于 Web 應(yīng)用的根目錄。

作用:DispatcherServlet 截獲請(qǐng)求后,就根據(jù) @RequestMapping 提供的映射信息確定請(qǐng)求所對(duì)應(yīng)的處理方法。

源碼參考

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
    String[] value() default {};

    RequestMethod[] method() default {};

    String[] params() default {};

    String[] headers() default {};

    String[] consumes() default {};

    String[] produces() default {};
}

上例代碼分析

package com.kernel.spring.web.controller;

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

// 標(biāo)注該類是一個(gè)控制器組件
@Controller
public class HelloController {
    // 映射請(qǐng)求信息
    @RequestMapping("/hello")
    public String hello() {
        // 返回值會(huì)通過視圖解析器解析為實(shí)際的物理視圖,然后做轉(zhuǎn)發(fā)操作
        // 對(duì)于 InternalResourceViewResolver,會(huì)通過 prefix + returnValue + suffix 這樣的方式得到實(shí)際的物理視圖
        return "/success";
    }
}

@RequestMapping 映射請(qǐng)求方式

該注解除了可以使用請(qǐng)求 URL 映射請(qǐng)求外,還可以使用請(qǐng)求方法、請(qǐng)求參數(shù)和請(qǐng)求頭映射請(qǐng)求。

結(jié)果使用可以讓請(qǐng)求更加精確化

@Controller
public class TestController {
    @RequestMapping(value = "/testMethod", method = RequestMethod.POST)
    public String testMethod(){
        System.out.println("testMethod...");
        return "/success";
    }
}

如果以 get 方式請(qǐng)求,會(huì)報(bào) 405 請(qǐng)求不支持錯(cuò)誤。

@RequestMapping 映射請(qǐng)求參數(shù)、請(qǐng)求頭

@Controller
public class TestController {
    @RequestMapping(value="/testParamsAndHeaders", 
                    params= {"username","age!=10"}, 
                    headers = { "Accept-Language=en-US,zh;q=0.9" })
    public String testParamsAndHeaders(){
        System.out.println("testParamsAndHeaders...");
        return "/success";
    }
}

測試

http://localhost:8080/testParamsAndHeaders 不可以訪問

http://localhost:8080/testParamsAndHeaders?username 可以訪問

http://localhost:8080/testParamsAndHeaders?username&age 可以訪問

http://localhost:8080/testParamsAndHeaders?username=kernel&age=10 不可以訪問

結(jié)論:必須至少攜帶一個(gè)參數(shù),參數(shù)可以傳空,但是 age 不能等于10。

@RequestMapping 映射請(qǐng)求占位符

@PathVariable 可以將請(qǐng)求中的參數(shù),傳遞給處理請(qǐng)求方法的入?yún)⒅小?/p>

@Controller
public class TestController {
    @RequestMapping("/testPathVariable/{id}")
    public String testPathVariable(@PathVariable("id") Integer id){
        System.out.println("testPathVariable...id" + id);
        return "/success";
    }
}

必須攜帶一個(gè)整數(shù)類型的參數(shù)才可以訪問。

HiddenHttpMethodFilter

什么是 REST 風(fēng)格

REST:

即 Representational State Transfer。(資源)表現(xiàn)層狀態(tài)轉(zhuǎn)化。是目前最流行的一種互聯(lián)網(wǎng)軟件架構(gòu)。它結(jié)構(gòu)清晰、符合標(biāo)準(zhǔn)、易于理解、擴(kuò)展方便,所以正得到越來越多網(wǎng)站的采用。

URL 風(fēng)格:

HTTP 方法 說明
POST 新增資源
DELETE 刪除資源
PUT 修改資源
GET 獲取資源

HiddenHttpMethodFilter:

總所周知,瀏覽器 from 表單只支持兩種請(qǐng)求,即 GET 和 POST,而 PUT 和 DELETE 不支持,Spring 3.0 提供了一個(gè)過濾器,將這些請(qǐng)求轉(zhuǎn)換為 PUT 和 DELETE 請(qǐng)求。

配置 HiddenHttpMethodFilter

web.xml

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

delete.jsp

<form action="/testRESTDelete/1" method="POST">
    <input type="hidden" name="_method" value="DELETE">
    <input type="submit" value="testRESTDelete">
</form>

源碼參考

public class HiddenHttpMethodFilter extends OncePerRequestFilter {
    public static final String DEFAULT_METHOD_PARAM = "_method";
    private String methodParam = "_method";

    public HiddenHttpMethodFilter() {
    }

    public void setMethodParam(String methodParam) {
        Assert.hasText(methodParam, "'methodParam' must not be empty");
        this.methodParam = methodParam;
    }

    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String paramValue = request.getParameter(this.methodParam);
        if ("POST".equals(request.getMethod()) && StringUtils.hasLength(paramValue)) {
            String method = paramValue.toUpperCase(Locale.ENGLISH);
            HttpServletRequest wrapper = new HiddenHttpMethodFilter.HttpMethodRequestWrapper(request, method);
            filterChain.doFilter(wrapper, response);
        } else {
            filterChain.doFilter(request, response);
        }

    }

    private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {
        private final String method;

        public HttpMethodRequestWrapper(HttpServletRequest request, String method) {
            super(request);
            this.method = method;
        }

        public String getMethod() {
            return this.method;
        }
    }
}

從源碼看出來,Spring 根據(jù)請(qǐng)求中的 _method 參數(shù)進(jìn)行轉(zhuǎn)換,并且只對(duì) POST 進(jìn)行處理, GET 請(qǐng)求進(jìn)行處理,所有如果想轉(zhuǎn)換請(qǐng)求,必須要將表單提交方式設(shè)置成 POST,必須將 _method 的值設(shè)置為 DELETE 或 PUT。

請(qǐng)求數(shù)據(jù)傳入

請(qǐng)求處理方法簽名

Spring MVC 通過分析處理方法的簽名,HTTP 請(qǐng)求信息綁定到處理方法的入?yún)⒅小?/p>

Spring MVC 對(duì)控制器處理方法簽名的限制是很寬松的,幾乎可以按照喜歡的方式對(duì)方法簽名。

必要時(shí)可以對(duì)方法及方法入?yún)?biāo)注響應(yīng)的注解(@PathVariable 、@RequestParam、@RequestHeader 等)。

Spring MVC 會(huì)將 HTTP 請(qǐng)求的信息綁定到響應(yīng)的方法入?yún)⒅校⒏鶕?jù)方法的返回值做出相應(yīng)的后續(xù)處理。

@RequestParam注解

在處理方法入?yún)r(shí)使用該注解可以將請(qǐng)求參數(shù)傳遞給方法入?yún)ⅰ?/p>

value:參數(shù)名

required:是否必須,默認(rèn)為 true,若不存在,拋出異常。

defaultValue:默認(rèn)值,沒有傳遞參數(shù)的時(shí)候使用。

@Controller
public class TestController {
    @RequestMapping("/testRequestParam")
    public String testRequestParam(@RequestParam(value = "id",
                                                 required = false, 
                                                 defaultValue = "0") Integer id) {
        System.out.println("testRequestParam...id" + id);
        return "/success";
    }
}

@RequestHeader 注解

在處理方法入?yún)r(shí)使用該注解可以將請(qǐng)求頭傳遞給方法入?yún)ⅰ?/p>

value:參數(shù)名

required:是否必須,默認(rèn)為 true,若不存在,拋出異常。

defaultValue:默認(rèn)值,沒有傳遞參數(shù)的時(shí)候使用。

@Controller
public class TestController {
    @RequestMapping("/testRequestHeader")
    public String testHeader(@RequestHeader("Accept-Encoding") String encoding,                                          @RequestHeader("Connection") String connection) {
        System.out.println("testRequestHeader...Accept-Encoding" + encoding);
        System.out.println("testRequestHeader...Connection" + connection);
        return "/success";
    }
}

@CookieValue 注解

使用該注解可以綁定請(qǐng)求中的 Cookie 值。

value:參數(shù)名

required:是否必須,默認(rèn)為 true,若不存在,拋出異常。

defaultValue:默認(rèn)值,沒有傳遞參數(shù)的時(shí)候使用。

@Controller
public class TestController {
    @RequestMapping("/testCookieValue")
    public String testCookieValue(@CookieValue("JSESSIONID") String jsessionid) {
        System.out.println("testCookieValue...JSESSIONID" + jsessionid);
        return "/success";
    }
}

使用POJO作為參數(shù)

使用 POJO 對(duì)象綁定請(qǐng)求參數(shù)值。

Spring MVC 會(huì)按照請(qǐng)求參數(shù)名和 POJO 屬性進(jìn)行自動(dòng)匹配,還支持級(jí)聯(lián)屬性。

TestController.java

@Controller
public class TestController {
    @RequestMapping("/testPOJO")
    public String testPOJO(User user) {
        System.out.println("testPOJO...user" + user);
        return "/success";
    }
}

.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>
  <form action="/testPOJO" method="post">
      ID:<input type="text" name="id"><br>
      賬戶:<input type="text" name="username"><br>
      密碼:<input type="text" name="password"><br>
      郵箱:<input type="text" name="email"><br>
      年齡:<input type="text" name="age"><br>
      省份:<input type="text" name="address.province"><br>
      城市:<input type="text" name="address.city"><br>
      <input type="submit" value="提交">
  </form>
  </body>
</html>

使用原生 ServletAPI 作為參數(shù)

Spring MVC 的 Handler 方法可以接受哪些 ServletAPI 類型的參數(shù)?

  • HttpServletRequest
  • HttpServletResponse
  • HttpSession
  • java.security.Principal
  • Locale
  • InputStream
  • OutputStream
  • Reader
  • Writer

響應(yīng)數(shù)據(jù)傳出

Spring MVC 提供了以下幾種途徑輸出模型數(shù)據(jù):

ModelAndView:處理方法返回值為 ModelAndView 時(shí),即可以通過該對(duì)象添加模型數(shù)據(jù)。

Map、Model:入?yún)?org.springframework.ui.Model、org.springframework.ui.ModelMap 或 java.uti.Map 時(shí),處理方法返回時(shí),Map 中的數(shù)據(jù)會(huì)自動(dòng)添加到模型中。

@SessionAttributes:將模型中的數(shù)據(jù)暫存到 HttpSession中,以便在多個(gè)請(qǐng)求中共享這個(gè)屬性。

@ModelAttribute:方法入?yún)?biāo)注該注解后,入?yún)⒌膶?duì)象會(huì)放入到模型數(shù)據(jù)中。

ModelAndView

控制器如果返回類型是 ModelAndView,則其既可以包含視圖信息,又可以包含模型數(shù)據(jù)信息。

添加模型:

MoelAndView addObject(String attributeName, Object attributeValue)

ModelAndView addAllObject(Map<String, ?> modelMap)

設(shè)置視圖:

void setView(View view)

void setViewName(String viewName)

@Controller
public class TestController {
    @RequestMapping("/testModelAndView")
    public ModelAndView testModelAndView(){
        System.out.println("testModelAndView...");
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("/success");
        modelAndView.addObject("time",new Date());
        return modelAndView;
    }
}

事實(shí)上,返回的類型可以是很多種,可以是 ModelAndView 類型,也可以是 String 類型,還可以是 View 類型,還有很多。但是他們最終會(huì)被解析為 ModelAndView 類型的對(duì)象。

Map

Spring MVC 在內(nèi)部使用了一個(gè) org.springframework.ui.Model 接口存儲(chǔ)模型數(shù)據(jù)。

具體使用步驟:

Spring MVC 在調(diào)用方法之前會(huì)創(chuàng)建一個(gè)隱含的模型對(duì)象作為模型對(duì)象的存儲(chǔ)容器。

如果方法的入?yún)?Map 或者 Model 類型,Spring MVC 會(huì)將隱含模型的引用傳遞給入?yún)ⅰ?/p>

在方法體內(nèi),可以通過這個(gè)入?yún)?duì)象訪問到模型中的所有數(shù)據(jù),也可以向模型中添加新的屬性數(shù)據(jù)。

@Controller
public class TestController {
    @RequestMapping("/testMap")
    public String testMap(Map<String, User> map) {
        map.put("user", new User(1, "kernel", "123456", "kernel@qq.com", 18, 
                                 new Address("山東", "德州")));
        return "/success";
    }
}

@SessionAttribute

若希望在多個(gè)請(qǐng)求之間共用某個(gè)模型對(duì)象,可以在控制器上標(biāo)志這個(gè)注解,Spring MVC 將對(duì)應(yīng)的模型屬性暫存到HttpSession 中。

@SessionAttributes(types=User.class) 會(huì)將隱含模型的所有類型為 User 的屬性全部添加到會(huì)話中。
@SessionAttributes(value={“user1”, “user2”}) 將 user1、user2 添加到會(huì)話中。

源碼參考

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface SessionAttributes {
    String[] value() default {}; // 推薦使用

    Class<?>[] types() default {}; // 作用范圍太廣
}
@Controller
@SessionAttribue("user)
public class TestController {
    @RequestMapping("/testMap")
    public String testMap(Map<String, User> map) {
        // 如果該類有 @SessionAttribute 注解,會(huì)同時(shí)將 user 存放到 request 作用域和 session 作用域
        // 否則將 user 放到 request 作用域。
        map.put("user", new User(1, "kernel", "123456", "kernel@qq.com", 18, 
                                 new Address("山東", "德州")));
        return "/success";
    }
}

@ModelAttribute

使用場景?

假如說我修改一個(gè)訂單的時(shí)候,訂單的創(chuàng)建時(shí)間是不允許被修改的,所以我更新時(shí),因?yàn)闀r(shí)間字段沒有值,所有更新會(huì)將該字段更新為 null。

解決方法有:

隱藏域,字段多太麻煩,還有用戶可以在源代碼中看到隱藏域中的信息,不安全。

先查詢數(shù)據(jù)庫,然后一一賦值,比較麻煩。

下面使用 @ModelAttribute

在方法定義上使用該注解,會(huì)在調(diào)用目標(biāo)方法之前逐個(gè)調(diào)用方法上標(biāo)注該注解的的方法。

在方法的入?yún)⑸蠘?biāo)注該注解,可以從隱含對(duì)象中獲取隱含模型數(shù)據(jù)中獲取對(duì)象,再將請(qǐng)求參數(shù)綁定到對(duì)象中,在傳入入?yún)ⅰ?/p>

@Controller
@SessionAttribue("user)
public class TestController {
    @ModelAttribute
    public void getUser(@RequestParam(value = "id", required = false) Integer id,
    Map<String, User> map) {
        if (id != null) {
            User user = new User(1, "kernel", "123456", "kernel@qq.com", 18, 
            new Address("山東", "德州"));
            System.out.println(user);
            map.put("user", user);
        }
    }

    @RequestMapping("/testModelAttribute")
    public String testModelAttribute(User user) {
        System.out.println("testModelAttribute...user" + user);
        return "/success";
    }
}

原理分析:

執(zhí)行 @ModelAttribute 注解所修飾的方法,將從數(shù)據(jù)庫中獲取的對(duì)象存放到 Map 集合中,key 為 user。

Spring MVC 從 map 中查找 user 對(duì)象,將表單數(shù)據(jù)封裝到與參數(shù)名稱對(duì)應(yīng)的 user 對(duì)象屬性上。

SpringMVC將user對(duì)象作為參數(shù),傳遞給目標(biāo)方法。

SpringMVC 確定目標(biāo)方法 POJO 類型入?yún)⒌倪^程:

確定一個(gè) key:若目標(biāo)方法的 POJO 類型的參數(shù)木有使用 @ModelAttribute 作為修飾,則 key 為 POJO 類名第一個(gè)字母的小寫,若使用了@ModelAttribute 來修飾,,則 key 為 @ModelAttribute 注解的 value 屬性值。

在 implicitModel 中查找 key 對(duì)應(yīng)的對(duì)象, 若存在,則作為入?yún)魅搿?/p>

若 implicitModel 中不存在 key 對(duì)應(yīng)的對(duì)象,則檢查當(dāng)前的 Handler 是否使用 @SessionAttributes 注解修飾。

若使用了該注解,且 @SessionAttributes 注解的 value 屬性值中包含了 key,則會(huì)從 HttpSession 中來獲取 key 所對(duì)應(yīng)的 value 值,若存在則直接傳入到目標(biāo)方法的入?yún)⒅?,若不存在則將拋出異常。

若 Handler 沒有標(biāo)識(shí) @SessionAttributes 注解或 @SessionAttributes 注解的 value 值中不包含 key,則會(huì)通過反射來創(chuàng)建 POJO 類型的參數(shù),傳入為目標(biāo)方法的參數(shù)。

SpringMVC 會(huì)把 key 和 POJO 類型的對(duì)象保存到 implicitModel 中,進(jìn)而會(huì)保存到 request 中。

視圖解析

視圖和視圖解析器

請(qǐng)求處理方法執(zhí)行后,會(huì)最終返回一個(gè) ModelAndView 對(duì)象,對(duì)于那些返回了 String、Model、Map 等的處理方法,內(nèi)部也會(huì)被裝配成一個(gè)ModelAndView,它包含了邏輯名和模型對(duì)象的視圖。

通過視圖解析器得到最終的視圖對(duì)象,最終的視圖可以是 JSP、Excel、Pdf 等各種形式。

對(duì)于采取什么視圖對(duì)模型渲染,處理器并不關(guān)心,從而實(shí)現(xiàn) MVC 的充分解耦。

常見的視圖類:

Spring MVC 入門
每個(gè)視圖解析器都實(shí)現(xiàn)了 Ordered 接口并開放出一個(gè) order 屬性,可以通過 order 屬性指定解析器的優(yōu)先順序,order 越小優(yōu)先級(jí)越高。

SpringMVC 會(huì)按視圖解析器順序的優(yōu)先順序?qū)壿嬕晥D名進(jìn)行解析,直到解析成功并返回視圖對(duì)象,否則將拋出 ServletException 異常。

JstlView

若項(xiàng)目中使用了 JSTL,則 InternalResourceView 會(huì)轉(zhuǎn)換成 JstlView。

若想使用 JSTL 的 fmt 標(biāo)簽,則必須配置國際化資源文件。

i8n國際化

# i18n.properties
i18n.username=Username
i18n.password=Password
# i18n_zh_CN.properties
i18n.username=用戶名
i18n.password=密碼
# i18n_en_US.properties
i18n.username=Username
i18n.password=Password

application.xml

<?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"
       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">
    <context:component-scan base-package="com.kernel.spring.web"/>
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/views"/>
        <property name="suffix" value=".jsp"/>
    </bean>
    <bean name="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basename" value="i18n"/>
    </bean>
    <!-- 直接配置響應(yīng)的頁面:無需經(jīng)過控制器來執(zhí)行結(jié)果 -->
    <mvc:view-controller path="/success" view-name="success"/>
</beans>

自定義視圖

若希望使用 Excel 展示數(shù)據(jù)列表,僅需要擴(kuò)展 SpringMVC 提供的 AbstractExcelView 或 AbstractJExcelView 即可。

實(shí)現(xiàn) buildExcelDocument() 方法,在方法中使用模型數(shù)據(jù)對(duì)象構(gòu)建 Excel 文檔就可以了。

AbstractExcelView 基于 POI API,而 AbstractJExcelView 是基于 JExcelAPI 的。

視圖對(duì)象需要配置 IOC 容器中的一個(gè) Bean ,使用 BeanNameViewResolver 作為視圖解析器即可。

若希望直接在瀏覽器中直接下載 Excel 文檔,則可以設(shè)置響應(yīng)頭 Content-Disposition 的值為attachment;filename=xxx.xls。

application.xml

<?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"
       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">
    <context:component-scan base-package="com.kernel.spring.web"/>
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/views"/>
        <property name="suffix" value=".jsp"/>
    </bean>
    <bean name="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basename" value="i18n"/>
    </bean>
    <bean class="org.springframework.web.servlet.view.BeanNameViewResolver">
        <property name="order" value="100"/>
    </bean>
</beans>

HelloView.java

@Component
public class HelloView implements View {
    @Override
    public String getContentType() {
        return "text/html";
    }

    @Override
    public void render(Map<String, ?> map, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
        httpServletResponse.getWriter().print(new Date());
    }
}

測試

@Controller
@SessionAttribue("user)
public class TestController {
    @RequestMapping("/testView")
    public String testView(){
        System.out.println("testView...");
        // 與視圖對(duì)象的 id 一致
        return "helloView"; 
    }
}

重定向

一般情況下,控制器方法返回字符串類型的值會(huì)被當(dāng)成邏輯視圖名處理

如果返回的字符串中帶 forward: 或 redirect:前綴時(shí),SpringMVC 會(huì)對(duì)他們進(jìn)行特殊處理。

redirect:success.jsp:會(huì)完成一個(gè)到 success.jsp 的重定向的操作。

forward:success.jsp:會(huì)完成一個(gè)到 success.jsp 的轉(zhuǎn)發(fā)操作。

數(shù)據(jù)綁定

數(shù)據(jù)綁定流程

Spring MVC 將 ServletRequest 對(duì)象及目標(biāo)方法的入?yún)鬟f給 WebDataBinderFactory,以創(chuàng)建 DataBinder 實(shí)例對(duì)象。

DataBinder 調(diào)用裝配在上下文中的 ConversionService 組件進(jìn)行數(shù)據(jù)類型轉(zhuǎn)換、數(shù)據(jù)格式化工作。

調(diào)用 Vaidator 組件對(duì)已綁定了請(qǐng)求消息的入?yún)?duì)象進(jìn)行數(shù)據(jù)合法性校驗(yàn),最終生成數(shù)據(jù)綁定 BindingData 對(duì)象。

Spring MVC 抽取 BindingResult 中的入?yún)?duì)象和校驗(yàn)錯(cuò)誤對(duì)象,將它們賦給處理方法的響應(yīng)入?yún)ⅰ?/p>

Spring MVC 通過反射機(jī)制對(duì)目標(biāo)處理方法進(jìn)行解析,將請(qǐng)求消息綁定到處理方法的入?yún)⒅?。?shù)據(jù)綁定的核心部件是 DataBinder,運(yùn)行機(jī)制如下:

Spring MVC 入門

自定義類型轉(zhuǎn)換器

ConversionService 是 Spring 類型轉(zhuǎn)換器的核心。

可以使用 ConversionServiceFactoryBean 在 Spring IOC 容器中定義一個(gè) ConversionService,Spring 將自動(dòng)識(shí)別出 IOC 容器中的 ConversionService,并在 Bean 屬性配置及 Sprng MVC 處理方法入?yún)⒔壎ǖ葓龊鲜褂盟M(jìn)行數(shù)據(jù)轉(zhuǎn)換。

Spring 支持的轉(zhuǎn)換器類型:

Converter<S, T>:將 S 類型對(duì)象轉(zhuǎn)為 T 類型對(duì)象

ConverterFactory:將相同系列多個(gè) “同質(zhì)” Converter 封裝在一起。如果希望將一種類型的對(duì)象轉(zhuǎn)換為另一種類型及其子類的對(duì)象。

GenericConverter:會(huì)根據(jù)源類對(duì)象及目標(biāo)類對(duì)象所在的宿主類中的上下文信息進(jìn)行類型轉(zhuǎn)換

StringToUserConverter.java

@Component
public class StringToUserConverter implements Converter<String, User> {

    @Override
    public User convert(String s) {
        if (s != null){
            String[] vals = s.split("-");
            String username = vals[0];
            String password = vals[1];
            String email = vals[2];
            Integer age = Integer.valueOf(vals[3]);
            String province = vals[4];
            String city = vals[5];
            return new User(null, username, password,email,age , new Address(province, city));
        }
        return null;
    }
}

application.xml

<?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/cache http://www.springframework.org/schema/cache/spring-cache.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
    <context:component-scan base-package="com.kernel.spring.web"/>
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/views"/>
        <property name="suffix" value=".jsp"/>
    </bean>
    <bean name="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basename" value="i18n"/>
    </bean>
    <bean class="org.springframework.web.servlet.view.BeanNameViewResolver">
        <property name="order" value="100"/>
    </bean>
    <bean id="serviceFactoryBean" class="org.springframework.context.support.ConversionServiceFactoryBean">
        <property name="converters">
            <set>
                <ref bean="stringToUserConverter"/>
            </set>
        </property>
    </bean>
    <mvc:annotation-driven conversion-service="serviceFactoryBean"/>
</beans>

測試

@Controller
@SessionAttribue("user)
public class TestController {
    @RequestMapping("/testConverter")
    public String testConverter(User user) {
        System.out.println(user);
        return "/success";
    }
}

<mvc:annotation-driven>

直接配置響應(yīng)的頁面,無需經(jīng)過控制器的處理,但是直接影響其他請(qǐng)求路徑失效。

找不到靜態(tài)資源需要配置,\<mvc:default-servlet-handler> 將在 SpringMVC 上下文中定義一個(gè)DefaultServletHttpRequestHandler,它會(huì)對(duì)進(jìn)入 DispatcherServlet 的請(qǐng)求進(jìn)行篩查,如果發(fā)現(xiàn)是沒有經(jīng)過映射的請(qǐng)求,就將該請(qǐng)求交由 WEB 應(yīng)用服務(wù)器默認(rèn)的 Servlet 處理,如果不是靜態(tài)資源的請(qǐng)求,才由 DispatcherServlet 繼續(xù)處理。

配置類型轉(zhuǎn)換器時(shí),需要指定轉(zhuǎn)換器引用。

完成 JSR303 數(shù)據(jù)驗(yàn)證,也需要配置該標(biāo)簽。

作用:

會(huì)自動(dòng)注冊:RequestMappingHandlerMapping、RequestMappingHandlerAdapter 與ExceptionHandlerExceptionResolver 三個(gè) bean。

還將提供以下支持:

支持使用 ConversionService 實(shí)例對(duì)表單參數(shù)進(jìn)行類型轉(zhuǎn)換。

支持使用 @NumberFormat、@DateTimeFormat 注解完成數(shù)據(jù)類型的格式化。

支持使用 @Valid 注解對(duì) JavaBean 實(shí)例進(jìn)行 JSR 303 驗(yàn)證。

支持使用 @RequestBody 和 @ResponseBody 注解。

@InitBinder注解

由 @InitBinder 標(biāo)識(shí)的方法,可以對(duì) WebDataBinder 對(duì)象進(jìn)行初始化,WebDataBinder 是 DataBinder 的子類,用于完成從表單字段到 JavaBean 的綁定。

由 @InitBinder 標(biāo)識(shí)的方法不能有返回值,必須是 void 類型。

由 @InitBinder 表示的方法入?yún)⑼ǔJ?WebDataBinder。

數(shù)據(jù)的格式化

Spring 在格式化模塊中定義了一個(gè)實(shí)現(xiàn)了 ConversionService 接口的 FormattingConversionService 實(shí)現(xiàn)類,該實(shí)現(xiàn)類擴(kuò)展了 GenericConversionService 實(shí)現(xiàn)類,該類既有類型轉(zhuǎn)換的功能,又有格式化的功能。

FormattingConversionService 擁有一個(gè) FormattingConversionServiceFactory 工廠類,后者用于在 Spring 上下文中給構(gòu)造前者,F(xiàn)ormattingConversionServiceFactory 內(nèi)部注冊了:

NumberFormatAnnotationFormatterFactroy:支持對(duì)數(shù)字使用 @NumberFormat 注解。

odaDateTimeFormatAnnotationFormatterFactroy:支持對(duì)日期類型使用 @DataTimeFormat 注解。

裝配了 FormattingConversionServiceFactroyBean 后,就可以在 Spring MVC 入?yún)⒔壎澳P蛿?shù)據(jù)輸出時(shí)使用注解驅(qū)動(dòng)了。

@DataTimeFormat 可以對(duì) Date、Calendar、Long 時(shí)間類型進(jìn)行標(biāo)注。

pattern:類型為字符串,指定解析/格式化字符串的模式。

iso:類型為 DateTimeFormat.ISO,指定解析/格式化字符串的 ISO 模式,包括四種:ISO.NONE(不使用,默認(rèn))、ISO.DATE(yyyy-MM-dd)、ISO.TIME(hh:mm:ss:SSSZ)、ISO.DATE_TIME(yyyy-MM-dd hh:mm:ss.SSSZ)

style:字符串類型,通過樣式指定日期時(shí)間的格式,由兩位字符組成,第一位表示日期的格式,第二位表示時(shí)間的格式:S(段日期/時(shí)間格式)、M(中日期/時(shí)間格式)、L(長日期/時(shí)間格式)、F(完整日期/時(shí)間格式)。

@NumberFormat,可對(duì)類似數(shù)字類型的屬性進(jìn)行標(biāo)注。

pattern:類型為 String,自定義樣式,如 "#,###"。

style:用于指定樣式類型,包括Style.NUMBER(正常數(shù)字類型)、Style.CURRENCY(貨幣類型)、
Style.PERCENT(百分?jǐn)?shù)類型)。

JSR303 數(shù)據(jù)校驗(yàn)

使用 JSR303 驗(yàn)證標(biāo)準(zhǔn)

加入 hibernate validator 驗(yàn)證框架

在 application.xml 文件中配置 \<mvc:annotation-driven/>

在 Bean 屬性上增加對(duì)應(yīng)的驗(yàn)證注解

在目標(biāo)方法的 Bean 類型的前面增加 @Valid 注解

錯(cuò)誤回顯及格式化

Spring MVC 除了會(huì)將表單/命令對(duì)象的校驗(yàn)結(jié)果保存到對(duì)應(yīng)的 BindingResult 或 Errors 對(duì)象中外,還會(huì)將所有校驗(yàn)結(jié)果保存到 “隱含模型”。
即使處理方法的簽名中沒有對(duì)應(yīng)于表單/命令對(duì)象的結(jié)果入?yún)ⅲr?yàn)結(jié)果也會(huì)保存在 “隱含對(duì)象” 中。
隱含模型中的所有數(shù)據(jù)最終將通過 HttpServletRequest 的屬性列表暴露給 JSP 視圖對(duì)象,因此在 JSP 中可以獲取錯(cuò)誤信息。
在 JSP 頁面上可通過 <form:errors path=“userName”> 顯示錯(cuò)誤消息。

<form:errors path="*"/> 顯示所有的錯(cuò)誤信息

<form:errors path="lastName"/> 顯示某個(gè)表單域的錯(cuò)誤信息

每個(gè)屬性在數(shù)據(jù)綁定和數(shù)據(jù)校驗(yàn)發(fā)生錯(cuò)誤時(shí),都會(huì)生成一個(gè)對(duì)應(yīng)的 FieldError 對(duì)象。

當(dāng)一個(gè)屬性校驗(yàn)失敗后,校驗(yàn)框架會(huì)為該屬性生成 4 個(gè)消息代碼,這些代碼以校驗(yàn)注解類名為前綴,結(jié)合 modleAttribute、屬性名及屬性類型名生成多個(gè)對(duì)應(yīng)的消息代碼:

例如 User 類中的 password 屬性標(biāo)注了一個(gè) @Pattern 注解,當(dāng)該屬性值不滿足 @Pattern 所定義的規(guī)則時(shí), 就會(huì)產(chǎn)生以下 4 個(gè)錯(cuò)誤代碼:
Pattern.user.password
Pattern.password
Pattern.java.lang.String
Pattern
當(dāng)使用 Spring MVC 標(biāo)簽顯示錯(cuò)誤消息時(shí), Spring MVC 會(huì)查看 WEB 上下文是否裝配了對(duì)應(yīng)的國際化消息,如果沒有,則顯示默認(rèn)的錯(cuò)誤消息,否則使用國際化消息。
若數(shù)據(jù)類型轉(zhuǎn)換或數(shù)據(jù)格式轉(zhuǎn)換時(shí)發(fā)生錯(cuò)誤,或該有的參數(shù)不存在,或調(diào)用處理方法時(shí)發(fā)生錯(cuò)誤,都會(huì)在隱含模型中創(chuàng)建錯(cuò)誤消息。

其錯(cuò)誤代碼前綴說明如下:
required:必要的參數(shù)不存在。如 @RequiredParam(“param1”) 標(biāo)注了一個(gè)入?yún)?,但是該參?shù)不存在
typeMismatch:在數(shù)據(jù)綁定時(shí),發(fā)生數(shù)據(jù)類型不匹配的問題
methodInvocation:Spring MVC 在調(diào)用處理方法時(shí)發(fā)生了錯(cuò)誤

國際化

國際化頁面中獲取國際化資源信息

使用 JSTL

application.xml

<?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">
    <context:component-scan base-package="com.kernel"/>
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/views/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
    <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basename" value="i18n"/>
    </bean>
    <bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver"/>
    <mvc:interceptors>
        <bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"/>
    </mvc:interceptors>
    <mvc:view-controller path="/i18n1" view-name="i18n1"/>
    <mvc:view-controller path="/i18n2" view-name="i18n2"/>
    <mvc:annotation-driven/>
</beans>

i18n1.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <fmt:message key="i18n.username"/>
    <a href="i18n2">i18n2</a>
</body>
</html>

i18n2.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <fmt:message key="i18n.password"/>
    <a href="i18n1">i18n1</a>
</body>
</html>

在 Bean 中獲取國際化資源文件 Locale 對(duì)應(yīng)的信息

@Controller
public class TestController {
    @Autowired
    private ResourceBundleMessageSource messageSource;

    @RequestMapping("/i18n")
    public String testi18n(Locale locale){
        System.out.println(locale);
        String userName = messageSource.getMessage("i18n.username", null, locale);
        System.out.println("i18n.username="+userName);
        return "i18n";
    }
}

通過超鏈接切換 Locale,而不再依賴于瀏覽器的語言設(shè)置情況。

<?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">
    <context:component-scan base-package="com.kernel"/>
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/views/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
    <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basename" value="i18n"/>
    </bean>
    <!-- 配置SessionLocaleResolver -->
    <bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver"/>
    <!-- 配置LocaleChangeInterceptor攔截器 -->
    <mvc:interceptors>
        <bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"/>
    </mvc:interceptors>
    <mvc:view-controller path="/i18n1" view-name="i18n1"/>
    <mvc:view-controller path="/i18n2" view-name="i18n2"/>
    <mvc:annotation-driven/>
</beans>

測試:

http://localhost:8080/i18n?locale=zh_CN

http://localhost:8080/i18n?locale=en_US

文件上傳

Spring MVC 為文件上傳提供了直接的支持,這種支持是通過即插即用的 MultipartResolver 實(shí)現(xiàn)的。
Spring 用 Jakarta Commons FileUpload 技術(shù)實(shí)現(xiàn)了一個(gè) MultipartResolver 實(shí)現(xiàn)類:

CommonsMultipartResovler :
Spring MVC 上下文中默認(rèn)沒有裝配 MultipartResovler,因此默認(rèn)情況下不能處理文件的上傳工作,如果想使用 Spring 的文件上傳功能,需現(xiàn)在上下文中配置 MultipartResolver。

application.xml

<?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">
    <context:component-scan base-package="com.kernel"/>
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/views/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
    <!-- 配置文件上傳解析器 -->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <property name="defaultEncoding" value="UTF-8"/>
        <property name="maxUploadSize" value="5242880"/>
    </bean>
    <mvc:annotation-driven/>
</beans>

upload.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>
  <form action="testUpload" method="post" enctype="multipart/form-data">
      文件: <input type="file" name="file"/><br><br>
      描述: <input type="text" name="desc"/><br><br>
      <input type="submit" value="提交"/>
  </form>

  </body>
</html>

測試

@Controller
public class TestController {
    @Autowired
    private ResourceBundleMessageSource messageSource;

    private static void writeToLocal(String destination, InputStream input)
            throws IOException {
        int index;
        byte[] bytes = new byte[1024];
        FileOutputStream downloadFile = new FileOutputStream(destination);
        while ((index = input.read(bytes)) != -1) {
            downloadFile.write(bytes, 0, index);
            downloadFile.flush();
        }
        downloadFile.close();
        input.close();
    }

    @RequestMapping(value = "/testUpload", method = RequestMethod.POST)
    public String testUpload(@RequestParam(value = "desc",required = false) String desc,
                             @RequestParam("file")MultipartFile multipartFile) throws IOException {
        System.out.println("desc " + desc);
        System.out.println("OriginalFilename" + multipartFile.getOriginalFilename());
        InputStream inputStream = multipartFile.getInputStream();
        System.out.println("available " + inputStream.available());
        System.out.println("inputStream " + inputStream);
        writeToLocal(multipartFile.getOriginalFilename(), inputStream);
        return "success";
    }
}

攔截器

自定義攔截器

Spring MVC 可以使用攔截器對(duì)請(qǐng)求進(jìn)行攔截處理,用戶可以自定義攔截器來實(shí)現(xiàn)特定功能,自定義攔截器必須實(shí)現(xiàn) HandlerInterceptor 接口。

preHandle():這個(gè)方法在業(yè)務(wù)處理器處理請(qǐng)求之前被調(diào)用,在該方法中對(duì)用戶請(qǐng)求 request 進(jìn)行處理。如果程序員決定該攔截器對(duì)請(qǐng)求進(jìn)行攔截處理后還要調(diào)用其他的攔截器,或者是業(yè)務(wù)處理器去進(jìn)行處理,則返回true;如果程序員決定不需要再調(diào)用其他的組件去處理請(qǐng)求,則返回false。

postHandle():這個(gè)方法在業(yè)務(wù)處理器處理完請(qǐng)求后,但是DispatcherServlet 向客戶端返回響應(yīng)前被調(diào)用,在該方法中對(duì)用戶請(qǐng)求request進(jìn)行處理。

afterCompletion():這個(gè)方法在 DispatcherServlet 完全處理完請(qǐng)求后被調(diào)用,可以在該方法中進(jìn)行一些資源清理的操作。

<?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">
    <context:component-scan base-package="com.kernel"/>
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/views/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
    <mvc:interceptors>
        <bean class="com.kernel.interceptor.FirstHandlerInterceptor"/>
        <bean class="com.kernel.interceptor.SecondHandlerInterceptor"/>
    </mvc:interceptors>
    <mvc:annotation-driven/>
</beans>

多個(gè)攔截器的執(zhí)行順序

按照攔截器的配置的順序依次調(diào)用每個(gè)攔截器的 preHandle 方法,當(dāng)請(qǐng)求業(yè)務(wù)處理器執(zhí)行完畢后,依次對(duì)倒敘對(duì)每個(gè)攔截器放行,執(zhí)行 postHandle 方法,當(dāng)請(qǐng)求完全處理完畢后,然后依次倒敘執(zhí)行每個(gè)攔截器的 afterCompletion 方法。

異常處理

Spring MVC 通過 HandlerExceptionResolver 處理程序的異常,包括 Handler 映射、數(shù)據(jù)綁定及目標(biāo)方法執(zhí)行時(shí)發(fā)生的異常。

HandlerExceptionResolver

@ExceptionHandler

可以通過 @ExceptionHandler(value = {java.lang.RuntimeException.class}) 的方式捕捉一個(gè)異常,如果捕捉成功,自動(dòng)執(zhí)行標(biāo)志該注解的方法。

@ExceptionHandler(value = {java.lang.RuntimeException.class})
public ModelAndView handlerException2(Exception ex) {
    ModelAndView mv = new ModelAndView("error");
    System.out.println("出現(xiàn)異常啦!" + ex);
    mv.addObject("exception", ex);
    return mv;
}

如何將異常對(duì)象從控制器攜帶給頁面

可以通過 ModelAndView 對(duì)象將異常對(duì)象添加。

異常對(duì)象捕捉的優(yōu)先級(jí)

Spring MVC 是有優(yōu)先級(jí)的,他會(huì)執(zhí)行離捕捉異常離發(fā)生異常最近的那個(gè)方法。

@ControllerAdvice

該注解是定義在類級(jí)別上的,在類上標(biāo)注了該注解后,所有控制器上發(fā)生了注解之后都會(huì)通過這個(gè)類的方法處理。

ExceptionHandlerExceptionResolver

主要處理 Handler 中用 @ExceptionHandler 注解定義的方法。

@ExceptionHandler 注解定義的方法優(yōu)先級(jí)問題:

例如發(fā)生的是NullPointerException,但是聲明的異常有 RuntimeException 和 Exception,此候會(huì)根據(jù)異常的最近繼承關(guān)系找到繼承深度最淺的那個(gè) @ExceptionHandler 注解方法,即標(biāo)記了 RuntimeException 的方法

ExceptionHandlerMethodResolver 內(nèi)部若找不到 @ExceptionHandler 注解的話,會(huì)找 @ControllerAdvice 中的@ExceptionHandler 注解方法。

ResponseStatusExceptionResolver

在類上標(biāo)注 @ResponseStatus 注解后,可以自定義狀態(tài)碼及提示信息。

@ResponseStatus(value=HttpStatus.FORBIDDEN, reason="用戶名和密碼不匹配")

DefaultHandlerExceptionResolver

對(duì)一些特殊的異常進(jìn)行處理,比如:
NoSuchRequestHandlingMethodException、HttpRequestMethodNotSupportedException、HttpMediaTypeNotSupportedException、HttpMediaTypeNotAcceptableException等。

SimpleMappingExceptionResolver

如果希望對(duì)所有異常進(jìn)行統(tǒng)一處理,可以使用 SimpleMappingExceptionResolver,它將異常類名映射為視圖名,即發(fā)生異常時(shí)使用對(duì)應(yīng)的視圖報(bào)告異常。

<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
    <property name="exceptionMappings">
        <props>
            <prop key="java.lang.ArithmeticException">error</prop>
        </props>
    </property>
</bean>

Spring Web 運(yùn)行流程

  1. 用戶向服務(wù)器發(fā)送請(qǐng)求,請(qǐng)求被 Spring MVC DispatcherServlet 捕獲。
  2. DispatcherServlet 對(duì)請(qǐng)求 URL 進(jìn)行解析,得到請(qǐng)求資源標(biāo)識(shí)符(URI),判斷是否存在。
  3. 如果不存在,判斷是否配置了 \<mvc:default-servlet-handler>,如果配置了,執(zhí)行目標(biāo)資源(一般為靜態(tài)資源);如果沒有配置,客戶端顯示 404 錯(cuò)誤。
  4. 如果存在,根據(jù)該 URI,調(diào)用 HandlerMapping 獲得該 Handler 配置的所有相關(guān)的對(duì)象(包括Handler對(duì)象以及Handler對(duì)象對(duì)應(yīng)的攔截器),最后以 HandlerExecutionChain 對(duì)象的形式返回。
  5. DispatcherServlet 根據(jù)獲得的 Handler,選擇一個(gè)合適的 HandlerAdapter。
  6. 獲得 HandlerAdapter 后,開始正序執(zhí)行攔截器的 preHandler 方法。
  7. 提取 Request 中的模型數(shù)據(jù),填充 Handler 入?yún)ⅲ_始執(zhí)行 Handler 方法,處理請(qǐng)求(數(shù)據(jù)轉(zhuǎn)換、數(shù)據(jù)格式化、數(shù)據(jù)驗(yàn)證)。
  8. Handler執(zhí)行完成后,向DispatcherServlet 返回一個(gè)ModelAndView對(duì)象。
  9. 逆向執(zhí)行攔截器的 postHandler 方法。
  10. 根據(jù)返回的 ModelAndView 選擇一個(gè)合適的視圖解析器來渲染視圖。
  11. 在返回給客戶端時(shí)需要逆向執(zhí)行攔截器的 AfterCompletion 方法。
  12. 將渲染結(jié)果返回給客戶端。
向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI