天天看點

SSM整合尚矽谷SpringMVCSpringMVC

SpringMVC

1.SpringMVC概述

MVC:

  • Model(模型): 資料模型,提供要展示的資料,:Value Object(資料Dao) 和 服務層(行為Service),提供資料和業務。
  • View(視圖): 負責進行模型的展示,即使用者界面
  • Controller(控制器): 排程員,接收使用者請求,委托給模型進行處理(狀态改變),處理完畢後把傳回的模型資料傳回給視圖,由視圖負責展示。

SpringMVC的特點:

  • Spring為展現層提供的基于MVC設計理念的Web架構
  • SpirngMVC通過一套MVC注解,讓POJO成為處理請求的控制器,而無須實作任何接口
  • 支援REST風格的URL請求
  • 采用了松散耦合可拔插元件結構,擴充性和靈活性

2. HelloWorld

1. 導入依賴

spring-webmvc的maven依賴

<dependencies>
        <!-- SpringWeb基礎包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>4.0.0.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>4.0.0.RELEASE</version>
        </dependency>
        
		<!--        核心包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.0.0.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>4.0.0.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>4.0.0.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-expression</artifactId>
            <version>4.0.0.RELEASE</version>
        </dependency>

       <!--       日志包-->
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.1.3</version>
        </dependency>
        
		<!--        注解支援包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>4.0.0.RELEASE</version>
        </dependency>
    </dependencies>      

2. 配置web.xml , 注冊DispatcherServlet

DispatcherServlet

:前端控制器,負責請求分發。

要綁定Spring的配置檔案

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

    <!--注冊DispatcherServlet,請求分發器(前端控制器)-->
    <servlet>
        <servlet-name>springDispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!--綁定Spring配置檔案-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springmvc-config.xml</param-value>
        </init-param>
        <!--啟動級别為1,即伺服器啟動後就啟動-->
        <!--值越小優先級越高,越先建立對象-->
        <load-on-startup>1</load-on-startup>
    </servlet>

    <!-- /  攔截所有的請求;(不包括.jsp,jsp由Tomcat來處理),
		覆寫了父類的DispatcherServlet的pattern,靜态資源被攔截。-->
    <!-- *.jsp 攔截jsp請求,覆寫了父類的JspServlet-->
    <!-- /* 攔截所有的請求;(包括.jsp,一旦攔截jsp頁面就不能顯示了)-->
    <servlet-mapping>
        <servlet-name>springDispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

</web-app>      

3. 導入Spring配置檔案

Spring的配置檔案Springmvc-config.xml。

  1. 開啟了包掃描,讓指定包下的注解生效,由IOC容器統一管理
  2. 配置了視圖解析器

    InternalResourceViewResolver

    ,這裡可以設定字首和字尾,拼接視圖名字
<?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">

    <!--開啟包掃描,讓指定包下的注解生效,由IOC容器統一管理-->
    <context:component-scan base-package="com.xiao.controller"/>

    <!--配置視圖解析器,拼接視圖名字,找到對應的視圖-->
    <bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!--字首-->
        <property name="prefix" value="/WEB-INF/page/"/>
        <!--字尾-->
        <property name="suffix" value=".jsp"/>
    </bean>
</beans>      

4. 編寫controller層

HelloController類:

  1. @Controller:告訴Spirng這是一個控制器,交給IOC容器管理
  2. @RequestMapping("/hello01"):/ 表示項目位址,當請求項目中的hello01時,傳回一個/WEB-INF/page/success.jsp頁面給前端
@Controller
public class HelloController {

    @RequestMapping("/hello01")
    public String toSuccess(){
        System.out.println("請求成功頁面");
        return "success";
    }
    @RequestMapping("/hello02")
    public String toError() {
        System.out.println("請求錯誤頁面");
        return "error";
    }
}      

5. 編寫跳轉的jsp頁面

項目首頁 index.jsp,兩個超連結,分别發出hello01和hello02的請求

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

  <a href="hello01" target="_blank" rel="external nofollow" >點這裡去成功頁面</a>
  <a href="hello02" target="_blank" rel="external nofollow" >點這裡去失敗頁面</a>

  </body>
</html>      

成功頁面success.jsp和失敗頁面error.jsp,要注意檔案的路徑/WEB-INF/page/…jsp,與上面的保持一緻

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>成功頁面</title>
</head>
<body>
	<h1>這裡是成功頁面</h1>
</body>
</html>      
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>錯誤頁面</title>
</head>
<body>
    <h1>這裡是錯誤頁面</h1>
</body>
</html>      

6) 通路

啟動項目:

點選去成功頁面,可以看到發出了/hello01請求,頁面轉發到/WEB-INF/page/success.jsp,控制台輸出了請求成功頁面。

3. Url請求

3.1 運作流程

  1. 用戶端點選連結發送請求:http://localhost:8080/hello01;
  2. 來到tomcat伺服器;
  3. SpringMVC的前端控制器收到所有請求;
  4. 看請求位址和@RequestMapping标注的哪個比對,來找到底使用哪個類的哪個方法來處理;
  5. 前端控制器找到目标處理器類和目标方法,直接利用反射執行目标方法;
  6. 方法執行完後有一個傳回值,SpringMVC認為這個傳回值就是要去的頁面位址;
  7. 拿到方法傳回值後,視圖解析器進行拼串得到完整的頁面位址
  8. 得到頁面位址,前端控制器幫我們轉發到頁面

3.2 url映射

RequestMapping

01 标注在方法上

告訴SpringMVC這個方法用來處理什麼請求。

@RequestMapping("/hello01")

中的

/

可以省略,就是預設從目前項目下開始。

02 标注在類上

表示為目前類中的所有方法的請求位址,指定一個基準路徑。toSuccess()方法處理的請求路徑是

/haha/hello01

@Controller
@RequestMapping("/haha")
public class HelloController {
​
    @RequestMapping(value = "/hello01")
    public String toSuccess(){
        System.out.println("請求成功頁面");
        return "success";
    }
}      

03 規定請求方式

method屬性規定請求方式,預設是所求請求方式都行。method = RequestMethod.GET,method = RequestMethod.POST。

如果方法不比對會報:HTTP Status 405 錯誤 – 方法不被允許

@RequestMapping(value = "/hello01",method = RequestMethod.GET)
public String toSuccess(){
    System.out.println("請求成功頁面");
    return "success";
}      

組合用法

  • @GetMapping 等價于 @RequestMapping(method =RequestMethod.GET)
  • @PostMapping
  • @PutMapping
  • @DeleteMapping
  • @PatchMapping

04 規定請求參數

params屬性規定請求參數。會造成錯誤:HTTP Status 400 – 錯誤的請求

不攜帶該參數,表示參數值為null;攜帶了不給值表示參數值是空串

//必須攜帶username參數
@RequestMapping(value = "/hello03",params ={"username"})
//必須不攜帶username參數
@RequestMapping(value = "/hello03",params ={"!username"})
//必須攜帶username參數,且值必須為123
@RequestMapping(value = "/hello03",params ={"username=123"})
//username參數值必須不為123,不攜帶或者攜帶了不是123都行
@RequestMapping(value = "/hello03",params ={"username=!123"})
//username參數值必須不為123,不攜帶password,攜帶page
@RequestMapping(value = "/hello03",params ={"username=!123","page","!password"})      

05 規定請求頭

headers屬性規定請求頭。其中User-Agent:浏覽器資訊

谷歌浏覽器:User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.3

06 Ant風格URL

URL位址可以寫模糊的通配符,模糊和精确多個比對情況下精确優先。

?:替代任意一個字元

@RequestMapping( "/hello0?") /      

*:替代任意多個字元或一層路徑

@RequestMapping( "/hello0*")   //任意多個字元
@RequestMapping( "/a/*/hello01")  //一層路徑      
@RequestMapping(value = "/test/*/a")
    public String myMethodTest01() {
        System.out.println("post01");
        return "success"; 
    }
    // test/[^\/]+/b ->post01
    // /test/*/b ->post02
    @RequestMapping(value = "/test/**/a")
    public String myMethodTest02() {
        System.out.println("post02");
        return "success";
    }      

**:替代任意多層路徑

@RequestMapping( "/a/**/hello01")  //任意多層路徑      

07 PathVariable

可以用/test/{paramsName1}/{paramsName2}來擷取Url上傳的參數值

//擷取到{id}占位符,占位符可以在任意路徑地方寫{變量名}
    //@PathVariable("id") 擷取請求路徑哪個占位符的值
    @RequestMapping(value = "/test/{id}", method = RequestMethod.GET)
    public String myMethodTest03(@PathVariable("id") String id) {
        System.out.println("路徑上占位符"+id);
        return "success";
    }      

3.3 Spring配置檔案的預設位置

預設位置是 /WEB-INF/xxx-servlet.xml,其中xxx是自己在web.xml檔案中配置的servlet-name屬性。

例如:

dispatcherServlet-servlet.xml

當然也可以手動指定檔案位置。

<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:applicationContext.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
</servlet>      

3.5 url-pattern

/ 攔截所有的請求,不攔截jsp

/* 攔截所有的請求,包括*.jsp,一旦攔截jsp頁面就不能顯示了。. jsp是tomcat處理的事情

看Tomcat的配置檔案web.xml中,有DefaultServlet和JspServlet,

  • DefaultServlet是Tomcat中處理靜态資源的,Tomcat會在伺服器下找到這個資源并傳回。如果我們自己配置

    url-pattern=/

    ,相當于禁用了Tomcat伺服器中的DefaultServlet,這樣如果請求靜态資源,就會去找前端控制器找@RequestMapping,這樣靜态資源就不能通路了。解決辦法:
    <!-- 告訴Spring MVC自己映射的請求就自己處理,不能處理的請求直接交給tomcat -->
    <mvc:default-servlet-handler />
    <!--開啟MVC注解驅動模式,保證動态請求和靜态請求都能通路-->
    <mvc:annotation-driven/>      

4. REST風格

4.1 概述

REST就是一個資源定位及資源操作的風格。不是标準也不是協定,隻是一種風格。基于這個風格設計的軟體可以更簡潔,更有層次,更易于實作緩存等機制。其強調HTTP應當以資源為中心,并且規範了URI的風格;規範了HTTP請求動作(GET/PUT/POST/DELETE/HEAD/OPTIONS)的使用,具有對應的語義。

  • 資源(Resource):網絡上的一個實體,每種資源對應一個特定的URI,即URI為每個資源的獨一無二的識别符;
  • 表現層(Representation):把資源具體呈現出來的形式,叫做它的表現層。比如txt、HTML、XML、JSON格式等;
  • 狀态轉化(State Transfer):每發出一個請求,就代表一次用戶端和伺服器的一次互動過程。GET用來擷取資源,POST用來建立資源,PUT用來更新資源,DELETE用來删除資源。

在參數上使用 @PathVariable 注解,可以擷取到請求路徑上的值,也可以寫多個

   @RequestMapping(value = "/hello04/username/{id}")
    public String test2(@PathVariable("id") int id){
        System.out.println(id);
        return "success";
    }
12345      

4.2 頁面上發出PUT請求

對一個資源的增删改查用請求方式來區分:

  • /book/1 GET:查詢1号圖書
  • /book/1 DELETE:删除1号圖書
  • /book/1 PUT:修改1号圖書
  • /book POST:新增圖書

頁面上隻能發出GET請求和POST請求。将POST請求轉化為put或者delete請求的步驟:

  1. 把前端發送方式改為post 。
  2. 在web.xml中配置一個filter:HiddenHttpMethodFilter過濾器
  3. 必須攜帶一個鍵值對,key=_method, value=put或者delete
<!--這個過濾器的作用 :就是将post請求轉化為put或者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>
​
<form action="hello03" method="post">
  <input type="hidden" name="_method" value="delete">
  <input type="submit" name="送出">
</form>
​      

注意

高版本Tomcat會出現問題:JSPs only permit GET POST or HEAD,在頁面上加上異常處理即可

<%@ page contentType="text/html;charset=UTF-8" language="java"  isErrorPage="true" %>
1      

5 請求參數處理

5.1 傳入參數

1. 如果送出的參數名稱和處理方法的參數名一緻,則無需處理,直接使用

送出資料 : http://localhost:8080/hello05?username=zhangsan,控制台會輸出zhangsan

@RequestMapping("/hello05")
public String test03(String username) {
    System.out.println(username);
    return "success";
}      

2. 送出的參數名稱和處理方法的參數名不一緻,使用@RequestParam注解

注解

@RequestParam

可以擷取請求參數,預設必須攜帶該參數,也可以指定

required=false

,和沒攜帶情況下的預設值

defaultValue

​
@RequestMapping(value = "/hello03",params ={"username"})      
public String test03(@RequestParam("user")String username 
                等價于   username = request.getParameter("user"))
    傳入網址為:"handle01?user=aaa"      
@RequestMapping("/hello05")
public String test03(@RequestParam(value = "username",required = false, defaultValue ="hehe" ) String name) {
    System.out.println(name);
    return "success";
}      
//比較
@ReqquestParam("user02") 擷取問号後面占位符
@PathVariable("user01") 擷取路徑占位符 /book/{user01}?user02=admin
​      

還有另外兩個注解:

  • @RequestHeader

    :擷取請求頭中的資訊,比如User-Agent:浏覽器資訊
    @RequestMapping("/hello05")
    public String test03(@RequestHeader("User-Agent" ) String name) {
        System.out.println(name);
        return "success";
    }      
  • @CookieValue

    :擷取某個cookie的值
    @RequestMapping("/hello05")
    public String test03(@CookieValue("JSESSIONID" ) String name) {
         System.out.println(name);
         return "success";
    }
    等價于
    Cookie[] cookies = request.getCookies();
    for(Cookie c:cookies){
        if(c.getName().equals("JSESSIONID")){
            String cookievalue = c.getValue();
        }
    }      

5.2 傳入一個對象

傳入POJO,SpringMVC會自動封裝,送出的表單域參數必須和對象的屬性名一緻,否則就是null,請求沒有攜帶的字段,值也會是null。同時也還可以級聯封裝。

建立兩個對象User和Address:

public class User {
    private String username;
    private Integer age;
    private Address address;
    //....
}
123456
public class Address {
    private String name;
    private Integer num;
        //....
}
12345      

前端請求:

<form action="hello06" method="post">
    姓名: <input type="text" name="username"> <br>
    年齡: <input type="text" name="age"><br>
    位址名:<input type="text" name="address.name"><br>
    位址編号:<input type="text" name="address.num"><br>
    <input type="submit" name="送出">
</form>      

後端通過對象名也能拿到對象的值,沒有對應的值則為null

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

5.3 傳入原生ServletAPI

處理方法還可以傳入原生的ServletAPI:

@RequestMapping("/hello07")
public String test04(HttpServletRequest request, HttpSession session) {
    session.setAttribute("sessionParam","我是session域中的值");
    request.setAttribute("reqParam","我是request域中的值");
    return "success";
}      

通過EL表達式擷取到值,

${requestScope.reqParam}

<%@ page contentType="text/html;charset=UTF-8" language="java"  isErrorPage="true" %>
<html>
<head>
    <title>成功頁面</title>
</head>
​
<body>
​
<h1>這裡是成功頁面</h1>
${requestScope.reqParam}
${sessionScope.sessionParam}
</body>
</html>      

5.4 亂碼問題

一定要放在在其他Filter前面。

<filter>
   <filter-name>encoding</filter-name>
   <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <!--解決請求亂碼-->
   <init-param>
       <param-name>encoding</param-name>
       <param-value>utf-8</param-value>
   </init-param>
    <!--解決響應亂碼-->
   <init-param>
        <param-name>forceEncoding</param-name>
        <param-value>true</param-value>
   </init-param>
</filter>
<filter-mapping>
   <filter-name>encoding</filter-name>
   <url-pattern>/*</url-pattern>
</filter-mapping>
​
<!--在Tomcat的server.xml中的8080處 URLEncoding="UTF-8"-->      

6. 資料輸出

6.1 Map、Model、ModelMap

實際上都是調用的 BindingAwareModelMap(隐含模型),将資料放在請求域(requestScope)中進行轉發,用EL表達式可以取出對應的值。

/**
 * SpringMVC除過在方法上傳入原生的request和session外還能怎麼樣把資料帶給頁面
 *
 * 1)、可以在方法處傳入Map、或者Model或者ModelMap。
 *      給這些參數裡面儲存的所有資料都會放在請求域中。可以在頁面擷取
 *   關系:
 *      Map,Model,ModelMap:最終都是BindingAwareModelMap在工作;
 *      相當于給BindingAwareModelMap中儲存的東西都會被放在請求域中;
 *
 *      Map(interface(jdk))      Model(interface(spring)) 
 *          ||                          //
 *          ||                         //
 *          \/                        //
 *      ModelMap(class)               //
 *                  \\              //
 *                   \\            //
 *                  ExtendedModelMap
 *                          ||
 *                          \/
 *                  BindingAwareModelMap
 *
 * 2)、方法的傳回值可以變為ModelAndView類型;
 *          既包含視圖資訊(頁面位址)也包含模型資料(給頁面帶的資料);
 *          而且資料是放在請求域中;
 *          request、session、application;
 *          
 *
 * @author lfy
 *
 */      
  • Map
