SpringMVC

目录

    • SpringMVC文档
    • SpringMVC特点&概述
    • Spring SpringMVC SpringBoot的关系
  • Spring MVC 快速入门
    • 思路分析
    • 具体实现
    • 注意事项和细节
  • SpringMVC执行流程分析
  • RequestMapping
    • 可以修饰类和方法
    • 可以指定请求方式
    • 可以指定params和headers支持简单表达式
    • 支持Ant 风格资源地址
    • 配合@PathVariable 映射 URL 绑定的占位符
    • 注意事项和使用细节
    • 作业布置
  • Postman
    • Postman是什么
    • Postman相关资源
    • Postman安装
    • Postman快速入门
    • Controller测试案例
    • 其它说明
    • 作业布置
  • Rest
    • Rest基本介绍
    • Rest实例
    • HiddenHttpMethodFilter机制
    • 注意事项和细节
    • 课后作业
  • SpringMVC映射请求数据
    • RequestParam - 获取参数值
    • 获取http请求消息头
    • 获取JavaBean对象
    • 使用Servlet API
  • 模型数据
    • 模型数据处理 - 数据放入request
    • 默认机制放入Request域
    • 通过HttpServletRequest放入request域
    • 通过方法参数Map 放入request域
    • ModelAndView使用
    • 模型数据处理 - 数据放入session
    • 模型数据 - @ModelAttribute
  • 视图和视图解析器
    • 自定义视图
    • 自定义视图-步骤梳理
    • 自定义视图解析器执行流程
    • 默认视图解析器执行流程
    • 多个视图解析器执行流程
    • 指定请求转发或者重定向
    • 指定请求转发流程分析
    • 指定重定向流程分析
    • 作业布置

SpringMVC_第1张图片

SpringMVC文档

资源已上传-离线文档 spring-framework-5.3.8\docs\reference\html
SpringMVC_第2张图片
SpringMVC_第3张图片

SpringMVC特点&概述

1.SpringMVC从易用性, 效率上, 比曾经流行的Struts2更好
2.SpringMVCWEB层框架 [解读: SpringMVC接管了Web层组件, 比如控制器, 视图, 视图解析, 返回给用户的数据格式, 同时支持MVC的开发模式/开发架构]
3.SpringMVC通过注解, 让POJO成为控制器, 不需要继承类或者实现接口
4.SpringMVC采用低耦合的组件设计方式, 具有更好地扩展性和灵活性
5.支持REST格式的URL请求
6.SpringMVC是基于Spring的, 也就是SpringMVC是在Spring基础上的. SpringMVC的核心包是 spring-web-5.3.8.jar, spring-webmvc-5.3.8.jar

Spring SpringMVC SpringBoot的关系

1.Spring MVC只是Spring处理WEB层请求的一个模块/组件, Spring MVC的基石是Servlet[Java WEB]
2.Spring Boot是为了简化开发者的使用, 推出的封神框架(约定优于配置, 简化了Spring的配置流程), SpringBoot包含很多组件/框架. Spring就是最核心的内容之一, 也包含Spring MVC
3.它们大概的关系是: SpringBoot > Spring > Spring MVC

Spring MVC 快速入门

思路分析

SpringMVC_第4张图片

具体实现

1.创建springmvc web工程, 导入jar包 新建javaweb项目, 参考
SpringMVC_第5张图片
SpringMVC_第6张图片

2.创建src/applicationContext-mvc.xml容器文件
SpringMVC_第7张图片

3.配置web.xml


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

    
    <servlet>
        <servlet-name>springDispatcherServletservlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
        
        <init-param>
            <param-name>contextConfigLocationparam-name>
            <param-value>classpath:applicationContext-mvc.xmlparam-value>
        init-param>
        
        <load-on-startup>1load-on-startup>
    servlet>
    
    <servlet-mapping>
        <servlet-name>springDispatcherServletservlet-name>
        
        <url-pattern>/url-pattern>
    servlet-mapping>
web-app>

说明: 这两个配置文件不一样
SpringMVC_第8张图片

4.web目录新建login.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>登录页面title>
head>
<body>
<%--后面再填写?--%>
<form action="?">
    username: <input type="text" name="username"/><br/>
    password: <input type="password" name="password"/><br/>
    <input type="submit" value="登录">
form>
body>
html>

5.在com.zzw.web包下新建UserServlet

/*
1.如果我们使用了SpringMVC, 在一个类上标识@Controller
2.表示将该类视为一个控制器, 注入到容器
3.比原生servlet开发要简化很多
*/
@Controller
public class UserServlet {

     //编写方法, 响应用户的请求
    /**
     * 解读
     * 1. login() 方法是用于响应用户的登录请求
     * 2. @RequestMapper(value="/login"), 类似于我们原生Servlet
     *    配置的url-pattern, 就是给方法配置一个url映射
     * 3. 即当用户在浏览器输入 http://localhost:8080/web工程路径/login 就能够访问到login()
     * 4. return "login ok"; 表示返回结果给视图解析器(InternalResourceViewResolver)
     *    , 视图解析器会根据配置, 来决定跳转到哪个页面
     *
     *     
     *
     *         
     *         
     *     
     *
     *     根据上面配置的 return "login_ok"; 就会转发到 /WEB-INF/pages/login_ok.jsp
     */
    @RequestMapping(value = "/login")
    public String login() {
        System.out.println("login ok...");
        return "login_ok";
    }
}

