Spring MVC 入門

Spring MVC 概述

Spring MVC 介紹

Spring 爲表現層提供的基於 MVC 設計理念的 Web 框架。html

Spring MVC 經過一套 MVC 註解,讓 POJO 成爲處理器請求的控制器,而無需實現任何接口。java

支持 REST 風格的 url 請求。程序員

採用鬆散耦合可插拔組件結構,比其餘 MVC 框架更具備擴展性和靈活性。
在這裏插入圖片描述web

永遠的 Hello World

首先,是要導入 jar 包spring

  • 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.xmlexpress

<?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.javaapi

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>
    <h1>SUCCESS!</h1>
</body>
</html>

HelloWorld 深度解析

  1. 當用戶請求 /hello 這個路徑時,首先通過 DispatcherServlet 的攔截。
  2. 而後經過 /hello 找到對應的 Controller。
  3. DispatcherServlet 將請求提交給 Controller。
  4. Controller 調用業務處理邏輯後,返回 ModelAndView。
  5. DispatcherServlet 查詢一個或多個 ViewResoler 視圖解析器,找到 ModelAndView 指定的視圖。
  6. 將結果返回給客戶端。

在這裏插入圖片描述

@RequestMapping

@RequestMapping 映射請求註解

在控制器的類定義及方法定義上均可以標註 @RequestMapping。spring-mvc

標記在類上:提供初步的映射信息,至關於 Web 應用的根目錄。

標記在方法上:提供細分的映射信息,若是類上未標註該註解,那麼方法處標記的 URL 相對於 Web 應用的根目錄。

做用:DispatcherServlet 截獲請求後,就根據 @RequestMapping 提供的映射信息肯定請求所對應的處理方法。

源碼參考

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

// 標註該類是一個控制器組件
@Controller
public class HelloController {
    // 映射請求信息
    @RequestMapping("/hello")
    public String hello() {
        // 返回值會經過視圖解析器解析爲實際的物理視圖,而後作轉發操做
        // 對於 InternalResourceViewResolver,會經過 prefix + returnValue + suffix 這樣的方式獲得實際的物理視圖
        return "/success";
    }
}

@RequestMapping 映射請求方式

該註解除了可使用請求 URL 映射請求外,還可使用請求方法、請求參數和請求頭映射請求。

結果使用可讓請求更加精確化

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

若是以 get 方式請求,會報 405 請求不支持錯誤。

@RequestMapping 映射請求參數、請求頭

@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 不能夠訪問

結論:必須至少攜帶一個參數,參數能夠傳空,可是 age 不能等於10。

@RequestMapping 映射請求佔位符

@PathVariable 能夠將請求中的參數,傳遞給處理請求方法的入參中。

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

必須攜帶一個整數類型的參數才能夠訪問。

HiddenHttpMethodFilter

什麼是 REST 風格

REST:

即 Representational State Transfer。(資源)表現層狀態轉化。是目前最流行的一種互聯網軟件架構。它結構清晰、符合標準、易於理解、擴展方便,因此正獲得愈來愈多網站的採用。

URL 風格:

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

HiddenHttpMethodFilter:

總所周知,瀏覽器 from 表單只支持兩種請求,即 GET 和 POST,而 PUT 和 DELETE 不支持,Spring 3.0 提供了一個過濾器,將這些請求轉換爲 PUT 和 DELETE 請求。

配置 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 根據請求中的 _method 參數進行轉換,而且只對 POST 進行處理, GET 請求進行處理,全部若是想轉換請求,必需要將表單提交方式設置成 POST,必須將 _method 的值設置爲 DELETE 或 PUT。

請求數據傳入

請求處理方法簽名

Spring MVC 經過分析處理方法的簽名,HTTP 請求信息綁定處處理方法的入參中。

Spring MVC 對控制器處理方法簽名的限制是很寬鬆的,幾乎能夠按照喜歡的方式對方法簽名。

必要時能夠對方法及方法入參標註響應的註解(@PathVariable 、@RequestParam、@RequestHeader 等)。

Spring MVC 會將 HTTP 請求的信息綁定到響應的方法入參中,並根據方法的返回值作出相應的後續處理。

@RequestParam註解

在處理方法入參時使用該註解能夠將請求參數傳遞給方法入參。

value:參數名

required:是否必須,默認爲 true,若不存在,拋出異常。

defaultValue:默認值,沒有傳遞參數的時候使用。

@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 註解

在處理方法入參時使用該註解能夠將請求頭傳遞給方法入參。

value:參數名

required:是否必須,默認爲 true,若不存在,拋出異常。

defaultValue:默認值,沒有傳遞參數的時候使用。

