SpringMVC進(jìn)階學(xué)習(xí)

1.SpringMVC

1.1SpringMVC簡(jiǎn)介

1.1.1MVC框架介紹

在項(xiàng)目中按照代碼架構(gòu)劃分:表現(xiàn)層(view視圖+Controller)、業(yè)務(wù)層(Service)葬凳、Dao層

MVC設(shè)計(jì)模式(代碼的組織方式):

M:Model(數(shù)據(jù)模型[pojo,vo,po,do]贮尖、業(yè)務(wù)模型【業(yè)務(wù)邏輯代碼】)

V:View視圖(jsp个扰、html)

C:Controller(Servlet)

MVC模式又叫做模型視圖控制器模式矾削。SpringMVC屬于表現(xiàn)層的一種框架肩刃,這個(gè)M是View與Controller之間傳輸?shù)腗odel。

1.1.2SpringMVC和傳統(tǒng)Servlet區(qū)別

傳統(tǒng)的Servlet:在一個(gè)項(xiàng)目中速缨,每個(gè)能夠處理請(qǐng)求的類都是一個(gè)Servlet锌妻,比如用戶和訂單就需要兩個(gè)Servlet來處理。

SpringMVC:全局只有一個(gè)Servlet就是DispatcherServlet來管理所有的請(qǐng)求旬牲,只不過分發(fā)給各個(gè)不同的controller進(jìn)行處理仿粹。SpringMVC只是對(duì)Servlet做了一層封裝,使用起來更加方便原茅。有點(diǎn)類似MyBatis對(duì)JDBC的封裝吭历。

相同:傳統(tǒng)的Servlet和SpringMVC起作用都是接受請(qǐng)求,返回響應(yīng)擂橘,跳轉(zhuǎn)頁面晌区。

MVC處理請(qǐng)求的流程

SprringMVC進(jìn)階學(xué)習(xí)

說明:通過一個(gè)Controller來處理請(qǐng)求,將獲取到的請(qǐng)求參數(shù)委派給業(yè)務(wù)層通贞,執(zhí)行業(yè)務(wù)之后返回一個(gè)model然后將model返回到一個(gè)視圖模板上/或者直接返回model在返回響應(yīng)朗若。

1.1.3SpringMVC流程

請(qǐng)求流程圖

SprringMVC進(jìn)階學(xué)習(xí)

處理流程:

  1. 前段控制器DispatchServlet收到請(qǐng)求
  2. 將請(qǐng)求派發(fā)給處理器映射器HandlerMapping獲取到執(zhí)行處理器的執(zhí)行鏈
  3. 將獲取到Handler找到處理器適配器,進(jìn)行處理(Controller)獲取到ModelAndView/data
  4. 如果返回的是ModelAndView找到視圖解析器昌罩,返回一個(gè)View
  5. 渲染視圖
  6. 返回請(qǐng)求

1.2SpringMVC使用

1.2.2 url-pattern配置及原理剖析

url-pattern作用:web服務(wù)器(tomcat)用攔截匹配規(guī)則的url請(qǐng)求哭懈。