@RequestMapping("/Api2")
    public String api2(Map<String,Object> map){
        map.put("msg","hello");
        return "map";
    } 
          
  • Model
@RequestMapping("/Api3")
    public String api3(Model model){
        model.addAttribute("msg","hello2");
        return "map";
    }      
  • ModelMap
@RequestMapping("/Api4")
    public String api4(ModelMap modelMap){
        modelMap.addAttribute("msg","hello3");
        return "map";
    }      

都放在請求域request中,類型都是BindingAwareModelMap,相當于放在BindingAwareModelMap的請求都放在請求域中

map頁面:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
​
<head>
    <title>Title</title>
</head>
​
<body>
​
pageScope:  ${pageScope.msg}
​
requestScope :   ${requestScope.msg}
​
sessionScope:     ${sessionScope.msg}
​
applicationScope:   ${applicationScope.msg}
​
</body>
</html>      

【補充】jsp的4個作用域 pageScope、requestScope、sessionScope、applicationScope的差別:

  • page指目前頁面有效。在一個jsp頁面裡有效
  • request 指在一次請求的全過程中有效,即從http請求到伺服器處理結束,傳回響應的整個過程,存放在HttpServletRequest對象中。在這個過程中可以使用forward方式跳轉多個jsp。在這些頁面裡都可以使用這個變量。
  • Session是使用者全局變量,在整個會話期間都有效。隻要頁面不關閉就一直有效(或者直到使用者一直未活動導緻會話過期,預設session過期時間為30分鐘,或調用HttpSession的invalidate()方法)。存放在HttpSession對象中
  • application是程式全局變量,對每個使用者每個頁面都有效。存放在ServletContext對象中。它的存活時間是最長的,如果不進行手工删除,它們就一直可以使用

6.2 ModelAndView

方法的傳回值變為ModelAndView,傳回一個模型視圖對象ModerAndView, 既包含視圖資訊(頁面位址),也包含模型資料(給頁面帶的資料)

@RequestMapping("/hello04")
public ModelAndView test04 (){
   //建立一個模型視圖對象,也可以直接傳入名字
   ModelAndView mv = new ModelAndView();
   //封裝要顯示到視圖中的資料
   //相當于req.setAttribute("msg",HelloWorld!);
   mv.addObject("msg","HelloWorld!");
   //設定視圖的名字,相當于之前的return "success";  WebContent/WEB-INF/pages/success.jsp
   mv.setViewName("success");
   return mv;
}      

6.3 @SessionAttributes

給Session域中攜帶資料使用注解

@SessionAttributes

,隻能标在類上,value屬性指定key,type可以指定儲存類型。這個注解會引發異常一般不用,就用原生API

@SessionAttributes(value = "msg")

:表示給BindingAwareModelMap中儲存key為msg的資料時,在session中也儲存一份;

@SessionAttributes(types = {String.class})

:表示隻要儲存String類型的資料時,給session中也放一份。

//表示給BindingAwareModelMap中儲存key為msg的資料時,在session中也儲存一份
@SessionAttributes(value = "msg")
@Controller
public class outputController {
    @RequestMapping("/hello01")
    public String test01 (Map<String,Object> map){
        map.put("msg","HelloWorld!");
        return "success";
    }
}      

6.4 @ModelAttribute

ModelAttribute:
使用場景:
1)、頁面:form送出更新
2)、dao:全字段更新。沒帶的字段會在資料庫中更新為null;
​
/**
 * 測試ModelAttribute注解;
 * 使用場景:書城的圖書修改為例;
 * 1)頁面端;
 *      顯示要修改的圖書的資訊,圖書的所有字段都在
 * 2)servlet收到修改請求,調用dao;
 *      String sql="update bs_book set title=?,
 *                  author=?,price=?,
 *                  sales=?,stock=?,img_path=?
 *              where id=?";
 * 3)實際場景?
 *      并不是全字段修改;隻會修改部分字段,以修改使用者資訊為例;
 *      username  password  address;
 *      1)、不修改的字段可以在頁面進行展示但是不要提供修改輸入框;
 *      2)、為了簡單,Controller直接在參數位置來寫Book對象
 *      3)、SpringMVC為我們自動封裝book;(沒有帶的值是null)
 *      4)、如果接下來調用了一個全字段更新的dao操作;會将其他的字段可能變為null;
 *          sql = "update bs_book set"
 *          if(book.getBookName()){
 *              sql +="bookName=?,"
 *          }
 *          if(book.getPrice()){
 *              sql +="price=?"
 *          }
 *
 * 4)、如何能保證全字段更新的時候,隻更新了頁面攜帶的資料;
 *      1)、修改dao;代價大?
 *      2)、Book對象是如何封裝的?
 *          1)、SpringMVC建立一個book對象,每個屬性都有預設值,bookName就是null;
 *              1、讓SpringMVC别建立book對象,直接從資料庫中先取出一個id=100的book對象的資訊
 *              2、Book [id=100, bookName=西遊記, author=張三, stock=12, sales=32, price=98.98]
 *
 *          2)、将請求中所有與book對應的屬性一一設定過來;
 *              3、使用剛才從資料庫取出的book對象,給它 的裡面設定值;(請求參數帶了哪些值就覆寫之前的值)
 *              4、帶了的字段就改為攜帶的值,沒帶的字段就保持之前的值
 *          3)、調用全字段更新就有問題;
 *              5、将之前從資料庫中查到的對象,并且封裝了請求參數的對象。進行儲存;
 *
 * @author lfy
 */      

方法入參标注該注解後,入參的對象就會放到資料模型中,會提前于控制方法先執行,并發方法允許的結果放在隐含模型中。

處理這樣的場景:

前端傳來資料,SpringMVC自動封裝成對象,實際上是建立了一個對象,每個屬性都有預設值,然後将請求參數中對應是屬性設定過來,但是如果沒有的值将會是null,如果拿着這個資料去更新資料庫,會造成其他字段也變為null。是以希望使用

@ModelAttribute

,會在目标方法執行前先做一些處理

@ModelAttribute
public void  myModelAttribute(ModelMap modelMap){
    System.out.println("modelAttribute方法執行了");
    //提前做一些處理
    User user = new User("zhangsan",20);
    //儲存一個資料到BindingAwareModelMap中,目标方法可以從中取出來
    modelMap.addAttribute("user",user);
}

@RequestMapping("/hello05")
public void  test05(@ModelAttribute("user") User user){
    System.out.println("目标方法執行了");
    //在參數上加上@ModelAttribute注解,可以拿到提前存入的資料
    System.out.println(user);

}      

6.5 @ResponseBody

在控制器類中,在方法上使用@ResponseBody注解可以不走視圖解析器,如果傳回值是字元串,那麼直接将字元串寫到用戶端;如果是一個對象,會将對象轉化為JSON串,然後寫到用戶端。

或者在類上加 @RestController注解,可以讓類中的所有方法都不走視圖解析器,直接傳回JSON字元串

7. 視圖源碼執行流程

7.0 SpringMVC的九大元件

  • multipartResolver:檔案上傳解析器
  • localeResolver:區域資訊解析器,和國際化有關
  • themeResolver:主題解析器
  • handlerMappings:handler的映射器
  • handlerAdapters:handler的擴充卡
  • handlerExceptionResolvers:異常解析功能
  • viewNameTranslator:請求到視圖名的轉換器
  • flashMapManager:SpringMVC中允許重定向攜帶資料的功能
  • viewResolvers:視圖解析器
/** 檔案上傳解析器*/
    private MultipartResolver multipartResolver;
    /** 區域資訊解析器;和國際化有關 */
    private LocaleResolver localeResolver;
    /** 主題解析器;強大的主題效果更換 */
    private ThemeResolver themeResolver;
    /** Handler映射資訊;HandlerMapping */
    private List<HandlerMapping> handlerMappings;
    /** Handler的擴充卡 */
    private List<HandlerAdapter> handlerAdapters;
    /** SpringMVC強大的異常解析功能;異常解析器 */
    private List<HandlerExceptionResolver> handlerExceptionResolvers;
    /**  */
    private RequestToViewNameTranslator viewNameTranslator;
    /** FlashMap+Manager:SpringMVC中運作重定向攜帶資料的功能 */
    private FlashMapManager flashMapManager;
    /** 視圖解析器; */
    private List<ViewResolver> viewResolvers;      

onRefresh()->initStrategies() DispatcherServlet中:

protected void initStrategies(ApplicationContext context) {
        initMultipartResolver(context);
        initLocaleResolver(context);
        initThemeResolver(context);
        initHandlerMappings(context);
        initHandlerAdapters(context);
        initHandlerExceptionResolvers(context);
        initRequestToViewNameTranslator(context);
        initViewResolvers(context);
        initFlashMapManager(context);
    }
      

例:初始化HandlerMapping

private void initHandlerMappings(ApplicationContext context) {
        this.handlerMappings = null;

        if (this.detectAllHandlerMappings) {
            // Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
            Map<String, HandlerMapping> matchingBeans =
                    BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
            if (!matchingBeans.isEmpty()) {
                this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
                // We keep HandlerMappings in sorted order.
                OrderComparator.sort(this.handlerMappings);
            }
        }
        else {
            try {
                HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
                this.handlerMappings = Collections.singletonList(hm);
            }
            catch (NoSuchBeanDefinitionException ex) {
                // Ignore, we'll add a default HandlerMapping later.
            }
        }

        // Ensure we have at least one HandlerMapping, by registering
        // a default HandlerMapping if no other mappings are found.
        if (this.handlerMappings == null) {
            this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
            if (logger.isDebugEnabled()) {
                logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
            }
        }
    }      
元件的初始化:   有些元件在容器中是使用類型找的,有些元件是使用id找的;      

去容器中找這個元件,如果沒有找到就用預設的配置;

7.1 前端控制器DisatcherServlet