@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 註解

使用該註解能夠綁定請求中的 Cookie 值。

value:參數名

required:是否必須,默認爲 true,若不存在,拋出異常。

defaultValue:默認值,沒有傳遞參數的時候使用。

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

使用POJO做爲參數

使用 POJO 對象綁定請求參數值。

Spring MVC 會按照請求參數名和 POJO 屬性進行自動匹配,還支持級聯屬性。

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 做爲參數

Spring MVC 的 Handler 方法能夠接受哪些 ServletAPI 類型的參數?

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

響應數據傳出

Spring MVC 提供瞭如下幾種途徑輸出模型數據:

ModelAndView:處理方法返回值爲 ModelAndView 時,便可以經過該對象添加模型數據。

Map、Model:入參爲 org.springframework.ui.Model、org.springframework.ui.ModelMap 或 java.uti.Map 時,處理方法返回時,Map 中的數據會自動添加到模型中。

@SessionAttributes:將模型中的數據暫存到 HttpSession中,以便在多個請求中共享這個屬性。

@ModelAttribute:方法入參標註該註解後,入參的對象會放入到模型數據中。

ModelAndView

控制器若是返回類型是 ModelAndView,則其既能夠包含視圖信息,又能夠包含模型數據信息。

添加模型:

MoelAndView addObject(String attributeName, Object attributeValue)

ModelAndView addAllObject(Map<String, ?> modelMap)

設置視圖:

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

事實上,返回的類型能夠是不少種,能夠是 ModelAndView 類型,也能夠是 String 類型,還能夠是 View 類型,還有不少。可是他們最終會被解析爲 ModelAndView 類型的對象。

Map

Spring MVC 在內部使用了一個 org.springframework.ui.Model 接口存儲模型數據。

具體使用步驟:

Spring MVC 在調用方法以前會建立一個隱含的模型對象做爲模型對象的存儲容器。

若是方法的入參爲 Map 或者 Model 類型,Spring MVC 會將隱含模型的引用傳遞給入參。

在方法體內,能夠經過這個入參對象訪問到模型中的全部數據,也能夠向模型中添加新的屬性數據。

@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

若但願在多個請求之間共用某個模型對象,能夠在控制器上標誌這個註解,Spring MVC 將對應的模型屬性暫存到HttpSession 中。

@SessionAttributes(types=User.class) 會將隱含模型的全部類型爲 User 的屬性所有添加到會話中。
@SessionAttributes(value={「user1」, 「user2」}) 將 user一、user2 添加到會話中。

源碼參考

@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 註解,會同時將 user 存放到 request 做用域和 session 做用域
        // 不然將 user 放到 request 做用域。
        map.put("user", new User(1, "kernel", "123456", "kernel@qq.com", 18, 
                                 new Address("山東", "德州")));
        return "/success";
    }
}

@ModelAttribute

使用場景?

假如說我修改一個訂單的時候,訂單的建立時間是不容許被修改的,因此我更新時,由於時間字段沒有值,全部更新會將該字段更新爲 null。

解決方法有:

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

先查詢數據庫,而後一一賦值,比較麻煩。

下面使用 @ModelAttribute

在方法定義上使用該註解,會在調用目標方法以前逐個調用方法上標註該註解的的方法。

在方法的入參上標註該註解,能夠從隱含對象中獲取隱含模型數據中獲取對象,再將請求參數綁定到對象中,在傳入入參。

@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";
    }
}

原理分析:

執行 @ModelAttribute 註解所修飾的方法,將從數據庫中獲取的對象存放到 Map 集合中,key 爲 user。

Spring MVC 從 map 中查找 user 對象,將表單數據封裝到與參數名稱對應的 user 對象屬性上。

SpringMVC將user對象做爲參數,傳遞給目標方法。

SpringMVC 肯定目標方法 POJO 類型入參的過程:

肯定一個 key:若目標方法的 POJO 類型的參數木有使用 @ModelAttribute 做爲修飾,則 key 爲 POJO 類名第一個字母的小寫,若使用了@ModelAttribute 來修飾,,則 key 爲 @ModelAttribute 註解的 value 屬性值。

在 implicitModel 中查找 key 對應的對象, 若存在,則做爲入參傳入。

若 implicitModel 中不存在 key 對應的對象,則檢查當前的 Handler 是否使用 @SessionAttributes 註解修飾。

若使用了該註解,且 @SessionAttributes 註解的 value 屬性值中包含了 key,則會從 HttpSession 中來獲取 key 所對應的 value 值,若存在則直接傳入到目標方法的入參中,若不存在則將拋出異常。

