![](https://img2020.cnblogs.com/blog/1938121/202005/1938121-20200527170332487-1768507356.png)
(ps:红色的字是表示学到的位置)
在SpringMVC中,MVC三部分的作用如下:
执行流程
具体到执行流程上,SpringMVC主要依赖了HandlerMapping 处理器映射器、HandlerAdapter 处理器适配器以及 ViewReslover 视图解析器三个组件。
SpringMVC执行流程示意图如下:
![](https://img2020.cnblogs.com/blog/1938121/202005/1938121-20200527171458602-823137755.png)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <!--配置 DispatcherServlet--> <servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!--配置 DispatcherServlet的一个初始化参数:配置springMVC 配置文件的位置和名称--> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </init-param> <!--也可以不通过contextConfigLocation来配文件,有默认初始化。 公式为:/WEB-INF/<servlet-name>-servlet.xml 本例中为:/WEB-INF/dispatcher-servlet.xml 如果使用默认方法,那么写对配置文件的名字就可以不需要配置上面的初始化代码了。 --> <!--值大于0时,在启动时就加载这个servlet,值是整数,1的优先级最高--> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <!--index.jsp发送的请求被拦截,再自动匹配@RequestMapping注解--> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!--配置自动扫描的包--> <context:component-scan base-package="hello1"> </context:component-scan> <!--配置视图解析器:如何把handler方法返回值(本例中就是"success")解析为实际的物理视图--> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/views/"> </property> <property name="suffix" value=".jsp"> </property> </bean> </beans>
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
package hello1; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; /** * Created by Zhuxiang on 2020/5/27. */ @Controller public class HelloWorld { /** * 1.使用@RequestMapping注解来映射请求的url * 2.返回值会通过视图解析器为实际的物理视图,对于InternalResourceViewResolver视图解析器, * 会做如下的解析:通过prefix + returnVal +后缀得到物理视图,然后做转发操作。 * 本例中会转发到 /web-int/views/success.jsp */ @RequestMapping("/hello1") public String helloWorld(){ System.out.println("helloWorld!"); return "success"; } }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>$Title$</title> </head> <body> <a href="hello1">hello</a> </body> </html>
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> success page </body> </html>
按顺序看一遍,然后理一遍思路。index.jsp里一个a标签发出请求,由servlet拦截,通过DispatcherServlet连接到applicationContext.xml里。配置好注解和视图解析器,(HelloWorld类)再通过@RequestMapping注解,映射请求的url,通过视图解析器转发到相应的jsp里。
![](https://img2020.cnblogs.com/blog/1938121/202005/1938121-20200527184037551-654394613.png)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
package hello1;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* Created by Zhuxiang on 2020/5/27.
*/
@RequestMapping("springmvc")
@Controller
public class SpringMVCTest {
public static final String SUCCESS="success";
/**
* 1.@RequestMapping除了修饰方法还可以修饰类。
* 2:
* 1)类定义处:提供初步的请求映射信息。相对于 WEB 应用的根目录
* 2) 方法处:提供进一步的细分映射信息。相对于类定义处的 URL。若
* 类定义处未标注 @RequestMapping,则方法处标记的 URL 相对于
* WEB 应用的根目录
* @return
*/
@RequestMapping("/testRequestMapping")
public String testRequestMapping(){
System.out.println("testRequestMapping");
return SUCCESS;
}
}
index.jsp里写 <a href="springmvc/testRequestMapping">testRequestMapping</a>
点击进入成功页面。
还是这个类,继续test。
![](https://img2020.cnblogs.com/blog/1938121/202005/1938121-20200527194646890-779547368.png)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/** * 使用method属性来指定请求方式(重要) * @return */ @RequestMapping(value = "testMethod",method = RequestMethod.POST) public String testMethod(){ System.out.println(); return SUCCESS; }
<input type="submit" value="submit">
</form>
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/** * 了解就行用的不多:可以使用params 和 headers 来精确映射请求,params和 headers支持简单的表达式。 * @return */ @RequestMapping(value = "testParamsAndHeaders",params = {"username,age!=10"},headers = {"Accept-Language=zh-CN,zh;q=0.9"}) public String testParamsAndHeaders(){ System.out.println("testParamsAndHeaders"); return SUCCESS; }
index.jsp里写上
<%-- 用params 和 headers--%>
<a href="springmvc/testParamsAndHeaders?username=小明&age=11">testParamsAndHeaders</a>
<br/>
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/** * 使用@PathVariable 可以来映射URl中的占位符到目标方法的参数中。(重要) * @return */ @RequestMapping("/testPathVariable/{id}") public String testPathVariable(@PathVariable("id") Integer id){ System.out.println("testPathVariable"+id); return SUCCESS; } /** * 使用通配符,*这个地方可以放无限多字符(不常用,了解) * @return */ @RequestMapping("/testAntPath/*/abc") public String testAntPath(){ System.out.println("/testAntPath/*/abc"); return SUCCESS; } /** * 了解就行用的不多:可以使用params 和 headers 来精确映射请求,params和 headers支持简单的表达式。 * @return */ @RequestMapping(value = "testParamsAndHeaders",params = {"username,age!=10"},headers = {"Accept-Language=zh-CN,zh;q=0.9"}) public String testParamsAndHeaders(){ System.out.println("testParamsAndHeaders"); return SUCCESS; }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<%-- 使用@PathVariable占位符--%> <a href="springmvc/testPathVariable/1">testPathVariable</a> <%-- 通配符--%> <a href="springmvc/testAntPath/1234abcd/abc">testAntPath</a> <%-- 用params 和 headers--%> <a href="springmvc/testParamsAndHeaders?username=小明&age=11">testParamsAndHeaders</a> <br/> <form action="springmvc/testMethod" method="post"> <input type="submit" value="submit"> </form>
rest风格了解一下。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<!--配置org.springframework.web.filter.HiddenHttpMethodFilter:可以把 post 请求转为 delete 或 put 请求--> <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>
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/**
* rest风格的url.(了解一下)
* 以crud为例: 比较
* 增:/order post
* 改:/order/1 put update?id=1
* 查:/order/1 get get?id=1
* 删:/order/1 delete delete?id=1
* 如何发送 put 请求和 delete 请求呢?
* 1.需要配置HiddenHttpMethodFilter
* 2.需要发送post请求
* 3.需要在发送post请求时携带一个name="_method"的隐藏域,值为 delete 或 put
*
* 如何得到id呢?用@PathVariable注解
*
* @param id
* @return
*/
@ResponseBody
@RequestMapping(value = "/testRest/{id}",method = RequestMethod.PUT)
public String testRest4(@PathVariable("id") Integer id){
System.out.println("testRest PUT " +id);
return SUCCESS;
}
@ResponseBody
@RequestMapping(value = "/testRest/{id}",method = RequestMethod.DELETE)
public String testRest3(@PathVariable("id") Integer id){
System.out.println("testRest DELETE " +id);
return SUCCESS;
}
@RequestMapping(value = "/testRest",method = RequestMethod.POST)
public String testRest2(){
System.out.println("testRest POST ");
return SUCCESS;
}
@RequestMapping(value = "/testRest/{id}",method = RequestMethod.GET)
public String testRest1(@PathVariable("id") Integer id){
System.out.println("testRest GET " +id);
return SUCCESS;
}
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<form action="springmvc/testRest/1" method="post"> <input type="hidden" name="_method" value="PUT"> <input type="submit" value="testRest PUT"> </form> <br/> <form action="springmvc/testRest/1" method="post"> <input type="hidden" name="_method" value="DELETE"> <input type="submit" value="testRest DELETE"> </form> <br/> <form action="springmvc/testRest" method="post"> <input type="submit" value="testRest POST"> </form> <br/> <a href="springmvc/TestRest/1"></a> <br/>
------------------------------------------------------
@RequestParam(重点看)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/** * 了解就行 * @CookieValue作用是映射一个cookie值。 * 属性同RequestParam */ @RequestMapping("/testCookieValue") public String testCookieValue(@CookieValue(value = "JSESSIONID") String session){ System.out.println("testCookieValue "+session); return SUCCESS; } /** * 了解就行 * @RequestHeader作用是映射请求头信息 * 用法同RequestParam */ @RequestMapping("/testRequestHeader") public String testRequestHeader(@RequestHeader(value = "Accept-Language") String al){ System.out.println("testRequestHeader "+al); return SUCCESS; } /** *@RequestParam来映射请求参数。(重要) * value 值即请求参数的参数名 * required 该参数是否必须,默认是true * defaultValue 请求参数的默认值 */ @RequestMapping(value = "/testRequestParam") public String testRequestParam(@RequestParam(value = "username") String un,@RequestParam(value = "age",required = false) Integer age){ System.out.println("testRequestParam username="+un+" age="+age); return SUCCESS; }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<a href="springmvc/testCookieValue">testCookieValue</a> <a href="springmvc/testRequestHeader">testRequestHeader</a> <a href="springmvc/testRequestParam?username=小明&age=13">testRequestParam</a>
Spring MVC 会按请求参数名和 POJO 属性名进行自动匹 配,自动为该对象填充属性值。支持级联属性。(重要)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<form action="springmvc/testPOJO" method="get"> username:<input type="text" name="username"> password:<input type="password" name="password"> city:<input type="text" name="address.city"> province:<input type="text" name="address.province"> <input type="submit" value="submit"> </form>
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
@RequestMapping("/testPOJO") public String testPOJO(User user){ System.out.println(user); return SUCCESS; }
-----------------------------------------------------------------------------------
MVC 的 Handler 方法可以接受 哪些 Servlet API 类型的参数
• HttpServletRequest
• HttpServletResponse
• HttpSession
• java.security.Principal
• Locale
• InputStream
• OutputStream
• Reader
• Writer
在上面的SpringMVCTest这个handle里加上下面代码(以HttpServletRequest,HttpServletResponse为例)
@RequestMapping("/testServletAPI") public String testServletAPI(HttpServletRequest request, HttpServletResponse response){ System.out.println(request+" "+response); return SUCCESS; }
jsp页面里加上
<a href="springmvc/testServletAPI">testServletAPI</a>
--------------------------------------
![](https://img2020.cnblogs.com/blog/1938121/202005/1938121-20200528173325199-307447168.png)
下面是四种方法:
第一种方法:ModelAndView
SpringMVCTest这个handle里加上下面代码
/** * 返回值可以是 ModelAndView 类型。 * 其中可以包含视图和模型信息。 * springMVC 会把 ModelAndView 的 model中数据放入到 request 域对象中。可以查看源码。 * @return */ @RequestMapping("/testModelAndView") public ModelAndView testModelAndView(){ String viewName="success"; ModelAndView mav = new ModelAndView(viewName); //添加模型数据到 ModelAndView 中。 mav.addObject("time",new Date()); return mav; }
<a href="springmvc/testModelAndView">testModelAndView</a>
-------------------------------
第二种方法: Map 和 Model
SpringMVCTest这个handle里加上下面代码
/**常用,重点 * 目标方法可以添加 Map 类型(实际上也可以是 model 类型 或 ModelMap 类型(了解一下))的参数 * @param map * @return */ @RequestMapping("/testMap") public String testMap(Map<String,Object> map){ //实际传入的是 org.springframework.validation.support.BindingAwareModelMap System.out.println(map.getClass().getName()); map.put("names", Arrays.asList("Tom","Jerry","Mike")); return SUCCESS; }
<a href="springmvc/testMap">testMap</a>
success.jsp加上:
/** * @SessionAttributes注解 * 除了可以通过属性名指定需要放到会话中的属性外,(如:value = {"user"}) * 还可以通过模型属性的对象类型指定哪些模型属性需要放到会话中。(如:types = {String.class}) * 所以,下面的结果就是,叫 user的类和 值是string类型的类都会到 Session域里。 * * 注意:该注解只能放在类的上面,而不能修饰方法。 */ @SessionAttributes(value = {"user"},types = {String.class}) @RequestMapping("springmvc") @Controller public class SpringMVCTest { public static final String SUCCESS="success"; @RequestMapping("/testSessionAttributes") public String testSessionAttributes(Map<String,Object> map){ User user = new User("xiaoming","123"); map.put("user",user); map.put("spring","atguigu"); return SUCCESS; }
session user :${sessionScope.user}
</br>
string:${sessionScope.spring}
</br>
![](https://img2020.cnblogs.com/blog/1938121/202005/1938121-20200528225542039-1955479477.png)
![](https://img2020.cnblogs.com/blog/1938121/202005/1938121-20200528230019559-1022417426.png)
-----------------------------------------------------
/** * 由@ModelAttribute 标记的方法,会在每个目标方法执行之前被springmvc调用! * */ @ModelAttribute public void getUser(@RequestParam(value = "id",required = false) Integer id, Map<String,Object> map){ if(id!=null){ User tom = new User("Tom", "123"); System.out.println("从数据库中获取一个对象:"+tom); map.put("user",tom); } } /** * 运行流程: * 1.执行 @ModelAttribute 注解修饰的方法:从数据库中取出对象,把对象放入到了map中,键为:user * 2.SpringMVC 从 Map中取出 User对象,并把表单的请求参数赋给该 User对象的对应属性。 * 3.SpringMVC 把上述对象传入目标方法的参数。 * * 注意:在@ModelAttribute修饰的方法中,放入到 map 时的键需要和 * 目标方法入参类型的第一个字母小写的字符串一致。 */ @RequestMapping("/testModelAttribute") public String testModelAttribute(User user){ System.out.println("修改 " +user); return SUCCESS; }
<!-- 模型修改操作: 1.原始数据为:1,Tom,123 2.密码不能被修改。 3.表单回显,模拟操作直接在表单填写对应的属性值。 --> <form action="springmvc/testModelAttribute" method="post"> <input type="hidden" name="id" value="1"> username:<input type="text" name="username" value="Tom"> <input type="submit" value="submit"> </form>
结果:
从数据库中获取一个对象:User{username='Tom', password='123'}
修改 User{username='Tom', password='123'}
---------------------------------------------------------------
搞一下源码,主要看一下思路。(了解一下)
//看了源码之后对上面内容的补充 /** * 1.(重要!)由@ModelAttribute 标记的方法,会在每个目标方法执行之前被springmvc调用! * 2. (了解)@ModelAttribute 注解也可以来修饰目标方法 POJO 类型的入参, 其 value 属性值有如下的作用: * 1). SpringMVC 会使用 value 属性值在 implicitModel 中查找对应的对象, 若存在则会直接传入到目标方法的入参中. * 2). SpringMVC 会以 value 为 key, POJO 类型的对象为 value, 存入到 request 中. */ //看了源码之后对上面内容的补充 /** * 运行流程:(使用为目的) * 1.执行 @ModelAttribute 注解修饰的方法:从数据库中取出对象,把对象放入到了map中,键为:user * 2.SpringMVC 从 Map中取出 User对象,并把表单的请求参数赋给该 User对象的对应属性。 * 3.SpringMVC 把上述对象传入目标方法的参数。 * * 注意:在@ModelAttribute修饰的方法中,放入到 map 时的键需要和 * 目标方法入参类型的第一个字母小写的字符串一致。 * * SpringMVC 确定目标方法 POJO 类型入参的过程(结论) * 1. 确定一个 key: * 1). 若目标方法的 POJO 类型的参数木有使用 @ModelAttribute 作为修饰, 则 key 为 POJO 类名第一个字母的小写 * 2). 若使用了 @ModelAttribute 来修饰, 则 key 为 @ModelAttribute 注解的 value 属性值. * 2. 在 implicitModel 中查找 key 对应的对象, 若存在, 则作为入参传入 * 1). 若在 @ModelAttribute 标记的方法中在 Map 中保存过, 且 key 和 1 确定的 key 一致, 则会获取到. * 3(需要多看看). 若 implicitModel 中不存在 key 对应的对象, 则检查当前的 Handler 是否使用 @SessionAttributes 注解修饰, * 若使用了该注解, 且 @SessionAttributes 注解的 value 属性值中包含了 key, 则会从 HttpSession 中来获取 key 所 * 对应的 value 值, 若存在则直接传入到目标方法的入参中. 若不存在则将抛出异常. * 4. 若 Handler 没有标识 @SessionAttributes 注解或 @SessionAttributes 注解的 value 值中不包含 key, 则 * 会通过反射来创建 POJO 类型的参数, 传入为目标方法的参数 * 5. SpringMVC 会把 key 和 POJO 类型的对象(value)保存到 implicitModel 中, 进而会保存到 request 中. * * * * *看了源码之后对上面内容的补充 * * 源代码分析的流程(看懂源码) * 1. 调用 @ModelAttribute 注解修饰的方法. 实际上把 @ModelAttribute 方法中 Map 中的数据放在了 implicitModel 中. * 2. 解析请求处理器的目标参数, 实际上该目标参数来自于 WebDataBinder 对象的 target 属性 * 1). 创建 WebDataBinder 对象: * ①. 确定 objectName 属性: 若传入的 attrName 属性值为 "", 则 objectName 为类名第一个字母小写. * *注意: attrName. 若目标方法的 POJO 属性使用了 @ModelAttribute 来修饰, 则 attrName 值即为 @ModelAttribute * 的 value 属性值 * * ②. 确定 target 属性: * > 在 implicitModel 中查找 attrName 对应的属性值. 若存在, ok * > *若不存在: 则验证当前 Handler 是否使用了 @SessionAttributes 进行修饰, 若使用了, 则尝试从 Session 中 * 获取 attrName 所对应的属性值. 若 session 中没有对应的属性值, 则抛出了异常. * > 若 Handler 没有使用 @SessionAttributes 进行修饰, 或 @SessionAttributes 中没有使用 value 值指定的 key * 和 attrName 相匹配, 则通过反射创建了 POJO 对象 * * 2). SpringMVC 把表单的请求参数赋给了 WebDataBinder 的 target 对应的属性. * 3). *SpringMVC 会把 WebDataBinder 的 attrName 和 target 给到 implicitModel. * 近而传到 request 域对象中. * 4). 把 WebDataBinder 的 target 作为参数传递给目标方法的入参. */
具体debug。
debug小帮助:
1:Step Over ,进入下一步,如果是方法,那就直接跳过(F8)
2:Step Into,进入下一步,如果是方法,就进入方法内部,但是不会进入jdk封装的方法。(F7)
3:Force Step Into:强制进入下一步,不管是什么方法,即使是jdk封装的方法,也会进入。(Alt+Shift+F7)
4:Step Out:跳转到下一个断点,没有一直运行到最后。(Shift+F8)
5: Run to Cursor:运行到光标处 (Alt+F9)
一,上面getUser方法里map放入键和值之后springMVC源码是如何把值传入testModelAttribute方法里?
(思路:“放入从数据库中获取的值” 这一步打上断点,“获得这个被修改后的值” 这一步打上断点。)
①
|| 经历了什么?
|| 把值给到。
②
二,debug开始,①经过了众多的类与方法到②,找到其中对值进行储存的类和方法。
(思路:“找到储存方法”打上断点,“后面一行代码,用于判断前一行是否储存了值” 打上断点)
③
在③中,我们发现其中一个属性叫implicitModel(隐式的model)为空。
④
在④中,我们发现这个属性有值。
三,③的方法需要两个参数,重点是args,看看它储存了什么,又是如何储存的。
⑤
点开这个方法,找到储存值的步骤。如果方法太多不方便找,就在User类的setUsername方法里打上断点。然后点前面几步就可以找到了。
⑥
args还是null
⑦
args已经有值了,表单提交修改的值和从数据库里得到没被修改的值都有。
四,⑦看binder是如何创建的,getTarget方法又是什么?
找到返回binder的类:
再找创建方法:
⑧
进入,看源码。
发现⑧的参数里bindObject就是上图的target,name就是上图的objectName
找到 name 和 bindObject 是如何赋值的。
name:通过下面的方法得到类的类名,第一个字母小写。
解释说明一下:
如果上面得到的name=implicitModel里的,那么把属性值给bindObject,
否则,从session域里找键为name的,如果有,把它的值给bindObject,
如果没有,抛出异常。
dobind方法里的binder属性就是储存经过表单提交修改后的值,最后加入到args里了。
最后,由这个方法把 target 和 objectName 给implicitModel
---------------------------------------------------------------
![](https://img2020.cnblogs.com/blog/1938121/202005/1938121-20200529111314533-1382383927.png)
下面的了解一下。
下面的了解一下。
<!--在实际开发中通常都需配置 mvc:annotation-driven标签。因为加上上面标签之后,以前的@requestMapping就都404了-->
<mvc:annotation-driven></mvc:annotation-driven>
----------------------------重点掌握下面的-----------------------------
SpringMVCTest类里加上。
@RequestMapping("/testRedirect")
public String testRedirect(){
System.out.println(testRedirect);
return "redirect:index.jsp";
}
index.jsp加上
<a href="testRedirect">testRedirect</a>
效果:点了a标签,重定向到index.jsp