SpringMVC(一)

只是个人学习笔记,肯定有错漏,请谨慎参考~

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类

<!-- 配置一个字符编码的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、客户端有些字段不需要更新,仅更新需要的字段
2Springmvc默认是new一个dao,全部字段都更新
3Springmvc自动装配dao的字段,客户端传过来的字段自动更新,客户端没传过来的字段改为null,如此便会引发问题
  • 解决思想
1Springmvc不用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)、再看是否是其他类型的比如SessionStatusHttpEntityErrors
           4)、再看是否简单类型的属性;比如是否IntegerString,基本类型
                    如果是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;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值