配置方式

  1. 可以配置*.do *.action這種, 直接可以攔截url以.do結(jié)尾的url
  2. 配置成/ 可以攔截除了.jsp之外的所有的url茎用, 包括靜態(tài)資源
  3. 配置成/* 攔截所有的url

為啥 /不會(huì)攔截.jsp 遣总,而會(huì)攔截靜態(tài)資源

因?yàn)閠omcat容器中有一個(gè)web.xml(父),自己的項(xiàng)目中也有一個(gè)web.xml(子)轨功,是一個(gè)繼承關(guān)系旭斥。父web.xml中有一個(gè)DefaultServlet, url-pattern 是一個(gè) /夯辖,此時(shí)我們自己的web.xml中也配置了一個(gè)/ 琉预,覆寫了父web.xml的配置。而攔截jsp的是JspServlet蒿褂,我們沒有重寫這個(gè)配置因此不會(huì)攔截到j(luò)sp圆米。

解決靜態(tài)資源不被攔截的方法

方式一:如果靜態(tài)資源文件直接放在webapp根目錄下邊,直接在springmvc.xml配置文件中添加<mvc:default-servlet-handler/> 配置啄栓。原理:該配置是在SpringMVC中注冊(cè)一個(gè)DefaultServletHttpRequestHandler

娄帖,這個(gè)handler專門過濾靜態(tài)資源的文件,如果是靜態(tài)資源文件直接訪問昙楚,不是的話交給框架來正常處理近速。

方式二:如果靜態(tài)資源文件不是在webapp根目錄下,可能放在resource下的話,需要通過resource來指定靜態(tài)目錄路徑來防止攔截削葱。

  1. <mvc:resources location="classpath:/" mapping="/resources/"/>**
  2. <mvc:resources location="/WEB-INF/js/" mapping="/js/"/>**

1.2.3形參的數(shù)據(jù)接受類型

SprringMVC進(jìn)階學(xué)習(xí)

根據(jù)使用demo中可以看出奖亚,返回BindingAwareModelMap、Model析砸、Map都可以成功的跳轉(zhuǎn)到j(luò)sp頁面昔字,那么到底是什么原因? 通過觀察這些都是接口那么具體的實(shí)現(xiàn)類是啥首繁?通過斷點(diǎn)可以看到具體的實(shí)現(xiàn)類都是BindingAwareModelMap作郭,因此可以知道不管創(chuàng)建的是Model還是Map底層都是通過BindAwareModelMap來傳值的。

那么為啥Model和Map都可以傳值呢弦疮?

看下BindingAwareModelMap的繼承圖看看夹攒。懂了吧。

SprringMVC進(jìn)階學(xué)習(xí)

1.2.4參數(shù)綁定

什么是參數(shù)綁定:SpringMVC如何接收請(qǐng)求參數(shù)

傳統(tǒng)的Servlet獲取參數(shù)的方式

<packaging>war</packaging> 

<!--指定編譯版本-->  
<properties> 
 <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> 
 <maven.compiler.source>8</maven.compiler.source> 
 <maven.compiler.target>8</maven.compiler.target> 
</properties> 

<dependencies> 
 <dependency> 
 <groupId>junit</groupId> 
 <artifactId>junit</artifactId> 
 <version>4.12</version> 
 <scope>test</scope> 
 </dependency> 

    <!--引入spring webmvc的依賴-->  
 <dependency> 
 <groupId>org.springframework</groupId> 
 <artifactId>spring-webmvc</artifactId> 
 <version>5.1.12.RELEASE</version> 
 </dependency> 

 <dependency> 
 <groupId>javax.servlet</groupId> 
 <artifactId>javax.servlet-api</artifactId> 
 <version>3.1.0</version> 
 <scope>provided</scope> 
 </dependency> 

</dependencies> 

<build> 
 <plugins> 
 <plugin> 
 <groupId>org.apache.tomcat.maven</groupId> 
 <artifactId>tomcat7-maven-plugin</artifactId> 
 <version>2.2</version> 
 <configuration> 
 <port>8080</port> 
 <path>/</path> 
 </configuration> 
 </plugin> 
 </plugins> 
</build> 

為啥Servlet的參數(shù)是一個(gè)字符串類型的胁塞?因?yàn)槭褂玫氖荋ttp請(qǐng)求咏尝,超文本傳輸協(xié)議,因此傳輸?shù)亩际俏谋绢愋偷臄?shù)據(jù)闲先。

SpringMVC如何接收參數(shù)

<?xml version="1.0" encoding="UTF-8"?> 
<beans xmlns="http://www.springframework.org/schema/beans"  
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
       xmlns:context="http://www.springframework.org/schema/context"  
       xmlns:mvc="http://www.springframework.org/schema/mvc"  
       xsi:schemaLocation="  
        http://www.springframework.org/schema/beans  
        https://www.springframework.org/schema/beans/spring-beans.xsd  
        http://www.springframework.org/schema/context  
        https://www.springframework.org/schema/context/spring-context.xsd  
        http://www.springframework.org/schema/mvc  
        https://www.springframework.org/schema/mvc/spring-mvc.xsd  
"> 
    <!--開啟注解掃描-->  
 <context:component-scan base-package="com.springmvc.demo._01SimpleUse"/> 

    <!--配置視圖解析器  作用就是前綴+后綴-->  
 <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> 
 <property name="prefix" value="/WEB-INF/jsp/"/> 
 <property name="suffix" value=".jsp"/> 
 </bean> 

    <!-- 
    自動(dòng)注冊(cè)最合適的處理器映射器状土,處理器適配器(調(diào)用handler方法) 
    -->  
 <mvc:annotation-driven /> 

</beans> 

SpringMVC底層就是通過對(duì)Servlet的封裝實(shí)現(xiàn)的无蜂,只不過通過Handler將參數(shù)類型處理成Integer類型伺糠。

1.2.5SpringMVC參數(shù)接收

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

<web-app> 
    <!-- 顯示名字 -->  
 <display-name>Archetype Created Web Application</display-name> 

    <!--配置啟動(dòng)的Servlet-->  
 <servlet> 
 <servlet-name>springmvc</servlet-name> 
 <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> 
        <!--指定xml配置文件-->  
 <init-param> 
 <param-name>contextConfigLocation</param-name> 
 <param-value>classpath:01/springmvc.xml</param-value> 
 </init-param> 
 </servlet> 

    <!--指定映射路徑-->  
 <servlet-mapping> 
 <url-pattern>/</url-pattern> 
 </servlet-mapping> 

</web-app> 

2、接收基本類型

Controller處理方法的形參為基本類型或者對(duì)應(yīng)的包裝類型并且接收的參數(shù)為1個(gè)并且請(qǐng)求的參數(shù)和方法的形參一致的 話可以直接處理斥季,但是注意训桶,boolean類型的只能接收0/1;true/false

http://localhost:8080/demo/receive02?age=true

@Controller  
@RequestMapping("/demo")  
public class DemoController {  

    /** 
     * 方式一返回一個(gè)ModelAndView 
     */  
    @RequestMapping("/getDate")  
 public ModelAndView getDate() {  
        ModelAndView modelAndView = new ModelAndView();  
        //將數(shù)據(jù)添加到Object方便獲取  
        modelAndView.addObject("date", LocalDateTime.now());  
        // 設(shè)置視圖信息  
        modelAndView.setViewName("success");  

 return modelAndView;  
    }  

    /** 
     * 方式二直接放到Map中jsp就可以獲取到數(shù)據(jù) 
     */  
    @RequestMapping("/getDate2")  
 public String getDate(Map map) {  
        map.put("date", LocalDateTime.now());  
 return "success";  
    }  

    /** 
     * 方式三 接收一個(gè)Model 
     */  
    @RequestMapping(value = "/getDate3")  
 public String getDate(Model model) {  
        model.addAttribute("date", LocalDateTime.now());  
 return "success";  
    }  
}  

如果對(duì)于多個(gè)參數(shù),或者url的參數(shù)和方法形參的名字不一致的時(shí)候需要使用@RequesyParam來映射

http://localhost:8080/demo/receive03?AGE=14&NAME=hello

<%@ page language="java" isELIgnored="false" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%> 
<html> 
<head> 
 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 
 <title>Insert title here</title> 
</head> 
<body> 
跳轉(zhuǎn)成功酣倾!服務(wù)器時(shí)間:${date}  
</body> 
</html> 

3舵揭、普通的pojo類型接收

url的名字和形參的名字保持一致

http://localhost:8080/demo/receive04?age=14&name=hello

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

如果Pojo里邊嵌套其他的pojo的話可以通過屬性名.內(nèi)嵌屬性的方式

http://localhost:8080/demo/receive04?user.age=14&user.name=hello

但是這種方式不經(jīng)常使用,因?yàn)镚et請(qǐng)求2048kb 防止超出長(zhǎng)度所以一般都會(huì)使用post請(qǐng)求躁锡。

4午绳、Date類型的數(shù)據(jù)封裝

封裝在pojo中的時(shí)間格式,通過@Dat兒Tim兒Format的方式來進(jìn)行解析

http://localhost:8080/demo/receive04?age=14&name=hello&date=2020-12-20

    @DateTimeFormat(pattern = "yyyy-MM-dd")  
private Date date;  

單獨(dú)的作為參數(shù) 在方法的形參上貼上這個(gè)注解

@RequestMapping("/receive05")  
public String receive05(@DateTimeFormat(pattern = "yyyy-MM-dd") Date date){  
    System.out.println(date);  
 return "success";  
}  

@DateTimeFormat實(shí)現(xiàn)原理

在綁定參數(shù)的時(shí)候判斷屬性值或形參值是否貼這個(gè)注解如果貼了這個(gè)注解就確定需要將當(dāng)前String類型轉(zhuǎn)換成Date類型映之,如果不使用@DateTimeFormat怎么實(shí)現(xiàn)時(shí)間格式的參數(shù)封裝拦焚。

自定義轉(zhuǎn)換器

//自定義轉(zhuǎn)換器  
import org.springframework.core.convert.converter.Converter;  
import java.text.ParseException;  
import java.text.SimpleDateFormat;  
import java.util.Date;  
public class DateConverter implements Converter<String, Date> {  
    @Override  
 public Date convert(String source) {  
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");  
 try {  
 return simpleDateFormat.parse(source);  
        } catch (ParseException e) {  
            e.printStackTrace();  
        }  
 return null;  
    }  
}  

注冊(cè)到Spring中

<mvc:annotation-driven conversion-service="myConverter" /> 
<bean id="myConverter" class="org.springframework.format.support.FormattingConversionServiceFactoryBean"> 
 <property name="converters"> 
 <set> 
 <bean class="com.springmvc.demo._01SimpleUse.convernt.DateConverter"></bean> 
 </set> 
 </property> 
</bean> 

使用

