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)求的流程
說明:通過一個(gè)Controller來處理請(qǐng)求,將獲取到的請(qǐng)求參數(shù)委派給業(yè)務(wù)層通贞,執(zhí)行業(yè)務(wù)之后返回一個(gè)model然后將model返回到一個(gè)視圖模板上/或者直接返回model在返回響應(yīng)朗若。
1.1.3SpringMVC流程
請(qǐng)求流程圖
處理流程:
- 前段控制器DispatchServlet收到請(qǐng)求
- 將請(qǐng)求派發(fā)給處理器映射器HandlerMapping獲取到執(zhí)行處理器的執(zhí)行鏈
- 將獲取到Handler找到處理器適配器,進(jìn)行處理(Controller)獲取到ModelAndView/data
- 如果返回的是ModelAndView找到視圖解析器昌罩,返回一個(gè)View
- 渲染視圖
- 返回請(qǐng)求
1.2SpringMVC使用
1.2.2 url-pattern配置及原理剖析
url-pattern作用:web服務(wù)器(tomcat)用攔截匹配規(guī)則的url請(qǐng)求哭懈。
配置方式:
- 可以配置*.do *.action這種, 直接可以攔截url以.do結(jié)尾的url
- 配置成/ 可以攔截除了.jsp之外的所有的url茎用, 包括靜態(tài)資源
- 配置成/* 攔截所有的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)目錄路徑來防止攔截削葱。
- <mvc:resources location="classpath:/" mapping="/resources/"/>**
- <mvc:resources location="/WEB-INF/js/" mapping="/js/"/>**
1.2.3形參的數(shù)據(jù)接受類型
根據(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的繼承圖看看夹攒。懂了吧。
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)聽器作用:
- 可以做一些初始化工作,web應(yīng)?中spring容器啟動(dòng)ContextLoaderListener竭业。
- 監(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)層框架??的配置?件中的。
攔截器基本使用
自定義攔截器需要實(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繼承圖
說明:當(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ī)
通過打斷點(diǎn)觀察到在doDispatcher中ha.handle是執(zhí)行Handler方法,這里的ha類型是
RequestMappingHndlerAdapter瓶堕。
跳轉(zhuǎn)視圖的時(shí)機(jī)
通過打斷點(diǎn)可以查看到跳轉(zhuǎn)視圖是在processDispatcherResult方法隘道。
分析doDispatcher主要流程
doDispatcher主要核心步驟:
- getHandler獲取方法執(zhí)行器鏈handlerExecutionChain
- getHandlerAdapter 獲取處理器適配器
- ha.handle 執(zhí)行controller方法
- 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);
}