7.2 SpringMVC執行流程

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
        try {
            ModelAndView mv = null;
            Exception dispatchException = null;
            try {
                
                //1、檢查是否檔案上傳請求
                
                processedRequest = checkMultipart(request);
                multipartRequestParsed = processedRequest != request;
                
                // Determine handler for the current request.
                //2、根據目前的請求位址找到那個類能來處理;
                
                mappedHandler = getHandler(processedRequest);

                //3、如果沒有找到哪個處理器(控制器)能處理這個請求就404,或者抛異常
                
                if (mappedHandler == null || mappedHandler.getHandler() == null) {
                    noHandlerFound(processedRequest, response);
                    return;
                }

                // Determine handler adapter for the current request.
                //4、拿到能執行這個類的所有方法的擴充卡;(反射工AnnotationMethodHandlerAdapter)
                
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
                
                // Process last-modified header, if supported by the handler.
                
                String method = request.getMethod();
                boolean isGet = "GET".equals(method);
                if (isGet || "HEAD".equals(method)) {
                    long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                    if (logger.isDebugEnabled()) {
                        String requestUri = urlPathHelper.getRequestUri(request);
                        logger.debug("Last-Modified value for [" + requestUri + "] is: " + lastModified);
                    }
                    if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                        return;
                    }
                }
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }
                try {
                    
                    // Actually invoke the handler.處理(控制)器的方法被調用
                    //控制器(Controller),處理器(Handler)
                    //5、擴充卡來執行目标方法;
                    //将目标方法執行完成後的傳回值作為視圖名,設定儲存到ModelAndView中
                    //目标方法無論怎麼寫,最終擴充卡執行完成以後都會将執行後的資訊封裝成ModelAndView
                    
                    mv = ha.handle(processedRequest,response,mappedHandler.getHandler());
                } finally {
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        return;
                    }
                }
                applyDefaultViewName(request, mv);//如果沒有視圖名設定一個預設的視圖名;
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            } catch (Exception ex) {
                dispatchException = ex;
            }
            
            //轉發到目标頁面;
            //6、根據方法最終執行完成後封裝的ModelAndView;
            //轉發到對應頁面,而且ModelAndView中的資料可以從請求域中擷取
            processDispatchResult(processedRequest, response, mappedHandler, 
                                  mv, dispatchException);
        } catch (Exception ex) {
            triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
        } catch (Error err) {
            triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
        } finally {
            if (asyncManager.isConcurrentHandlingStarted()) {
                
                // Instead of postHandle and afterCompletion
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                return;
            }
            // Clean up any resources used by a multipart request.
            if (multipartRequestParsed) {
                cleanupMultipart(processedRequest);
            }
        }
    }      

總體概覽

  1. 使用者送出請求,DispatcherServlet接收請求并攔截請求。
  2. 調用doDispatch()方法進行處理:
    1. getHandler():根據目前請求位址中找到能處理這個請求的目标處理器類(處理器);
      • 根據目前請求在HandlerMapping中找到這個請求的映射資訊,擷取到目标處理器類
      • mappedHandler = getHandler(processedRequest);
    2. getHandlerAdapter():根據目前處理器類找到能執行這個處理器方法的擴充卡;
      • 根據目前處理器類,找到目前類的HandlerAdapter(擴充卡)
      • HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
    3. 使用剛才擷取到的擴充卡(AnnotationMethodHandlerAdapter)執行目标方法;
      • mv = ha.handle(processedRequest,response,mappedHandler.getHandler());
    4. 目标方法執行後,會傳回一個ModerAndView對象
      • mv = ha.handle(processedRequest,response,mappedHandler.getHandler());
    5. 根據ModerAndView的資訊轉發到具體頁面,并可以在請求域中取出ModerAndView中的模型資料
      • processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

    HandlerMapping為處理器映射器,儲存了每一個處理器能處理哪些請求的映射資訊,handlerMap

    HandlerAdapter為處理器擴充卡,能解析注解方法的擴充卡,其按照特定的規則去執行Handler

具體細節

步驟一:

getHandler():

**怎麼根據目前請求就能找到哪個類能來處理?**      
  • getHandler()會傳回目标處理器類的執行鍊
  • HandlerMapping:處理器映射:他裡面儲存了每一個處理器能處理哪些請求的映射資訊
  • handlerMap:ioc容器啟動建立Controller對象的時候掃描每個處理器都能處理什麼請求,儲存在HandlerMapping的handlerMap屬性中;下一次請求過來,就來看哪個HandlerMapping中有這個請求映射資訊就行了

循環周遊拿到能處理url的類

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        for (HandlerMapping hm : this.handlerMappings) {
            if (logger.isTraceEnabled()) {
                logger.trace(
                        "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
            }
            HandlerExecutionChain handler = hm.getHandler(request);
            if (handler != null) {
                return handler;
            }
        }
        return null;
    }      

步驟二:

getHandlerAdapter():

如何找到目标處理器類的擴充卡。要拿擴充卡才去執行目标方法

AnnotationMethodHandlerAdapter:

  • 能解析注解方法的擴充卡;
  • 處理器類中隻要有标了注解的這些方法就能用;
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
        for (HandlerAdapter ha : this.handlerAdapters) {
            if (logger.isTraceEnabled()) {
                logger.trace("Testing handler adapter [" + ha + "]");
            }
            if (ha.supports(handler)) {
                return ha;
            }
        }
        throw new ServletException("No adapter for handler [" + handler +
                "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
    }      

步驟三:

執行目标方法的細節;

mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

return invokeHandlerMethod(request, response, handler);

protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
          //拿到方法的解析器
        ServletHandlerMethodResolver methodResolver = getMethodResolver(handler);
          //方法解析器根據目前請求位址找到真正的目标方法
        Method handlerMethod = methodResolver.resolveHandlerMethod(request);
          //建立一個方法執行器;
        ServletHandlerMethodInvoker methodInvoker = new ServletHandlerMethodInvoker(methodResolver);
          //包裝原生的request, response,
        ServletWebRequest webRequest = new ServletWebRequest(request, response);
          //建立了一個,隐含模型
    
        ExtendedModelMap implicitModel = new BindingAwareModelMap();//**重點

         //真正執行目标方法;目标方法利用反射執行期間确定參數值,提前執行modelattribute等所有的操作都在這個方法中;
        Object result = methodInvoker.invokeHandlerMethod(handlerMethod, handler, webRequest, implicitModel);
    	//=======================看後邊補充的代碼塊===========================
        ModelAndView mav =
                methodInvoker.getModelAndView(handlerMethod, handler.getClass(), result, implicitModel, webRequest);
    
        methodInvoker.updateModelAttributes(handler, (mav != null ? mav.getModel() : null), implicitModel, webRequest);
    
        return mav;
    }      

Object result = methodInvoker.invokeHandlerMethod(handlerMethod, handler, webRequest, implicitModel);      
publicfinal Object invokeHandlerMethod(Method handlerMethod, Object handler,
            NativeWebRequest webRequest, ExtendedModelMap implicitModel) throws Exception {
        Method handlerMethodToInvoke = BridgeMethodResolver.findBridgedMethod(handlerMethod);
        try {
            boolean debug = logger.isDebugEnabled();
            for (String attrName : this.methodResolver.getActualSessionAttributeNames()) {
                Object attrValue = this.sessionAttributeStore.retrieveAttribute(webRequest, attrName);
                if (attrValue != null) {
                    implicitModel.addAttribute(attrName, attrValue);
                }
            }
               
          //找到所有@ModelAttribute注解标注的方法;
            for (Method attributeMethod : this.methodResolver.getModelAttributeMethods()) {
                Method attributeMethodToInvoke = BridgeMethodResolver.findBridgedMethod(attributeMethod);
                //先确定modelattribute方法執行時要使用的每一個參數的值;
               Object[] args = resolveHandlerArguments(attributeMethodToInvoke, handler, webRequest, implicitModel);
       //==========================看後邊補充的代碼塊=====================================
                if (debug) {
                    logger.debug("Invoking model attribute method: " + attributeMethodToInvoke);
                }
                String attrName = AnnotationUtils.findAnnotation(attributeMethod, ModelAttribute.class).value();
                
                if (!"".equals(attrName) && implicitModel.containsAttribute(attrName)) {
                    continue;
                }
                
                ReflectionUtils.makeAccessible(attributeMethodToInvoke);
               
               //提前運作ModelAttribute,
                Object attrValue = attributeMethodToInvoke.invoke(handler, args);
                if ("".equals(attrName)) {
                    Class<?> resolvedType = GenericTypeResolver.resolveReturnType(attributeMethodToInvoke, handler.getClass());
                    attrName = Conventions.getVariableNameForReturnType(attributeMethodToInvoke, resolvedType, attrValue);
                }
                
                /*
                
                方法上标注的ModelAttribute注解如果有value值   
				@ModelAttribute("abc")
				hahaMyModelAttribute()
				
                标了:	attrName="abc"
                沒标:	attrName="";attrName就會變為傳回值類型首字母小寫,
                     比如void ,或者book;
                     
                     【  
                        @ModelAttribute标在方法上的另外一個作用;
                        可以把方法運作後的傳回值按照方法上@ModelAttribute("abc")
                        指定的key放到隐含模型中;
                        如果沒有指定這個key;就用傳回值類型的首字母小寫
                      】
                        
                        {
                            haha=Book [id=100, bookName=西遊記, author=吳承恩, stock=98, 									sales=10, price=98.98], 
                            void=null
                      	}
                */
                //把提前運作的ModelAttribute方法的傳回值也放在隐含模型中
                if (!implicitModel.containsAttribute(attrName)) {
                    implicitModel.addAttribute(attrName, attrValue);
                }
            }

               //再次解析目标方法參數是哪些值
            Object[] args = resolveHandlerArguments(handlerMethodToInvoke, handler, webRequest, implicitModel);
            if (debug) {
                logger.debug("Invoking request handler method: " + handlerMethodToInvoke);
            }
            ReflectionUtils.makeAccessible(handlerMethodToInvoke);

            
            //執行目标方法
            return handlerMethodToInvoke.invoke(handler, args);
        }
        catch (IllegalStateException ex) {
            // Internal assertion failed (e.g. invalid signature):
            // throw exception with full handler method context...
            throw new HandlerMethodInvocationException(handlerMethodToInvoke, ex);
        }
        catch (InvocationTargetException ex) {
            // User-defined @ModelAttribute/@InitBinder/@RequestMapping method threw an exception...
            ReflectionUtils.rethrowException(ex.getTargetException());
            return null;
        }
    }      

确定方法運作時使用的每一個參數的值

Object[] args = resolveHandlerArguments(attributeMethodToInvoke, handler, webRequest, implicitModel);

@RequestMapping("/updateBook")
    public String updateBook
           (
                @RequestParam(value="author")String author,
                Map<String, Object> model,
                HttpServletRequest request,
                @ModelAttribute("haha")Book book
            )      
标了注解:
          儲存時哪個注解的詳細資訊;
          如果參數有ModelAttribute注解;
               拿到ModelAttribute注解的值讓attrName儲存
                    attrName="haha"

沒标注解:
          1)、先看是否普通參數(是否原生API)
               再看是否Model或者Map,如果是就傳入隐含模型;
          2)、自定義類型的參數沒有ModelAttribute 注解
                    1)、先看是否原生API
                    2)、再看是否Model或者Map
                    3)、再看是否是其他類型的比如SessionStatus、HttpEntity、Errors
           			4)、再看是否簡單類型的屬性;比如是否Integer,String,基本類型
                    		如果是paramName=“”
           			5)、attrName="";
           			
如果是自定義類型對象,最終會産生兩個效果;
     1)、如果這個參數标注了ModelAttribute注解就給attrName指派為這個注解的value值
     2)、如果這個參數沒有标注ModelAttribute注解就給attrName指派"";      