@RequestMapping("/receive06")  
public String receive06(Date date){  
    System.out.println(date);  
    return "success";  
}

測(cè)試url http://localhost:8080/demo/receive06?date=2020-12-20

1.2.6SpringMVC的Rest風(fēng)格請(qǐng)求

使用postman模擬不同的請(qǐng)求方式,如果使用提交可以使用_method隱藏域的方式來更換方法類型

localhost:8080/restFul/demo/get/ 1212
@RequestMapping(path = "/demo/get/{id}", method= RequestMethod.GET)  
public String getDemo(@PathVariable("id") Integer id){  
    System.out.println(id);  
 return "success";  
}  

localhost:8080/restFul/demo/post?name=康康&id=1212
@RequestMapping(path = "/demo/post", method= RequestMethod.POST)  
public String postDemo(@RequestParam("id") Integer id, @RequestParam("name") String name){  
    System.out.println(id);  
    System.out.println(name);  
 return "success";  
}  
localhost:8080/restFul/demo/put/1212/12
@RequestMapping(path = "/demo/put/{id}/{age}", method= RequestMethod.PUT)  
public String getPut(@PathVariable("id") Integer id, @PathVariable("age") Integer age){  
    System.out.println(age);  
 return "success";  
}  

localhost:8080/restFul/demo/delete/1212
@RequestMapping(path = "/demo/delete/{id}", method= RequestMethod.DELETE)  
public String getDelete(@PathVariable("id") Integer id){  
    System.out.println(id);  
 return "success";  
}  

亂碼問題解決:通過web.xml的方式來解決

<!--springmvc提供的針對(duì)post請(qǐng)求的編碼過濾器-->  
<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> 
</filter> 
<filter-mapping> 
 <filter-name>encoding</filter-name> 
 <url-pattern>/*</url-pattern> 
</filter-mapping> 

1.2.7Json方式請(qǐng)求

1杠输、后端接受赎败,后端接受必須使用注解@RequestBody來進(jìn)行接受,必須使用post方式來請(qǐng)求蠢甲,因?yàn)橹挥衟ost請(qǐng)求才會(huì)有請(qǐng)求體僵刮。

2、后端響應(yīng)json,需要在方法上貼上@ResponseBody 搞糕, 貼上這個(gè)注解之后返回的就是json格式的數(shù)據(jù)并且不會(huì)跳轉(zhuǎn)視圖勇吊。

SpringMVC默認(rèn)使用的是jackson因此需要先引入 pom

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

前端編寫請(qǐng)求

<script> 
    $(function () {  

        $("#ajaxBtn").bind("click",function () {  
            // 發(fā)送ajax請(qǐng)求  
            $.ajax({  
                url: '/json/demo',  
                type: 'POST',  
                data: '{"id":"1","name":"李四"}',  
                contentType: 'application/json;charset=utf-8',//指定后端接受類型為json類型  
                dataType: 'json',  //指定前端接受類型為json類型
                success: function (data) {  
                    alert(data.name);  
                }  
            })  
        })  
    })  
</script> 

后端接受響應(yīng)請(qǐng)求

@Controller  
@RequestMapping("/json")  
public class JsonController {  
    @RequestMapping(value = "/demo", method = RequestMethod.POST)  
    @ResponseBody  
 public JsonPojo jsonDemo(@RequestBody JsonPojo jsonPojo){  
        System.out.println(jsonPojo);  
 return jsonPojo;  
    }  
}  

1.3SpringMVC高級(jí)應(yīng)用

1.3.1監(jiān)聽器、攔截器窍仰、過濾器

servlet:處理Request請(qǐng)求和Response響應(yīng)萧福。

過濾器(Filter):對(duì)Request請(qǐng)求起到過濾的作?,作?在Servlet之前辈赋,如果配置為/*可以對(duì)所有的資源訪問(servlet鲫忍、js/css靜態(tài)資源等)進(jìn)?過濾處理。

監(jiān)聽器(Listener):實(shí)現(xiàn)了javax.servlet.ServletContextListener 接?的服務(wù)器端組件钥屈,它隨Web應(yīng)?的啟動(dòng)?啟動(dòng)悟民,只初始化?次,然后會(huì)?直運(yùn)?監(jiān)視篷就,隨Web應(yīng)?的停??銷毀射亏。

監(jiān)聽器作用

  1. 可以做一些初始化工作,web應(yīng)?中spring容器啟動(dòng)ContextLoaderListener竭业。
  2. 監(jiān)聽web中的特定事件智润,?如HttpSession,ServletRequest的創(chuàng)建和銷毀;變量的創(chuàng)建未辆、銷毀和修改等窟绷。可以在某些動(dòng)作前后增加處理咐柜,實(shí)現(xiàn)監(jiān)控兼蜈,?如統(tǒng)計(jì)在線?數(shù),利?HttpSessionLisener等拙友。

攔截器(Interceptor):是SpringMVC为狸、Struts等表現(xiàn)層框架??的,不會(huì)攔截jsp/html/css/image的靜態(tài)訪問等遗契,只會(huì)攔截訪問的控制器?法(Handler)辐棒。

攔截器(interceptor)作用時(shí)機(jī):

  • 在Handler業(yè)務(wù)邏輯執(zhí)?之前攔截?次
  • 在Handler邏輯執(zhí)?完畢但未跳轉(zhuǎn)??之前攔截?次
  • 在跳轉(zhuǎn)??之后攔截?次

配置位置:從配置的?度也能夠總結(jié)發(fā)現(xiàn):serlvet、filter牍蜂、listener是配置在web.xml中的漾根,?interceptor是配置在表現(xiàn)層框架??的配置?件中的。

SprringMVC進(jìn)階學(xué)習(xí)

攔截器基本使用

自定義攔截器需要實(shí)現(xiàn)InterceptorHandler

public class MyInterceptor01 implements HandlerInterceptor {  

    /** 
     *在handler方法業(yè)務(wù)邏輯執(zhí)行之前捷兰, 
     * 常用與權(quán)限控制 
     * @return 返回值boolean代表是否放行立叛,true代表放行,false代表中止 
     */  
    @Override  
 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {  
        System.out.println("MyInterceptor01 preHandle......");  
        //TODO 權(quán)限控制  
 return true;  
    }  

    /** 
     * 會(huì)在handler方法業(yè)務(wù)邏輯執(zhí)行之后尚未跳轉(zhuǎn)頁面時(shí)執(zhí)行 
     * @Param modelAndView   封裝了視圖和數(shù)據(jù)贡茅,此時(shí)尚未跳轉(zhuǎn)頁面呢秘蛇,你可以在這里針對(duì)返回的數(shù)據(jù)和視圖信息進(jìn)行修改 
     */  
    @Override  
 public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {  
        System.out.println("MyInterceptor01 postHandle......");  

    }  

    /** 
     * 頁面已經(jīng)跳轉(zhuǎn)渲染完畢之后執(zhí)行 
     * @param ex  可以在這里捕獲異常 
     */  
    @Override  
 public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {  
        System.out.println("MyInterceptor01 afterCompletion......");  
    }  
}  