若 Handler 沒有標識 @SessionAttributes 註解或 @SessionAttributes 註解的 value 值中不包含 key,則會經過反射來建立 POJO 類型的參數,傳入爲目標方法的參數。

SpringMVC 會把 key 和 POJO 類型的對象保存到 implicitModel 中,進而會保存到 request 中。

視圖解析

視圖和視圖解析器

請求處理方法執行後,會最終返回一個 ModelAndView 對象,對於那些返回了 String、Model、Map 等的處理方法,內部也會被裝配成一個ModelAndView,它包含了邏輯名和模型對象的視圖。

經過視圖解析器獲得最終的視圖對象,最終的視圖能夠是 JSP、Excel、Pdf 等各類形式。

對於採起什麼視圖對模型渲染,處理器並不關心,從而實現 MVC 的充分解耦。

常見的視圖類:

在這裏插入圖片描述
每一個視圖解析器都實現了 Ordered 接口並開放出一個 order 屬性,能夠經過 order 屬性指定解析器的優先順序,order 越小優先級越高。

SpringMVC 會按視圖解析器順序的優先順序對邏輯視圖名進行解析,直到解析成功並返回視圖對象,不然將拋出 ServletException 異常。

JstlView

若項目中使用了 JSTL,則 InternalResourceView 會轉換成 JstlView。

若想使用 JSTL 的 fmt 標籤,則必須配置國際化資源文件。

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>
    <!-- 直接配置響應的頁面:無需通過控制器來執行結果 -->
    <mvc:view-controller path="/success" view-name="success"/>
</beans>

自定義視圖

若但願使用 Excel 展現數據列表,僅須要擴展 SpringMVC 提供的 AbstractExcelView 或 AbstractJExcelView 便可。

實現 buildExcelDocument() 方法,在方法中使用模型數據對象構建 Excel 文檔就能夠了。

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

視圖對象須要配置 IOC 容器中的一個 Bean ,使用 BeanNameViewResolver 做爲視圖解析器便可。

若但願直接在瀏覽器中直接下載 Excel 文檔,則能夠設置響應頭 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...");
        // 與視圖對象的 id 一致
        return "helloView"; 
    }
}

重定向

通常狀況下,控制器方法返回字符串類型的值會被當成邏輯視圖名處理

若是返回的字符串中帶 forward: 或 redirect:前綴時,SpringMVC 會對他們進行特殊處理。

redirect:success.jsp:會完成一個到 success.jsp 的重定向的操做。

forward:success.jsp:會完成一個到 success.jsp 的轉發操做。

數據綁定

數據綁定流程

Spring MVC 將 ServletRequest 對象及目標方法的入參傳遞給 WebDataBinderFactory,以建立 DataBinder 實例對象。

DataBinder 調用裝配在上下文中的 ConversionService 組件進行數據類型轉換、數據格式化工做。

調用 Vaidator 組件對已綁定了請求消息的入參對象進行數據合法性校驗,最終生成數據綁定 BindingData 對象。

Spring MVC 抽取 BindingResult 中的入參對象和校驗錯誤對象,將它們賦給處理方法的響應入參。

Spring MVC 經過反射機制對目標處理方法進行解析,將請求消息綁定處處理方法的入參中。數據綁定的核心部件是 DataBinder,運行機制以下:

在這裏插入圖片描述

自定義類型轉換器

ConversionService 是 Spring 類型轉換器的核心。

可使用 ConversionServiceFactoryBean 在 Spring IOC 容器中定義一個 ConversionService,Spring 將自動識別出 IOC 容器中的 ConversionService,並在 Bean 屬性配置及 Sprng MVC 處理方法入參綁定等場合使用它進行數據轉換。

Spring 支持的轉換器類型:

Converter<S, T>:將 S 類型對象轉爲 T 類型對象

ConverterFactory:將相同系列多個 「同質」 Converter 封裝在一塊兒。若是但願將一種類型的對象轉換爲另外一種類型及其子類的對象。

GenericConverter:會根據源類對象及目標類對象所在的宿主類中的上下文信息進行類型轉換

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>

直接配置響應的頁面,無需通過控制器的處理,可是直接影響其餘請求路徑失效。

找不到靜態資源須要配置,\<mvc:default-servlet-handler> 將在 SpringMVC 上下文中定義一個DefaultServletHttpRequestHandler,它會對進入 DispatcherServlet 的請求進行篩查,若是發現是沒有通過映射的請求,就將該請求交由 WEB 應用服務器默認的 Servlet 處理,若是不是靜態資源的請求,才由 DispatcherServlet 繼續處理。

配置類型轉換器時,須要指定轉換器引用。

完成 JSR303 數據驗證,也須要配置該標籤。