private Object[] resolveHandlerArguments(Method handlerMethod, Object handler,
            NativeWebRequest webRequest, ExtendedModelMap implicitModel) throws Exception {
        Class<?>[] paramTypes = handlerMethod.getParameterTypes();
          //建立了一個和參數個數一樣多的數組,會用來儲存每一個參數的值
        Object[] args = new Object[paramTypes.length];

                      
        for (int i = 0; i < args.length; i++) {
            MethodParameter methodParam = new MethodParameter(handlerMethod, i);
            methodParam.initParameterNameDiscovery(this.parameterNameDiscoverer);
            GenericTypeResolver.resolveParameterType(methodParam, handler.getClass());
            String paramName = null;
            String headerName = null;
            boolean requestBodyFound = false;
            String cookieName = null;
            String pathVarName = null;
            String attrName = null;
            boolean required = false;
            String defaultValue = null;
            boolean validate = false;
            Object[] validationHints = null;
            int annotationsFound = 0;
            Annotation[] paramAnns = methodParam.getParameterAnnotations();
            
            //找到目标方法這個參數的所有注解,如果有注解就解析并儲存注解的資訊;
            for (Annotation paramAnn : paramAnns) {
                if (RequestParam.class.isInstance(paramAnn)) {
                    RequestParam requestParam = (RequestParam) paramAnn;
                    paramName = requestParam.value();
                    required = requestParam.required();
                    defaultValue = parseDefaultValueAttribute(requestParam.defaultValue());
                    annotationsFound++;
                }
                else if (RequestHeader.class.isInstance(paramAnn)) {
                    RequestHeader requestHeader = (RequestHeader) paramAnn;
                    headerName = requestHeader.value();
                    required = requestHeader.required();
                    defaultValue = parseDefaultValueAttribute(requestHeader.defaultValue());
                    annotationsFound++;
                }
                else if (RequestBody.class.isInstance(paramAnn)) {
                    requestBodyFound = true;
                    annotationsFound++;
                }
                else if (CookieValue.class.isInstance(paramAnn)) {
                    CookieValue cookieValue = (CookieValue) paramAnn;
                    cookieName = cookieValue.value();
                    required = cookieValue.required();
                    defaultValue = parseDefaultValueAttribute(cookieValue.defaultValue());
                    annotationsFound++;
                }
                else if (PathVariable.class.isInstance(paramAnn)) {
                    PathVariable pathVar = (PathVariable) paramAnn;
                    pathVarName = pathVar.value();
                    annotationsFound++;
                }
                else if (ModelAttribute.class.isInstance(paramAnn)) {
                    ModelAttribute attr = (ModelAttribute) paramAnn;
                    attrName = attr.value();
                    annotationsFound++;
                }
                else if (Value.class.isInstance(paramAnn)) {
                    defaultValue = ((Value) paramAnn).value();
                }
                else if (paramAnn.annotationType().getSimpleName().startsWith("Valid")) {
                    validate = true;
                    Object value = AnnotationUtils.getValue(paramAnn);
                    validationHints = (value instanceof Object[] ? (Object[]) value : new Object[] {value});
                }
            }
            if (annotationsFound > 1) {
                throw new IllegalStateException("Handler parameter annotations are exclusive choices - " +
                        "do not specify more than one such annotation on the same parameter: " + handlerMethod);
            }

             //沒有找到注解的情況;
            if (annotationsFound == 0) {
                
                //解析普通參數
                Object argValue = resolveCommonArgument(methodParam, webRequest);
                //=====================看後邊補充的代碼塊=========================
                 //會進入resolveStandardArgument(解析标準參數)
                
                
                if (argValue != WebArgumentResolver.UNRESOLVED) {
                    args[i] = argValue;
                }
                else if (defaultValue != null) {
                    args[i] = resolveDefaultValue(defaultValue);
                }
                else {
                    
               //判斷是否是Model或者是Map旗下的,如果是将之前建立的隐含模型直接指派給這個參數
                    Class<?> paramType = methodParam.getParameterType();
                    if (Model.class.isAssignableFrom(paramType) || Map.class.isAssignableFrom(paramType)) {
                        if (!paramType.isAssignableFrom(implicitModel.getClass())) {
                            throw new IllegalStateException("Argument [" + paramType.getSimpleName() + "] is of type " +
                                    "Model or Map but is not assignable from the actual model. You may need to switch " +
                                    "newer MVC infrastructure classes to use this argument.");
                        }
                        args[i] = implicitModel;
                    }
                    else if (SessionStatus.class.isAssignableFrom(paramType)) {
                        args[i] = this.sessionStatus;
                    }
                    else if (HttpEntity.class.isAssignableFrom(paramType)) {
                        args[i] = resolveHttpEntityRequest(methodParam, webRequest);
                    }
                    else if (Errors.class.isAssignableFrom(paramType)) {
                        throw new IllegalStateException("Errors/BindingResult argument declared " +
                                "without preceding model attribute. Check your handler method signature!");
                    }
                    else if (BeanUtils.isSimpleProperty(paramType)) {
                        paramName = "";
                    }
                    else {
                        attrName = "";
                    }
                }
            }


               //确定值的環節
            if (paramName != null) {
                args[i] = resolveRequestParam(paramName, required, defaultValue, methodParam, webRequest, handler);
            }
            else if (headerName != null) {
                args[i] = resolveRequestHeader(headerName, required, defaultValue, methodParam, webRequest, handler);
            }
            else if (requestBodyFound) {
                args[i] = resolveRequestBody(methodParam, webRequest, handler);
            }
            else if (cookieName != null) {
                args[i] = resolveCookieValue(cookieName, required, defaultValue, methodParam, webRequest, handler);
            }
            else if (pathVarName != null) {
                args[i] = resolvePathVariable(pathVarName, methodParam, webRequest, handler);
            }

            
            //确定自定義類型參數的值;還要将請求中的每一個參數指派給這個對象
            else if (attrName != null) {
                WebDataBinder binder = resolveModelAttribute(attrName, methodParam, implicitModel, webRequest, handler);
                //=====================看後邊代碼補充============================
                boolean assignBindingResult = (args.length > i + 1 && Errors.class.isAssignableFrom(paramTypes[i + 1]));
                if (binder.getTarget() != null) {
                    doBind(binder, webRequest, validate, validationHints, !assignBindingResult);
                }
                args[i] = binder.getTarget();
                if (assignBindingResult) {
                    args[i + 1] = binder.getBindingResult();
                    i++;
                }
                implicitModel.putAll(binder.getBindingResult().getModel());
            }
        }
        return args;
    }      

如果沒有注解:

resolveCommonArgument)就是确定目前的參數是否是原生API;

@Override
        protected Object resolveStandardArgument(Class<?> parameterType, NativeWebRequest webRequest) throws Exception {
            HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
            HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);

            if (ServletRequest.class.isAssignableFrom(parameterType) ||
                    MultipartRequest.class.isAssignableFrom(parameterType)) {
                Object nativeRequest = webRequest.getNativeRequest(parameterType);
                if (nativeRequest == null) {
                    throw new IllegalStateException(
                            "Current request is not of type [" + parameterType.getName() + "]: " + request);
                }
                return nativeRequest;
            }
            else if (ServletResponse.class.isAssignableFrom(parameterType)) {
                this.responseArgumentUsed = true;
                Object nativeResponse = webRequest.getNativeResponse(parameterType);
                if (nativeResponse == null) {
                    throw new IllegalStateException(
                            "Current response is not of type [" + parameterType.getName() + "]: " + response);
                }
                return nativeResponse;
            }
            else if (HttpSession.class.isAssignableFrom(parameterType)) {
                return request.getSession();
            }
            else if (Principal.class.isAssignableFrom(parameterType)) {
                return request.getUserPrincipal();
            }
            else if (Locale.class.equals(parameterType)) {
                return RequestContextUtils.getLocale(request);
            }
            else if (InputStream.class.isAssignableFrom(parameterType)) {
                return request.getInputStream();
            }
            else if (Reader.class.isAssignableFrom(parameterType)) {
                return request.getReader();
            }
            else if (OutputStream.class.isAssignableFrom(parameterType)) {
                this.responseArgumentUsed = true;
                return response.getOutputStream();
            }
            else if (Writer.class.isAssignableFrom(parameterType)) {
                this.responseArgumentUsed = true;
                return response.getWriter();
            }
            return super.resolveStandardArgument(parameterType, webRequest);
        }      

resolveModelAttribute

SpringMVC确定POJO值的三步;
1、如果隐含模型中有這個key(标了ModelAttribute注解就是注解指定的value,沒标就是參數類型的首字母小寫)指定的值;
     如果有将這個值指派給bindObject;
2、如果是SessionAttributes标注的屬性,就從session中拿;
3、如果都不是就利用反射建立對象;      
private WebDataBinder resolveModelAttribute(String attrName, MethodParameter methodParam,
            ExtendedModelMap implicitModel, NativeWebRequest webRequest, Object handler) throws Exception {

        // Bind request parameter onto object...  
        String name = attrName;
     
        if ("".equals(name)) {
               //如果attrName是空串;就将參數類型的首字母小寫作為值 
            	//Book book2121 -> name=book
            name = Conventions.getVariableNameForParameter(methodParam);
        }
        Class<?> paramType = methodParam.getParameterType();
        Object bindObject;
    
   		 //确定目标對象的值
        if (implicitModel.containsKey(name)) {
            bindObject = implicitModel.get(name);
        }
        else if (this.methodResolver.isSessionAttribute(name, paramType)) {
            bindObject = this.sessionAttributeStore.retrieveAttribute(webRequest, name);
            if (bindObject == null) {
                raiseSessionRequiredException("Session attribute '" + name + "' required - not found in session");
            }
        }
        else {
            bindObject = BeanUtils.instantiateClass(paramType);
        }
    
    
        WebDataBinder binder = createBinder(webRequest, bindObject, name);
        initBinder(handler, name, binder, webRequest);
        return binder;
    }      

總結:

  1. 運作流程簡單版;
  2. 确定方法每個參數的值;
    1. 标注解:儲存注解的資訊;最終得到這個注解應該對應解析的值;
    2. 沒标注解:
      1. 看是否是原生API;
      2. 看是否是Model或者是Map,SessionStatus、HttpEntity、Errors...
      3. 看是否是簡單類型;paramName=""
      4. 給attrName指派;attrName(參數标了@ModelAttribute("")就是指定的,沒标就是"")
        1. attrName使用參數的類型首字母小寫;或者使用之前@ModelAttribute("")的值
        2. 先看隐含模型中有每個這個attrName作為key對應的值;如果有就從隐含模型中擷取并指派
        3. 看是否是@SessionAttributes(value="haha");标注的屬性,如果是從session中拿;
        4. 不是@SessionAttributes标注的,利用反射建立一個對象;
      5. 不是@SessionAttributes标注的,利用反射建立一個對象;

步驟四:

  1. 任何方法的傳回值,最終都會被包裝成ModelAndView對象

步驟五:

SpringMVC視圖解析:

1、方法執行後的傳回值會作為頁面位址參考,轉發或者重定向到頁面

2、視圖解析器可能會進行頁面位址的拼串