通過xml配置

<!--配置攔截器-->  
<mvc:interceptors> 
    <!--藍(lán)節(jié)所有的handler-->  
    <!--<bean class="com.springmvc.demo._02advince.interceptor.MyInterceptor01"/>-->  

 <mvc:interceptor> 
        <!--匹配到的uri-->  
 <mvc:mapping path="/**"/> 
        <!--排除的uri-->  
 <mvc:exclude-mapping path="/demo/**"/> 
 <bean class="com.springmvc.demo._02advince.interceptor.MyInterceptor01"/> 
 </mvc:interceptor> 
</mvc:interceptors> 

思考:如果項(xiàng)目中配置了多個(gè)攔截器的話其做,執(zhí)行順序是如何的?

攔截器的執(zhí)行順序是根據(jù)配置在xml中的配置順序決定的赁还,

 <interceptors> 
 <interceptor01> 
 <interceptor02> 
</ interceptors> 
        執(zhí)行順序是:  
Interceptor01 preHandle......  
Interceptor02 preHandle......  
//業(yè)務(wù)邏輯  
Interceptor02 postHandle......  
Interceptor01 postHandle......  
Interceptor02 afterCompletion......  
Interceptor01 afterCompletion......  

1.3.2文件上傳

了解即可妖泄,現(xiàn)在在實(shí)戰(zhàn)的項(xiàng)目中文件都會(huì)上傳到文件服務(wù)器上的。

客戶端要求:必須post請(qǐng)求艘策,請(qǐng)求方式必須是multipart蹈胡, type為file

服務(wù)器要求:用MultipartFile來接收 進(jìn)行存儲(chǔ)

客戶端

 <form method="post" enctype="multipart/form-data" action="/demo/uploadFile"> 
 <input type="file" name="multipartFile"/> 
 <input type="submit" value="上傳"/> 
</form> 

服務(wù)端

需要引入POM

    <!--文件上傳所需坐標(biāo)-->  
<dependency> 
 <groupId>commons-fileupload</groupId> 
 <artifactId>commons-fileupload</artifactId> 
 <version>1.3.1</version> 
</dependency> 

Handler

@Controller  
@RequestMapping("/demo")  
public class FileUploadController {  
    @RequestMapping(value = "/uploadFile" , method = RequestMethod.POST)  
 public String uploadFile(MultipartFile multipartFile, HttpSession session) throws IOException {  
        //存儲(chǔ)在項(xiàng)目的webapp的根目錄下,需要找到絕對(duì)路徑朋蔫, 通過Session  
        String realPath = session.getServletContext().getRealPath("/WEB-INF");  
        //根據(jù)日期存儲(chǔ)文件避免單個(gè)目錄下文件數(shù)量過多  
        String datePath = new SimpleDateFormat("yyyy-MM-dd").format(new Date());  
        //獲取到文件判斷是否存在不存在需要進(jìn)行創(chuàng)建  
        File file = new File(realPath + "/" + datePath);  
 if (!file.exists()) {  
            file.mkdir();  
        }  

        //獲取到文件名 生成一個(gè)隨機(jī)的文件名避免重復(fù)  
        String fileName = UUID.randomUUID().toString().replaceAll("-", "");  
        String extendName = multipartFile.getOriginalFilename().substring(multipartFile.getOriginalFilename().indexOf("."));  
        fileName = fileName  + extendName;  

        //存儲(chǔ)文件  
        multipartFile.transferTo(new File(file, fileName));  

        //返回  
 return "success fileName:" + fileName;  
    }  
}   

配置文件解析器罚渐,SpringMVC配置文件中

<!--文件上傳解析器  這個(gè)id必須是固定的 SpringMVC框架可以自動(dòng)識(shí)別到-->  
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> 
    <!--設(shè)置上傳文件大小上限,單位是字節(jié)驯妄,-1代表沒有限制也是默認(rèn)的-->  
 <property name="maxUploadSize" value="5000000"/> 
</bean> 

1.3.3SpringMVC異常處理

自定義異常處理器需要實(shí)現(xiàn)HandlerExceptionResolver也可以使用注解的方式進(jìn)行處理荷并。

全局異常處理,@ControllerAdvince這個(gè)注解主要使用的是AOP的方式來進(jìn)行處理的青扔。

@ControllerAdvice  
public class GlobalExceptionResolver {  

    @ExceptionHandler  
 public ModelAndView handleException(RuntimeException runtimeException){  
        ModelAndView modelAndView = new ModelAndView();  
        modelAndView.addObject("msg",runtimeException);  
        modelAndView.setViewName("error");  
 return modelAndView;  
    }  

}  

1.3.4重定向攜帶參數(shù)

轉(zhuǎn)發(fā)和重定向區(qū)別:

1源织、轉(zhuǎn)發(fā)是一個(gè)url請(qǐng)求,經(jīng)過層層往下傳遞微猖,過程中參數(shù)不會(huì)丟失

2谈息、重定向是在一個(gè)請(qǐng)求如果A完成不了就會(huì)重定向到B重定向會(huì)導(dǎo)致數(shù)據(jù)丟失。

重定向之后的handler需要使用@ModelAttribute獲取到屬性值凛剥。

    @Controller  
public class RedirectController {  

    @RequestMapping("/handlerA")  
 public String handlerA(String name, RedirectAttributes redirectAttributes){  
        redirectAttributes.addFlashAttribute("name", name);  
        System.out.println(name);  
 return "redirect:handlerB";  
    }  

    @RequestMapping("/handlerB")  
 public String handlerB(@ModelAttribute("name") String name){  
        System.out.println(name);  
 return "success";  
    }  
}  

1.5SpringMVC源碼分析

1.5.1MVC九大組建

名稱作用HandlerMapping(處理器映射器)[核心]就是處理請(qǐng)求的url找到對(duì)應(yīng)的controller根據(jù)(@RequestMapping)侠仇。HandlerAdapter(處理器適配器)[核心]對(duì)上一步找到的controller進(jìn)行適配,參數(shù)適配当悔,類型適配(controller實(shí)現(xiàn)接口/@RequestMapping)傅瞻。HandlerExceptionResolver(異常解析器)處理異常踢代,當(dāng)發(fā)生異常的時(shí)候撲捉到異常進(jìn)行處理異常 比如跳轉(zhuǎn)500頁面盲憎。ViewResolver(視圖解析器)[核心]在使用過程中經(jīng)常在Spring配置文件中配置前綴和后綴方便在controller層直接寫死視圖的路徑。RequestToViewNameTranslator根據(jù)視圖解析器和返回的路徑找到對(duì)應(yīng)視圖的名字胳挎。LocaleResolver國際化處理饼疙,國際化語言、時(shí)間等處理慕爬。ThemeResolver主題處理窑眯,現(xiàn)在前后端分離很少使用。MultipartResolver這個(gè)組件專門處理上傳的文件医窿。FlashMapManager用于請(qǐng)求轉(zhuǎn)發(fā)磅甩,處理攜帶的參數(shù)。

