只是个人学习笔记,肯定有错漏,请谨慎参考~
SpringMVC(一)
一、理解SpringMVC
HelloWorld
导包
WebContent—>Web-INF—>lib
spring-aop-4.0.0.RELEASE.jar
spring-beans-4.0.0.RELEASE.jar
spring-context-4.0.0.RELEASE.jar
spring-core-4.0.0.RELEASE.jar
spring-expression-4.0.0.RELEASE.jar
commons-logging-1.1.3.jar
spring-web-4.0.0.RELEASE.jar
spring-webmvc-4.0.0.RELEASE.jar
配置
WebContent—>WEB-INF—>web.xml
配置springmvc的前端控制器,指定springmvc.xml配置文件位置
<!-- SpringMVC思想是有一个前端控制器能拦截所有请求,并智能派发;
这个前端控制器是一个servlet;应该在web.xml中配置这个servlet来拦截所有请求
-->
<servlet>
<!-- <servlet-name>设置前端控制器名称-->
<servlet-name>springDispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<!-- contextConfigLocation:指定SpringMVC配置文件位置 -->
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<!-- servlet启动加载,servlet原本是第一次访问创建对象;
load-on-startup:服务器启动的时候创建对象;值越小优先级越高,越先创建对象;
-->
<load-on-startup>1</load-on-startup>
</servlet>
<!-- Map all requests to the DispatcherServlet for handling -->
<servlet-mapping>
<servlet-name>springDispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
src—>com—>springmvc.xml
springmvc框架自身的配置
<!-- 扫描所有组件 -->
<context:component-scan base-package="com.atguigu"></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>
WebContent—>index.jsp
页面请求helloworld
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<a href="hello">helloworld</a>
</body>
</html>
WebContent—>WEB-INF—>pages—>success.jsp
helloworld请求过来之后转发success.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>成功!</h1>
</body>
</html>
告诉SpringMVC哪个是Controller类
@Controller
@Controller
public class MyFirstController {
/**
* @RequestMapping:告诉SpringMVC这个方法处理什么请求;
* /代表从当前项目下开始;处理当前项目下的hello请求
*/
@RequestMapping("/hello")
public String myfirstRequest(){
System.out.println("请求收到了....正在处理中");
//视图解析器自动拼串;
// <property name="prefix" value="/WEB-INF/pages/"></property>
// <property name="suffix" value=".jsp"></property>
// (前缀)/WEB-INF/pages/+返回值(success)+后缀(.jsp)
return "success";
}
}
二、HelloWorld细节
1、 HelloWorld运行流程
1)、客户端点击链接会发送 http://localhost:8080/springmvc/hello 请求
2)、来到tomcat服务器;
3)、SpringMVC的前端控制器收到所有请求;
4)、看请求地址和@RequestMapping标注的哪个匹配,找到使用那个类的哪个方法来处理
5)、前端控制器找到了目标处理器类和目标方法,返回执行目标方法;
6)、方法执行完成以后会有一个返回值;SpringMVC认为这个返回值就是要去的页面地址
7)、拿到方法返回值以后;用视图解析器进行拼串得到完整的页面地址;
8)、拿到页面地址,前端控制器帮我们转发到页面;
2、RequestMapping
* 作用:DispatcherServlet 截获请求后,就通过控制器上 @RequestMapping 提供的映射信息确定请求所对应的处理方法。
* 标记在类上:提供初步的请求映射信息。相对于 WEB 应用的根目录
* 标记在方法上:提供进一步的细分映射信息。相对于标记在类上的 URL
* 类上未标注 @RequestMapping,则方法处标记的URL相对于WEB应用的根目录
*
1)value(默认):处理请求
//处理请求“/haha/handle01”
@RequestMapping("/haha")
@Controller
public class RequestMappingTestController {
@RequestMapping("/handle01")
public String handle01(){
System.out.println("RequestMappingTestController...handle01");
return "success";
}
}
2)method:处理请求http方式
* http协议请求方式:GET、 HEAD、 POST、 PUT、 PATCH、 DELETE、 OPTIONS、TRACE、GET、POST
@RequestMapping(value="/handle02",method=RequestMethod.POST)
public String handle02(){
System.out.println("handle02...");
return "success";
}
3)params:规定请求参数
@RequestMapping(value="/handle03",params={"username!=123","pwd","!age"})
public String handle03(){
System.out.println("handle03....");
return "success";
}
4) headers:规定浏览器请求头
@RequestMapping(value="/handle04",headers={"User-Agent=Mozilla/5.0 (Windows NT 6.3; WOW64; rv:34.0) Gecko/20100101 Firefox/34.0"})
public String handle04(){
System.out.println("handle04....");
return "success";
}
5)consumes
只接受内容类型是哪种的请求,规定请求头中的Content-Type
6)produces
告诉浏览器返回的内容类型是什么,给响应头中加上Content-Type:text/html;charset=utf-8
7) Ant风格url
Ant 风格资源地址支持 3 种匹配符:
1、?:匹配文件名中的一个字符
2、*:匹配文件名中的任意字符
3、**:** 匹配多层路径
@RequestMapping 支持Ant风格的URL:
1、/user/*/createUser
匹配 /user/aaa/createUser、/user/bbb/createUser 等 URL
2、/user/**/createUser
匹配 /user/createUser、/user/aaa/bbb/createUser 等 URL
3、/user/createUser??
匹配 /user/createUseraa、/user/createUserbb 等 URL
8)@PathVariable()获取路径参数
//路径上可以有占位符: 占位符 语法就是可以在任意路径的地方写一个{变量名}
// /user/admin /user/leifengyang
// 路径上的占位符只能占一层路径
@RequestMapping("/user/{id}")
public String pathVariableTest(@PathVariable("id")String id){
System.out.println("路径上的占位符的值"+id);
return "success";
}
3、 指定前端控制器配置文件
不指定配置文件位置
默认到/WEB-INF下找一个名叫
"前端控制器名-servlet.xml"的配置文件
指定配置文件位置
根据<servlet-name>设置前端控制器名称,到/WEB-INF创建一个名叫
"前端控制器名-servlet.xml"的配置文件
<servlet>
<!-- <servlet-name>设置前端控制器名称-->
<servlet-name>springDispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<!-- contextConfigLocation:指定SpringMVC配置文件位置 -->
<param-name>contextConfigLocation</param-name>
<!-- classpath:/WEB-INF
xxx.xml配置文件
-->
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<!-- servlet启动加载,servlet原本是第一次访问创建对象;
load-on-startup:服务器启动的时候创建对象;值越小优先级越高,越先创建对象;
-->
<load-on-startup>1</load-on-startup>
</servlet>
<!-- <servlet-mapping>配置指定前端控制器的拦截需求-->
<servlet-mapping>
<servlet-name>springDispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
4、url-pattern
* /:拦截所有请求,但是不会拦截.jsp;能保证jsp访问正常;
* /*:拦截所有请求,还会拦截.jsp请求;一但拦截jsp页面就不能显示了;
* tomcat服务器的web.xml中有一个DefaultServlet是url-pattern=/,我们的web.xml是继承tomcat服务器的web.xml
* 我们配置前端控制器 url-pattern=/ 是重写了tomcat服务器的url-pattern=/,jsp请求在服务器中找不到匹配的方法处理请求,会来到我们自己的pattern=/找匹配的方法处理请求
* url-pattern=/* 不是重写,是直接就是拦截所有请求,包括.jsp请求
5、Rest风格url
- 配置web.xml
<servlet>
<!-- 设置前端控制器-->
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<!-- 设置前端控制器的拦截条件-->
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
- 声明前端控制器的配置文件springmvc.xml
<context:component-scan base-package="com.atguigu"></context:component-scan>
<!-- 声明URL前后缀-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/pages/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
- 配置filter
SpringMVC中有一个Filter,他可以把普通的请求转化为规定形式的请求
<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>
- 使用
@Controller
public class BookController {
/**
* 处理查询图书请求
* @param id
* @return
*/
@RequestMapping(value="/book/{bid}",method=RequestMethod.GET)
public String getBook(@PathVariable("bid")Integer id) {
System.out.println("查询到了"+id+"号图书");
return "success";
}
/**
* 图书删除
* @param id
* @return
*/
@RequestMapping(value="/book/{bid}",method=RequestMethod.DELETE)
public String deleteBook(@PathVariable("bid")Integer id) {
System.out.println("删除了"+id+"号图书");
return "success";
}
/**
* 图书更新
* @return
*/
@RequestMapping(value="/book/{bid}",method=RequestMethod.PUT)
public String updateBook(@PathVariable("bid")Integer id) {
System.out.println("更新了"+id+"号图书");
return "success";
}
@RequestMapping(value="/book",method=RequestMethod.POST)
public String addBook() {
System.out.println("添加了新的图书");
return "success";
}
}
- 测试
1、创建一个post类型的表单
2、表单项中携带一个_method的参数
3、这个_method的值就是DELETE、PUT
<!-- 发送DELETE请求 -->
<form action="book/1" method="post">
<input name="_method" value="delete"/>
<input type="submit" value="删除1号图书"/>
</form><br/>
<!-- 发送PUT请求 -->
<form action="book/1" method="post">
<input name="_method" value="put"/>
<input type="submit" value="更新1号图书"/>
</form><br/>
- 高版本tomcat不支持rest的put、delete
springmvc返回给tomcat的请求方法默认是客户端原来的请求方式,tomcat8以上不支持put、delete
解决方法:isErrorPage=“true”
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" isErrorPage="true" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>你成功了,6666</h1>
</body>
</html>
6、Springmvc获取请求带来的信息
1)默认方式
直接给方法入参上写一个和请求参数名相同的变量。
这个变量就来接收请求参数的值,带值:有值,没带:null
@RequestMapping("/hello")
public String handle01(String username) {
System.out.println("这个变量值是:" + username);
return "success";
}
<a href="handle01?username=tomcat">handle01</a><br/>
2)@RequestParam
- @RequestParam:获取请求参数的;参数默认是必须带的
@RequestParam("user")String username username = request.getParameter("user")
- @RequestParam的属性
- value:指定要获取的参数的key ,value = “user”,获取user的值
- required:这个参数是否必须的,required = false,这个参数不是必须的
- defaultValue:默认值。没带默认是null;
@RequestMapping("/handle01")
public String handle02(
@RequestParam(value = "user", required = false, defaultValue = "你没带") String username,
System.out.println("这个变量的值:" + username);
return "success";
}
3)@RequestHeader
- 获取请求头中某个key的值,参数默认是必须带的
request.getHeader("User-Agent")
@RequestMapping("/handle01")
public String handle02(
@RequestHeader(value = "AHAHA", required = false, defaultValue = "她也没带") String userAgent,
System.out.println("请求头中浏览器的信息:" + userAgent);
return "success";
}
- RequestHeader的属性
和RequestParam一样
4)@CookieValue
- 获取某个cookie的值,参数默认是必须带的
@RequestMapping("/handle01")
public String handle02(
@CookieValue(value="JSESSIONID",required=false)String jid) {
System.out.println("cookie中的jid的值"+jid);
return "success";
}
- CookieValue属性
和和RequestParam一样
5)Springmvc自动封装POJO 属性
- Spring MVC 会按请求参数名和 POJO(自定义对象) 属性名进行自动匹配,自动为该对象填充属性值,支持级联属性。
客户端带来的信息
<form action="book" method="post">
书名:<input type="text" name="bookName"/><br/>
作者:<input type="text" name="author"/><br/>
价格:<input type="text" name="price"/><br/>
库存:<input type="text" name="stock"/><br/>
销量:<input type="text" name="sales"/><br/>
<hr/>
省:<input type="text" name="address.province"/>
市:<input type="text" name="address.city"/>
街道:<input type="text" name="address.street"/><br/>
<input type="submit"/>
</form>
book对象有无参构造器、getter/setter方法、toString()方法
public class Book {
private String bookName;
private String author;
private Double price;
private Integer stock;
private Integer sales;
private Address address; //级联属性
//一定有无参构造器
/**
* @return the bookName
*/
public String getBookName() {
return bookName;
}
/**
* @return the address
*/
public Address getAddress() {
return address;
}
/**
* @param address the address to set
*/
public void setAddress(Address address) {
this.address = address;
}
/**
* @param bookName the bookName to set
*/
public void setBookName(String bookName) {
this.bookName = bookName;
}
/**
* @return the author
*/
public String getAuthor() {
return author;
}
/**
* @param author the author to set
*/
public void setAuthor(String author) {
this.author = author;
}
/**
* @return the price
*/
public Double getPrice() {
return price;
}
/**
* @param price the price to set
*/
public void setPrice(Double price) {
this.price = price;
}
/**
* @return the stock
*/
public Integer getStock() {
return stock;
}
/**
* @param stock the stock to set
*/
public void setStock(Integer stock) {
this.stock = stock;
}
/**
* @return the sales
*/
public Integer getSales() {
return sales;
}
/**
* @param sales the sales to set
*/
public void setSales(Integer sales) {
this.sales = sales;
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "Book [bookName=" + bookName + ", author=" + author + ", price="
+ price + ", stock=" + stock + ", sales=" + sales
+ ", address=" + address + "]";
}
}
级联属性
Address对象有无参构造器、getter/setter方法、toString()方法
public class Address {
private String province;
private String city;
private String street;
/**
* @return the province
*/
public String getProvince() {
return province;
}
/**
* @param province the province to set
*/
public void setProvince(String province) {
this.province = province;
}
/**
* @return the city
*/
public String getCity() {
return city;
}
/**
* @param city the city to set
*/
public void setCity(String city) {
this.city = city;
}
/**
* @return the street
*/
public String getStreet() {
return street;
}
/**
* @param street the street to set
*/
public void setStreet(String street) {
this.street = street;
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "Address [province=" + province + ", city=" + city + ", street="
+ street + "]";
}
}
自动封装book属性
@RequestMapping("/book")
public String addBook(Book book){
System.out.println("我要保存的图书:"+book);
return "success";
}
6)传入原生api
* SpringMVC可以直接在参数上写原生API;
*
* HttpServletRequest
* HttpServletResponse
* HttpSession
*
* java.security.Principal
* Locale:国际化有关的区域信息对象
* InputStream:
* ServletInputStream inputStream = request.getInputStream();
* OutputStream:
* ServletOutputStream outputStream = response.getOutputStream();
* Reader:
* BufferedReader reader = request.getReader();
* Writer:
* PrintWriter writer = response.getWriter();
使用
@RequestMapping("/handle03")
public String handle03(HttpSession session,
HttpServletRequest request,HttpServletResponse response) throws IOException {
request.setAttribute("reqParam", "我是请求域中的");
session.setAttribute("sessionParam", "额我是Session域中的");
return "success";
}
测试
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>成功!</h1>
<!-- huoqu -->
请求:${requestScope.reqParam }<br/>
session:${sessionScope.sessionParam }<br/>
</body>
</html>
7、字符编码问题
- 1)请求乱码
- GET请求:改tomcat服务器的server.xml
在8080端口处URIEncoding="UTF-8"
- PUT请求:
-
方法一:在第一次获取请求参数之前设置,
request.setCharacterEncoding("UTF-8");
-
方法二:自己写一个filter;SpringMVC有这个filter类
-
- GET请求:改tomcat服务器的server.xml
<!-- 配置一个字符编码的Filter;一定注意:字符编码filter一般都在其他Filter之前; -->
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<!-- encoding:指定解决POST请求乱码 -->
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<!-- forceEncoding:顺手解决响应乱码;response.setCharacterEncoding(this.encoding); -->
<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>
<!-- 支持Rest风格的Filter -->
<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>
<!-- 使用SpringMVC前端控制器写完就直接写字符编码过滤器;
Tomcat一装上,上手就是server.xml的8080处添加URIEncoding="UTF-8"
-->
- 2)响应乱码
response.setContentType("text/html;charset=utf-8")
8、数据输出
1)传入model、map、modelmap
- success.jsp
<h1>Hello</h1>
pageContext:${pageScope.msg }<br/>
reuest:${requestScope.msg }<br/>
session:${sessionScope.msg }-${sessionScope.haha}<br/>
application:${applicationScope.msg }<br/>
</body>
- model、map、modelmap传的数据都在请求域中
- OutputController
@Controller
public class OutputController {
@RequestMapping("/handle01")
public String handle01(Map<String, Object> map){
map.put("msg", "你好");
map.put("haha", "哈哈哈");
System.out.println("map的类型:"+map.getClass());
return "success";
}
/**
* Model:一个接口
* @param model
* @return
*/
@RequestMapping("/handle02")
public String handle02(Model model){
model.addAttribute("msg", "你好坏!");
model.addAttribute("haha", 18);
System.out.println("model的类型:"+model.getClass());
return "success";
}
@RequestMapping("/handle03")
public String handle03(ModelMap modelMap){
modelMap.addAttribute("msg", "你好棒!");
System.out.println("modelmap的类型:"+modelMap.getClass());
return "success";
}
- index.html
<a href="hello">hello</a>
<!-- SpringMVC如何给页面携带数据过来; --><br/>
<a href="handle01">handle01</a><br/>
<a href="handle02">handle02</a><br/>
<a href="handle03">handle03</a><br/>
- model、map、modelmap的关系
* Map,Model,ModelMap:最终都是BindingAwareModelMap在工作;
* 相当于给BindingAwareModelMap中保存的东西都会被放在请求域中;
*
* Map(interface(jdk)) Model(interface(spring))
* || //
* || //
* \/ //
* ModelMap(class) //
* \\ //
* \\ //
* ExtendedModelMap
* ||
* \/
* BindingAwareModelMap
- ModelAndView
* 方法的返回值不单止String类型,还可以变为ModelAndView类型
* ModelAndView既包含视图信息(页面地址)也包含模型数据(给页面带的数据)
* ModelAndView的数据放在请求域(request、session、application)中
@RequestMapping("/handle04")
public ModelAndView handle04(){
//之前的返回值"success"就叫视图名;视图名视图解析器是会帮我们最终拼串得到页面的真实地址;
//ModelAndView mv = new ModelAndView("success");
ModelAndView mv = new ModelAndView();
mv.setViewName("success");
mv.addObject("msg", "你好哦!");
return mv;
}
- @SessionAttributes(了解)
* SpringMVC用注解@SessionAttributes(只能标在类上)可以临时给Session域中保存数据,即request和session都放一份数据
*
* @SessionAttributes(value="msg"):value指定保存数据时要给session中放的数据的key
* value={"msg"}:只要保存的是这种key的数据,给Session中放一份
* types={String.class}:只要保存的是这种类型的数据,给Session中也放一份
*
* @SessionAttributes可能会引发异常,给session中放数据推荐使用原生API
SessionAttributes(value={"msg"},types={String.class})
@Controller
public class OutputController {}
2) 全字段更新引发的问题及解决思想
- 全字段更新引发的问题
1、客户端有些字段不需要更新,仅更新需要的字段
2、Springmvc默认是new一个dao,全部字段都更新
3、Springmvc自动装配dao的字段,客户端传过来的字段自动更新,客户端没传过来的字段改为null,如此便会引发问题
- 解决思想
1、Springmvc不用new一个dao,直接查询数据库的dao
2、客户端传来的字段就更新数据库,没传的字段不用更改,用原数据库的
3、@ModelAttribute就是解决全字段更新问题的方法
- @ModelAttribute
//目标方法
@RequestMapping("/updateBook")
public String updateBook(@RequestParam(value="author")String author,
Map<String, Object> model,
HttpServletRequest request,
@ModelAttribute("haha")Book book //取出key为haha的book对象
){
o2 = model;
b2 = book;
Object haha = model.get("haha");
//System.out.println("传入的model:"+model.getClass());
System.out.println("o1==o2?"+(o1 == o2));
System.out.println("b1==b2?"+(b1 == b2)+"-->"+(b2 == haha));
System.out.println("页面要提交过来的图书信息:"+book);
return "success";
}
//@ModelAttribute标注的方法提前于目标方法运行
@ModelAttribute
public void hahaMyModelAttribute(Map<String, Object> map){
Book book = new Book(100, "西游记", "吴承恩", 98, 10, 98.98);//只是假设book对象是的数据是这样,不要纠结为什么在这里设置book对象的属性
System.out.println("数据库中查到的图书信息是:"+book);
map.put("haha", book); //保存了一个key为haha的book对象
b1 = book;
o1 = map;
System.out.println("modelAttribute方法...查询了图书并给你保存起来了...他用的map的类型:"+map.getClass());
}
9、SpringMVC源码
(一)前端控制器架构
- DispatcherServlet
(二)请求的处理流程
//处理请求
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)doDispatcher()
DispatcherServlet调用doDispatcher()方法
2)getHandler()找请求处理器
找到对应的请求处理器
3)getHandlerAdapter()找适配器HandlerAdapter
找到匹配的适配器HandlerAdapter
4)执行目标方法,返回一个ModelAndView对象mv
适配器执行目标方法
mv = ha.handle(processedRequest, response, mappedHandler.getHandler())
适配器里执行 invokeHandlerMethod
return invokeHandlerMethod(request, response, handler);
5)返回页面
根据方法最终执行完成后封装的ModelAndView,转发到对应页面
(三)getHandler()获取处理器细节
mappedHandler = getHandler(processedRequest);
1)getHandler()会返回目标处理器类的执行链mappedHandler
2)processedRequest是apache原生的请求对象包含有请求地址
processedRequest里有请求地址
3)getHandler()方法内部代码===>for循环HandlerMapping
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;
}
HandlerMapping:处理器映射===>保存了每一个处理器能处理哪些请求的映射信息
BeanNameUrlHandlerMapping==>保存基于配置的映射信息
DefaultAnnotationHandlerMapping==>保存基于注解的映射信息
handlerMap:ioc容器启动创建Controller对象的时候扫描每个处理器都能处理什么请求,保存在HandlerMapping的handlerMap属性中,下次请求过来,就看哪个HandlerMapping中有这个请求映射信息
(四)getHandlerAdapter()获取适配器细节
适配器的种类
- HttpRequestHandlerAdapter
- SimpleControllerHandlerAdapter
- AnnotationMethodHandlerAdapter
适配器作用:执行目标方法
//getHandlerAdapter()方法内部代码
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");
}
遍历handlerAdapters,看哪种适配器支持目标请求处理器
supports(handler)方法内部判断适配器类型
supports(handler)判断目标控制器是否可执行
第一步:判断这个控制器是否有处理方法
第二步:控制器有处理方法就返回支持注解类型适配器,如果只有控制器HelloController,没有处理方法/hello,也是不返回支持注解类型适配器的
(五)九大组件
/** 文件上传解析器*/
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;
九大组件初始化的地方
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
(六)获取invokeHandlerMethod
mv = ha.handle(processedRequest, response, mappedHandler.getHandler())
||
||
\/
invokeHandlerMethod()
//目标方法:updateBook
@Controller
public class HelloController {
public String updateBook(@RequestParam(value="author")String author,
Map<String, Object> model,
HttpServletRequest request,
@ModelAttribute("haha")Book book
)
}
1)sessionAnnotatedClassesCache
检查目标方法是否包含@SessionAttribute注解
2)返回invokeHandlerMethod
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);
//创建了一个,隐含模型implicitModel
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;
}
(七)invokeHandlerMethod执行细节
//invokeHandlerMethod源码:
public final 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);
}
//findAnnotation找到ModelAttribute
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方法的返回值也放在隐含模型中
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;
}
}
第一步:根据handleMethod获取handleMethodToInvoke(找到要执行的目标方法updateBook)
第二步:检查@SessionAttribute注解是否有value值,有的话放到隐含模型 implicitModel
第三步:获取所有标注@ModelAttribute注解的方法
第四步:确定@ModelAttribute注解标注的方法
确定@ModelAttribute注解标注的方法执行时要使用的每一个参数的值
//确定方法运行时使用的每一个参数的值
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 {
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;
}
(1)创建一个数组,长度和参数个数一样,用于保存参数
(2)有注解参数就保存起来
(3)没有注解的情况
- 解析普通参数resolveCommonArgument,就是确定当前的参数是否是原生API
//没有找到注解的情况;
if (annotationsFound == 0) {
//解析普通参数,会进入resolveStandardArgument(解析标准参数)
Object argValue = resolveCommonArgument(methodParam, webRequest);
//是否原生api
if (argValue != WebArgumentResolver.UNRESOLVED) {
args[i] = argValue;
}
//是否有默认值
else if (defaultValue != null) {
args[i] = resolveDefaultValue(defaultValue);
}
//否是Model或Map旗下,如果是将之前创建的隐含模型直接赋值给这个参数
else {
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 = "";
}
}
}
- 解析普通参数会进入解析标准参数resolveStandardArgument
@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);
}
第五步:返回@ModerAttribute标注的方法运行的返回值
(1) 要运行的方法是hahaMyModelAttribute()
(2) 运行attrituteMethodInvoke,@moderAttribute标注的方法就会提前运行,运行结果会有一个返回值
(3) 确定attrituteMethodInvok的返回值的类型
方法上标注的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}
第六步:执行目标方法updateBook()
- 执行目标方法之前
- 提前运行@ModelAttrubute标注的方法
- 提前把@ModelAttrubute标注的方法运行的返回值放到隐含模型中
- 调用resolveHanderlerArgument再次解析目标方法的参数的值
- 执行目标方法handlerMethodToInvoke()---->updateBook
(八)resolveHanderlerArgument确认参数细节
Object[] args = resolveHandlerArguments(attributeMethodToInvoke, handler, webRequest, implicitModel);
||
|| step into
\/
private Object[] resolveHandlerArguments(Method handlerMethod, Object handler,
NativeWebRequest webRequest, ExtendedModelMap implicitModel) throws Exception {
Class<?>[] paramTypes = handlerMethod.getParameterTypes();
//创建了一个和参数个数一样多的数组,会用来保存每一个参数的值
Object[] args = new Object[paramTypes.length];
(1)Object[] args = new Object[paramTypes.length]获取所有的参数(参数列表有4个参数)
||
||
\/
@RequestMapping("/updateBook")
public String updateBook(@RequestParam(value="author")String author,
Map<String, Object> model,
HttpServletRequest request,
@ModelAttribute("haha")Book book
)
(2)遍历参数数组,判断每个参数的类型,确认值
||
||
\/
(3)找到参数标注的所有注解(第二个参数)
Annotation[] paramAnns = methodParam.getParameterAnnotations()
整个保存注解信息的for循环,有注解就进入循环,没有注解就跳过
(4)异常
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);
}
(5)没有找到注解
if (annotationsFound == 0) {
//解析普通参数
Object argValue = resolveCommonArgument(methodParam, webRequest);
//会进入resolveStandardArgument(解析标准参数)
解析普通参数resolveCommonArgument
||
|| step into
\/
解析标准参数
@Override
protected Object resolveStandardArgument(Class<?> parameterType, NativeWebRequest webRequest) throws Exception {
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
||
|| 解析标准参数里解析自定义参数(没有就跳过)
\/
|| 拿到参数类型,进入解析标准参数
||
\/
|| 解析标准参数里判断是否原生api参数,不是就跳出
||
\/
|| 解析标准参数里判断是否原生api参数,不是就跳出回到annotationsFound == 0
|| annotationsFound == 0继续判断参数是否有默认值、是否model/map类型
||是model/map类型就把隐含模型赋值过来 args[i] = implicitModel
\/
|| 如果参数不是model/map类型
||annotationsFound == 0继续判断参数是否SessionStatus、HttpEntity、Error类型
\/
||
||判断是否简单类型(基本类型Integer、String等)
\/
(6)判断Request对象(原生api)参数 (第三个参数)
解析普通参数resolveCommonArgument
||
|| step into
\/
解析标准参数
(7)确定POJO自定义类型参数(第四个参数)
标了注解:
保存时哪个注解的详细信息;
如果参数有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赋值"";
判断隐含模型中是否包含key
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;
}
//确定自定义类型参数的值;还要将请求中的每一个参数赋值给这个对象
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方法将请求里的数据和对象绑定
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;
//确定值细节
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;
确定方法每个参数的值;
1)、标注解:保存注解的信息;最终得到这个注解应该对应解析的值;
2)、没标注解:
1)、看是否是原生API;
2)、看是否是Model或者是Map,xxxx
3)、都不是,看是否是简单类型;paramName;
4)、给attrName赋值;attrName(参数标了@ModelAttribute("")就是指定的,没标就是"")
确定自定义类型参数:
1)、attrName使用参数的类型首字母小写;或者使用之前@ModelAttribute("")的值
2)、先看隐含模型中有每个这个attrName作为key对应的值;如果有就从隐含模型中获取并赋值
3)、看是否是@SessionAttributes(value="haha");标注的属性,如果是从session中拿;
如果拿不到就会抛异常;
4)、不是@SessionAttributes标注的,利用反射创建一个对象;
5)、拿到之前创建好的对象,使用数据绑定器(WebDataBinder)将请求中的每个数据绑定到这个对象中;
(8)确认参数总结
确定方法每个参数的值;
1)、标注解:保存注解的信息;最终得到这个注解应该对应解析的值;
2)、没标注解:
1)、看是否是原生API;
2)、看是否是Model或者是Map,xxxx
3)、都不是,看是否是简单类型;paramName;
4)、给attrName赋值;attrName(参数标了@ModelAttribute("")就是指定的,没标就是"")
确定自定义类型参数:
1)、attrName使用参数的类型首字母小写;或者使用之前@ModelAttribute("")的值
2)、先看隐含模型中有每个这个attrName作为key对应的值;如果有就从隐含模型中获取并赋值
3)、看是否是@SessionAttributes(value="haha");标注的属性,如果是从session中拿;
如果拿不到就会抛异常;
4)、不是@SessionAttributes标注的,利用反射创建一个对象;
5)、拿到之前创建好的对象,使用数据绑定器(WebDataBinder)将请求中的每个数据绑定到这个对象中;
(9)确认参数流程源码
确定方法运行时使用的每一个参数的值
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) {
//整个保存注解信息的for循环,有注解就进入循环,没有注解就跳过
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 {
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;
}