processDispatchResult(processedRequest, response, mappedHandler, 
    mv, dispatchException);      
  1. 調用processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException)
    • 來到頁面的方法視圖渲染流程
    • 将域中的資料在頁面展示
    • 頁面就是用來渲染模型資料的
  2. 調用render(mv, request, response)
    • 渲染頁面
  3. View與ViewResolver
    • ViewResolver的作用是根據視圖名(方法的傳回值)得到View對象
  4. 怎麼能根據方法的傳回值(視圖名)得到View對象?
    protected View resolveViewName(String viewName, Map<String, Object> model, Locale locale,
                HttpServletRequest request) throws Exception {
    
              //周遊所有的ViewResolver;
            for (ViewResolver viewResolver : this.viewResolvers) {
                
                
              //viewResolver視圖解析器根據方法的傳回值,得到一個View對象;
                View view = viewResolver.resolveViewName(viewName, locale);
                
                
                if (view != null) {
                    return view;
                }
            }
            return null;
        }      
    • resolveViewName實作
      @Override
          public View resolveViewName(String viewName, Locale locale) throws Exception {
              if (!isCache()) {
                  return createView(viewName, locale);
              }
              else {
                  Object cacheKey = getCacheKey(viewName, locale);
                  View view = this.viewAccessCache.get(cacheKey);
                  if (view == null) {
                      synchronized (this.viewCreationCache) {
                          view = this.viewCreationCache.get(cacheKey);
                          if (view == null) {
                              
                              
                              // Ask the subclass to create the View object.
                               //根據方法的傳回值建立出視圖View對象;
                              view = createView(viewName, locale);
                              
                              
                              if (view == null && this.cacheUnresolved) {
                                  view = UNRESOLVED_VIEW;
                              }
                              if (view != null) {
                                  this.viewAccessCache.put(cacheKey, view);
                                  this.viewCreationCache.put(cacheKey, view);
                                  if (logger.isTraceEnabled()) {
                                      logger.trace("Cached view [" + cacheKey + "]");
                                  }
                              }
                          }
                      }
                  }
                  return (view != UNRESOLVED_VIEW ? view : null);
              }
          }      
    • 建立View對象
    @Override
          protected View createView(String viewName, Locale locale) throws Exception {
              // If this resolver is not supposed to handle the given view,
              // return null to pass on to the next resolver in the chain.
              if (!canHandle(viewName, locale)) {
                  return null;
              }
              // Check for special "redirect:" prefix.
              if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
                  String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
                  RedirectView view = new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible());
                  return applyLifecycleMethods(viewName, view);
              }
              // Check for special "forward:" prefix.
              if (viewName.startsWith(FORWARD_URL_PREFIX)) {
                  String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
                  return new InternalResourceView(forwardUrl);
              }
              // Else fall back to superclass implementation: calling loadView.
              //如果沒有字首就使用父類預設建立一個View;
              return super.createView(viewName, locale);
          }      
    • 傳回View對象
      • 視圖解析器得到View對象的流程就是,所有配置的視圖解析器都來嘗試根據視圖名(傳回值)得到View(視圖)對象;如果能得到就傳回,得不到就換下一個視圖解析器;
      • 調用View對象的render方法
      @Override
          public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
              if (logger.isTraceEnabled()) {
                  logger.trace("Rendering view with name '" + this.beanName + "' with model " + model +
                      " and static attributes " + this.staticAttributes);
              }
      
              Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
      
              prepareResponse(request, response);
              
              
              //渲染要給頁面輸出的所有資料
              renderMergedOutputModel(mergedModel, request, response);
          }      
    • InternalResourceView有這個方法renderMergedOutputModel;
      @Override
          protected void renderMergedOutputModel(
                  Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
      
              // Determine which request handle to expose to the RequestDispatcher.
              HttpServletRequest requestToExpose = getRequestToExpose(request);
      
              // Expose the model object as request attributes.
              
              
              //将模型中的資料放在請求域中
              exposeModelAsRequestAttributes(model, requestToExpose);
      
              
              
              // Expose helpers as request attributes, if any.
              exposeHelpers(requestToExpose);
      
              // Determine the path for the request dispatcher.
              String dispatcherPath = prepareForRendering(requestToExpose, response);
      
              // Obtain a RequestDispatcher for the target resource (typically a JSP).
              RequestDispatcher rd = getRequestDispatcher(requestToExpose, dispatcherPath);
              if (rd == null) {
                  throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
                          "]: Check that the corresponding file exists within your web application archive!");
              }
      
              // If already included or response already committed, perform include, else forward.
              if (useInclude(requestToExpose, response)) {
                  response.setContentType(getContentType());
                  if (logger.isDebugEnabled()) {
                      logger.debug("Including resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
                  }
                  rd.include(requestToExpose, response);
              }
      
              else {
                  // Note: The forwarded resource is supposed to determine the content type itself.
                  if (logger.isDebugEnabled()) {
                      logger.debug("Forwarding to resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
                  }
                  
                  //轉發頁面
                  rd.forward(requestToExpose, response);
              }
          }      
    • 将模型中的所有資料取出來全放在request域中
      protected void exposeModelAsRequestAttributes(Map<String, Object> model, HttpServletRequest request) throws Exception {
              for (Map.Entry<String, Object> entry : model.entrySet()) {
                  String modelName = entry.getKey();
                  Object modelValue = entry.getValue();
                  if (modelValue != null) {
                      
                      //将ModelMap中的資料放到請求域中
                      request.setAttribute(modelName, modelValue);
                      
                      
                      if (logger.isDebugEnabled()) {
                          logger.debug("Added model object '" + modelName + "' of type [" + modelValue.getClass().getName() +
                                  "] to request in view with name '" + getBeanName() + "'");
                      }
                  }
                  else {
                      request.removeAttribute(modelName);
                      if (logger.isDebugEnabled()) {
                          logger.debug("Removed model object '" + modelName +
                                  "' from request in view with name '" + getBeanName() + "'");
                      }
                  }
              }
          }      
      總結:
      • 視圖解析器隻是為了得到視圖對象
      • 視圖對象才能真正的轉發(将模型資料全部放在請求域中)或者重定向到頁面視圖對象才能真正的渲染視圖
    • ViewResolver
    • View:

8. 視圖解析

8.1 forward和redirect字首

通過SpringMVC來實作轉發和重定向。

  • 直接 return “success”,會走視圖解析器進行拼串
  • 轉發:return “forward:/succes.jsp”;直接寫絕對路徑,/表示目前項目下,不走視圖解析器
  • 重定向:return “redirect:/success.jsp”;不走視圖解析器
@Controller
public class ResultSpringMVC {
   @RequestMapping("/hello01")
   public String test1(){
       //轉發
       //會走視圖解析器
       return "success";
  }

   @RequestMapping("/hello02")
   public String test2(){
       //轉發二
       //不走視圖解析器
       return "forward:/success.jsp";
  }

   @RequestMapping("/hello03")
   public String test3(){
       //重定向
       //不走視圖解析器
       return "redirect:/success.jsp";
  }
}      

使用原生的ServletAPI時要注意,/路徑需要加上項目名才能成功

@RequestMapping("/result/t2")
   public void test2(HttpServletRequest req, HttpServletResponse resp) throwsIOException {	
       //重定向
       resp.sendRedirect("/index.jsp");
  }

   @RequestMapping("/result/t3")
   public void test3(HttpServletRequest req, HttpServletResponse resp) throwsException {
       //轉發
       req.setAttribute("msg","/result/t3");
       req.getRequestDispatcher("/WEB-INF/jsp/test.jsp").forward(req,resp);
  }      

8.2 jstlView

導包導入了jstl的時候會自動建立為一個jstlView;可以快速友善的支援國際化功能;

可以支援快速國際化;

javaWeb國際化步驟;

  1. 得得到一個Locale對象;
  2. 使用ResourceBundle綁定國際化資源檔案
  3. 使用ResourceBundle.getString("key");擷取到國際化配置檔案中的值
  4. web頁面的國際化,fmt标簽庫來做
    • <fmt:setLocale>

    • <fmt:setBundle>

    • <fmt:message>

有了JstlView以後

  1. 讓Spring管理國際化資源就行
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
            <property name="prefix" value="/WEB-INF/pages/"></property>
            <property name="suffix" value=".jsp"></property>
            <property name="viewClass" value="org.springframework.web.servlet.view.JstlView">			
            </property>
    </bean>
    
    <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
            <property name="basename" value="i18n"></property>
    </bean>      
  2. 直接在頁面使用

    <fmt:message>

<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>@%>
    ...
<h1>
	<fmt:message key="welcomeinfo"/>
</h1>
<form action="">
    <fmt:message key="username"/>:<input /><br/>
    <fmt:message key="password"/>:<input /><br/>
    <input type="submit" value='<fmt:message key="loginBtn"/>'/>
</form>
    ...      

注意:

一定要過SpringMVC的視圖解析流程,人家會建立一個jstlView幫你快速國際化;

  • 不能寫redirect:
  • 不能寫forward:
if (viewName.startsWith(FORWARD_URL_PREFIX)) {
            String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
            return new InternalResourceView(forwardUrl);
        }      

8.3 mvc:view-controller

mvc:view-controller

直接将請求映射到某個頁面,不需要寫方法了:

注意:會走視圖解析的功能

在ioc.xml中加入

<mvc:view-controller path="/toLogin" view-name="login"/>
<!--開啟MVC注解驅動模式-->
<mvc:annotation-driven/>      

8.4 自定義視圖解析器

擴充:加深視圖解析器和視圖對象;

  • 視圖解析器根據方法的傳回值得到視圖對象
  • 多個視圖解析器都會嘗試能否得到視圖對象;
  • 視圖對象不同就可以具有不同功能
for (ViewResolver viewResolver : this.viewResolvers) {
          //viewResolver視圖解析器根據方法的傳回值,得到一個View對象;
            View view = viewResolver.resolveViewName(viewName, locale);
            if (view != null) {
                return view;
            }
        }      
  • 讓我們的視圖解析器工作
  • 得到我們的視圖對象
  • 我們的視圖對象自定義渲染邏輯

自定義視圖和視圖解析器的步驟

  1. 編寫自定義的視圖解析器,和視圖實作類
    public class MyViewResolver implements ViewResolver {
        public View resolveViewName(String viewName, Locale locale) throws Exception {
            if (viewName.startsWith("myView:")){
               return new MyView();
            }else{
                return null;
            }
        }
    }      
    public class MyView implements View {
        public String getContentType() {
            return "text/html";
        }
    
        public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
            System.out.println("儲存的資料:"+model);
            response.getWriter().write("即将展現内容:");
        }
    }      
  2. 視圖解析器必須放在ioc容器中,讓其工作,能建立出我們的自定義視圖對象
    <bean class="com.chenhui.view.MyViewResolver"></bean>      

    在源碼中看到我們的編寫的解析器

    但是被InternalResourceViewResolver先攔截了執行了render

MyViewResolver要實作Ordered接口

public class MyViewResolver implements ViewResolver, Ordered {

    private Integer order = 0;

    public View resolveViewName(String viewName, Locale locale) throws Exception {
        if (viewName.startsWith("myView:")) {
            return new MyView();
        } else {
            return null;
        }
    }

    public int getOrder() {
        return this.order;
    }

    public void setOrder(Integer order) {
        this.order = order;
    }
}
      
<bean class="com.chenhui.view.MyViewResolver">
        <property name="order" value="1"></property>
    </bean>      

發現順序已經改變

到了我們的頁面(雖然亂碼),需要設定ContentType

response.setContentType("text/html ");

public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
        System.out.println("儲存的資料:"+model);
        response.setContentType("text/html ");
        response.getWriter().write("即将展現内容:");
    }      

成功!

9. ResetCRUD

9.1 環境搭建

配置檔案

ioc.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.chenhui"></context:component-scan>

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


</beans>      

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:ioc.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>
    
    <filter>
        <filter-name>CharacterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CharacterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <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>
</web-app>      

bean

Employee

package com.chenhui.bean;


import java.util.Date;

public class Employee {

    private Integer id;
    private String lastName;

    private String email;
    //1 male, 0 female
    private Integer gender;

    private Department department;

    public Integer getId() {
        return id;
    }

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

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Integer getGender() {
        return gender;
    }

    public void setGender(Integer gender) {
        this.gender = gender;
    }

    public Department getDepartment() {
        return department;
    }

    public void setDepartment(Department department) {
        this.department = department;
    }

    public Employee(Integer id, String lastName, String email, Integer gender,
                    Department department) {
        super();
        this.id = id;
        this.lastName = lastName;
        this.email = email;
        this.gender = gender;
        this.department = department;
    }

    public Employee() {
    }

    @Override
    public String toString() {
        return "Employee [id=" + id + ", lastName=" + lastName + ", email="
                + email + ", gender=" + gender + ", department=" + department
                + "]";
    }


}      

Department

package com.chenhui.bean;


import java.util.Date;

public class Employee {

    private Integer id;
    private String lastName;

    private String email;
    //1 male, 0 female
    private Integer gender;

    private Department department;

    public Integer getId() {
        return id;
    }

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

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Integer getGender() {
        return gender;
    }

    public void setGender(Integer gender) {
        this.gender = gender;
    }

    public Department getDepartment() {
        return department;
    }

    public void setDepartment(Department department) {
        this.department = department;
    }

    public Employee(Integer id, String lastName, String email, Integer gender,
                    Department department) {
        super();
        this.id = id;
        this.lastName = lastName;
        this.email = email;
        this.gender = gender;
        this.department = department;
    }

    public Employee() {
    }

    @Override
    public String toString() {
        return "Employee [id=" + id + ", lastName=" + lastName + ", email="
                + email + ", gender=" + gender + ", department=" + department
                + "]";
    }


}
      

dao

DepartmentDao

package com.chenhui.dao;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import com.chenhui.bean.Department;
import org.springframework.stereotype.Repository;



@Repository
public class DepartmentDao {

    private static Map<Integer, Department> departments = null;

    static{
        departments = new HashMap<Integer, Department>();

        departments.put(101, new Department(101, "D-AA"));
        departments.put(102, new Department(102, "D-BB"));
        departments.put(103, new Department(103, "D-CC"));
        departments.put(104, new Department(104, "D-DD"));
        departments.put(105, new Department(105, "D-EE"));
    }

    public Collection<Department> getDepartments(){
        return departments.values();
    }

    public Department getDepartment(Integer id){
        return departments.get(id);
    }

}      

EmployeeDao

package com.chenhui.dao;


import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import com.chenhui.bean.Department;
import com.chenhui.bean.Employee;
import org.springframework.stereotype.Repository;
import org.springframework.beans.factory.annotation.Autowired;




@Repository
public class EmployeeDao {

    private static Map<Integer, Employee> employees = null;

    @Autowired
    private DepartmentDao departmentDao;

    static{
        employees = new HashMap<Integer, Employee>();

        employees.put(1001, new Employee(1001, "E-AA", "[email protected]", 1, new Department(101, "D-AA")));
        employees.put(1002, new Employee(1002, "E-BB", "[email protected]", 1, new Department(102, "D-BB")));
        employees.put(1003, new Employee(1003, "E-CC", "[email protected]", 0, new Department(103, "D-CC")));
        employees.put(1004, new Employee(1004, "E-DD", "[email protected]", 0, new Department(104, "D-DD")));
        employees.put(1005, new Employee(1005, "E-EE", "[email protected]", 1, new Department(105, "D-EE")));
    }

    private static Integer initId = 1006;

    public void save(Employee employee){
        if(employee.getId() == null){
            employee.setId(initId++);
        }

        employee.setDepartment(departmentDao.getDepartment(employee.getDepartment().getId()));
        employees.put(employee.getId(), employee);
    }

    public Collection<Employee> getAll(){
        return employees.values();
    }

    public Employee get(Integer id){
        return employees.get(id);
    }

    public void delete(Integer id){
        employees.remove(id);
    }
}
      

9.2 Controller編寫

EmployeeController

package com.chenhui.controller;

import com.chenhui.bean.Department;
import com.chenhui.bean.Employee;
import com.chenhui.dao.DepartmentDao;
import com.chenhui.dao.EmployeeDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

import java.util.Collection;

@Controller
public class EmployeeController {

    @Autowired
    EmployeeDao employees;

    @Autowired
    DepartmentDao departments;

    @RequestMapping(value = "/emp", method = RequestMethod.GET)
    public String getEmps(Model model) {
        Collection<Employee> all = employees.getAll();
        model.addAttribute("emps", all);
        return "list";
    }

    @RequestMapping(value = "/emp", method = RequestMethod.POST)
    public String addEmp(Employee employee, Model model) {

        employees.save(employee);

        return "redirect:/emp";
    }

    @RequestMapping(value = "/emp/{id}", method = RequestMethod.GET)
    public String getEmp(@PathVariable("id") Integer id, Model model) {
        Employee employee = employees.get(id);
        Collection<Department> departments = this.departments.getDepartments();
        //此處給spring表單添加一個employee對象,以免發生command未找到的異常
        model.addAttribute("employee", employee);
        model.addAttribute("departments", departments);
        return "editEmp";
    }

    @RequestMapping(value = "/emp/{id}", method = RequestMethod.PUT)
    public String updateEmp(@ModelAttribute("employee") Employee employee, @PathVariable("id") Integer integer) {
        System.out.println("要修改的:" + employee);
        employees.save(employee);
        return "redirect:/emp";
    }

    @RequestMapping(value = "/emp/{id}", method = RequestMethod.DELETE)
    public String deleteEmp(@PathVariable("id") Integer id) {
        employees.delete(id);
        return "redirect:/emp";
    }

    @ModelAttribute
    public void myMethodAttribute(@RequestParam(value = "id", required = false) Integer id, Model model) {
        System.out.println("modelAttribute");
        if (id != null) {
            Employee employee = employees.get(id);
            model.addAttribute("employee", employee);
        }

    }


    @RequestMapping("/toaddpage")
    public String toAddPage(Model model) {
        Collection<Department> all = departments.getDepartments();

        model.addAttribute("departments", all);
        model.addAttribute("command", new Employee());
        return "addEmp";
    }

}      

9.3 Jsp編寫

list.jsp

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%--
  Created by IntelliJ IDEA.
  User: admin
  Date: 2020/11/13
  Time: 9:18
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>員工清單</title>
</head>
<body>
<% pageContext.setAttribute("ctp", request.getContextPath());
//    System.out.println(request.getContextPath());
%>
<h1>員工清單</h1>
<table  cellpadding="5px" cellspacing="0">
    <%--
        private Integer id;
        private String lastName;
        private String email;
        //1 male, 0 female
        private Integer gender;

        private Department department;--%>
    <thead>
    <tr>
        <th>ID</th>
        <th>lastName</th>
        <th>email</th>
        <th>gender</th>
        <th>departmentName</th>
        <th>EDIT</th>
        <th>DELETE</th>
    </tr>
    </thead>
    <tbody>
    <c:forEach items="${emps}" var="emp">
        <tr>
            <td>${emp.id}</td>
            <td>${emp.lastName}</td>
            <td>${emp.email}</td>
            <td>${emp.gender==0?"女":"男"}</td>
            <td>${emp.department.departmentName}</td>
            <td><a href="${ctp}/emp/${emp.id}" target="_blank" rel="external nofollow" >修改</a></td>
            <!--删除操作可以綁定單擊事件,使用ajax發送delete請求-->
            <td>
                <form action="${ctp}/emp/${emp.id}" method="post">
                    <input type="hidden" name="_method" value="DELETE">
                    <input type="submit" value="delete">
                </form>
            </td>
        </tr>
    </c:forEach>
    </tbody>