1.5.2SpringMVC初始化

初始化執(zhí)行流程

源碼分析 容器啟動(dòng)調(diào)用HttpServlet.init方法 HttpServletBean.init方法 調(diào)用initServletBean方法 FrameworkServlet.initServletBean方法 FrameworkServlet.initWebApplicationContext方法 FrameworkServlet.createWebApplicationContext創(chuàng)建一個(gè)WebApplicationContext方法 FrameworkServlet.configureAndRefreshWebApplicationContext配置并刷新webContext wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));//添加一個(gè)容器刷新監(jiān)聽器 wac.refresh()//執(zhí)行刷新 AbstractApplicationContext.finishRefresh方法中發(fā)布剛剛注冊(cè)的監(jiān)聽器事件publishEvent getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType) //通過多播器進(jìn)行發(fā)布事件 SourceFilteringListener. onApplicationEvent方法 DispatcherServlet.onRefresh方法 initStrategies方法 initHandlerMappings

String ageStr = request.getParameter(“age”);   
Integer age = Integer.parseInt(ageStr);  

initMultipartResolver 初始化文件上傳處理器

private void initMultipartResolver(ApplicationContext context) {  
 try {  //直接從容器中獲取id為multipartResolver的Multipart解析器 因此在注冊(cè)的時(shí)候id一定要注冊(cè)為multipartResolver
 this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);  
   }  
 catch (NoSuchBeanDefinitionException ex) {  
        // Default is no multipart resolver.  
 this.multipartResolver = null;  
    }  
}  

initHandlerMappings初始化handlerMapping

private void initHandlerMappings(ApplicationContext context) {  
 this.handlerMappings = null;  
   //是否需要查找所有的處理器映射器 默認(rèn)為true 可以在web.xml中查找到對(duì)應(yīng)的處理器映射器
 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<>(matchingBeans.values());  
            // We keep HandlerMappings in sorted order.  
            AnnotationAwareOrderComparator.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) {  
       //如果沒有找到處理器映射器的話直接獲取到一個(gè)默認(rèn)的處理器映射器  沒有自動(dòng)注入的話都會(huì)走到這一步
 this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);  
 if (logger.isTraceEnabled()) {  
            logger.trace("No HandlerMappings declared for servlet '" + getServletName() +  
                    "': using default strategies from DispatcherServlet.properties");  
        }  
    }  
}  

初始化默認(rèn)的處理器映射器

protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {  
  //獲取到接口的名字
    String key = strategyInterface.getName();   
      //從默認(rèn)的配置文件中獲取到實(shí)現(xiàn)類的名字
    String value = defaultStrategies.getProperty(key);  
 if (value != null) {  
        //通過反射進(jìn)行初始化并返回
        String[] classNames = StringUtils.commaDelimitedListToStringArray(value);  
        List<T> strategies = new ArrayList<>(classNames.length);  
 for (String className : classNames) {  
 try {  
                Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());  
                Object strategy = createDefaultStrategy(context, clazz);  
                strategies.add((T) strategy);  
            }  
 catch (ClassNotFoundException ex) {  
 throw new BeanInitializationException(  
                        "Could not find DispatcherServlet's default strategy class [" + className +  
                        "] for interface [" + key + "]", ex);  
            }  
 catch (LinkageError err) {  
          }  
        }  
 return strategies;  
    }  
 else {  
 return new LinkedList<>();  
    }  
}  

defaultStrategies 默認(rèn)的初始化 在DispatcherServlet的static靜態(tài)代碼塊中

@RequestMapping("/Demo")  
public String getDemo(Integer demo){  
    System.out.println(demo);  
 return "demo";  
}  

其他的組件初始化方式跟initHandlerMappings一致姥卢。

1.5.3SpringMVC運(yùn)行時(shí)源碼分析

DispatcherServlet繼承圖

SprringMVC進(jìn)階學(xué)習(xí)

說明:當(dāng)請(qǐng)求過來的時(shí)候會(huì)調(diào)用HttpServlet中的doPost和doGet方法卷要,doGet和doPost方法都是FrameworkServlet 中被復(fù)寫渣聚,并且都調(diào)用process處理方法。

1僧叉、接收原生的參數(shù)
    SpringMVC只是對(duì)Servlet的封裝因此也支持原生的參數(shù)接收
    @RequestMapping("/receive01")  
public String receive01(HttpServletRequest request, HttpServletResponse response, HttpSession session){  
    System.out.println(request);  
    System.out.println(response);  
    System.out.println(session);  
 return "success";  
}  

在process方法中核心處理doService

doDispatcher核心處理方法

先通過打斷點(diǎn)的觀察調(diào)用棧的方式來確定執(zhí)行時(shí)機(jī)奕枝。

執(zhí)行Controller方法中的時(shí)機(jī)

SprringMVC進(jìn)階學(xué)習(xí)

通過打斷點(diǎn)觀察到在doDispatcher中ha.handle是執(zhí)行Handler方法,這里的ha類型是

RequestMappingHndlerAdapter瓶堕。

跳轉(zhuǎn)視圖的時(shí)機(jī)

SprringMVC進(jìn)階學(xué)習(xí)

通過打斷點(diǎn)可以查看到跳轉(zhuǎn)視圖是在processDispatcherResult方法隘道。

分析doDispatcher主要流程

doDispatcher主要核心步驟:

  1. getHandler獲取方法執(zhí)行器鏈handlerExecutionChain
  2. getHandlerAdapter 獲取處理器適配器
  3. ha.handle 執(zhí)行controller方法
  4. processDispatchResult 渲染視圖

getHandler方法

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {  
 if (this.handlerMappings != null) { 
        //遍歷handlerMappings屬性通過mapping.getHandler來獲取到處理器
 for (HandlerMapping mapping : this.handlerMappings) {  
            HandlerExecutionChain handler = mapping.getHandler(request);  
 if (handler != null) {  
 return handler;  
            }  
        }  
    }  
 return null;  
}  

mapping.getHandler