6.在web路径/WEB-INF/pages目录下 新建login_ok.jsp`

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>登陆成功title>
head>
<body>
<h1>登陆成功~h1>
body>
html>

7.配置applicationContext-mvc.xml

说明: InternalResourceViewResolver的父类UrlBasedViewResovler, 有属性prefix, suffix
SpringMVC_第9张图片
SpringMVC_第10张图片


<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 https://www.springframework.org/schema/context/spring-context.xsd">

    
    <context:component-scan base-package="com.zzw.web"/>

    
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        
        <property name="prefix" value="/WEB-INF/pages/"/>
        <property name="suffix" value=".jsp"/>
    bean>
beans>

8.配置Tomcat
SpringMVC_第11张图片
SpringMVC_第12张图片

9.配置login.jsp页面访问路径 参考->web工程路径专题

<body>
<%--
这里需要回顾 javaweb-web工程路径专题
1. action="login" 表示url 是 http://localhost:8080/springmvc/login
   前提: 当前页面在web路径下, 相对路径为 http://localhost:8080/springmvc/login.jsp
2. action="/login" 表示url 是 http://localhost:8080/login
--%>
<form action="login">
    username: <input type="text" name="username"/><br/>
    password: <input type="password" name="password"/><br/>
    <input type="submit" value="登录">
form>
body>

注意事项和细节

1.学习如何搭建一个springmvc项目, 初步理解springmvc工作流程

2.这里的UserServlet需要注解成@Controller, 我们称之为一个Handler处理器

3.UserServlet指定url时, 是可以省略的

4.关于SpringMVCDispatcherServlet的配置文件, 如果不在web.xml中指定applicationContext-mvc.xml, 默认在/WEB-INF/springDispatcherServlet-servlet.xml找这个配置文件 (推荐使用, 做修改, 并完成测试)

查看DispatcherServlet父类FrameworkServlet的源码
SpringMVC_第13张图片

1)修改web.xml, 注销init-param配置节点


<servlet>
    <servlet-name>springDispatcherServletservlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
    

    
    
    
    
    

    
    <load-on-startup>1load-on-startup>
servlet>

这时运行会报错. 因为在WEB-INF目录下找不到springDispatcherServlet-servlet.xml文件

2)剪切原applicationContext-mvc.xml/WEB-INF目录下, 文件名为: 你配置的DispatcherServlet名字-servlet.xml, 即/WEB-INF/springDispatcherServlet-servlet.xml
SpringMVC_第14张图片

运行, 正常.

SpringMVC执行流程分析

SpringMVC_第15张图片

RequestMapping

RequestMapping注解可以指定控制器/处理器的某个方法的请求的url

可以修饰类和方法

1.说明: @RequestMapping注解可以修饰方法, 还可以修饰类. 当同时修饰类和方法时, 请求的url 就是组合 /类请求值/方法请求值

案例
1.com.zzw.web包下 新建UserHandler

@RequestMapping(value = "/user")
@Controller //UserHandler就是一个处理器/控制器, 会注入到容器
public class UserHandler {

    /**
     * 1.method=RequestMethod.POST: 表示请求buy目标方法必须是 post
     * 2.RequestMethod 四个常用选项 POST, GET, PUT, DELETE[后面会详解]
     * 3.SpringMVC 控制器默认支持GET和POST两种方式
     *
     * buy()方法请求的url: http://ip:port/工程路径/user/buy
     * @return
     */
    @RequestMapping(value = "/buy", method = RequestMethod.POST)
    public String buy() {
        System.out.println("购买.");
        return "success";
    }
}

2.web路径/WEB-INF/pages目录 新建success.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>操作成功title>
head>
<body>
<h1>恭喜, 操作成功~h1>
body>
html>

3.web路径下 新建request.jsp, 测试

<body>
<%--解读
1. action="user/buy" 对应 url http://localhost:8080/工程路径/user/buy
--%>
<form action="user/buy" method="post">
    购买人: <input type="text" name="username"/><br/>
    购买量: <input type="password" name="nums"/><br/>
    <input type="submit" value="购买">
form>
body>

4.访问成功
SpringMVC_第16张图片

可以指定请求方式

1.说明: @RequestMapping可以指定请求方式(post/get/put/delete..), 请求的方式要和指定的一样, 否则报错.

2.SpringMVC控制器默认支持GETPOST两种方式, 也就是你不指定method, 可以接收GETPOST请求

3.应用案例
SpringMVC_第17张图片
SpringMVC_第18张图片

4.当你明确指定了method, 则需要按指定方式请求, 否则会报错.
SpringMVC_第19张图片

可以指定params和headers支持简单表达式

1.param1:表示请求必须包含名为param1的请求参数. 比如 params = "bookId"
2.!param1:表示请求不能包含名为param1的请求参数. 比如 params = "!bookId"
3.param1 = value1:表示请求包含名为param1的请求参数, 且其值必须为value1. 比如 params = "bookId=100"
4.param1 != value1:表示请求包含名为param1的请求参数, 但其值不能为value1. 比如 params = "bookId!=100"
5.{"param1=value1", "param2"}:请求必须包含名为param1, param2的两个请求参数, 且param1参数的值必须为value1. 比如params = {"bookId=100", "price"}


案例
1.修改UserHandler.java增加方法search

@RequestMapping(value = "/user")
@Controller //UserHandler就是一个处理器/控制器, 会注入到容器
public class UserHandler {
    /**
     * 解读
     * 1. params="bookId" 表示请求该目标方法时, 必须给一个bookId参数, 值没有限定
     * 2. search(String bookId) 表示请求目标方法时, 携带的bookId=100, 就会将请求携带的 bookId
     *    对应的值, 赋给 String bookId
     * @param bookId
     * @return
     */
    @RequestMapping(value = "/find", params = "bookId", method = RequestMethod.GET)
    public String search(String bookId) {
        System.out.println("查询书籍 bookId=" + bookId);
        return "success";
    }
}

2.修改request.jsp

<body>
<h1>演示params的使用h1>
<a href="user/find?bookId=100">查询书籍a>
body>

3.操作成功
SpringMVC_第20张图片
如果bookId改为bookIdx, 报错
SpringMVC_第21张图片


细节1:如果需要有bookId参数, 并且值为100. 否则报错.

@RequestMapping(value = "/find", params = "bookId=100", method = RequestMethod.GET)

修改request.jsp

<body>
<h1>演示params的使用h1>
<a href="user/find?bookId=200">查询书籍a>
body>

报错
SpringMVC_第22张图片

细节2:需要有bookId参数, 并且值不为100. 否则报错.

@RequestMapping(value = "/find", params = "bookId!=100", method = RequestMethod.GET)

修改request.jsp

<body>
<h1>演示params的使用h1>
<a href="user/find?bookId=100">查询书籍a>
body>

报错
SpringMVC_第23张图片

支持Ant 风格资源地址

1.?: 匹配文件名中的一个字符
2.*: 匹配文件名中的任意字符
3.**: 匹配多层路径

4.举例
/user/*/createUser:匹配/user/aaa/createUser, /user/bbb/createUserURL
/user/**/createUser:匹配/user/createUser, /user/aaa/bbb/createUserURL
/user/createUser??:匹配/user/createUseraa, /user/createUserbbURL

案例
1.修改UserHandler.java增加方法im

@RequestMapping(value = "/user")
@Controller //UserHandler就是一个处理器/控制器, 会注入到容器
public class UserHandler {

    /**
     * 要求: 可以配置 /user/message/aa, /user/message/aa/bb/cc
     * @RequestMapping(value="/message/**") 表示可以匹配多层路径
     */
    @RequestMapping(value = "/message/**")
    public String im() {
        System.out.println("发送消息");
        return "success";
    }
}

2.修改request.jsp

<body>
<hr><h1>演示Ant风格的请求资源方式h1>
<a href="user/message/aa">发送消息1a>
<a href="user/message/aa/bb/cc">发送消息2a>
body>

3.测试成功…

配合@PathVariable 映射 URL 绑定的占位符

1.@RequestMapping可以配合@PathVariable映射URL绑定的占位符
2.这样就不需要在url地址上带参数名了, 更加的简洁明了.

案例
1.修改UserHandler.java增加方法register

@RequestMapping(value = "/user")
@Controller //UserHandler就是一个处理器/控制器, 会注入到容器
public class UserHandler {

    /**
     * 前端页面: 占位符的演示
     * (value="/reg/{username}/{userId}"): 表示Kristina=>{username} 300=>{userId}
     *
     * @return
     */
    @RequestMapping(value = "/reg/{username}/{userId}")
    public String register(@PathVariable("username") String name,
                           @PathVariable("userId") int id) {
        System.out.println("接收到参数--" + "username=" + name + "--" + "userId=" + id);
        return "success";
    }
}

2.修改request.jsp

<body>
<hr/><h1>占位符的演示h1>
<a href="user/reg/Kristina/300">占位符的演示a>
body>

3.测试成功…

注意事项和使用细节

1.映射的URL, 不能重复

@RequestMapping(value = "/user")
@Controller //UserHandler就是一个处理器/控制器, 会注入到容器
public class UserHandler {

    @RequestMapping(value = "/hi")
    public String hi() {
        System.out.println("hi");
        return "success";
    }

    @RequestMapping(value = "/hi")
    public String hi2() {
        System.out.println("hi");
        return "success";
    }
}

启动或重新发布时, 会报错. to { [/user/hi]}: There is already 'userHandler' bean method


2.各种简写的方式

@RequestMapping(value=“/buy”,method=RequestMethod.POST) 等价 @PostMapping(value=“/buy”)

简写方式一览: @GetMapping @PostMapping @PutMapping @DeleteMapping
SpringMVC_第24张图片

案例

@RequestMapping(value = "/user")
@Controller //UserHandler就是一个处理器/控制器, 会注入到容器
public class UserHandler {

    /**
     * 5.@PostMapping(value="/buy") 等价 @Request(value="/buy", method=RequestMapping.POST)
     * @return
     */
    //@RequestMapping(value = "/buy", method = RequestMethod.POST)
    @PostMapping(value = "/buy")
    public String buy() {
        System.out.println("购买.");
        return "success";
    }
}

测试request.jsp

<body>
<form action="user/buy" method="get">
    购买人: <input type="text" name="username"/><br/>
    购买量: <input type="password" name="nums"/><br/>
    <input type="submit" value="购买">
form>
body>

报错
SpringMVC_第25张图片


3.如果我们确定表单或者超链接会提交某个字段数据比如email, 要求提交的参数名和目标方法的参数名保持一致.

案例
1.修改UserHandler.java增加方法hello3

@RequestMapping(value = "/user")
@Controller //UserHandler就是一个处理器/控制器, 会注入到容器
public class UserHandler {
    /**
     * hello3(String email) 表示如果我们的请求参数有 email=xx, 就会将传递的值, 赋给String email
     * , 要求名称保持一致, 如果不一致, 那么接收不到数据, 而是null
     * @param email
     * @return
     */
    @RequestMapping(value = "/hello3")
    public String hello3(String email) {
        System.out.println("email=" + email);
        return "success";
    }
}

2.测试 浏览器地址栏 输入http://localhost:8080/springmvc/user/[email protected], 一定要注意提交参数名和后台方法的形参名保持一致, 否则后端接收不到参数

SpringMVC_第26张图片
在这里插入图片描述

3.如果输入一个错误的参数名, 那么后端接收不到数据
在这里插入图片描述
SpringMVC_第27张图片

在这里插入图片描述

作业布置

1.熟悉SpringMVC的执行流程图
2.熟悉@RequestMapping注解的使用方式
3.编写一个表单, 以Post的方式提交Computer信息, 后端编写ComputerHandler, 可以接收到信息.


代码实现

1.修改request.jsp

<body>
<h1>电脑信息h1>
<form action="?" method="post">
    品牌:<input type="text" name="brand"/><br/>
    价格:<input type="text" name="price"/><br/>
    数量:<input type="text" name="nums"/><br/>
    <input type="submit" value="提交">
form>
body>

2.com.zzw.web包下 新建ComputerHandler

@RequestMapping(value = "/computer")
@Controller
public class ComputerHandler {

    //这里一定要注意, info方法的形参名需要和请求的参数名保持一致
    @PostMapping(value = "/info", params = {"brand", "price", "nums"})
    public String info(String brand, String price, String nums) {
        System.out.println("电脑信息--brand=" +  brand
                + "--price=" + price + "--nums" + nums);
        return "success";
    }
}

3.配置页面访问路径

<form action="computer/info" method="post">

4.测试成功…
SpringMVC_第28张图片

Postman

SpringMVC_第29张图片

Postman是什么

1.Postman是一款功能超级强大的用于发送HTTP请求的 测试工具.
2.做WEB页面开发和测试的人员常用工具.
3.创建和发送任何的HTTP请求(GET/Post/Put/Delete)

Postman相关资源

官方网站: https://www.postman.com/
文档: https://learning.postman.com/docs/introduction/overview/
下载地址: https://www.postman.com/downloads/.

资料已上传…

Postman安装

1.资料已上传, 下载后右键管理员身份打开即可安装(非常简单), Postman不会让你选择安装路径, 会直接安装, 一般安装在系统盘.
2.安装成功, 在桌面上有快捷图标. 双击打开Postman.

Postman快速入门

●要求:使用Postmanhttp://www.baidu.com发出get请求, 得到返回的html格式数据

调整字体大小: File–Settings
SpringMVC_第30张图片
调整页面大小: ctrl++, ctrl+ -


注册账号:(可选, 不注册不影响使用) 输入邮件, 用户名, 密码
SpringMVC_第31张图片


1.创建Http Request, 如果你已经创建过, 会直接进入Workspace

File–New
SpringMVC_第32张图片
SpringMVC_第33张图片
SpringMVC_第34张图片
SpringMVC_第35张图片
SpringMVC_第36张图片
SpringMVC_第37张图片

2.发出请求
SpringMVC_第38张图片

Controller测试案例

需求说明: 使用Postman, 完成UserHandler方法的请求


1.完成请求
SpringMVC_第39张图片
使用Postman测试Controller方法的步骤
1.确定请求的地址 url: http://localhost:8080/springmvc/user/buy
2.请求的方式 -Post
3.确定请求的参数/数据 -无
4.确定Header有没有特殊的指定 -无 http协议


2.完成请求
在这里插入图片描述

SpringMVC_第40张图片
SpringMVC_第41张图片

使用Postman测试Controller方法的步骤
1.确定请求的地址 url: http://localhost:8080/springmvc/user/find
2.请求的方式 -Get
3.确定请求的参数/数据 -bookId=100
4.确定Header有没有特殊的指定 -无


3.完成请求
在这里插入图片描述

SpringMVC_第42张图片
SpringMVC_第43张图片

使用Postman测试Controller方法的步骤
1.确定请求的地址 url: http://localhost:8080/springmvc/user/message/aa/bb/cc
2.请求的方式 -Get/Post
3.确定请求的参数/数据 -无
4.确定Header有没有特殊的指定 -无


4.完成请求
在这里插入图片描述

SpringMVC_第44张图片

使用Postman测试Controller方法的步骤
1.确定请求的地址 url: http://localhost:8080/springmvc/user/reg/zzw/23
2.请求的方式 -Get/Post
3.确定请求的参数/数据 -无
4.确定Header有没有特殊的指定 -无


5.完成请求
在这里插入图片描述

SpringMVC_第45张图片

使用Postman测试Controller方法的步骤
1.确定请求的地址 url: http://localhost:8080/springmvc/user/hello3
2.请求的方式 -Get
3.确定请求的参数/数据 [email protected]
4.确定Header有没有特殊的指定 -无

其它说明

1.创建 对应的Http Request, 放到已有的Collection

SpringMVC_第46张图片
SpringMVC_第47张图片

2.在Headers选项页, 增加 Content-Type applicatoin/json
SpringMVC_第48张图片

3.因为是Post请求, 在Body选项填写Json数据/Furn数据
SpringMVC_第49张图片

作业布置

1.创建新的Collection, 命名为你的名字, 比如 zzwCollection
SpringMVC_第50张图片

2.创建多个http request, 完成对UserHandler的各个方法的请求
SpringMVC_第51张图片

测试1

@RequestMapping(value = "/user")
@Controller //UserHandler就是一个处理器/控制器, 会注入到容器
public class UserHandler {
    @PostMapping(value = "/buy")
    public String buy() {
        System.out.println("购买.");
        return "success";
    }
}

使用Postman测试Controller方法的步骤
1.确定请求的地址 url: http://localhost:8080/springmvc/user/buy
2.请求的方式 -Post
3.确定请求的参数/数据 -无
4.确定Header有没有特殊的指定 -无


测试2

@RequestMapping(value = "/user")
@Controller //UserHandler就是一个处理器/控制器, 会注入到容器
public class UserHandler {
    @RequestMapping(value = "/find", params = "bookId=100", method = RequestMethod.GET)
    public String search(String bookId) {
        System.out.println("查询书籍 bookId=" + bookId);
        return "success";
    }
}

使用Postman测试Controller方法的步骤
1.确定请求的地址 url: http://localhost:8080/springmvc/user/find
2.请求的方式 -Get
3.确定请求的参数/数据 -bookId=100
4.确定Header有没有特殊的指定 -无


测试3

@RequestMapping(value = "/user")
@Controller //UserHandler就是一个处理器/控制器, 会注入到容器
public class UserHandler {
    @RequestMapping(value = "/message/**")
    public String im() {
        System.out.println("发送消息");
        return "success";
    }
}

使用Postman测试Controller方法的步骤
1.确定请求的地址 url: http://localhost:8080/springmvc/user/message/aa/bb/cc
2.请求的方式 -Get/Post
3.确定请求的参数/数据
4.确定Header有没有特殊的指定 -无


测试4

@RequestMapping(value = "/user")
@Controller //UserHandler就是一个处理器/控制器, 会注入到容器
public class UserHandler {
    @RequestMapping(value = "/reg/{username}/{userId}")
    public String register(@PathVariable("username") String name,
                           @PathVariable("userId") int id) {
        System.out.println("接收到参数--" + "username=" + name + "--" + "userId=" + id);
        return "success";
    }
}

使用Postman测试Controller方法的步骤
1.确定请求的地址 url: http://localhost:8080/springmvc/user/reg/star/3000000
2.请求的方式 -Get/Post
3.确定请求的参数/数据 -无
4.确定Header有没有特殊的指定 -无


测试5

@RequestMapping(value = "/user")
@Controller //UserHandler就是一个处理器/控制器, 会注入到容器
public class UserHandler {
    @GetMapping(value = "/hello3")
    public String hello3(String email) {
        System.out.println("email=" + email);
        return "success";
    }
}

使用Postman测试Controller方法的步骤
1.确定请求的地址 url: http://localhost:8080/springmvc/user/hello3
2.请求的方式 -Get
3.确定请求的参数/数据 [email protected]
4.确定Header有没有特殊的指定 -无

Rest

Rest基本介绍

●说明

1.REST:Representational State Transfer. (资源)表现层状态转化. 是目前流行的请求方式. 它结构清晰, 很多网站使用.
2.HTTP协议里面, 四个表示操作方式的动词: GET, POST, PUT, DELETE, 它们分别对应四种基本操作: GET用来获取资源, POST用来新建资源, PUT用来更新资源, DELETE用来删除资源
3.实例. 传统的请求方法:
getBook?id=1 GET
delete?id=1 GET
update POST
add POST
4.说明: 传统的url是通过参数来说明crud的类型, rest是通过get/post/put/delete来说明crud的类型

REST的核心过滤器
1.当前的浏览器只支持post/get请求, 因此为了得到put/delete的请求方式需要使用Spring提供的HiddenHttpMethodFilter过滤器进行转换.
2.HiddenHttpMethodFilter:浏览器form表单只支持GETPOST请求, 而DELETE, PUTmethod并不支持, Spring添加了一个过滤器, 可以将这些请求转换为标准的http方法, 使得支持GET, POST, PUTDELETE请求.
3.HiddenHttpMethodFilter只能对post请求方式进行转换.
4.这个过滤器需要在web.xml中配置

Rest实例

需求说明
小明去书店买书, 完成购买书籍的增删改查

1.修改web.xml, 配置HiddenHttpMethodFilter


<filter>
    <filter-name>hiddenHttpMethodFilterfilter-name>
    <filter-class>org.springframework.web.filter.HiddenHttpMethodFilterfilter-class>
filter>
<filter-mapping>
    <filter-name>hiddenHttpMethodFilterfilter-name>
    <url-pattern>/*url-pattern>
filter-mapping>

2.修改springDispatcherServlet-serlvet.xml 添加配置



<mvc:annotation-driven>mvc:annotation-driven>

<mvc:default-servlet-handler/>

3.在web路径下创建rest.jsp, 注意引入jquery, 测试查询/添加/删除/修改

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>rest测试title>
head>
<body>
<h3>Rest风格的crud操作案例h3>
<br/><hr>
<h3>rest风格的url 查询书籍[get]h3>
<a href="?">点击查询书籍a>
<br/><hr>
<h3>rest风格的url 添加书籍[post]h3>
<form action="?" method="?">
    name:<input name="bookName" type="text"/><br/>
    <input type="submit" value="添加书籍">
form>
<br/><hr>
<h3>rest风格的url 删除一本书h3>
<a href="?" id="?">删除指定id的书a>
<br/><hr>
<h3>rest风格的url 修改书籍[put]h3>
<form action="?">
    <input type="submit" value="修改书籍">
form>
body>
html>

4.在com.zzw.web.rest下, 创建BookHandler.java

1.完成查询

 //BookHandler 处理rest风格的请求-增删改查
@RequestMapping(value = "/user")
@Controller
public class BookHandler {

    //查询[GET]
    @GetMapping(value = "/book/{id}")
    public String getBook(@PathVariable("id") String id) {
        System.out.println("查询书籍 id=" + id);
        return "success";
    }
}

5.前端修改请求地址

<h3>rest风格的url 查询书籍[get]h3>
<a href="user/book/200">点击查询书籍a>

2.完成添加
@PostMapping(value = "/book") @GetMapping(value = "/book/{id}")不重复

//BookHandler 处理rest风格的请求-增删改查
@RequestMapping(value = "/user")
@Controller
public class BookHandler {

    //查询[GET]
    @GetMapping(value = "/book/{id}")
    public String getBook(@PathVariable("id") String id) {
        System.out.println("查询书籍 id=" + id);
        return "success";
    }

    //添加[POST]
    @PostMapping(value = "/book")
    public String addBook(String bookName) {
        System.out.println("添加书籍 bookName=" + bookName);
        return "success";
    }
}

前端修改请求地址

<h3>rest风格的url 添加书籍[post]h3>
<form action="user/book" method="post">
    name:<input name="bookName" type="text"/><br/>
    <input type="submit" value="添加书籍">
form>

3.完成删除

//BookHandler 处理rest风格的请求-增删改查
@RequestMapping(value = "/user")
@Controller
public class BookHandler {
    //查询[GET]
    @GetMapping(value = "/book/{id}")
    public String getBook(@PathVariable("id") String id) {
        System.out.println("查询书籍 id=" + id);
        return "success";
    }

    //添加[POST]
    @PostMapping(value = "/book")
    public String addBook(String bookName) {
        System.out.println("添加书籍 bookName=" + bookName);
        return "success";
    }

    //删除[DELETE]
    @RequestMapping(value = "/book/{id}", method = RequestMethod.DELETE)
    public String deleteBook(@PathVariable("id") String id) {
        System.out.println("删除书籍 id=" + id);
        //return "success";//[如果这样写会报错 JSP 只允许 GET、POST 或 HEAD]
        //解读
        //1. redirect:/user/success重定向
        //2. 会被解析成 /工程路径/user/success
        return "redirect:/user/success";//提示: 重定向不能重定向到WEB-INF下的资源, 所以需要借助successGeneral方法
    }

    //如果请求时 /user/success, 就转发到 success.jsp
    //successGeneral 对应的url http://localhost:8080/springmvc/user/success
    @RequestMapping(value = "/success")
    public String successGeneral() {
        return "success";//由该方法 转发到success.jsp页面
    }
}

知识点:
1.web路径/script目录下存放jquery文件, jquery复习

2.为什么前端做了一个操作后, 就可以被过滤器过滤处理, 定位到后端delete方法? 回答: HiddenHttpMethodFilter机制

3.return “success”; 会报以下错误
SpringMVC_第52张图片


前端修改

<head>
    <title>rest测试title>
    <%--script标签建议放在head内--引入jquery--%>
    <script type="text/javascript" src="script/jquery-3.6.0.min.js">script>
    <script type="text/javascript">
        $(function () {//当页面加载完成后就执行
            // alert("ok...");
            //给删除超链接绑定一个点击事件
            $("#deleteBook").click(function () {
                // alert("点击....");
                //我们自己定义一个提交的行为
                $("#hiddenForm").attr("action", this.href);
                $("input:hidden").val("DELETE");
                $("#hiddenForm").submit();
                return false;//改变点击超链接的行为, 不再提交
            })
        });
    script>
head>
<body>
<h3>rest风格的url 删除一本书h3>
<%--解读
 1. 默认情况下, <a href="user/book/600">删除指定id的书a> 是get请求
 2. 怎么样将 get 请求转成 springmvc 可以识别的 delete 请求, 就要考虑HiddenHttpMethodFilter
    public static final String DEFAULT_METHOD_PARAM = "_method";
    -------------------------------------------------------------------------------------------------------------
    if ("POST".equals(request.getMethod()) && request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == null) {
        String paramValue = request.getParameter(this.methodParam);
        if (StringUtils.hasLength(paramValue)) {
            String method = paramValue.toUpperCase(Locale.ENGLISH);
            if (ALLOWED_METHODS.contains(method)) {
                requestToUse = new HttpMethodRequestWrapper(request, method);
            }
        }
    }
    -------------------------------------------------------------------------------------------------------------
    private static final List<String> ALLOWED_METHODS =
			Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(),
					HttpMethod.DELETE.name(), HttpMethod.PATCH.name()));
 3. 从上面代码可以看到 HiddenHttpMethodFilter 过滤器可以对以Post方式提交的delete, put, patch进行转换
    , 转换称springmvc可以识别的 RequestMethod.DELETE / RequestMethod.PUT...
 4. 我们需要将 get <a href="user/book/600">删除指定id的书a> 以 post方式提交给后端handler, 这样过滤器才会生效
 5. 我们可以通过jquery来处理--引入jquery
--%>
<a href="user/book/600" id="deleteBook">删除指定id的书a>
<form action="" method="post" id="hiddenForm">
    <input type="hidden" name="_method"/>
form>
body>

4.完成修改

//BookHandler 处理rest风格的请求-增删改查
@RequestMapping(value = "/user")
@Controller
public class BookHandler {
    //修改[PUT]
    @PutMapping(value = "/book/{id}")
    public String updateBook(@PathVariable("id") String id) {
        System.out.println("修改书籍 id=" + id);
        return "redirect:/user/success";
    }
}

前端修改请求代码

<h3>rest风格的url 修改书籍[put]h3>
<form action="user/book/666" method="post">
    <input type="button" name="_method" value="PUT"/>
    <input type="submit" value="修改书籍"/>
form>

HiddenHttpMethodFilter机制

打断点, 进行debug
SpringMVC_第53张图片
SpringMVC_第54张图片

SpringMVC_第55张图片
SpringMVC_第56张图片
SpringMVC_第57张图片
SpringMVC_第58张图片

注意事项和细节

1.HiddenHttpMethodFilter, 在将post转成delete / put请求时, 是按_method参数名 来读取的
2.如果web项目是运行在Tomcat8及以上, 会发现被过滤成DELETEPUT请求, 到达控制器时能顺利执行, 但是返回时(forward)会报HTTP 405的错误提示: JSP 只允许 GET、POST 或 HEAD

  1. 解决方式1: 使用Tomcat7
  2. 解决方式2: 将请求转发(forward)改为请求重定向(redirect): 重定向到一个Handler, 由Handler转发到页面

3.页面测试时, 如果出现点击修改书籍, 仍然走的是删除url, 是因为浏览器原因(缓存等原因).

课后作业

需求说明
小王去商超买衣服, 完成购买衣服的增删改查

1.在web路径下创建restBuyClothes.jsp, 注意引入jquery, 测试查询/添加/删除/修改

<head>
    <title>购买衣服title>
    <%--引入jquery--%>
    <script type="text/javascript" src="script/jquery-3.6.0.min.js">script>
head>
<body>
<h3>rest风格的url 挑选衣服[get]h3>
<a href="?">点击挑选衣服a>
<br/><hr>
<h3>rest风格的url 添加衣服[post]h3>
<a href="?">点击添加衣服a>
<form action="?" method="post">
    clothes: <input name="clothes" type="text"><br/>
    <input type="submit" value="添加衣服">
form>
<br/><hr>
<h3>rest风格的url 删除一件衣服[delete]h3>
<a href="?" id="deleteClothes">删除一件衣服a>
<form action="" method="post" id="deleteForm">
    <input type="hidden" name="_method"/>
form>
<br/><hr>
<h3>rest风格的url 修改衣服[get]h3>
<form action="?" method="post">
    <input type="hidden" name="_method">
form>
body>

2.在com.zzw.web.rest下, 创建ClothesHandler.java

1.完成查询
@PostMapping(value = "/clothes") @GetMapping(value = "/clothes/{brand}")不重复

@RequestMapping(value = "/user")//处理Rest风格的请求 增删改查
@Controller
public class ClothesHandler {

    //挑选[GET]
    @GetMapping(value = "/clothes/{brand}")
    public String queryClothes(@PathVariable("brand") String brand) {
        System.out.println("挑选衣服 brand=" + brand);
        return "success";
    }
}

.前端修改请求

<h3>rest风格的url 挑选衣服[get]h3>
<a href="user/clothes/阿迪达斯">点击挑选衣服a>

2.完成添加

@RequestMapping(value = "/user")
@Controller
public class ClothesHandler {//演示Rest风格的请求

    //挑选[GET]
    @GetMapping(value = "/clothes/{brand}")
    public String queryClothes(@PathVariable("brand") String brand) {
        System.out.println("挑选衣服 brand=" + brand);
        return "success";
    }

    //添加[POST]
    @PostMapping(value = "/clothes")
    public String addClothes(String brand) {
        System.out.println("添加衣服 brand=" + brand);
        return "success";
    }
}

.前端修改请求

<h3>rest风格的url 添加衣服[post]h3>
<form action="user/clothes" method="post">
    clothes: <input name="brand" type="text"><br/>
    <input type="submit" value="添加衣服">
form>

3.完成删除

@RequestMapping(value = "/user")
@Controller
public class ClothesHandler {//演示Rest风格的请求

    //挑选[GET]
    @GetMapping(value = "/clothes/{brand}")
    public String queryClothes(@PathVariable("brand") String brand) {
        System.out.println("挑选衣服 brand=" + brand);
        return "success";
    }

    //添加[POST]
    @PostMapping(value = "/clothes")
    public String addClothes(String brand) {
        System.out.println("添加衣服 brand=" + brand);
		//return "success";
        return "redirect:/user/success";
    }

    @RequestMapping(value = "/success2")
    public String successGeneral() {
        return "success";
    }
}

.前端修改请求

<head>
    <title>购买衣服title>
    <%--引入jquery--%>
    <script type="text/javascript" src="script/jquery-3.6.0.min.js">script>
    <script type="text/javascript">
        $(function () {
            // alert("123");
            $("#deleteClothes").click(function () {
                // alert("ok..");
                $("#deleteForm")[0].action = this.href;
                $("input:hidden")[0].value = "DELETE";
                $("#deleteForm").submit();
                return false;
            })
        })
    script>
head>
<body>
<h3>rest风格的url 删除一件衣服[delete]h3>
<a href="user/clothes/361" id="deleteClothes">删除一件衣服a>
<form action="" method="post" id="deleteForm">
    <input type="hidden" name="_method"/>
form>
body>

4.完成修改

@RequestMapping(value = "/user")
@Controller
public class ClothesHandler {//演示Rest风格的请求

    //挑选[GET]
    @GetMapping(value = "/clothes/{brand}")
    public String queryClothes(@PathVariable("brand") String brand) {
        System.out.println("挑选衣服 brand=" + brand);
        return "success";
    }

    //添加[POST]
    @PostMapping(value = "/clothes")
    public String addClothes(String brand) {
        System.out.println("添加衣服 brand=" + brand);
		//return "success";
        return "redirect:/user/success";
    }

    @RequestMapping(value = "/success2")
    public String successGeneral() {
        return "success";
    }

    //修改[PUT]
    @PutMapping(value = "/clothes/{brand}")
    public String updateClothes(@PathVariable("brand") String brand) {
        System.out.println("修改衣服 brand=" + brand);
        return "redirect:/user/success2";
    }
}

.前端修改请求

<body>
<h3>rest风格的url 修改衣服[get]h3>
<form action="user/clothes/李宁" method="post">
    <input type="hidden" name="_method" value="PUT">
    <input type="submit" value="修改衣服"/>
form>
body>

SpringMVC映射请求数据

RequestParam - 获取参数值

开发中, 如何获取到http://ip:port/url?参数名=参数值&参数名=参数值

1.com.zzw.web.requestParam新建VoteHandler

@RequestMapping("/vote")
@Controller
public class VoteHandler {

    /**
     * 解读
     * 1.获取到超链接传递的数据 请求 http://localhost:8080/springmvc/vote/vote01?name=xx
     * 2.@RequestParam 表示会接收提交的参数
     * 3,value="name" 表示提交的参数名是name
     * 4.required=false 表示该参数可以没有; 默认是true, 表示必须有这个参数
     * 5.当我们使用了 @RequestParam(value="name", required=false)后 请求的参数名和方法的形参名可以不一致
     */
    @RequestMapping(value = "/vote01")
    public String test01(@RequestParam(value = "name", required = false) String username) {
        System.out.println("得到username=" + username);
        return "success";
    }
}

2.web路径下新建request_parameter.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>测试 request parametertitle>
head>
<body>
<h2>获取到超链接参数值h2>
<hr>
<a href="vote/vote01?name=zzw">获取超链接的参数a>
body>
html>

3.Postman测试

get方式提交
SpringMVC_第59张图片post方式提交
SpringMVC_第60张图片

获取http请求消息头

●说明
1.在开发中, 如何获取到http请求的消息头信息
2.使用较少

案例
1.修改VoteHandler.java, 增加方法 test02

@RequestMapping("/vote")
@Controller
public class VoteHandler {
    /**
     * 需求: 获取http请求头信息, 获取到Accept-Encoding 和 Host
     * [涉及到知识点 http协议]
     * @param a1
     * @param a2
     * @return
     */
    @RequestMapping(value = "/vote02")
    public String test02(@RequestHeader("Accept-Encoding") String a1, @RequestHeader("Host") String a2) {
        System.out.println("Accept-Encoding=" + a1);
        System.out.println("Host=" + a2);
        //返回一个结果
        return "success";
    }
}

修改request_parameter.jsp

<h2>获取到消息头h2>
<a href="vote/vote02">获取到Http消息头信息a>

3.Postman测试
SpringMVC_第61张图片

获取JavaBean对象

开发中, 如何获取到javabean对象, 就是以前的entity/pojo对象数据

案例
1.com.zzw.web.requestParam.entity包下新建Pet

public class Pet {
    private Integer id;
    private String name;

	//setter, getter, toString方法
}

同包下新建Master

public class Master {
    private Integer id;
    private String name;
    private Pet pet;//对象的属性是另外一个对象[涉及到属性级联]
    
	//setter, getter, toString方法
}

2.修改VoteHandler.java, 增加方法 test03

public class VoteHandler {
    /**
     * 演示如何获取到提交数据->封装成java对象
     * 说明
     * 1.方法的形参用对应的类型来指定即可, SpringMVC会自动的进行封装
     * 2.如果要自动地完成封装, 要求提交的数据, 参数名和对象的字段名保持一致
     * 3.如果属性是对象, 这里仍然是通过 字段名.字段名 比如Master [pet]
     *   , 即提交的数据 参数名 是 pet.id pet.name, 这就是级联操作
     * 4.如果提交的数据 的参数名和对象的字段名不匹配, 则对象的属性值就是null
     * @return
     */
    @RequestMapping(value = "/vote03")
    public String test03(Master master) {
        System.out.println("master=" + master);
        //返回结果
        return "success";
    }
}

3.修改request_parameter.jsp

<%--解读
1.这是一个表单, 表单的数据对应Master对象
2.提交的数据参数名和对象的字段名一致即可
--%>
<h1>添加主人信息h1>
<form action="vote/vote03" method="post">
    主人:<input type="text" name="id"/><br/>
    主人:<input type="text" name="name"/><br/>
    宠物名:<input type="text" name="pet.id"/><br/>
    宠物名:<input type="text" name="pet.name"/><br/>
    <input type="submit" value="添加主人和动物"/>
form>

3.Postman测试

get方式提交
SpringMVC_第62张图片post方式提交
SpringMVC_第63张图片

注意事项和细节

1.支持级联数据获取
2.表单的控件名称name需要和javabean对象名称呼应, 否则就是null

使用Servlet API

●说明
1.开发中, 我们可以需要使用到原生的servlet api
2.使用servlet api, 需要引入tomcat/lib下的servlet-api.jar

案例
1.修改VoteHandler.java, 增加方法 test04

@RequestMapping("/vote")
@Controller
public class VoteHandler {
    /**
     * 使用servlet api, 来获取提交的数据
     * @param request
     * @param response
     * @return
     */
    @RequestMapping(value = "/vote04")
    public String test04(HttpServletRequest request,
                         HttpServletResponse response) {
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        System.out.println("username=" + username);
        System.out.println("password=" + password);
        //返回结果
        return "success";
    }
}

2.修改request_parameter.jsp

<h1>演示 servlet api的使用h1>
<form action="vote/vote04" method="post">
    用户名:<input type="text" name="username"/><br/>
    密 码:<input type="password" name="password"/><br/>
    <input type="submit" value="提交"/>
form>

3.Postman测试

post方式提交
SpringMVC_第64张图片


注意事项和细节
1.除了HttpServletRequest, HttpServletResponse其它对象还可以以这样的方式获取
2.HttpSession, java.security.Principal, InputStream, OutputStream, Reader, Writer
3.其中一些对象也可以通过 HttpServletRequest / HttpServletResponse对象获取, 比如Session对象, 既可以通过参数传入, 也可以通过request.getSession()获取, 效果一样, 推荐使用参数形式传入, 更加简单明了

@RequestMapping("/vote")
@Controller
public class VoteHandler {
    /**
     * 使用servlet api, 来获取提交的数据
     * @param request
     * @param response
     * @return
     */
    @RequestMapping(value = "/vote04")
    public String test04(HttpServletRequest request,
                         HttpServletResponse response,
                         HttpSession session2) {
        //获取到session
        //servlet的原生的方式
        HttpSession session = request.getSession();
        System.out.println("session=" + session);
        //注意: 通过参数传入的 session2 和 通过request.getSession()得到的 session对象是同一个
        System.out.println("session2=" + session2);
        //返回结果
        return "success";
    }
}

模型数据

模型数据处理 - 数据放入request

●说明
1.开发中, 控制器/处理器中获取的数据如何放入request域, 然后在前端(VUE/JSP/...)中显示

方式1:
通过HttpServletRequest放入request

方式2:
通过请求的方法参数Map 放入request

方式3:
通过返回 ModelAndView 对象实现request域数据

默认机制放入Request域

1.web路径下新建model_data.jsp

<head>
    <title>测试 模型数据title>
head>
<body>
<h1>添加主人信息h1>
<form action="?" method="?">
  主人号:<input type="text" name="?"/><br/>
  主人名:<input type="text" name="?"/><br/>
  宠物号:<input type="text" name="?"/><br/>
  宠物名:<input type="text" name="?"/><br/>
  <input type="submit" value="添加主人和动物"/>
form>
body>

2.修改VoteHandler.java, 增加方法 test05

@RequestMapping("/vote")
@Controller
public class VoteHandler {
    /**
     * 1.演示将提交的数据->springmvc 封装到java对象->springmvc 会自动的将其放入到request域 [springmvc底层机制]
     * 2.这样我们就可以在跳转到的页面取出数据.
     * @return 参数名和对象的字段名保持一致
     */
    @RequestMapping(value = "/vote05")
    public String test05(Master master) {

        //解读
        //1.springmvc会自动地把获取的model模型, 放入到request域中, 名字就是master
        //  在request域中: ("master", master对象)
        //2.也可以手动将master放入到request...

        //返回一个结果
        return "vote_ok";
    }
}

3.web路径/WEB-INF/pages目录 新建vote_ok.jsp el表达式

<body>
<h1>获取到的数据显示页面h1>
<hr>
取出 request域的数据-通过el表达式获取即可
<br/>
address: <br/>
主人名字= ${requestScope.master.name}
主人id= ${requestScope.master.id}
宠物名字= ${requestScope.master.pet.name}
body>

4.完善model_data.jsp

<h1>添加主人信息h1>
<form action="vote/vote05" method="post">
    主人号:<input type="text" name="id"/><br/>
    主人名:<input type="text" name="name"/><br/>
    宠物号:<input type="text" name="pet.id"/><br/>
    宠物名:<input type="text" name="pet.name"/><br/>
    <input type="submit" value="添加主人和动物"/>
form>

通过HttpServletRequest放入request域

1.修改VoteHandler.java, 增加代码

@RequestMapping("/vote")
@Controller
public class VoteHandler {
        /**
     * 1.演示将提交的数据->springmvc 封装到java对象->springmvc 会自动的将其放入到request域 [springmvc底层机制]
     * 2.这样我们就可以在跳转到的页面取出数据.
     * @return 参数名和对象的字段名保持一致
     */
    @RequestMapping(value = "/vote05")
    public String test05(Master master, HttpServletRequest request) {

        //解读
        //1.springmvc会自动地把获取的model模型, 放入到request域中, 名字就是master
        //  在request域中: ("master", master对象)

        //2.也可以手动将master放入到request
        request.setAttribute("address", "济南");
        //3.如果我们希望修改master的属性值
        master.setName("coco");
        //4.分析一下springmvc默认存放对象到request域中, 属性名是如何确定的
        //  request域 ("master", master) 属性名是类名/类型名 首字母小写

        //返回一个结果
        return "vote_ok";
    }
}

2.vote_ok.jsp

<body>
<h1>获取到的数据显示页面h1>
<hr>
取出 request域的数据-通过el表达式获取即可
<br/>
address: <br/>
主人名字= ${requestScope.master.name}
主人id= ${requestScope.master.id}
宠物名字= ${requestScope.master.pet.name}
body>

测试
SpringMVC_第65张图片

通过方法参数Map 放入request域

1.修改model_data.jsp, 增加代码

<h1>添加主人信息[测试 Map ]h1>
<form action="vote/vote06" method="post">
    主人号:<input type="text" name="id"/><br/>
    主人名:<input type="text" name="name"/><br/>
    宠物号:<input type="text" name="pet.id"/><br/>
    宠物名:<input type="text" name="pet.name"/><br/>
    <input type="submit" value="添加主人和动物"/>
form>

2.修改VoteHandler.java, 增加方法test06

@RequestMapping("/vote")
@Controller
public class VoteHandler {
    /**
     * 演示通过Map 设置数据到request域
     * @return
     */
    @RequestMapping(value = "/vote06")
    public String test06(Master master, Map<String, Object> map) {
        //解读
        //1.需求是通过map对象, 添加属性到request中
        //2.原理分析: springmvc会遍历map, 然后将map的k-v存放到request域中
        map.put("address", "beijing...");
        map.put("master", null);
        //返回一个结果
        return "vote_ok";
    }
}

3.Postman测试

post方式提交
SpringMVC_第66张图片SpringMVC_第67张图片

ModelAndView使用

1.修改model_data.jsp, 增加代码

<h1>添加主人信息[测试ModelAndView]h1>
<form action="?" method="?">
    主人号:<input type="text" name="id"/><br/>
    主人名:<input type="text" name="name"/><br/>
    宠物号:<input type="text" name="pet.id"/><br/>
    宠物名:<input type="text" name="pet.name"/><br/>
    <input type="submit" value="添加主人和动物"/>
form>

2.修改VoteHandler.java, 增加方法test07

@RequestMapping("/vote")
@Controller
public class VoteHandler {
    /**
     * 演示通过返回ModelAndView对象, 将数据放入request中
     * @return
     */
    @RequestMapping(value = "/vote07")
    public ModelAndView test07(Master master) {

        ModelAndView modelAndView = new ModelAndView();
        //放入属性到modelAndView对象
        modelAndView.addObject("address", "shanghai~");
        modelAndView.addObject("master", null);
        //可以把从数据库得到的数据->对象->放入到modelAndView[service-dao-db]
        //这里指定要跳转的视图名称
        modelAndView.setViewName("vote_ok");
        //返回结果
        return modelAndView;
    }
}

3.完善model_data.jsp

<h1>添加主人信息[测试ModelAndView]h1>
<form action="vote/vote07" method="post">
    主人号:<input type="text" name="id"/><br/>
    主人名:<input type="text" name="name"/><br/>
    宠物号:<input type="text" name="pet.id"/><br/>
    宠物名:<input type="text" name="pet.name"/><br/>
    <input type="submit" value="添加主人和动物"/>
form>

4.Postman测试

post方式提交
SpringMVC_第68张图片
SpringMVC_第69张图片
注意事项和细节
1.从本质看, 请求响应的方法return xx时返回了一个字符串, 其实本质是返回了一个ModeAndView对象, 只是默认被封装起来的.
2.ModelAndView即可以包含model数据, 也可以包含视图信息
3.ModelAndView对象的addObject方法可以添加key-val数据.
4.ModelAndView对象setView方法可以指定视图名称

模型数据处理 - 数据放入session

说明
开发中, 控制器 / 处理器中获取的数据如何放入session域, 然后在前端(VUE/JSP/...)取出显示.

案例
1.修改model_data.jsp, 增加代码

<h1>添加主人信息[测试session]h1>
<form action="?" method="?">
    主人号:<input type="text" name="id"/><br/>
    主人名:<input type="text" name="name"/><br/>
    宠物号:<input type="text" name="pet.id"/><br/>
    宠物名:<input type="text" name="pet.name"/><br/>
    <input type="submit" value="添加主人和动物"/>
form>

2.修改VoteHandler.java, 增加方法test08

@RequestMapping("/vote")
@Controller
public class VoteHandler {
    /**
     * 演示如何将数据设置到session域中
     * @return
     */
    @RequestMapping(value = "/vote08")
    public String test08(Master master, HttpSession httpSession) {
        //master对象是默认放在request域中

        //我们将master对象放入到session
        httpSession.setAttribute("master", master);
        httpSession.setAttribute("address", "beijing``");
        return "vote_ok";
    }
}

3.vote_ok.jsp增加代码

<head>
    <title>vote_oktitle>
head>
<body>
取出 session域的数据<br/>
address: ${sessionScope.address}<br/>
主人名字= ${sessionScope.master.name}<br/>
主人信息= ${sessionScope.master}<br/>
body>

4.完善model_data.jsp

<h1>添加主人信息[测试session]h1>
<form action="vote/vote08" method="post">
    主人号:<input type="text" name="id"/><br/>
    主人名:<input type="text" name="name"/><br/>
    宠物号:<input type="text" name="pet.id"/><br/>
    宠物名:<input type="text" name="pet.name"/><br/>
    <input type="submit" value="添加主人和动物"/>
form>

4.Postman测试

post方式提交
SpringMVC_第70张图片

模型数据 - @ModelAttribute

●基本说明
开发中, 有时需要使用某个前置方法(比如prepareXxx(), 方法名由程序员定)给目标方法准备一个模型对象

1.@ModelAttribute注解可以实现 这样的需求
2.在某个方法上, 增加了@ModelAttribute注解后
3.那么在调用该Handler的任何一个方法时, 都会先调用这个方法

案例
1.修改VoteHandler.java, 增加方法prepareModel

    /**
     * 解读
     * 1.当Handler的方法被标识 @ModelAttribute, 就视为一个前置方法
     * 2.当调用Handler的其它方法时, 都会先执行该前置方法
     * 3.类似我们前面学习Spring时, AOP的前置通知
     * 4.prepareModel 前置方法, 会切入到其它方法前执行. 底层是AOP机制
     */
    @ModelAttribute
    public void prepareModel() {
        System.out.println("prepareModel.........完成准备工作.........");
    }

@ModelAttribute最佳实践
修改用户信息(就是经典的使用这种机制的应用), 流程如下
1.在修改前, 在前置方法中从数据库查出这个用户
2.在修改方法(目标方法)中, 可以使用前置方法从数据库查询这个对象
3.如果表单中对用户的某个属性修改了, 则以新的数据为准, 如果没有修改, 则以数据库的信息为准. 比如, 用户的某个属性不能修改, 就保持原来的值.

视图和视图解析器

基本介绍
1.在SpirngMVC中的目标方法最终返回都是一个视图(有各种视图)
2.返回的视图都会由一个视图解析器来处理(视图解析器有很多种)

自定义视图

1.在默认情况下, 我们都是返回默认的视图, 然后返回的视图交由SpringMVCInternalResourceViewResolver视图解析器来处理的.


<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    
    <property name="prefix" value="/WEB-INF/pages/"/>
    <property name="suffix" value=".jsp"/>
bean>

2.在实际开发中, 我们有时需要自定义视图, 这样可以满足更多更复杂的需求.


实例-代码实现
1.在web路径目录下 新建view.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>自定义视图测试title>
head>
<body>
<h2>自定义视图测试h2>
<a href="?">点击到自定义视图a>
body>
html>

2.com.zzw.web.viewresolver包下 新建GoodsHandler

@RequestMapping("/goods")
@Controller
public class GoodsHandler {

    @RequestMapping("/buy")
    public String buy() {
        System.out.println("buy() 被调用...");
        return "?";//待会再填写自定义视图名称
    }
}

3.com.zzw.web.viewresolver包下 新建MyView

/**
 * 解读
 * 1.MyView 继承了AbstractView, 就可以作为一个视图使用
 * 2.@Component(value="myView"), 该视图会注入到容器中, 名字/id 是 zzwView
 */
@Component(value = "zzwView")
public class MyView extends AbstractView {
    @Override
    protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
        //完成视图渲染
        //并且可以确定我们要跳转的页面
        System.out.println("进入到自己的视图");
    }
}

4.在web路径/WEB-INF/pages目录下 新建my_view.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>my_view页面title>
head>
<body>
<h2>进入到my_view页面h2>
<p>是从自定义视图来的p>
body>
html>

5.补充MyView的代码

/**
 * 解读
 * 1.MyView 继承了AbstractView, 就可以作为一个视图使用
 * 2.@Component(value="myView"), 该视图会注入到容器中, 名字/id 是 zzwView
 */
@Component(value = "zzwView")
public class MyView extends AbstractView {
    @Override
    protected void renderMergedOutputModel(Map<String, Object> model,
                                           HttpServletRequest request,
                                           HttpServletResponse response) throws Exception {
        //完成视图渲染
        //并且可以确定我们要跳转的页面[请求转发] /WEB-INF/pages/my_view.jsp
        System.out.println("进入到自己的视图");
        //解读
        //1.下面就是进行请求转发到 /WEB-INF/pages/my_view.jsp
        //2./WEB-INF/pages/my_view.jsp 会被springmvc解析
        //  /springmvc/WEB-INF/pages/my_view.jsp
        request.getRequestDispatcher("/WEB-INF/pages/my_view.jsp")
                .forward(request, response);
    }
}

6.配置springDispatcherServlet-servlet.xml, 增加自定义视图解析器


<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    
    <property name="prefix" value="/WEB-INF/pages/"/>
    <property name="suffix" value=".jsp"/>
bean>

<bean class="org.springframework.web.servlet.view.BeanNameViewResolver">
    <property name="order" value="99"/>
bean>

7.view.jsp修改请求路径

<h2>自定义视图测试h2>
<a href="goods/buy">点击到自定义视图a>

8.GoodsHandler.java完善代码

@RequestMapping("/goods")
@Controller
public class GoodsHandler {

    @RequestMapping("/buy")
    public String buy() {
        System.out.println("buy() 被调用...");
        return "zzwView";//待会再填写自定义视图名称
    }
}

9.Postman测试
SpringMVC_第71张图片

自定义视图-步骤梳理

1.创建一个Viewbean, 该bean需要继承自AbstractView, 并实现renderMergedOutputModel方法
2.并把自定义View加入到IOC容器中
3.自定义视图的视图处理器, 使用BeanNameViewResolver, 这个视图处理器也需要配置到ioc容器
4.BeanNameViewResolver的调用优先级需要设置一下, 设置orderInteger.MAX_VALUE小的值, 以确保其在InternalResourceViewResolver之前被调用

自定义视图解析器执行流程

1.SpringMVC调用目标方法, 返回自定义ViewIOC容器中的id
2.SpringMVC调用BeanNameViewResolver解析视图: 从IOC容器中获取 返回id值对应的bean, 即自定义View的对象
3,SpringMVC调用自定义视图的renderMergedOutputModel方法渲染视图
4.说明:如果在SpringMVC调用目标方法, 返回自定义View容器中的id, 不存在, 则仍然按照默认的视图处理器机制处理.


debug源码
SpringMVC_第72张图片

SpringMVC_第73张图片
SpringMVC_第74张图片
SpringMVC_第75张图片
判断是否实现了View接口, 如果实现了, 返回View对象
SpringMVC_第76张图片
SpringMVC_第77张图片
SpringMVC_第78张图片
SpringMVC_第79张图片
进入到my_view.jsp
SpringMVC_第80张图片

默认视图解析器执行流程

1.配置默认视图解析器的优先级


<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    
    <property name="prefix" value="/WEB-INF/pages/"/>
    <property name="suffix" value=".jsp"/>
    
    <property name="order" value="10"/>
bean>

2.debug源码

SpringMVC_第81张图片

SpringMVC_第82张图片
进入到默认视图解析器
SpringMVC_第83张图片
SpringMVC_第84张图片
由于没有zzwView.jsp页面, 页面会报错
SpringMVC_第85张图片

多个视图解析器执行流程

案例1: 假设自定义视图解析器的优先级大于默认视图解析器

1.将默认视图解析器的优先级大小设置为默认


<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    
    <property name="prefix" value="/WEB-INF/pages/"/>
    <property name="suffix" value=".jsp"/>
    
    
bean>

2.将自定义视图MyView.java的beanName改为zzwViewx

/**
 * 解读
 * 1.MyView 继承了AbstractView, 就可以作为一个视图使用
 * 2.@Component(value="myView"), 该视图会注入到容器中, 名字/id 是 zzwView
 */
@Component(value = "zzwViewx")
public class MyView extends AbstractView {
    @Override
    protected void renderMergedOutputModel(Map<String, Object> model,
                                           HttpServletRequest request,
                                           HttpServletResponse response) throws Exception {
        //完成视图渲染
        //并且可以确定我们要跳转的页面[请求转发] /WEB-INF/pages/my_view.jsp
        System.out.println("进入到自己的视图");
        //解读
        //1.下面就是进行请求转发到 /WEB-INF/pages/my_view.jsp
        //2./WEB-INF/pages/my_view.jsp 会被springmvc解析
        //  /springmvc/WEB-INF/pages/my_view.jsp
        request.getRequestDispatcher("/WEB-INF/pages/my_view.jsp")
                .forward(request, response);
    }
}

3.debug源码

SpringMVC_第86张图片
SpringMVC_第87张图片
在容器中找不到id为zzwView的bean对象. 参数viewNamezzwView,
SpringMVC_第88张图片BeanName视图解析器返回了空, 开始循环第二个默认视图解析器
SpringMVC_第89张图片SpringMVC_第90张图片对返回的view对象进行估算. 没有 /WEB-INF/pages/zzwView.jsp 这个文件, 会报404错误.
SpringMVC_第91张图片SpringMVC_第92张图片

案例2: 假设默认视图解析器的优先级大于自定义视图解析器

1.将默认视图解析器的优先级大小设置为10, 此时自定义视图解析器的优先级是99.


<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    
    <property name="prefix" value="/WEB-INF/pages/"/>
    <property name="suffix" value=".jsp"/>
    
    <property name="order" value="10"/>
bean>

2.将自定义视图MyView.java的beanName改为zzwView

/**
 * 解读
 * 1.MyView 继承了AbstractView, 就可以作为一个视图使用
 * 2.@Component(value="myView"), 该视图会注入到容器中, 名字/id 是 zzwView
 */
@Component(value = "zzwView")
public class MyView extends AbstractView {
    @Override
    protected void renderMergedOutputModel(Map<String, Object> model,
                                           HttpServletRequest request,
                                           HttpServletResponse response) throws Exception {
        //完成视图渲染
        //并且可以确定我们要跳转的页面[请求转发] /WEB-INF/pages/my_view.jsp
        System.out.println("进入到自己的视图");
        //解读
        //1.下面就是进行请求转发到 /WEB-INF/pages/my_view.jsp
        //2./WEB-INF/pages/my_view.jsp 会被springmvc解析
        //  /springmvc/WEB-INF/pages/my_view.jsp
        request.getRequestDispatcher("/WEB-INF/pages/my_view.jsp")
                .forward(request, response);
    }
}

3.debug源码
SpringMVC_第93张图片
SpringMVC_第94张图片SpringMVC_第95张图片只要执行了默认视图解析器, 找不到页面, 会报404, 不会再执行下一个视图解析器.
SpringMVC_第96张图片

指定请求转发或者重定向

● 目标方法中指定转发或者重定向

1.默认返回的方法是请求转发, 然后用视图处理器进行处理, 比如在目标方法中这样写:
SpringMVC_第97张图片

2.也可以在目标方法中直接指定重定向或请求转发的url地址

3.如果指定重定向, 可以定向到web路径下的资源SpringMVC_第98张图片

但是有个例外: 不能重定向到/WEB-INF目录.
原因: WEB-INF下的资源是服务器内部访问的, 浏览器不能访问
SpringMVC_第99张图片

案例
1.修改com.zzw.web.viewresolver.GoodsHandler.java, 增加方法order

@RequestMapping("/goods")
@Controller
public class GoodsHandler {

    @RequestMapping("/order")
    public String order() {
        System.out.println("===========order()===========");
        //请求转发到 /WEB-INF/pages/my_view.jsp
        //下面的这个路径 /WEB-INF/pages/my_view.jsp 会被解析成 /springmvc/WEB-INF/pages/my_view.jsp
        //return "forward:/WEB-INF/pages/my_view.jsp";
        //return "forward:/aa/bb/ok.jsp";

        //直接指定要重定向的页面
        //1. 对应重定向来说, 不能重定向到 /WEB-INF/ 目录下
        //2. redirect 关键字, 表示进行重定向
        //3. /login.jsp 在服务器解析成 /springmvc/login.jsp
        return "redirect:/login.jsp";

        //http://localhost:8080/springmvc/WEB-INF/pages/my_view.jsp
        //浏览器不能访问WEB-INF下的资源
        //return "redirect:/WEB-INF/pages/my_view.jsp";
    }
}

2.view.jsp增加代码

<body>
<h2>自定义视图测试h2>
<a href="goods/buy">点击到自定义视图a><br/>
<a href="goods/order">测试在目标方法中指定请求转发或者重定向到页面a>
body>

测试1
1.请求转发到 /WEB-INF/pages/my_view.jsp
2.下面的这个路径 /WEB-INF/pages/my_view.jsp 会被解析成 /springmvc/WEB-INF/pages/my_view.jsp

return "forward:/WEB-INF/pages/my_view.jsp";

测试2
1.请求转发到 /aa/bb/ok.jsp
2.下面的这个路径 /aa/bb/ok.jsp 会被解析成 /springmvc/aa/bb/ok.jsp

return "forward:/aa/bb/ok.jsp";

测试3
直接指定要重定向的页面
1.对应重定向来说, 不能重定向到 /WEB-INF/ 目录下
2.redirect 关键字, 表示进行重定向
3./login.jsp 在服务器解析成 /springmvc/login.jsp

return "redirect:/login.jsp";

在这里插入图片描述
SpringMVC_第100张图片

测试4
1.重定向到 /WEB-INF/pages/my_view.jsp
2.下面的这个路径 /WEB-INF/pages/my_view.jsp 在服务器会被解析成 /springmvc/WEB-INF/pages/my_view.jsp

//http://localhost:8080/springmvc/WEB-INF/pages/my_view.jsp
return "redirect:/WEB-INF/pages/my_view.jsp";

SpringMVC_第101张图片WEB-INF下的资源是服务器内部访问的, 浏览器不能访问
SpringMVC_第102张图片

指定请求转发流程分析

SpringMVC_第103张图片

打断点
SpringMVC_第104张图片SpringMVC_第105张图片

debug源码

SpringMVC_第106张图片
SpringMVC_第107张图片

调用默认的视图解析器
SpringMVC_第108张图片SpringMVC_第109张图片

已经作为一个bean对象存储在容器中
SpringMVC_第110张图片

开始渲染

renderMergedOutputModel()MyView.java中的相仿

rd = request.getRequestDispatcher(dispatcherPath);

指定重定向流程分析

SpringMVC_第111张图片
打断点
SpringMVC_第112张图片SpringMVC_第113张图片

debug源码

SpringMVC_第114张图片
SpringMVC_第115张图片
SpringMVC_第116张图片

SpringMVC有很多视图种类
SpringMVC_第117张图片

调用RedirectView的视图渲染方法

作业布置

1.熟悉前面的SpringMVC映射数据请求, 模型数据, 视图和视图解析的案例
2.清晰Debug源码的流程
3.完成一个简单的用户登录案例
1)编写登陆页面login.jsp
2)LoginHandler [doLogin方法], 如果用户输入用户名是zzw, 密码123, 就可以登陆成功; 否则登陆失败
3)创建JavaBean: User.java
4)表单提交数据到doLogin方法, 以User对象形式接收
5)登陆成功到, 页面 login_ok.jsp, 并显示登陆欢迎信息 [显示用户名, 模型数据会自动填充到request域中]
6)登陆失败到, 页面 login_err.jsp, 并给出重新登录的超链接 [考察web工程路径应用问题]


代码实现

1.在web路径/homework下 新建login.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>登陆页面title>
head>
<body>
<form action="?">
    用户名: <input name="username" type="text"/><br/>
    密 码: <input name="password" type="password"/><br/>
    <input type="submit" value="登录"/>
form>
body>
html>

2.在com.zzw.web.homework下新建LoginHandler

@Controller
@RequestMapping(value = "/user")
public class LoginHandler {

	//处理登录
    @RequestMapping(value = "/doLogin", params ={"username","password"}, method = RequestMethod.GET)
    public String doLogin(String username, String password) {
   		 System.out.println(username);//测试
   		 System.out.println(password);//测试
   		 
        if ("zzw".equals(username) && "123".equals(password)) {
            System.out.println("登录成功");
        } else {
            System.out.println("登陆失败");
        }
        return "";
    }
}

3.填充login.jsp的请求路径 涉及-web工程路径问题

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>登陆页面title>
head>
<body>
<form action="../user/doLogin">
    用户名: <input name="username" type="text"/><br/>
    密 码: <input name="password" type="password"/><br/>
    <input type="submit" value="登录"/>
form>
body>
html>

也可以这么写

<%--
    <%=request.getContextPath()%>/user/doLogin => /springmvc/user/doLogin
    /springmvc/user/doLogin 被浏览器解析成 http://localhost:8080/springmvc/user/doLogin
--%>
<form action="<%=request.getContextPath()%>/user/doLogin">

4.在com.zzw.web.homework.entity下新建User.java

public class User {
    private String username;
    private String password;

	//setter, getter, toString方法
}

5.表单登录提交到doLogin方法, 以User对象形式接收 封装成javaBean. 修改UserHandler

@Controller
@RequestMapping(value = "/user")
public class LoginHandler {

    @RequestMapping(value = "/doLogin", params ={"username","password"}, method = RequestMethod.GET)
    public String doLogin(User user) {
        System.out.println(user.getUsername());//测试
        System.out.println(user.getPassword());//测试

        if ("zzw".equals(user.getUsername()) && "123".equals(user.getPassword())) {
            System.out.println("登录成功");
        } else {
            System.out.println("登陆失败");
        }
        return "";
    }
}

6.登录成功到login_ok.jsp, 并显示登陆欢迎信息 模型数据默认放入request域. 在web路径/WEB-INF/pages/homework下 新建login_ok.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>登陆成功title>
head>
<body>
<h1>欢迎 ${requestScope.user.username} 登陆成功h1>
body>
html>

7.登陆失败到login_err.jsp, 并给出重新登陆的超链接. 在web路径/WEB-INF/pages/homework下 新建login_err.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>登陆失败</title>
</head>
<body>
<h1>登陆失败</h1>
<a href="?">重新登陆</a>
</body>
</html>

8.在springDispatcherServlet-servlet.xml中增加一个优先级为9解析器


<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver02">
    
    <property name="prefix" value="/WEB-INF/pages/homework/"/>
    <property name="suffix" value=".jsp"/>
    
    <property name="order" value="9"/>
bean>

9.修改UserHandler 注释部分参考服务器对路径的解析

@Controller
@RequestMapping(value = "/user")
public class LoginHandler {

    @RequestMapping(value = "/doLogin", params ={"username","password"}, method = RequestMethod.GET)
    public String doLogin(User user) {
        if ("zzw".equals(user.getUsername()) && "123".equals(user.getPassword())) {
        	//也可以这么写
        	//return "forward:/WEB-INF/pages/homework/login_ok.jsp";
            return "login_ok";
        } else {
      	    //也可以这么写
        	//return "forward:/WEB-INF/pages/homework/login_err.jsp";
            return "login_err";
        }
    }
}

10.测试
SpringMVC_第118张图片SpringMVC_第119张图片

11.完成重新登录的超链接

SpringMVC_第120张图片SpringMVC_第121张图片
我们可以查看到当前浏览器地址栏的路径, 根据 相对路径的知识点, 我们完善login_err.jsp重新登陆的超链接

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>登陆失败title>
head>
<body>
<h1>登陆失败h1>
<a href="../homework/login.jsp">重新登陆a>
body>
html>

也可以这么写

<%--
    <%=request.getContextPath()%>/homework/login.jsp
    即 /springmvc/homework/login.jsp
    会被浏览器解析成 http://localhost:8080/springmvc/homework/login.jsp
--%>
<a href="<%=request.getContextPath()%>/homework/login.jsp">重新登陆a>

在这里插入图片描述
下一篇: 手动实现SpringMVC底层机制, 敬请期待…

你可能感兴趣的:(SpringMVC,spring,springmvc,java)