</table>
<a href="toaddpage" target="_blank" rel="external nofollow"  target="_blank" rel="external nofollow" >添加員工</a>
</body>
</html>      

addEmp.jsp

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%--
  Created by IntelliJ IDEA.
  User: admin
  Date: 2020/11/13
  Time: 9:42
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>添加員工</title>
</head>
<body>
原生表單:<br>

<%
    pageContext.setAttribute("ctp",request.getContextPath());
%>
<form action="${ctp}/emp" method="post">
    姓名:<input type="text" name="lastName"><br>
    郵箱:<input type="text" name="email"><br>
    性别:<br>
    男:<input type="radio" name="gender" value="1"><br>
    女:<input type="radio" name="gender" value="0"><br>
    部門:<select name="department.id">
    <c:forEach items="${departments}" var="department">
        <option value="${department.id}">${department.departmentName}</option>
    </c:forEach>
</select>
    <input type="submit" value="送出">
</form>

SpringMVC表單:<br>
<form:form action="${ctp}/emp" method="post">
    姓名:<form:input path="lastName"></form:input><br>
    郵箱:<form:input path="email"></form:input><br>
    性别:<br>
    男:<form:radiobutton path="gender" value="1"></form:radiobutton>
    女:<form:radiobutton path="gender" value="0"></form:radiobutton><br>
    部門:<form:select path="department.id" items="${departments}"
                    itemLabel="departmentName" itemValue="id">
        </form:select>
    <input type="submit" value="送出">
</form:form>
</body>
</html>      

Spring表單需要在model中添加command:

<form:form action="" modelAttribute="xxxx">      

也可以用modelAttribute替換command變量名

  • command對象的資訊會放在SpringForm中
@RequestMapping("/toaddpage")
    public String toAddPage(Model model) {
        Collection<Department> all = departments.getDepartments();

        model.addAttribute("departments", all);
        model.addAttribute("command", new Employee());
        return "addEmp";
    }      

不然Spring表單會報錯:

editEmp.jsp

<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%--
  Created by IntelliJ IDEA.
  User: admin
  Date: 2020/11/13
  Time: 11:34
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
    pageContext.setAttribute("ctp", request.getContextPath());
%>
<html>
<head>
    <title>編輯員工</title>
</head>
<body>

<form:form action="${ctp}/emp/${employee.id}" method="post" modelAttribute="employee">
    <input type="hidden" name="_method" value="put">
    <input type="hidden" name="id" value="${employee.id}">

    姓名:<form:input path="lastName"></form:input><br>
    郵箱:<form:input path="email"></form:input><br>
    性别:<br>
    男:<form:radiobutton path="gender" value="1"></form:radiobutton>
    女:<form:radiobutton path="gender" value="0"></form:radiobutton><br>
    部門:
    <form:select path="department.id" items="${departments}"
                 itemLabel="departmentName" itemValue="id">
    </form:select>
    <input type="submit" value="修改">
</form:form>
</body>
</html>
      

9.4 解決DispatcherServlet攔截靜态檔案

讓Tomcat托管js檔案

  • 在ioc.xml檔案中加入
<mvc:default-servlet-handler/>
   <mvc:annotation-driven/>      

10. 資料轉換 & 資料格式化 & 資料校驗

10.1 資料轉換

SpringMVC封裝自定義類型對象的時候?
javaBean要和頁面送出的資料進行一一綁定?
1)、頁面送出的所有資料都是字元串?
2)、Integer age,Date birth;
 employName=zhangsan&age=18&gender=1
 String age = request.getParameter("age");
牽扯到以下操作;
1)、資料綁定期間的資料類型轉換?String--Integer String--Boolean,xxx
2)、資料綁定期間的資料格式化問題?比如送出的日期進行轉換
      birth=2017-12-15----->Date    2017/12/15  2017.12.15  2017-12-15
3)、資料校驗?
      我們送出的資料必須是合法的?
      前端校驗:js+正規表達式;
      後端校驗:重要資料也是必須的;
      1)、校驗成功!資料合法
      2)、校驗失敗?      

bindRequestParameters方法将請求參數于JavaBean進行綁定,為自定義對象指派。

ModelAttributeMethodProcessor
public final Object resolveArgument(
            MethodParameter parameter, ModelAndViewContainer mavContainer,
            NativeWebRequest request, WebDataBinderFactory binderFactory)
            throws Exception {
        String name = ModelFactory.getNameForParameter(parameter);
        Object attribute = (mavContainer.containsAttribute(name)) ?
                mavContainer.getModel().get(name) : createAttribute(name, parameter, binderFactory, request);
    
    	//WebDataBinder
        WebDataBinder binder = binderFactory.createBinder(request, attribute, name);
    
    
        if (binder.getTarget() != null) {
            
               //将頁面送出過來的資料封裝到javaBean的屬性中
            bindRequestParameters(binder, request);
               //+++++++++
            
            validateIfApplicable(binder, parameter);
            if (binder.getBindingResult().hasErrors()) {
                if (isBindExceptionRequired(binder, parameter)) {
                    throw new BindException(binder.getBindingResult());
                }
            }
        }
      

WebDataBinder:

資料綁定器有什麼用?

  1. 資料綁定器負責資料綁定工作
  2. 資料綁定期間産生的類型轉換、格式化、資料校驗等問題
  • conversionService元件:
    • 負責資料類型的轉換以及格式化功能;
    • ConversionService中有非常多的converter;
    • 不同類型的轉換和格式化用它自己的converter
    ...
    @org.springframework.format.annotation.DateTimeFormat java.util.Date -> java.lang.String: org.springframework.format.datetime.DateTimeFormatAnnotationFormatterFactory@32abc654
        @org.springframework.format.annotation.NumberFormat java.lang.Double -> java.lang.String: org.springframework.format.number.NumberFormatAnnotationFormatterFactory@140bb45d
        @org.springframework.format.annotation.NumberFormat java.lang.Float -> java.lang.String: org.springframework.format.number.NumberFormatAnnotationFormatterFactory@140bb45d
        ....
    org.springframework.format.number.NumberFormatAnnotationFormatterFactory@140bb45d
        java.lang.String -> @org.springframework.format.annotation.NumberFormat java.math.BigInteger: org.springframework.format.number.NumberFormatAnnotationFormatterFactory@140bb45d
        java.lang.String -> java.lang.Boolean : org.springframework.core.convert.support.StringToBooleanConverter@22f562e2
        java.lang.String -> java.lang.Character : org.springframework.core.convert.support.StringToCharacterConverter@5f2594f5
        java.lang.String -> java.lang.Enum : org.springframework.core.convert.support.StringToEnumConverterFactory@1347a7be
        【java.lang.String -> java.lang.Number : 
    ...
        java...      
  • validators負責資料校驗工作
  • bindingResult負責儲存以及解析資料綁定期間資料校驗産生的錯誤

自定義類型轉換器:

步驟:

  1. ConversionService::是一個接口
  2. Converter是ConversionService中的元件;
    1. Converter得放進ConversionService 中;
    2. 将WebDataBinder中的ConversionService設定成我們這個加了自定義類型轉換器的ConversionService;
  3. 配置ConversionService

需要實作的步驟

  1. 實作Converter接口,寫一個自定義的類型轉換器
    public class MyStringToEmployeeConverter implements Converter<String, Employee> {
    
        @Autowired
        DepartmentDao departmentDao;
    
        public Employee convert(String source) {
            System.out.println("将要轉換的字元串" + source);
            Employee employee = new Employee();
            if (source.contains("-")) {
                String[] split = source.split("-");
                employee.setLastName(split[0]);
                employee.setEmail(split[1]);
                employee.setGender(Integer.parseInt(split[2]));
                employee.setDepartment(departmentDao.getDepartment(Integer.parseInt(split[3])));
            }
            return employee;
        }
    }      
  2. 配置出ConversionService

    在ioc.xml中

    <bean id="myconversionService"			class="org.springframework.context.support.ConversionServiceFactoryBean">
            <!--
    				ConversionServiceFactoryBean:
    				建立的ConversionService元件是沒有格式化器(formatter)存在的;
    				推薦使用:
    		"org.springframework.format.support.FormattingConversionServiceFactoryBean"
    		-->
            <property name="converters">
                <set>
                    <bean class="com.chenhui.component.MyStringToEmployeeConverter"/>	
                </set>
            </property>
        </bean>      
  3. 讓SpringMVC用我們的ConversionService
    <mvc:annotation-driven conversion-service="myconversionService"></mvc:annotation-driven>      

動态資源和靜态資源通路

  1. <mvc:default-servlet-handler/>

    <mvc:annotation-driven/>

    1. 都沒配
      • 動态能通路:

        DefaultAnnotationHandlerMapping中的handlerMap中儲存了每一個資源的映射資訊

      • 靜态不能通路:

        handlerMap中沒有儲存靜态資源映射的請求

      • handleAdapter
    2. <mvc:default-servlet-handler/>

      不加

      <mvc:annotation-driven/>

      • 動态不能通路:DefaultAnnotationHandlerMapping被SimpleUrlHandlerMapping替換。
      • 靜态能通路的原因:SimpleUrlHandlerMapping把所有請求都映射給tomcat;
      • handleAdapter
    3. 都加上
      • 都能通路

        handlerMap

      • RequestMappingHandlerMapping:動态資源可以通路

        handleMethods屬性儲存了每一個請求用哪個方法來處理;

        SimpleUrlHandlerMapping:将請求直接交給tomcat;有他,靜态資源就沒問題

      • handleAdapter

        原來的AnnotationMethodHandlerAdapter被換成RequestMappingHandlerAdapter

    4. 隻加

      <mvc:annotation-driven/>

      • 動态能通路,靜态無法通路

10.2 資料格式化

自定義資料格式化

  1. 在屬性上加Format标簽
  2. 更改轉換器

    例:

@DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date birth;      
<bean id="myconversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
        <property name="converters">
            <set>
                <bean class="com.chenhui.component.MyStringToEmployeeConverter"/>
            </set>
        </property>
    </bean>      

10.3 資料校驗

步驟

  • 導入Jar包
<dependency>
            <groupId>javax.validation</groupId>
            <artifactId>validation-api</artifactId>
            <version>1.1.0.Final</version>
        </dependency>

        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>5.4.1.Final</version>
        </dependency>
        <dependency>
            <groupId>org.jboss.logging</groupId>
            <artifactId>jboss-logging</artifactId>
            <version>3.3.0.Final</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml</groupId>
            <artifactId>classmate</artifactId>
            <version>1.3.3</version>
        </dependency>      
  • 在變量上放上注解,錯誤資訊message
@NotNull
    @Length(min = 5, max = 10,message='xxxx')
    private String lastName;


    @DateTimeFormat(pattern = "yyyy-MM-dd")
    @Past
    private Date birth;
      
  • 對SpringMVC封裝對象加上@Valid注解
  • 校驗結果在BindingResult的result中
@RequestMapping(value = "/emp", method = RequestMethod.POST)
    public String addEmp(@Valid Employee employee, BindingResult result, Model model) {

        if (result.hasErrors()){
            System.out.println("有校驗錯誤");
            return "addEmp";
        }else{
            employees.save(employee);
        }


        return "redirect:/emp";
    }      
  • 來到頁面使用form:errors取出錯誤資訊
  • 可以把錯誤資訊存到Model中,然後在頁面中取Model的對應的key
<form:form action="${ctp}/emp" method="post">
    姓名:<form:input path="lastName"></form:input><form:errors path="lastName"></form:errors><br>
    郵箱:<form:input path="email"></form:input><form:errors path="email"></form:errors><br>
    生日:<form:input path="birth"></form:input><form:errors path="birth"></form:errors><br>
    性别:<br>
    男:<form:radiobutton path="gender" value="1"></form:radiobutton>
    女:<form:radiobutton path="gender" value="0"></form:radiobutton><br>
    部門:<form:select path="department.id" items="${departments}"
                    itemLabel="departmentName" itemValue="id">
        </form:select>
    <input type="submit" value="送出">
</form:form>      

原生Form顯示錯誤:

1)、原生的表單怎麼辦? 将錯誤放在Model中就行了

國際化定制

國際化定制自己的錯誤消息顯示

編寫國際化的檔案      
  • errors_zh_CN.properties
  • errors_en_US.properties

key有規定(精确優先):

codes
     [
          Email.employee.email,      校驗規則.隐含模型中這個對象的key.對象的屬性
          Email.email,                       校驗規則.屬性名
          Email.java.lang.String,      校驗規則.屬性類型
          Email
    ];
      

1、先編寫國際化配置檔案

2、讓SpringMVC管理國際化資源檔案

<!-- 管理國際化資源檔案 -->
    <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basename" value="errors"></property>
    </bean>      

3、來到頁面取值

4、進階國際化?

動态傳入消息參數;      

{0}:永遠都是目前屬性名;

@Length(min = 5, max = 10,message='xxxx')

按照字母排序

{1}為max {2}為min

11. 其他資料響應與接受

ajax;
1、SpringMVC快速的完成ajax功能?
     1)、傳回資料是json就ok;
     2)、頁面,$.ajax();
2、原生javaWeb:
     1)、導入GSON;
     2)、傳回的資料用GSON轉成json
     3)、寫出去;
3、SpringMVC-ajax:
     1、導包
        jackson-annotations-2.1.5.jar
        jackson-core-2.1.5.jar
        jackson-databind-2.1.5.jar
     2、寫配置
     3、測試      