public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { 
  //獲取到執(zhí)行的方法對(duì)象包裝成一個(gè)handlerMethod對(duì)象 
    Object handler = ①getHandlerInternal(request);   
 if (handler == null) {  
        handler = getDefaultHandler();   //如果不存在的話從默認(rèn)的defaultHandler屬性中返回
    }  
 if (handler == null) {  
 return null;  
    }  
    // Bean name or resolved handler?  
 if (handler instanceof String) {   //如果是string類型的處理器的話就從ioc容器中獲取
        String handlerName = (String) handler;  
        handler = obtainApplicationContext().getBean(handlerName);  
    }  
    //根據(jù)獲取到的handler獲取到執(zhí)行器鏈  //判斷是攔截器還是執(zhí)行器 分別添加到執(zhí)行器鏈中
    HandlerExecutionChain executionChain = ②getHandlerExecutionChain(handler, request);  

  //根據(jù)header中判斷是否跨域 如果跨域就從跨域的配置中獲取到執(zhí)行器鏈并返回 跨域 header中包含Origin
 if (CorsUtils.isCorsRequest(request)) {  
        CorsConfiguration globalConfig = this.corsConfigurationSource.getCorsConfiguration(request);  
        CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);  
        CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);  
        executionChain = getCorsHandlerExecutionChain(request, executionChain, config);  
    }  

 return executionChain;  
}   
//獲取到執(zhí)行的方法
protected HandlerMethod ①getHandlerInternal(HttpServletRequest request) throws Exception {  
    String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);  //獲取到請(qǐng)求的uri
 this.mappingRegistry.acquireReadLock();   //上鎖ReentrantReadWriteLock 可重入的讀寫鎖因?yàn)槭菃卫乐蛊渌€程獲取到
 try {  //通過uri獲取到方法處理對(duì)象 mappingRegistry這個(gè)屬性中查找
        HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);  
 return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);  
    }  
 finally {  //釋放鎖
 this.mappingRegistry.releaseReadLock();  
    }  
}  
protected HandlerExecutionChain ②getHandlerExecutionChain(Object handler, HttpServletRequest request) {  
 //判斷handler是否是執(zhí)行器鏈 如果是的 話強(qiáng)轉(zhuǎn)不是的話new一個(gè)執(zhí)行器鏈
    HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?  
            (HandlerExecutionChain) handler : new HandlerExecutionChain(handler));  
  //獲取到uri
    String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);  
 for (HandlerInterceptor interceptor : this.adaptedInterceptors) {  //遍歷所有的攔截器
         //如果攔截器屬于 MappedInterceptor并且方法與當(dāng)前執(zhí)行的方法匹配就添加到執(zhí)行器鏈
 if (interceptor instanceof MappedInterceptor) { 
            MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;  
 if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {  
                chain.addInterceptor(mappedInterceptor.getInterceptor());  
            }  
        }  
 else {  //否則直接將handler(方法)添加到執(zhí)行器鏈中
            chain.addInterceptor(interceptor);  
        }  
    }  
 return chain;  
}  

getHandlerAdapter

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {  
 if (this.handlerAdapters != null) {  
         //從初始化好的handlerAdapters屬性中去判斷是否是支持  一般請(qǐng)的請(qǐng)求判斷當(dāng)前的handler是否HandlerMethod 并且框架是否支持默認(rèn)為true支持 最終會(huì)返回一個(gè)RequestMappingHandlerAdapter來進(jìn)行處理
        // handlerAdapters這個(gè)屬性中還包括HttpRequestAdapter 原生的Serrvlet請(qǐng)求登颓,SimpleControllerHandler 通過集成Controller接口的方式處理
 for (HandlerAdapter adapter : this.handlerAdapters) {  
 if (adapter.supports(handler)) {  
 return adapter;  
            }  
        }  
    }  
 throw new ServletException("No adapter for handler [" + handler +  
            "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");  
}  

▲handler

這一步主要是執(zhí)行方法鞠抑,會(huì)執(zhí)行到RequestMappingHandlerAdapter#handleInternal方法冷溃。

protected ModelAndView handleInternal(HttpServletRequest request,  
        HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {  

    ModelAndView mav;  
       //校驗(yàn)入?yún)? 主要校驗(yàn)獲取到的執(zhí)行方法存在并且session也存在
    checkRequest(request);  

    // Execute invokeHandlerMethod in synchronized block if required. 
      //是否需要同步執(zhí)行捏顺,因?yàn)閟ession是共享的可能會(huì)有線程安全的問題 默認(rèn)不需要 
 if (this.synchronizeOnSession) {  
        HttpSession session = request.getSession(false);  
 if (session != null) { //通過session獲取到一個(gè)隨機(jī)的key作為鎖進(jìn)行同步執(zhí)行 
            Object mutex = WebUtils.getSessionMutex(session);  
 synchronized (mutex) {  
                mav = invokeHandlerMethod(request, response, handlerMethod);  
            }  
        }  
 else {  
            // No HttpSession available -> no mutex necessary  
            mav = invokeHandlerMethod(request, response, handlerMethod);  
        }  
    }  
 else {  
        // No synchronization on session demanded at all... //真正執(zhí)行方法 
        mav = invokeHandlerMethod(request, response, handlerMethod);  
    }  

       //響應(yīng)體中不包括Cache-Control的話在判斷是否有session存在session屬性的話進(jìn)行緩存
 if (!response.containsHeader(HEADER_CACHE_CONTROL)) {  
 if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {  
            applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);  
        }  
 else {  //準(zhǔn)備響應(yīng)對(duì)象 
            prepareResponse(response);  
        }  
    }  

 return mav;  
}  

invokeHandlerMethod

protected ModelAndView invokeHandlerMethod(HttpServletRequest request,  
        HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {  
    //封裝了一個(gè)ServletWebRequest對(duì)象
    ServletWebRequest webRequest = new ServletWebRequest(request, response);  
 try {  //獲取一個(gè)數(shù)據(jù)綁定工廠 用來創(chuàng)建WebDataBinder 
        WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);  
           //獲取一個(gè)模型工廠用來填充model的屬性 【創(chuàng)建套路跟上邊的差不多先從緩存中獲取是否存在腔丧,不存在的話在new】
        ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);  

          //將當(dāng)前的handlerMethod封裝成一個(gè)ServletInvocableHandlerMethod  只是單純的封裝  方便下邊封裝更多的屬性
        ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);  
 if (this.argumentResolvers != null) {  
            //設(shè)置一個(gè)方法參數(shù)處理解析器HandlerMethodArgumentResolvers  這個(gè)解析器是在初始化的時(shí)候new出來的
            invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);  
        }  
 if (this.returnValueHandlers != null) {  
            //初始化一個(gè)方法返回處理器HandlerMethodReturnValueHandlers
            invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);  
        }  
          //設(shè)置上邊創(chuàng)建的數(shù)據(jù)綁定器
        invocableMethod.setDataBinderFactory(binderFactory);  
         //設(shè)置參數(shù)名字發(fā)現(xiàn)器
        invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);  

          //new 了一個(gè)視圖發(fā)現(xiàn)器
        ModelAndViewContainer mavContainer = new ModelAndViewContainer();  
          //往這個(gè)視圖發(fā)現(xiàn)器中添加一些屬性 flashMap
        mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));  
         //往modelFactory中初始化了一個(gè)模型
        modelFactory.initModel(webRequest, mavContainer, invocableMethod);  
          //設(shè)置是否需要忽略默認(rèn)的模型  默認(rèn)為false
        mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);  

          //創(chuàng)建一個(gè)異步請(qǐng)求為Servlet3.0準(zhǔn)備的
        AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);  
        asyncWebRequest.setTimeout(this.asyncRequestTimeout);  //設(shè)置超時(shí)時(shí)間
         //獲取到異步管理器
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);  
        asyncManager.setTaskExecutor(this.taskExecutor);  
        asyncManager.setAsyncWebRequest(asyncWebRequest);  
        asyncManager.registerCallableInterceptors(this.callableInterceptors);  
        asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);  

          //從異步處理器中判斷是否已經(jīng)存在當(dāng)前的執(zhí)行結(jié)果默認(rèn)不存在
 if (asyncManager.hasConcurrentResult()) {  
            //存在的話直接獲取到結(jié)果
            Object result = asyncManager.getConcurrentResult();  
            mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];  
            asyncManager.clearConcurrentResult();  
            LogFormatUtils.traceDebug(logger, traceOn -> {  
                String formatted = LogFormatUtils.formatValue(result, !traceOn);  
 return "Resume with async result [" + formatted + "]";  
            });  
             //包裝一個(gè)結(jié)果
            invocableMethod = invocableMethod.wrapConcurrentResult(result);  
        }  

          //執(zhí)行處理方法  
 ① invocableMethod.invokeAndHandle(webRequest, mavContainer);  
        //判斷當(dāng)前異步管理器是否已經(jīng)開始處理
 if (asyncManager.isConcurrentHandlingStarted()) {  
 return null;  
        }  
    //獲取到視圖并返回
 return ②getModelAndView(mavContainer, modelFactory, webRequest);  
    }  
 finally {  
        webRequest.requestCompleted();  
    }  
}  