做用:

會自動註冊:RequestMappingHandlerMapping、RequestMappingHandlerAdapter 與ExceptionHandlerExceptionResolver 三個 bean。

還將提供如下支持:

支持使用 ConversionService 實例對錶單參數進行類型轉換。

支持使用 @NumberFormat、@DateTimeFormat 註解完成數據類型的格式化。

支持使用 @Valid 註解對 JavaBean 實例進行 JSR 303 驗證。

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

@InitBinder註解

由 @InitBinder 標識的方法,能夠對 WebDataBinder 對象進行初始化,WebDataBinder 是 DataBinder 的子類,用於完成從表單字段到 JavaBean 的綁定。

由 @InitBinder 標識的方法不能有返回值,必須是 void 類型。

由 @InitBinder 表示的方法入參一般是 WebDataBinder。

數據的格式化

Spring 在格式化模塊中定義了一個實現了 ConversionService 接口的 FormattingConversionService 實現類,該實現類擴展了 GenericConversionService 實現類,該類既有類型轉換的功能,又有格式化的功能。

FormattingConversionService 擁有一個 FormattingConversionServiceFactory 工廠類,後者用於在 Spring 上下文中給構造前者,FormattingConversionServiceFactory 內部註冊了:

NumberFormatAnnotationFormatterFactroy:支持對數字使用 @NumberFormat 註解。

odaDateTimeFormatAnnotationFormatterFactroy:支持對日期類型使用 @DataTimeFormat 註解。

裝配了 FormattingConversionServiceFactroyBean 後,就能夠在 Spring MVC 入參綁定及模型數據輸出時使用註解驅動了。

@DataTimeFormat 能夠對 Date、Calendar、Long 時間類型進行標註。

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

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

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

@NumberFormat,可對相似數字類型的屬性進行標註。

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

style:用於指定樣式類型,包括Style.NUMBER(正常數字類型)、Style.CURRENCY(貨幣類型)、
Style.PERCENT(百分數類型)。

JSR303 數據校驗

使用 JSR303 驗證標準

加入 hibernate validator 驗證框架

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

在 Bean 屬性上增長對應的驗證註解

在目標方法的 Bean 類型的前面增長 @Valid 註解

錯誤回顯及格式化

Spring MVC 除了會將表單/命令對象的校驗結果保存到對應的 BindingResult 或 Errors 對象中外,還會將全部校驗結果保存到 「隱含模型」。
即便處理方法的簽名中沒有對應於表單/命令對象的結果入參,校驗結果也會保存在 「隱含對象」 中。
隱含模型中的全部數據最終將經過 HttpServletRequest 的屬性列表暴露給 JSP 視圖對象,所以在 JSP 中能夠獲取錯誤信息。
在 JSP 頁面上可經過 <form:errors path=「userName」> 顯示錯誤消息。

<form:errors path="*"/> 顯示全部的錯誤信息

<form:errors path="lastName"/> 顯示某個表單域的錯誤信息

每一個屬性在數據綁定和數據校驗發生錯誤時,都會生成一個對應的 FieldError 對象。

當一個屬性校驗失敗後,校驗框架會爲該屬性生成 4 個消息代碼,這些代碼以校驗註解類名爲前綴,結合 modleAttribute、屬性名及屬性類型名生成多個對應的消息代碼:

例如 User 類中的 password 屬性標註了一個 @Pattern 註解,當該屬性值不知足 @Pattern 所定義的規則時, 就會產生如下 4 個錯誤代碼:
Pattern.user.password
Pattern.password
Pattern.java.lang.String
Pattern
當使用 Spring MVC 標籤顯示錯誤消息時, Spring MVC 會查看 WEB 上下文是否裝配了對應的國際化消息,若是沒有,則顯示默認的錯誤消息,不然使用國際化消息。
若數據類型轉換或數據格式轉換時發生錯誤,或該有的參數不存在,或調用處理方法時發生錯誤,都會在隱含模型中建立錯誤消息。

其錯誤代碼前綴說明以下:
required:必要的參數不存在。如 @RequiredParam(「param1」) 標註了一個入參,可是該參數不存在
typeMismatch:在數據綁定時,發生數據類型不匹配的問題
methodInvocation:Spring MVC 在調用處理方法時發生了錯誤

國際化

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

使用 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 對應的信息

@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,而再也不依賴於瀏覽器的語言設置狀況。

<?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 實現的。
Spring 用 Jakarta Commons FileUpload 技術實現了一個 MultipartResolver 實現類:

CommonsMultipartResovler :
Spring MVC 上下文中默認沒有裝配 MultipartResovler,所以默認狀況下不能處理文件的上傳工做,若是想使用 Spring 的文件上傳功能,需如今上下文中配置 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 可使用攔截器對請求進行攔截處理,用戶能夠自定義攔截器來實現特定功能,自定義攔截器必須實現 HandlerInterceptor 接口。

preHandle():這個方法在業務處理器處理請求以前被調用,在該方法中對用戶請求 request 進行處理。若是程序員決定該攔截器對請求進行攔截處理後還要調用其餘的攔截器,或者是業務處理器去進行處理,則返回true;若是程序員決定不須要再調用其餘的組件去處理請求,則返回false。

postHandle():這個方法在業務處理器處理完請求後,可是DispatcherServlet 向客戶端返回響應前被調用,在該方法中對用戶請求request進行處理。

afterCompletion():這個方法在 DispatcherServlet 徹底處理完請求後被調用,能夠在該方法中進行一些資源清理的操做。

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

多個攔截器的執行順序

按照攔截器的配置的順序依次調用每一個攔截器的 preHandle 方法,當請求業務處理器執行完畢後,依次對倒敘對每一個攔截器放行,執行 postHandle 方法,當請求徹底處理完畢後,而後依次倒敘執行每一個攔截器的 afterCompletion 方法。

異常處理

Spring MVC 經過 HandlerExceptionResolver 處理程序的異常,包括 Handler 映射、數據綁定及目標方法執行時發生的異常。

HandlerExceptionResolver

@ExceptionHandler

能夠經過 @ExceptionHandler(value = {java.lang.RuntimeException.class}) 的方式捕捉一個異常,若是捕捉成功,自動執行標誌該註解的方法。

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

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

能夠經過 ModelAndView 對象將異常對象添加。

異常對象捕捉的優先級

Spring MVC 是有優先級的,他會執行離捕捉異常離發生異常最近的那個方法。

@ControllerAdvice

該註解是定義在類級別上的,在類上標註了該註解後,全部控制器上發生了註解以後都會經過這個類的方法處理。

ExceptionHandlerExceptionResolver

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

@ExceptionHandler 註解定義的方法優先級問題:

例如發生的是NullPointerException,可是聲明的異常有 RuntimeException 和 Exception,此候會根據異常的最近繼承關係找到繼承深度最淺的那個 @ExceptionHandler 註解方法,即標記了 RuntimeException 的方法

ExceptionHandlerMethodResolver 內部若找不到 @ExceptionHandler 註解的話,會找 @ControllerAdvice 中的@ExceptionHandler 註解方法。

ResponseStatusExceptionResolver

在類上標註 @ResponseStatus 註解後,能夠自定義狀態碼及提示信息。

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

DefaultHandlerExceptionResolver

對一些特殊的異常進行處理,好比:
NoSuchRequestHandlingMethodException、HttpRequestMethodNotSupportedException、HttpMediaTypeNotSupportedException、HttpMediaTypeNotAcceptableException等。

SimpleMappingExceptionResolver

若是但願對全部異常進行統一處理,可使用 SimpleMappingExceptionResolver,它將異常類名映射爲視圖名,即發生異常時使用對應的視圖報告異常。

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

Spring Web 運行流程

  1. 用戶向服務器發送請求,請求被 Spring MVC DispatcherServlet 捕獲。
  2. DispatcherServlet 對請求 URL 進行解析,獲得請求資源標識符(URI),判斷是否存在。
  3. 若是不存在,判斷是否配置了 \<mvc:default-servlet-handler>,若是配置了,執行目標資源(通常爲靜態資源);若是沒有配置,客戶端顯示 404 錯誤。
  4. 若是存在,根據該 URI,調用 HandlerMapping 得到該 Handler 配置的全部相關的對象(包括Handler對象以及Handler對象對應的攔截器),最後以 HandlerExecutionChain 對象的形式返回。
  5. DispatcherServlet 根據得到的 Handler,選擇一個合適的 HandlerAdapter。
  6. 得到 HandlerAdapter 後,開始正序執行攔截器的 preHandler 方法。
  7. 提取 Request 中的模型數據,填充 Handler 入參,開始執行 Handler 方法,處理請求(數據轉換、數據格式化、數據驗證)。
  8. Handler執行完成後,向DispatcherServlet 返回一個ModelAndView對象。
  9. 逆向執行攔截器的 postHandler 方法。
  10. 根據返回的 ModelAndView 選擇一個合適的視圖解析器來渲染視圖。
  11. 在返回給客戶端時須要逆向執行攔截器的 AfterCompletion 方法。
  12. 將渲染結果返回給客戶端。
相關文章
相關標籤/搜索