11.1 Json資料響應與接受

ResponseBody

maven導入包

<dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
            <version>2.1.5</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>2.1.5</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.1.5</version>
        </dependency>      
@Controller
public class AjaxController {
    @Autowired
    EmployeeDao employeeDao;


    @ResponseBody
    @RequestMapping("/getallajax")
    public Collection<Employee> ajaxGetAll() {
        Collection<Employee> all = employeeDao.getAll();
        return all;
    }
}      
  • @JsonIgnore可以忽略字段
  • @JsonFormat(pattern="")
  • @DateTimeFormat(pattern = "yyyy-MM-dd")
        @Past
        @JsonFormat(pattern = "yyyy-MM-dd")
        private Date birth;
    
        private String email;
        //1 male, 0 female
    
        private Integer gender;
    
        @JsonIgnore
        private Department department;      

    輸入:

    結果:

RequestBody

  • 可以接受json資料

HttpEntity<String>

  • 代替RequestBody,
  • 不僅能拿請求體資料,還能拿請求頭資料

ResponseEntity<String>

  • 可以設定響應頭

11.2 檔案上傳與下載下傳

檔案上傳

單檔案上傳:

maven導入包

<dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.0</version>
        </dependency>
        <dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>1.2.1</version>
        </dependency>      

編寫控制器

package com.chenhui.controller;

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

import java.io.File;
import java.io.IOException;

@Controller
public class FileUploadController {
    @RequestMapping("/upload")
    public String upload(@RequestParam(value = "username", required = false) String username,
                         @RequestParam(value = "headerImg", required = false) MultipartFile file,
                         Model model){


        System.out.println("上傳資訊");
        System.out.println("檔案名"+file.getName());
        System.out.println("檔案初始名"+file.getOriginalFilename());

        try {
            file.transferTo(new File("D:\\upload\\"+file.getOriginalFilename()));
            model.addAttribute("message","檔案上傳成功");
        } catch (IOException e) {
            e.printStackTrace();
            model.addAttribute("message","檔案上傳失敗"+e.getCause());
        }

        return "list";

    }
}      

注冊檔案上傳解析器

<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <property name="maxUploadSize" value="#{1024*1024*20}"></property>
        <property name="defaultEncoding" value="utf-8"></property>
    </bean>      

編寫jsp頁面

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%--
  Created by IntelliJ IDEA.
  User: admin
  Date: 2020/11/13
  Time: 9:18
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>員工清單</title>
</head>
<body>
<% pageContext.setAttribute("ctp", request.getContextPath());
//    System.out.println(request.getContextPath());
%>
<a href="toaddpage" target="_blank" rel="external nofollow"  target="_blank" rel="external nofollow" >添加員工</a><br>
<hr>
<h1>${message}</h1>
<form action="${ctp}/upload" method="post" enctype="multipart/form-data">
    頭像:<input type="file" name="headerImg">
    昵稱:<input type="text" name="username">
    <input type="submit" value="送出">
</form>
</body>
</html>
      

結果

多檔案上傳:

@Controller
public class FileUploadController {
    @RequestMapping("/upload")
    public String upload(@RequestParam(value = "username", required = false) String username,
                         @RequestParam(value = "headerImg", required = false) MultipartFile[] files,
                         Model model){
		
        for(MultipartFile file: files){
            
            System.out.println("上傳資訊");
            System.out.println("檔案名"+file.getName());
            System.out.println("檔案初始名"+file.getOriginalFilename());
			if(!file.isEmpty()){
                try {
                    file.transferTo(new File("D:\\upload\\"+file.getOriginalFilename()));
                    model.addAttribute("message","檔案上傳成功");
                } catch (IOException e) {
                    e.printStackTrace();
                    model.addAttribute("message","檔案上傳失敗"+e.getCause());
                }
            }
            return "list";                       
        }       
    }
}      

檔案下載下傳

@Controller
public class DownloadController {
    @RequestMapping(value = "/DownLoad/{fileName}/{fileType}", method = RequestMethod.GET)
    public ResponseEntity<byte[]> download(HttpServletRequest request, @PathVariable String fileName, @PathVariable String fileType) throws IOException {
        File file = new File("D:\\Apks\\" + fileName + "." + fileType);
        byte[] body = null;
        InputStream is = new FileInputStream(file);
        body = new byte[is.available()];
        is.read(body);
        HttpHeaders headers = new HttpHeaders();
        headers.add("Content-Disposition", "attchement;filename=" + file.getName());
        HttpStatus statusCode = HttpStatus.OK;
        ResponseEntity<byte[]> entity = new ResponseEntity<>(body, headers, statusCode);
        return entity;
    }
}      

11.3 HttpMessageConverter<T>接口:

Spring3.0 新添加的一個接口,負責

将請求資訊轉換為一個對象(類型為 T)

将對象(類型為 T)輸出為響應資訊

注意:一般Controller傳回String類型是走視圖解析(ViewResolver)

如果傳回其他類型是由HttpMessageConverter負責      

HttpMessageConverter<T>接口定義的方法:

  • Boolean canRead(Class<?> clazz,MediaType mediaType):
    • 指定轉換器可以讀取的對象類型,即轉換器是否可将請求資訊轉換為 clazz 類型的對象,同時指定支援 MIME 類型(text/html,applaiction/json等)
  • Boolean canWrite(Class<?> clazz,MediaType mediaType):
    • 指定轉換器是否可将 clazz 類型的對象寫到響應流中,響應流支援的媒體類型在MediaType 中定義
  • LIst<MediaType> getSupportMediaTypes():
    • 該轉換器支援的媒體類型
  • T read(Class<? extends T> clazz,HttpInputMessage inputMessage):
    • 将請求資訊流轉換為 T 類型的對象
  • void write(T t,MediaType contnetType,HttpOutputMessgae outputMessage):
    • 将T類型的對象寫到響應流中,同時指定相應的媒體類型為 contentType

12. 攔截器

SpringMVC提供了攔截器機制: 允許運作目标方法之前進行一些攔截工作,或者目标方法運作之後進行一些其他處理。 Filter:javaWeb HandlerInterceptor:SpringMVC

HandlerInterceptor:

  • preHandle:在目标方法運作之前調用:
    • 傳回boolean
      • return true;(chain.doFilter())放行;
      • return false;不放行
  • postHandle:在目标方法運作之後調用
  • afterCompletion:資源響應之後調用

12.1 操作步驟

  1. 實作HandlerInterceptor接口
    package com.chenhui.interceptor;
    
    import org.springframework.web.servlet.HandlerInterceptor;
    import org.springframework.web.servlet.ModelAndView;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    public class MyFirstInterceptor implements HandlerInterceptor {
    
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            System.out.println("MyFirstInterceptor...preHandle");
            return true;
        }
    
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
            System.out.println("MyFirstInterceptor...postHandle");
        }
    
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            System.out.println("MyFirstInterceptor...afterCompletion");
        }
    }      
  2. 配置攔截器
    <mvc:interceptors>
            <!--預設攔截所有請求↓-->
            <!-- <bean class="com.chenhui.interceptor.MyFirstInterceptor"></bean>-->
    
            <!--攔截具體請求↓-->
            <mvc:interceptor>
                <!--隻攔截path所對應的請求-->
                <mvc:mapping path="/testInter"/>
                <bean class="com.chenhui.interceptor.MyFirstInterceptor"></bean>
            </mvc:interceptor>
        </mvc:interceptors>      
    testInter控制器如下
    @Controller
    public class InterceptorTestController {
    
        @RequestMapping("/testInter")
        public String testInterceptor(){
            return "hello";
        }
    }
          
    hello.jsp:
    <% pageContext.setAttribute("ctp",request.getContextPath());%>
    
    <%@ page contentType="text/html;charset=UTF-8" language="java" pageEncoding="UTF-8" %>
    <html>
      <head>
        <title>$Title$</title>
      </head>
      <body>
      <a href="testInter" target="_blank" rel="external nofollow" >測試攔截器</a>
      </body>
    </html>
    <%--<jsp:forward page="/emp"></jsp:forward>--%>      
  3. 攔截器的運作流程
    1. preHandle
    2. 目标方法
    3. postHandle
    4. 頁面渲染
    5. afterCompletion
    其他流程:
    1. 隻要preHandle不放行就沒有以後的流程;
      • preHandle return false
    2. 隻要放行了,afterCompletion都會執行;
      • 目标方法出現異常,afterCompletion也會執行

12.2 多個攔截器

MyFirstInterceptor...preHandle...
MySecondInterceptor...preHandle...
目标方法....
MySecondInterceptor...postHandle...
MyFirstInterceptor...postHandle...
響應頁面....
MySecondInterceptor...afterCompletion...
MyFirstInterceptor...afterCompletion      

異常流程:

  1. 哪一塊Interceptor不放行
    1. 哪一塊不放行從此以後都沒有
  2. MySecondInterceptor不放行
    1. 但是他前面已經放行了的攔截器的afterCompletion總會執行

總結interceptor的流程:

攔截器的preHandle:是按照順序執行

攔截器的postHandle:是按照逆序執行

攔截器的afterCompletion:是按照逆序執行

已經放行了的攔截器的afterCompletion總會執行

12.3 攔截器源碼

在DispatcherServlet中

try {
            ModelAndView mv = null;
            Exception dispatchException = null;

            try {
                processedRequest = checkMultipart(request);
                multipartRequestParsed = processedRequest != request;

                
                // Determine handler for the current request.拿到方法的執行鍊,包含攔截器
                mappedHandler = getHandler(processedRequest);
                
                
                
                if (mappedHandler == null || mappedHandler.getHandler() == null) {
                    noHandlerFound(processedRequest, response);
                    return;
                }

                
                // Determine handler adapter for the current request.
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

                // Process last-modified header, if supported by the handler.
                String method = request.getMethod();
                boolean isGet = "GET".equals(method);
                if (isGet || "HEAD".equals(method)) {
                    long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                    if (logger.isDebugEnabled()) {
                        String requestUri = urlPathHelper.getRequestUri(request);
                        logger.debug("Last-Modified value for [" + requestUri + "] is: " + lastModified);
                    }
                    if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                        return;
                    }
                }

                
                
               //攔截器preHandle執行位置;有一個攔截器傳回false目标方法以後都不會執行;直接跳到afterCompletion
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }

                
                
                try {
                    // Actually invoke the handler.擴充卡執行目标方法
                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                }
                finally {
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        return;
                    }
                }

                applyDefaultViewName(request, mv);
                
                
                
                 //目标方法隻要正常就會走到postHandle;任何期間有異常
                mappedHandler.applyPostHandle(processedRequest, response, mv);
                
                
                
            }
            catch (Exception ex) {
                dispatchException = ex;
            }

    
    
            //頁面渲染;如果完蛋也是直接跳到afterCompletion;
            processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    
    
    
        }
        catch (Exception ex) {
            triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
        }
        catch (Error err) {
            triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
        }
        finally {
            if (asyncManager.isConcurrentHandlingStarted()) {
                // Instead of postHandle and afterCompletion
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                return;
            }
            // Clean up any resources used by a multipart request.
            if (multipartRequestParsed) {
                cleanupMultipart(processedRequest);
            }
        }
    }      

順序周遊所有攔截器的preHandle方法

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
        if (getInterceptors() != null) {
            for (int i = 0; i < getInterceptors().length; i++) {
                HandlerInterceptor interceptor = getInterceptors()[i];

                //preHandle-true-false
                if (!interceptor.preHandle(request, response, this.handler)) {
                    //執行完afterCompletion();
                    triggerAfterCompletion(request, response, null);
                    //傳回一個false
                    return false;
                }
               //記錄一下索引
               //this.interceptorIndex = i;
            }
        }
        return true;
    }      

逆序周遊所有攔截器的postHandle方法

void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {
        if (getInterceptors() == null) {
            return;
        }
        //逆向執行每個攔截器的postHandle
        for (int i = getInterceptors().length - 1; i >= 0; i--) {
            HandlerInterceptor interceptor = getInterceptors()[i];
            interceptor.postHandle(request, response, this.handler, mv);
        }
    }      

頁面渲染方法

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
            HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {

        boolean errorView = false;

        if (exception != null) {
            if (exception instanceof ModelAndViewDefiningException) {
                logger.debug("ModelAndViewDefiningException encountered", exception);
                mv = ((ModelAndViewDefiningException) exception).getModelAndView();
            }
            else {
                Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
                mv = processHandlerException(request, response, handler, exception);
                errorView = (mv != null);
            }
        }

        // Did the handler return a view to render?
        if (mv != null && !mv.wasCleared()) {
            
             //頁面渲染
            render(mv, request, response);
            if (errorView) {
                WebUtils.clearErrorRequestAttributes(request);
            }
        }
        else {
            if (logger.isDebugEnabled()) {
                logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
                        "': assuming HandlerAdapter completed request handling");
            }
        }

        if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
            // Concurrent handling started during a forward
            return;
        }

    
        if (mappedHandler != null) {
               //頁面正常執行afterCompletion;即使沒走到這,afterCompletion總會執行;
            mappedHandler.triggerAfterCompletion(request, response, null);
        }
    }      

afterCompletion:

void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex)
            throws Exception {

        if (getInterceptors() == null) {
            return;
        }
         
          //有記錄最後一個放行攔截器的索引,從他開始把之前所有放行的攔截器的afterCompletion都執行
        for (int i = this.interceptorIndex; i >= 0; i--) {
            HandlerInterceptor interceptor = getInterceptors()[i];
            try {
                interceptor.afterCompletion(request, response, this.handler, ex);
            }
            catch (Throwable ex2) {
                logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
            }
        }
    }      

第二個攔截器不放行的情況:

preHandle:

第一次:ConversionServiceExposingInterceptor  interceptorIndex=0;
第二次:MyFirstInterceptor                    interceptorIndex=1
第三次;MySecondInterceptor          執行afterCompletion()
已經放行了的攔截器的afterCompletion總會執行      

從記錄的索引開始倒叙執行afterCompletion方法:

for (int i = this.interceptorIndex; i >= 0; i--) {
            HandlerInterceptor interceptor = getInterceptors()[i];
            try {
                interceptor.afterCompletion(request, response, this.handler, ex);
            }
            catch (Throwable ex2) {
                logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
            }
	}      

13. 國際化

13.1 步驟

  1. 寫好國際化資源檔案
    username=UserName
    password=PassWord
    login=Login      
    username=使用者名
    password=密碼
    login=登入      
  2. 讓Spring的ResourceBundleMessageSource管理國際化資源檔案
    <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
            <property name="basename" value="loginpage/login"></property>
        </bean>      
  3. 直接去頁面取值
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
    <html>
    <head>
        <title>Title</title>
    </head>
    <body>
    <form>
        <fmt:message key="username"/>:<input type="text"><br>
        <fmt:message key="password"/>:<input type="password"><br>
        <input type="submit" value="<fmt:message key="login"/>">
    </form>
    </body>
    </html>      
  4. 現象:是按照浏覽器帶來語言資訊決定

13.2 自定義LocaleResolver

實作LocaleResolver接口

public class MyLocalResolver implements LocaleResolver {
    public Locale resolveLocale(HttpServletRequest request) {
        System.out.println("自己的區域解析器");
        Locale l = null;

        String locale = request.getParameter("locale");
        System.out.println("自己區域解析器接受的locale:"+locale);
        if (locale != null && !"".equals(locale)) {
            l = new Locale(locale.split("_")[0], locale.split("_")[1]);
        } else {
            l = request.getLocale();
        }
        System.out.println("Locale:"+l.toString());
        return l;
    }

    public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
        throw new UnsupportedOperationException("Can't set Locale message");
    }
}

      

注冊到ioc.xml中

<bean id="localeResolver" class="com.chenhui.component.MyLocalResolver"></bean>      

jsp頁面修改為

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<form>
    <fmt:message key="username"/>:<input type="text"><br>
    <fmt:message key="password"/>:<input type="password"><br>
    <input type="submit" value="<fmt:message key="login"/>">
</form>
<a href="tologinpage?locale=zh_CN" target="_blank" rel="external nofollow" >中文</a>
<a href="tologinpage?locale=en_US" target="_blank" rel="external nofollow" >英文</a>
</body>
</html>      

效果

13.3 FixedLocaleResolver:

使用系統預設的區域資訊

@Override
    public Locale resolveLocale(HttpServletRequest request) {
        Locale locale = getDefaultLocale();
        if (locale == null) {
            locale = Locale.getDefault();
        }
        return locale;
    }

    @Override
    public LocaleContext resolveLocaleContext(HttpServletRequest request) {
        return new TimeZoneAwareLocaleContext() {
            @Override
            public Locale getLocale() {
                return getDefaultLocale();
            }
            @Override
            public TimeZone getTimeZone() {
                return getDefaultTimeZone();
            }
        };
    }

    @Override
    public void setLocaleContext(HttpServletRequest request, HttpServletResponse response, LocaleContext localeContext) {
        throw new UnsupportedOperationException("Cannot change fixed locale - use a different locale resolution strategy");
    }      

13.4 SessionLocaleResolver:

區域資訊是從session中擷取,可以根據請求參數建立一個locale對象,把他放在session中。

@Override
    public Locale resolveLocale(HttpServletRequest request) {
        Locale locale = (Locale) WebUtils.getSessionAttribute(request, LOCALE_SESSION_ATTRIBUTE_NAME);
        if (locale == null) {
            locale = determineDefaultLocale(request);
        }
        return locale;
    }      

13.5 CookieLocaleResolver

區域資訊是從cookie中擷取

@Override
    public Locale resolveLocale(HttpServletRequest request) {
        parseLocaleCookieIfNecessary(request);
        return (Locale) request.getAttribute(LOCALE_REQUEST_ATTRIBUTE_NAME);
    }      

14. 異常處理

14.1 異常源碼

processDispatchResult(processedRequest, response, mappedHandler, 
    mv, dispatchException);      

加了MVC異常處理,預設就是這個幾個HandlerExceptionResolver

  • ExceptionHandlerExceptionResolver
  • ResponseStatusExceptionResolver
  • DefaultHandlerExceptionResolver

如果異常解析器都不能處理就直接抛出去;

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
            HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {

        boolean errorView = false;

    	//如果有異常
        if (exception != null) {
            if (exception instanceof ModelAndViewDefiningException) {
                logger.debug("ModelAndViewDefiningException encountered", exception);
                mv = ((ModelAndViewDefiningException) exception).getModelAndView();
            }
            else {
                
                //處理異常
                Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
                
                //===================================
                mv = processHandlerException(request, response, handler, exception);
                
                
                errorView = (mv != null);
            }
        }

        // Did the handler return a view to render?
        if (mv != null && !mv.wasCleared()) {
               //來到頁面
            render(mv, request, response);
            if (errorView) {
                WebUtils.clearErrorRequestAttributes(request);
            }
        }
        else {
            if (logger.isDebugEnabled()) {
                logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
                        "': assuming HandlerAdapter completed request handling");
            }
        }

        if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
            
            // Concurrent handling started during a forward
            return;
        }

        if (mappedHandler != null) {
            mappedHandler.triggerAfterCompletion(request, response, null);
        }
    }      

所有異常解析器嘗試解析,解析完成進行後續,解析失敗下一個解析器繼續解析

protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
            Object handler, Exception ex) throws Exception {

        // Check registered HandlerExceptionResolvers...
        ModelAndView exMv = null;
        for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) {
            exMv = handlerExceptionResolver.resolveException(request, response, handler, ex);
            if (exMv != null) {
                break;
            }
        }
        if (exMv != null) {
            if (exMv.isEmpty()) {
                return null;
            }
            
            // We might still need view name translation for a plain error model...
            if (!exMv.hasView()) {
                exMv.setViewName(getDefaultViewName(request));
            }
            if (logger.isDebugEnabled()) {
                logger.debug("Handler execution resulted in exception - forwarding to resolved error view: " + exMv, ex);
            }
            WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
            return exMv;
        }

        throw ex;
    }
      

14.2 ExceptionHandler

局部異常處理

@Controller
public class ExceptionTestController {
    @RequestMapping("/testException")
    public String exceptionTest(Integer integer){
        System.out.println("testException");
        System.out.println(10/integer);
        return "exception";
    }

    @ExceptionHandler(value = {ArithmeticException.class})
    public String handleException01(){
        System.out.println("handleException-Arithmetic");
        return "myError";
    }
}      

Jsp頁面:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<h1>運算出錯</h1>
</body>
</html>      

若要攜帶異常資訊, 可以傳回ModelAndView

注意:

  • 異常資訊不能給參數位置寫Model
  • 同個作用域,有多個Exception異常處理器,精确優先
@ExceptionHandler(value = {ArithmeticException.class})
    public ModelAndView handleException01(Exception exception){
        System.out.println("handleException-Arithmetic");
        System.out.println("exception:"+exception);
        ModelAndView myError = new ModelAndView("myError");
        myError.addObject("ex",exception);
        return myError;
    }      

全局異常處理

異常處理控制器可以放在@ControllerAdvice下,作用域是全局

@ControllerAdvice
public class MyExceptionController {
    @ExceptionHandler(value = {ArithmeticException.class})
    public ModelAndView handleException01(Exception exception){
        System.out.println("handleException-Arithmetic");
        System.out.println("exception:"+exception);
        ModelAndView myError = new ModelAndView("myError");
        myError.addObject("ex",exception);
        return myError;
    }
}      

全局與本類都有比對的異常處理器,本類的優先運作

14.3 ResponseStatus

編寫一個異常類

package com.chenhui.component;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(reason = "拒絕登入", value = HttpStatus.NOT_ACCEPTABLE)
public class UsernameNotFoundException extends RuntimeException {
    static final long serialVersionUID = 1L;
}
      

測試:

@RequestMapping("/testException2")
    public String exceptionTest2(String username){
        System.out.println("testException");
        if (!"admin".equals(username)){
            System.out.println("登入失敗");
            //+++++抛出自己的錯誤資訊
            throw new UsernameNotFoundException();
            
        }
        System.out.println("登陸成功");
        return "success";
    }      

結果:

14.4 DefaultHandlerExceptionResolver

DefaultHandlerExceptionResolver:

判斷是否是SpringMVC自帶的異常或Spring自己的異常:

如:HttpRequestMethodNotSupportedException。如果沒人處理則它自己處理

預設的異常有

try {
            if (ex instanceof NoSuchRequestHandlingMethodException) {
                return handleNoSuchRequestHandlingMethod((NoSuchRequestHandlingMethodException) ex, request, response,
                        handler);
            }
            else if (ex instanceof HttpRequestMethodNotSupportedException) {
                return handleHttpRequestMethodNotSupported((HttpRequestMethodNotSupportedException) ex, request,
                        response, handler);
            }
            else if (ex instanceof HttpMediaTypeNotSupportedException) {
                return handleHttpMediaTypeNotSupported((HttpMediaTypeNotSupportedException) ex, request, response,
                        handler);
            }
            else if (ex instanceof HttpMediaTypeNotAcceptableException) {
                return handleHttpMediaTypeNotAcceptable((HttpMediaTypeNotAcceptableException) ex, request, response,
                        handler);
            }
            else if (ex instanceof MissingServletRequestParameterException) {
                return handleMissingServletRequestParameter((MissingServletRequestParameterException) ex, request,
                        response, handler);
            }
            else if (ex instanceof ServletRequestBindingException) {
                return handleServletRequestBindingException((ServletRequestBindingException) ex, request, response,
                        handler);
            }
            else if (ex instanceof ConversionNotSupportedException) {
                return handleConversionNotSupported((ConversionNotSupportedException) ex, request, response, handler);
            }
            else if (ex instanceof TypeMismatchException) {
                return handleTypeMismatch((TypeMismatchException) ex, request, response, handler);
            }
            else if (ex instanceof HttpMessageNotReadableException) {
                return handleHttpMessageNotReadable((HttpMessageNotReadableException) ex, request, response, handler);
            }
            else if (ex instanceof HttpMessageNotWritableException) {
                return handleHttpMessageNotWritable((HttpMessageNotWritableException) ex, request, response, handler);
            }
            else if (ex instanceof MethodArgumentNotValidException) {
                return handleMethodArgumentNotValidException((MethodArgumentNotValidException) ex, request, response, handler);
            }
            else if (ex instanceof MissingServletRequestPartException) {
                return handleMissingServletRequestPartException((MissingServletRequestPartException) ex, request, response, handler);
            }
            else if (ex instanceof BindException) {
                return handleBindException((BindException) ex, request, response, handler);
            }
            else if (ex instanceof NoHandlerFoundException) {
                return handleNoHandlerFoundException((NoHandlerFoundException) ex, request, response, handler);
            }
        }
        catch (Exception handlerException) {
            logger.warn("Handling of [" + ex.getClass().getName() + "] resulted in Exception", handlerException);
        }
        return null;
    }      

14.5 SimpleMappingExceptionResolver:

通過配置的方式進行異常處理

<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
        <!-- exceptionMappings:配置哪些異常去哪些頁面 -->
        <property name="exceptionMappings">
            <props>
                <!-- key:異常全類名;value:要去的頁面視圖名;會走視圖解析 -->
                <prop key="java.lang.NullPointerException">myerror</prop>
            </props>
        </property>
        <!--指定錯誤資訊取出時使用的key  -->
        <property name="exceptionAttribute" value="ex"></property>
    </bean>      

15. SpringMVC總結

SpringMVC運作流程:

1、所有請求,前端控制器(DispatcherServlet)收到請求,調用doDispatch進行處理
2、根據HandlerMapping中儲存的請求映射資訊找到,處理目前請求的,處理器執行鍊(包含攔截器)
3、根據目前處理器找到他的HandlerAdapter(擴充卡)
4、攔截器的preHandle先執行
5、擴充卡執行目标方法,并傳回ModelAndView
          1)、ModelAttribute注解标注的方法提前運作
          2)、執行目标方法的時候(确定目标方法用的參數)
                    1)、有注解
                    2)、沒注解:
                             1)、 看是否Model、Map以及其他的
                              2)、如果是自定義類型
                                             1)、從隐含模型中看有沒有,如果有就從隐含模型中拿
                                              2)、如果沒有,再看是否SessionAttributes标注的屬性,如果是從Session中拿,如果拿不到會抛異常
                                             3)、都不是,就利用反射建立對象
6、攔截器的postHandle執行
7、處理結果;(頁面渲染流程)
             1)、如果有異常使用異常解析器處理異常;處理完後還會傳回ModelAndView
              2)、調用render進行頁面渲染
                         1)、視圖解析器根據視圖名得到視圖對象
                         2)、視圖對象調用render方法;
               3)、執行攔截器的afterCompletion;      

16. SpringMVC與Spring整合

16.1 分容目的

  1. SpringMVC和Spring整合的目的:分工明确
    1. SpringMVC的配置檔案就來配置和網站轉發邏輯以及網站功能有關的

      (視圖解析器,檔案上傳解析器,支援ajax,xxx)

    2. Spring的配置檔案來配置和業務有關的(事務控制,資料源,xxx)

16.2 SpringMVC和Spring分容器

Spring管理業務邏輯元件

<context:component-scan base-package="com.atguigu">
        <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>      

SpringMVC管理控制器元件

<context:component-scan base-package="com.atguigu" use-default-filters="false">
        <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是一個父容器

SpringMVC是一個子容器

  • 子容器還可以引用父容器的元件
  • 父容器不能引用子容器的元件