03-springMVC(上)

基于 Spring4.x

 (ps:红色的字是表示学到的位置)

在SpringMVC中,MVC三部分的作用如下:

执行流程

具体到执行流程上,SpringMVC主要依赖了HandlerMapping 处理器映射器、HandlerAdapter 处理器适配器以及 ViewReslover 视图解析器三个组件。

SpringMVC执行流程示意图如下:

 

二,用springmvc做helloworld测试,效果是网页里点击a标签之后控制台输出helloworld,并转到成功页面。
第一步:导包。

 

web
<?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>
配置 DispatcherServlet
<?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>
applicationContext.xml
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";
    }
}
HelloWorld
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>
  <a href="hello1">hello</a>
  </body>
</html>
index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
success page
</body>
</html>
success.jsp

按顺序看一遍,然后理一遍思路。index.jsp里一个a标签发出请求,由servlet拦截,通过DispatcherServlet连接到applicationContext.xml里。配置好注解和视图解析器,(HelloWorld类)再通过@RequestMapping注解,映射请求的url,通过视图解析器转发到相应的jsp里。

 ----------------------------------------------------

 

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;
    }
}
SpringMVCTest
index.jsp里写 <a href="springmvc/testRequestMapping">testRequestMapping</a>
点击进入成功页面。
还是这个类,继续test。
method
    /**
     * 使用method属性来指定请求方式(重要)
     * @return
     */
    @RequestMapping(value = "testMethod",method = RequestMethod.POST)
    public String testMethod(){
        System.out.println();
        return SUCCESS;
    }
SpringMVCTest
index.jsp里写上 
<form action="springmvc/testMethod" method="post">
<input type="submit" value="submit">
</form>
 params和headers了解一下
 /**
     * 了解就行用的不多:可以使用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;
    }
SpringMVCTest

index.jsp里写上 

<%-- 用params 和 headers--%>
<a href="springmvc/testParamsAndHeaders?username=小明&age=11">testParamsAndHeaders</a>
<br/>

 

    /**
     * 使用@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;
    }
SpringMVCTest
<%--  使用@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>
index.jsp

rest风格了解一下。

 

 

    <!--配置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>
web里加上
 /**
     * 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;
    }
SpringMVCTest
<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/>
index.jsp

 ------------------------------------------------------

 

@RequestParam(重点看)

    /**
     * 了解就行
     * @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;
    }
SpringMVCTest
  <a href="springmvc/testCookieValue">testCookieValue</a>
  <a href="springmvc/testRequestHeader">testRequestHeader</a>
  <a href="springmvc/testRequestParam?username=小明&age=13">testRequestParam</a>
index.jsp

 Spring MVC 会按请求参数名和 POJO 属性名进行自动匹 配,自动为该对象填充属性值。支持级联属性。(重要)

  <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>
index.jsp
    @RequestMapping("/testPOJO")
    public String testPOJO(User user){
        System.out.println(user);
        return SUCCESS;
    }
SpringMVCTest

 -----------------------------------------------------------------------------------

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>
--------------------------------------

 下面是四种方法:

 

第一种方法: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;
    }

index.jsp页面里加上:
  <a href="springmvc/testModelAndView">testModelAndView</a>
success.jsp加上:  
  time: ${requestScope.time}
-------------------------------

第二种方法: 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;
    }
index.jsp页面里加上:
  <a href="springmvc/testMap">testMap</a>
success.jsp加上:
   names :${requestScope.names}
 
@SessionAttributes 注解
/**
 * @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;
    }
 
success.jsp加上:
  request user :${requestScope.user}
  session user :${sessionScope.user}
  </br>
  string:${sessionScope.spring}
  </br>
---------------------------------------------
第四种方法:
@ModelAttribute 注解
一般使用场景:

 -----------------------------------------------------
解决上面的问题。
    /**
     * 由@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

 

---------------------------------------------------------------

 

 

 

 

下面的了解一下。

 

 下面的了解一下。

 

 <!--在实际开发中通常都需配置 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

原文地址:https://www.cnblogs.com/zhuxiang1029/p/12970344.html