invocableMethod.invokeAndHandle

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,  
        Object... providedArgs) throws Exception {  
    //執(zhí)行方法
    Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);  
    setResponseStatus(webRequest);  //設(shè)置響應(yīng)狀態(tài)

 if (returnValue == null) {  //如果返回值為null進(jìn)行特殊處理
 if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {              disableContentCachingIfNecessary(webRequest); //判斷是否修改過闻坚,響應(yīng)狀態(tài)是否正常 
            mavContainer.setRequestHandled(true);   //設(shè)置請(qǐng)求已經(jīng)處理標(biāo)記
 return;  
        }  
    }  
 else if (StringUtils.hasText(getResponseStatusReason())) {  
        mavContainer.setRequestHandled(true);  //如果響應(yīng)值有值的話設(shè)置已經(jīng)處理
 return;  
    }  

    mavContainer.setRequestHandled(false);  
 try {  
//處理響應(yīng)結(jié)果 獲取到視圖解析器 對(duì)返回的名字進(jìn)行解析货岭,添加上前綴和后綴并封裝在mavContainer.setViewName(viewName)中
//以及是否屬于方法的重定向進(jìn)行判斷處理
 this.returnValueHandlers.handleReturnValue(  
                returnValue, getReturnValueType(returnValue), mavContainer, webRequest);  
    }  
 catch (Exception ex) {  
 throw ex;  
    }  
}  

invokeForRequest

    public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,  
        Object... providedArgs) throws Exception {  
    //獲取到方法執(zhí)行的參數(shù)
    Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);  
    //執(zhí)行方法
 return doInvoke(args); //直接通過反射調(diào)用方法 
}  

getMethodArgumentValues獲取方法執(zhí)行參數(shù)

protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer
, Object... providedArgs) throws Exception {  
    //獲取到方法的形參 從parameters中獲取到 meters是在初始化的時(shí)候就已經(jīng)對(duì)方法進(jìn)行解析了
    MethodParameter[] parameters = getMethodParameters();  
   //如果參數(shù)為空的話直接返回
 if (ObjectUtils.isEmpty(parameters)) {  
 return EMPTY_ARGS;  
    }   
    //創(chuàng)建一個(gè)數(shù)組
    Object[] args = new Object[parameters.length];  
 for (int i = 0; i < parameters.length; i++) {  
        MethodParameter parameter = parameters[i]; 
          //初始化參數(shù)名字發(fā)現(xiàn)器 
        parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);  
         //默認(rèn)綁定 一些實(shí)現(xiàn)設(shè)置好的值比如Map HttpServletRequest等
        args[i] = findProvidedArgument(parameter, providedArgs);   
 if (args[i] != null) {  
 continue;  
        }  
 if (!this.resolvers.supportsParameter(parameter)) {  //如果不支持的話拋出異常
 throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));  
        }  
 try { 
             //根據(jù)參數(shù)和和請(qǐng)求進(jìn)行封裝參數(shù) 
            args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);  
        }  
 catch (Exception ex) {  
        }  
    }  
 return args;  
}  

resolveArgument 方法

public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,  
        NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {  
    // parameter根據(jù)parameter從緩存中argumentResolverCache中獲取參數(shù)
    HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);  
 if (resolver == null) {  
 throw new IllegalArgumentException("Unsupported parameter type [" +  
                parameter.getParameterType().getName() + "]. supportsParameter should be called first.");  
    }  
       //解析參數(shù) 
 return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);  
}  

調(diào)用AbstractNamedValueMethodArgumentResolver 類的resolveArgument進(jìn)行解析

public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,  
        NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {  
   //獲取到綁定的參數(shù) 為null
    MethodParameter nestedParameter = parameter.nestedIfOptional();  
      //獲取到形參的名字 比如name
    NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);  
    Object resolvedName = resolveStringValue(namedValueInfo.name);  //真正解析出來形參的名字name
 if (resolvedName == null) {  
 throw new IllegalArgumentException(  
                "Specified name must not resolve to null: [" + namedValueInfo.name + "]");  
    }  
    //通過名字去解析參數(shù) 去名字解析出值
    Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);  
 if (arg == null) {  //如果值為null的話解析字符串值
 if (namedValueInfo.defaultValue != null) {  
            arg = resolveStringValue(namedValueInfo.defaultValue);  
        }  
 else if (namedValueInfo.required && !nestedParameter.isOptional()) {  
            handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);  
        }  //處理null值
        arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());  
    }  
 else if ("".equals(arg) && namedValueInfo.defaultValue != null) {  
        arg = resolveStringValue(namedValueInfo.defaultValue);  
    }  

 if (binderFactory != null) {  //綁定web參數(shù)
        WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);  
 try {  
            arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);  
        }  
 catch (ConversionNotSupportedException ex) {  
        }  
    }  
    //處理值 @ModelAttribute注解是否需要特殊處理
    handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);  

 return arg;  
}  

processDispatchResult

視圖渲染

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

 boolean errorView = false;  
  //判斷異常部位null的話跳轉(zhuǎn)到異常頁面
 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) {  //如果是錯(cuò)誤視圖就清理屬性
            WebUtils.clearErrorRequestAttributes(request);  
        }  
    }  
 else {  
        //日志打印
    }  

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

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

Render

protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {  
    //本地化處理
    Locale locale =  
            (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());  
      response.setLocale(locale);  

    View view;
       //獲取視圖名字
    String viewName = mv.getViewName();  
 if (viewName != null) {  
        // We need to resolve the view name.  解析視圖名字 
        view = ①resolveViewName(viewName, mv.getModelInternal(), locale, request);  
 if (view == null) {  
 throw new ServletException("Could not resolve view with name '" + mv.getViewName() +  
                    "' in servlet with name '" + getServletName() + "'");  
        }  
    }  
 else {  
        // No need to lookup: the ModelAndView object contains the actual View object.  
        view = mv.getView();  
 if (view == null) {  
 throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +  
                    "View object in servlet with name '" + getServletName() + "'");  
        }  
    }  

 try {  
 if (mv.getStatus() != null) {  
            response.setStatus(mv.getStatus().value());  
        } 
         //渲染視圖 
        ②view.render(mv.getModelInternal(), request, response);  
    }  
 catch (Exception ex) {  
 throw ex;  
    }  
}  
resolveViewName 解析視圖名字
protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,  
        Locale locale, HttpServletRequest request) throws Exception {  
    //進(jìn)行循環(huán)遍歷解析視圖名字
 if (this.viewResolvers != null) {  
 for (ViewResolver viewResolver : this.viewResolvers) {  
            View view = viewResolver.resolveViewName(viewName, locale);  
 if (view != null) {  
 return view;  
            }  
        }  
    }  
 return null;  
}  

解析視圖名字

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.   創(chuàng)建一個(gè)view對(duì)象
                    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);  
                    }  
                }  
            }  
        }  
 else {  
       }  
 return (view != UNRESOLVED_VIEW ? view : null);  
    }  
}  

調(diào)用UrlBasedViewResolver中的createView創(chuàng)建視圖

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.  如果以redirect開頭就創(chuàng)建一個(gè)重定向視圖 
 if (viewName.startsWith(REDIRECT_URL_PREFIX)) {  
        String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());  

        RedirectView view = new RedirectView(redirectUrl,  
                isRedirectContextRelative(), isRedirectHttp10Compatible());  
        String[] hosts = getRedirectHosts();  
 if (hosts != null) {  
            view.setHosts(hosts);  
        }  
 return applyLifecycleMethods(REDIRECT_URL_PREFIX, view);  
    }  

    // Check for special "forward:" prefix.  
 if (viewName.startsWith(FORWARD_URL_PREFIX)) {  //如果以forward開頭創(chuàng)建轉(zhuǎn)發(fā)視圖 
        String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());  
        InternalResourceView view = new InternalResourceView(forwardUrl);  
 return applyLifecycleMethods(FORWARD_URL_PREFIX, view);  
    }  

    // Else fall back to superclass implementation: calling loadView.  
 return super.createView(viewName, locale);  //創(chuàng)建真正的視圖
}

最終調(diào)用父類的UrlBasedViewResolver buildView創(chuàng)建視圖

protected AbstractUrlBasedView buildView(String viewName) throws Exception {  
   //獲取到InternalResourceView 視圖
    Class<?> viewClass = getViewClass();   
    Assert.state(viewClass != null, "No view class");  

      //反射實(shí)例化對(duì)象
    AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(viewClass);  
    //獲取到視圖解析器拼裝出來物理地址
    view.setUrl(getPrefix() + viewName + getSuffix());  

      //獲取到contentType 
    String contentType = getContentType();  
 if (contentType != null) {  
        view.setContentType(contentType);  
    }  
     //設(shè)置請(qǐng)求上下文屬性
    view.setRequestContextAttribute(getRequestContextAttribute());
    //設(shè)置屬性Map  
    view.setAttributesMap(getAttributesMap());  
    //是否需要暴露路徑變量 默認(rèn)為null
    Boolean exposePathVariables = getExposePathVariables();  
 if (exposePathVariables != null) {  
        view.setExposePathVariables(exposePathVariables);  
    }  

      Boolean exposeContextBeansAsAttributes = getExposeContextBeansAsAttributes();  
 if (exposeContextBeansAsAttributes != null) {  
        view.setExposeContextBeansAsAttributes(exposeContextBeansAsAttributes);  
    }  
       //暴露上下文beanName
    String[] exposedContextBeanNames = getExposedContextBeanNames();  
 if (exposedContextBeanNames != null) {  
        view.setExposedContextBeanNames(exposedContextBeanNames);  
    }  

 return view;  
}  

②render渲染數(shù)據(jù)

public void render(@Nullable Map<String, ?> model, HttpServletRequest request,  
        HttpServletResponse response) throws Exception {  

    //將所有需要暴露的字段合并成Map   
    Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);  
       //入?yún)⒑统鰠?zhǔn)備好  判斷是否需要生成下載的Pragma  和 Cache-Control
    prepareResponse(request, response); 
      //渲染并合并視圖 
    renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);  
}  
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末鲜漩,一起剝皮案震驚了整個(gè)濱河市苍息,隨后出現(xiàn)的幾起案子缩幸,更是在濱河造成了極大的恐慌,老刑警劉巖竞思,帶你破解...
    沈念sama閱讀 218,525評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件表谊,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡盖喷,警方通過查閱死者的電腦和手機(jī)爆办,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來课梳,“玉大人距辆,你說我怎么就攤上這事∧喝校” “怎么了跨算?”我有些...
    開封第一講書人閱讀 164,862評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)椭懊。 經(jīng)常有香客問我诸蚕,道長(zhǎng),這世上最難降的妖魔是什么氧猬? 我笑而不...
    開封第一講書人閱讀 58,728評(píng)論 1 294
  • 正文 為了忘掉前任背犯,我火速辦了婚禮,結(jié)果婚禮上盅抚,老公的妹妹穿的比我還像新娘漠魏。我一直安慰自己,他們只是感情好妄均,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,743評(píng)論 6 392
  • 文/花漫 我一把揭開白布柱锹。 她就那樣靜靜地躺著破讨,像睡著了一般。 火紅的嫁衣襯著肌膚如雪奕纫。 梳的紋絲不亂的頭發(fā)上提陶,一...
    開封第一講書人閱讀 51,590評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音匹层,去河邊找鬼隙笆。 笑死,一個(gè)胖子當(dāng)著我的面吹牛升筏,可吹牛的內(nèi)容都是我干的撑柔。 我是一名探鬼主播,決...
    沈念sama閱讀 40,330評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼您访,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼铅忿!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起灵汪,我...
    開封第一講書人閱讀 39,244評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤檀训,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后享言,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體峻凫,經(jīng)...
    沈念sama閱讀 45,693評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,885評(píng)論 3 336
  • 正文 我和宋清朗相戀三年览露,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了荧琼。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,001評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡差牛,死狀恐怖命锄,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情偏化,我是刑警寧澤脐恩,帶...
    沈念sama閱讀 35,723評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站夹孔,受9級(jí)特大地震影響被盈,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜搭伤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,343評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望袜瞬。 院中可真熱鬧怜俐,春花似錦、人聲如沸邓尤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至季稳,卻和暖如春擅这,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背景鼠。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評(píng)論 1 270
  • 我被黑心中介騙來泰國打工仲翎, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人铛漓。 一個(gè)月前我還...
    沈念sama閱讀 48,191評(píng)論 3 370
  • 正文 我出身青樓溯香,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親浓恶。 傳聞我的和親對(duì)象是個(gè)殘疾皇子玫坛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,955評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容