Spring MVC基础学习笔记

学习来源:黑马程序员

学习时间:2021年3月14日,2023年6月12日

1 三层架构

1.1 介绍

服务器端分成三层架构:

image-20211215195132179

1.2 MVC模型

MVC的全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,是一种软件设计典范。它是用一种业务逻辑、数据与界面显示分离的方法来组织代码,将众多的业务逻辑聚集到一个部件里面,在需要改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑,达到减少编码的时间。

  • V即View视图是指用户看到并与之交互的界面。比如由html元素组成的网页界面,或者软件的客户端界面。MVC的好处之一在于它能为应用程序处理很多不同的视图。在视图中其实没有真正的处理发生,它只是作为一种输出数据并允许用户操纵的方式。
  • M即Model模型是指模型,表示业务规则。在MVC的三个部件中,模型拥有最多的处理任务。被模型返回的数据是中立的,模型与数据格式无关,这样一个模型能为多个视图提供数据,由于应用于模型的代码只需写一次就可以被多个视图重用,所以减少了代码的重复性。
  • C即Controller控制器是指控制器接受用户的输入并调用模型和视图去完成用户的需求,控制器本身不输出任何东西和做任何处理。它只是接收请求并决定调用哪个模型构件去处理请求,然后再确定用哪个视图来显示返回的数据。

image-20211215195140962

image-20231014174625911

过程:

  1. 首先用户发送请求——>DispatcherServlet,前端控制器收到请求后自己不进行处理,而是委托给其他的解析器进行处理,作为统一访问点,进行全局的流程控制;
  2. DispatcherServlet——>HandlerMapping, HandlerMapping 将会把请求映射为 HandlerExecutionChain 对象(包含一个Handler处理器(页面控制器)对象、多个HandlerInterceptor 拦截器)对象,通过这种策略模式,很容易添加新的映射策略;
  3. DispatcherServlet——>HandlerAdapter,HandlerAdapter 将会把处理器包装为适配器,从而支持多种类型的处理器, 即适配器设计模式的应用,从而很容易支持很多类型的处理器;
  4. HandlerAdapter——>处理器功能处理方法的调用,HandlerAdapter 将会根据适配的结果调用真正的处理器的功能处理方法,完成功能处理;并返回一个ModelAndView 对象(包含模型数据、逻辑视图名);
  5. ModelAndView 的逻辑视图名——> ViewResolver,ViewResolver 将把逻辑视图名解析为具体的View,通过这种策略模式,很容易更换其他视图技术;
  6. View——>渲染,View 会根据传进来的Model 模型数据进行渲染,此处的Model 实际是一个Map 数据结构,因此 很容易支持其他视图技术;
  7. 返回控制权给DispatcherServlet,由DispatcherServlet 返回响应给用户,到此一个流程结束。

1.3 Spring MVC概述

SpringMVC 是一种基于 Java 的实现 MVC 设计模型的请求驱动类型的轻量级 Web 框架,属于 Spring FrameWork 的后续产品,已经融合在 Spring Web Flow 里面。Spring 框架提供了构建 Web 应用程序的全功能 MVC 模块。使用 Spring 可插入的 MVC 架构,从而在使用 Spring 进行 WEB 开发时,可以选择使用Spring 的 Spring MVC 框架或集成其他 MVC 开发框架,如 Struts1(现在一般不用),Struts2 等。

SpringMVC 已经成为目前最主流的 MVC 框架之一,并且随着 Spring3.0 的发布,全面超越 Struts2,成为最优秀的 MVC 框架。它通过一套注解,让一个简单的 Java 类成为处理请求的控制器,而无须实现任何接口。同时它还支持RESTful 编程风格的请求。

image-20211215195150570

2 SpringMVC入门程序

2.1 需求分析

image-20211215195159663

  1. 搭建开发环境

  2. 编写入门程序

2.2 搭建环境

导入坐标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<!-- 版本锁定 --> 
<properties>
<spring.version>5.0.2.RELEASE</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.0</version>
<scope>provided</scope>
</dependency>
</dependencies>

前端拦截器

web.xml

1
2
3
4
5
6
7
8
9
10
11
12
<web-app>
<display-name>Archetype Created Web Application</display-name>
<!--前端控制器-->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>

配置Tomcat服务器

image-20211215195209617

image-20211215195217672

2.3 代码编写

项目结构

image-20211215195237123

前端代码

index.jsp

1
2
3
4
5
6
7
8
9
10
11
12
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h3>入门程序</h3>

<a href="/hello">入门程序</a>

</body>
</html>

success.jsp

1
2
3
4
5
6
7
8
9
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h3>入门成功</h3>
</body>
</html>

后端代码

HelloController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 控制器类
*/
@Controller
public class HelloController {

// path:sayHello()的请求路径
@RequestMapping(path = "/hello")
public String sayHello(){
System.out.println("Hello SpringMVC!");
return "success";// 默认返回到success.jsp页面上
}
}

配置文件

SpringMVC框架的配置文件springmvc.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=" http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--配置要扫描注解的包-->
<context:component-scan base-package="com.hongyi"></context:component-scan>

<!--配置视图解析器对象-->
<!--class:固定;id自取-->
<bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!--prefix:视图资源所在目录-->
<!--suffix:视图的后缀名-->
<property name="prefix" value="/WEB-INF/pages/"></property>
<property name="suffix" value=".jsp"></property>
</bean>

<!--开启SpringMVC框架注解的支持-->
<mvc:annotation-driven></mvc:annotation-driven>

</beans>

web.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<web-app>
<display-name>Archetype Created Web Application</display-name>
<!--前端控制器、中央控制器-->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--加载springmvc的配置文件-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<!--只要服务器启动就加载-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<!--拦截所有的请求-->
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>

2.4 入门程序流程分析

  1. 启动服务器,加载一些配置文件
  • DispatcherServlet对象
  • springmvc.xml被加载了
  • HelloController(处理器映射器),InternalResourceViewResolver(视图解析器类)被spring创建成单例对象,被Spring的ioc容器管理
  1. 发送请求,后台处理请求

流程图如下:

image-20211215195248315

  1. 总结:
  • 服务器启动,应用被加载。读取到 web.xml 中的配置创建 spring 容器并且初始化容器中的对象。
  • 浏览器发送请求,被 DispatherServlet 捕获,该 Servlet 并不处理请求,而是把请求转发出去。转发的路径是根据请求 URL,匹配@RequestMapping 中的内容。
  • 匹配到了后,执行对应方法。该方法有一个返回值。
  • 根据方法的返回值,借助 InternalResourceViewResolver 找到对应的结果视图。
  • 渲染结果视图,响应浏览器。

2.5 涉及的组件

image-20211215195255878

DispatcherServlet:前端控制器

用户请求到达前端控制器,它就相当于 mvc 模式中的 c,dispatcherServlet 是整个流程控制的中心,由它调用其它组件处理用户的请求,dispatcherServlet 的存在降低了组件之间的耦合性。

HandlerMapping:处理器映射器

HandlerMapping 负责根据用户请求找到 Handler 即处理器,SpringMVC 提供了不同的映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。

Handler:处理器

它就是我们开发中要编写的具体业务控制器controller。由 DispatcherServlet 把用户请求转发到 Handler。由Handler 对具体的用户请求进行处理。

HandlAdapter:处理器适配器

通过 HandlerAdapter 对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。

View Resolver:视图解析器

View Resolver 负责将处理结果生成 View 视图,View Resolver 首先根据逻辑视图名解析成物理视图名即具体的页面地址,再生成 View 视图对象,最后对 View 进行渲染将处理结果通过页面展示给用户。

View:视图

SpringMVC 框架提供了很多的 View 视图类型的支持,包括:jstlView、freemarkerView、pdfView等。我们最常用的视图就是 jsp。一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要由程序员根据业务需求开发具体的页面。

<mvc:annotation-driven>说明

在 SpringMVC 的各个组件中,==处理器映射器、处理器适配器、视图解析器称为 SpringMVC 的三大组件==。使 用 <mvc:annotation-driven>自动加载 RequestMappingHandlerMapping (处理映射器) 和RequestMappingHandlerAdapter ( 处 理 适 配 器 ) , 可 用 在 SpringMVC.xml 配 置 文 件 中 使 用<mvc:annotation-driven>替代注解处理器和适配器的配置。

它就相当于在 xml 中配置了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!-- 上面的标签相当于 如下配置-->
<!-- Begin -->
<!-- HandlerMapping -->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"></bean>

<bean
class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"></bean>
<!-- HandlerAdapter -->
<bean
class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerA
dapter">
</bean>
<bean
class="org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter"></bean>
<bean
class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"></bean>
<!-- HadnlerExceptionResolvers -->
<bean
class="org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExcept
ionResolver"></bean>
<bean
class="org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolv
er"></bean>
<bean
class="org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver"
></bean>
<!-- End -->

2.6 RequestMapping注解

源码

1
2
3
4
5
6
7
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
//...
}

作用

用于建立请求 URL 和处理请求方法之间的对应关系。

出现位置

  • 类上:请求 URL 的第一级访问目录。此处不写的话,就相当于应用的根目录。写的话需要以/开头。它出现的目的是为了使我们的 URL 可以按照模块化管理:例如:
1
2
3
4
5
6
7
8
9
账户模块:
/account/add
/account/update
/account/delete
...
订单模块:
/order/add
/order/update
/order/delete

前面的部分就是把RequsetMappding 写在类上,使我们的 URL 更加精细。

  • 方法上:请求 URL 的第二级访问目录。

  • 代码演示:

1
<a href="/user/testRequestMapping">RequestMapping注解</a>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Controller
@RequestMapping(path = "/user")// 注解在类上,表示请求url的第一部分的地址
public class HelloController {

// path:sayHello()的请求路径
@RequestMapping(path = "/hello")
public String sayHello(){
System.out.println("Hello SpringMVC!");
return "success";// 默认返回到success.jsp页面上
}

/**
* RequestMapping注解
* @return
*/
@RequestMapping(path = "/testRequestMapping")// 实际对应请求地址为/user/testRequestMapping
public String testRequestMapping(){
System.out.println("测试RequestMapping注解...");
return "success";
}
}

RequestMapping注解的属性

  • value:用于指定请求的 URL。它和 path 属性的作用是一样的。当只有一个value或path属性时可以省略,否则不能省略。
1
@RequestMapping("/hello")
  • method:用于指定请求的方式。
1
@RequestMapping(method = {RequestMethod.GET,RequestMethod.POST})// RequestMethod是一个枚举类
  • params:用于指定限制请求参数的条件。它支持简单的表达式。要求请求参数的 key 和 value 必须和配置的一模一样。例如:
1
2
params = {"accountName"}// 表示请求参数必须有 accountName
params = {"moeny!100"}// 表示请求参数中 money 不能是 100。
  • headers:用于指定限制请求消息头的条件。

注意:以上4个属性只要出现 2 个或以上时,他们的关系是与的关系。

3 请求参数的绑定

3.1 绑定机制

表单中请求参数都是基于 key=value 键值对的。SpringMVC 绑定请求参数的过程是通过把表单提交请求参数,作为控制器中方法参数进行绑定的。例如:

1
2
<!--请求参数是:accountId=10-->
<a href="account/findAccount?accountId=10">查询账户</a>
1
2
3
4
5
6
7
8
9
/**
* 查询账户
* @return
*/
@RequestMapping("/findAccount")
public String findAccount(Integer accountId) {
System.out.println("查询了账户。。。。"+accountId);
return "success";
}

3.2 支持的数据类型

  • 基本类型参数:包括基本类型和 String 类型

  • POJO类型参数:包括实体类,以及关联的实体类

  • 数组和集合类型参数:包括 List 结构和 Map 结构的集合(包括数组)

说明:SpringMVC 绑定请求参数是自动实现的,但是要想使用,必须遵循使用要求。

3.3 基本类型和String类型作为参数

  • 使用要求:要求我们的参数名称必须和控制器中方法的形参名称保持一致

代码演示

jsp代码:

1
<a href="param/testParam?username=hehe&password=123456">请求参数绑定</a>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 请求参数绑定
*/
@Controller
@RequestMapping("/param")
public class ParamController {
/**
* 请求参数绑定的入门案例
* 形参名必须是username和password
* @return
*/
@RequestMapping("/testParam")
public String testParam(String username,String password){
System.out.println("执行了...");
System.out.println("你的用户名是:"+username);
System.out.println("你的密码是:"+password);
return "success";
}
}

执行结果:

image-20211215195309666

3.4 POJO类型作为参数

3.4.1 要求

要求表单中参数名称和 POJO 类的属性名称保持一致,并且控制器方法的参数类型是 POJO 类型。

代码演示

Account.java

1
2
3
4
5
6
7
8
public class Account implements Serializable {
private String username;
private String password;
private double money;
// 引用类型的属性
private User user;
// 省略了set/get方法
}

User.java

1
2
3
4
5
public class User implements Serializable {
private String uname;
private Integer age;
// 省略了set/get方法
}

jsp代码

1
2
3
4
5
6
7
8
<form action="/param/saveAccount" method="post">
姓名:<input type="text" name="username"/><br/>
密码:<input type="text" name="password"/><br/>
金额:<input type="text" name="money"/><br/>
用户姓名:<input type="text" name="user.uname"/><br/>
用户年龄:<input type="text" name="user.age"/><br/>
<input type="submit" value="提交"/>
</form>

控制层代码

1
2
3
4
5
6
7
8
9
10
/**
* 请求参数绑定把数据封装到javabean的类中
* @return
*/
@RequestMapping("/saveAccount")
public String saveAccount(Account account){
System.out.println("执行了...");
System.out.println(account);
return "success";
}

执行结果

image-20211215195317905

3.4.2 配置解决中文乱码的过滤器

web.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
<!--配置解决中文乱码的过滤器-->
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

3.5 集合类型作为参数

要求

  • 第一种:要求集合类型的请求参数必须在 POJO 中。在表单中请求参数名称要和 POJO 中集合属性名称相同。给 List 集合中的元素赋值,使用下标。给 Map 集合中的元素赋值,使用键值对。

  • 第二种:接收的请求参数是 json 格式数据。需要借助一个注解实现。

代码演示

1
2
3
4
5
6
7
8
9
10
11
12
13
<!--类中存在list和map的集合-->
<form action="/param/saveAccount" method="post">
姓名:<input type="text" name="username"/><br/>
密码:<input type="text" name="password"/><br/>
金额:<input type="text" name="money"/><br/>

用户姓名:<input type="text" name="list[0].uname"/><br/>
用户年龄:<input type="text" name="list[0].age"/><br/>

用户姓名:<input type="text" name="map['one'].uname"/><br/>
用户年龄:<input type="text" name="map['one'].age"/><br/>
<input type="submit" value="提交"/>
</form>

Account类属性:

1
2
3
4
5
6
private String username;
private String password;
private double money;
// 集合属性
private List<User> list;
private Map<String,User> map;

执行结果:

image-20211215195325747

3.6 自定义类型转换器

3.6.1 演示异常

1
2
3
4
5
6
7
<!--自定义类型转换器-->
<form action="/param/saveUser" method="post">
用户姓名:<input type="text" name="uname"/><br/>
用户年龄:<input type="text" name="age"/><br/>
用户生日:<input type="text" name="date"/><br/>
<input type="submit" value="提交"/>
</form>
1
2
3
4
5
public class User implements Serializable {
private String uname;
private Integer age;
private Date date;
}
1
2
3
4
5
6
7
8
9
10
11
/**
* 自定义类型转换器
* @param user
* @return
*/
@RequestMapping("/saveUser")
public String saveUser(User user){
System.out.println("执行了...");
System.out.println(user);
return "success";
}

正常情况:

image-20211215195347575

image-20211215195359638

异常情况:

image-20211215195408371

image-20211215195416343

3.6.2 转换器代码编写

字符串转日期的工具类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 把字符串转换成日期
*/
public class StringToDateConverter implements Converter<String, Date> {

/**
* @param s 传入进来的字符串
* @return
*/
@Override
public Date convert(String s) {
if(s == null){
throw new RuntimeException("请传入数据");
}
DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
try {
// 把字符串转换成日期
return df.parse(s);
} catch (Exception e) {
throw new RuntimeException("数据类型转换异常");
}
}
}

springmvc.xml配置

1
2
3
4
5
6
7
8
9
10
11
<!--配置自定义类型转换器-->
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="com.hongyi.utils.StringToDateConverter"></bean>
</set>
</property>
</bean>

<!--开启SpringMVC框架注解的支持-->
<mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>

执行结果

image-20211215195431508

3.7 获取Servlet原生的API

SpringMVC 还支持使用原始 ServletAPI对象作为控制器方法的参数。可以把上述对象,直接写在控制的方法参数中使用。

代码演示

1
<a href="param/testServlet">测试Servlet原生API</a>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 原生API的获取
* @return
*/
@RequestMapping("/testServlet")
public String testServlet(HttpServletRequest request, HttpServletResponse response){
System.out.println("执行了...");
System.out.println(request);

HttpSession session = request.getSession();
System.out.println(session);

ServletContext servletContext = session.getServletContext();
System.out.println(servletContext);

System.out.println(response);
return "success";
}

执行结果

image-20211215195441557

4 常用注解

4.1 RequestParam

作用:把请求中指定名称的参数给控制器中的形参赋值。

位置:形参

属性:

  • value:请求参数中的名称。
  • required:请求参数中是否必须提供此参数。默认值:true。表示必须提供,如果不提供将报错。

使用示例

1
2
3
4
5
6
@RequestMapping("/useRequestParam")
public String useRequestParam(@RequestParam("name") String username,
@RequestParam(value = "age", required = false) Integer age) {
System.out.println("username: " + username + ", age: " + age);
return "success";
}

执行结果:

1
http://localhost:8080/useRequestParam?name=HongyiZeng
1
username: HongyiZeng, age: null

4.2 RequestBody

作用:用于获取请求体内容。直接使用得到是 key=value&key=value...结构的数据。get请求方式不适用,因为get没有请求体。

位置:形参

属性:required:是否必须有请求体。默认值是true。当取值为 true 时,get 请求方式会报错。如果取值为 false,get 请求得到是 null。

使用示例

  • post请求jsp代码
1
2
3
4
5
6
<form action="/useRequestBody" method="post">
用户名称:<input type="text" name="username" ><br/>
用户密码:<input type="password" name="password" ><br/>
用户年龄:<input type="text" name="age" ><br/>
<input type="submit" value="保存">
</form>
  • get请求jsp代码
1
<a href="/useRequestBody?body=test">requestBody注解get请求</a>
  • 请求器代码
1
2
3
4
5
@RequestMapping("/useRequestBody")
public String useRequestBody(@RequestBody(required = false) String body) {
System.out.println(body);
return "success";
}

执行结果:

1
2
username=admin&password=123456&age=25 post请求
null get请求

4.3 PathVariable

4.3.1 作用和示例

作用:用于绑定 url 中的占位符。

例如:请求 url 中 /delete/{id},这个{id}就是 url 占位符。url 支持占位符是 spring3.0 之后加入的,是 springmvc 支持 rest 风格 URL 的一个重要标志。

位置:形参

属性:

  • value:用于指定 url 中占位符名称。
  • required:是否必须提供占位符。

使用示例

  • jsp
1
2
<!-- PathVariable 注解 -->
<a href="/usePathVariable/100">pathVariable注解</a>
  • 控制器
1
2
3
4
5
@RequestMapping("/usePathVariable/{id}")
public String usePathVariable(@PathVariable("id") Integer id){
System.out.println(id);
return "success";
}

4.3.2 Restful

REST(英文:Representational State Transfer,简称 REST):表现层状态转换。

restful 的特性:

  • 资源(Resources):网络上的一个实体,或者说是网络上的一个具体信息。它可以是一段文本、一张图片、一首歌曲、一种服务,总之就是一个具体的存在。可以用一个 URI(统一资源定位符)指向它,每种资源对应一个特定的 URI 。要获取这个资源,访问它的 URI 就可以,因此 URI 即为每一个资源的独一无二的识别符。
  • 表现层(Representation):把资源具体呈现出来的形式,叫做它的表现层(Representation)。比如,文本可以用 txt 格式表现,也可以用 HTML 格式、XML 格式、JSON 格式表现,甚至可以采用二进制格式。
  • 状态转化(State Transfer):每发出一个请求,就代表了客户端和服务器的一次交互过程。HTTP 协议,是一个无状态协议,即所有的状态都保存在服务器端。因此,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生“状态转化”(State Transfer)。而这种转化是建立在表现层之上的,所以就是 “表现层状态转化”。具体说,就是 HTTP 协议里面,四个表示操作方式的动词:GET 、POST 、PUT、DELETE。它们分别对应四种基本操作:GET 用来获取资源(查),POST 用来新建资源(增),PUT 用来更新资源(改),DELETE 用来删除资源(删)。

示例

1
2
3
4
/account/1 HTTP GET     得到 id = 1 的 account 
/account/1 HTTP DELETE 删除 id = 1 的 account
/account/1 HTTP PUT 更新 id = 1 的 account
/account HTTP POST 新增 account

背诵:

RESTful风格是一种基于HTTP协议设计Web API的软件架构风格,强调使用HTTP动词来表示对资源的操作(GET、POST、PUT、PATCH、DELETE等),并通过URI表示资源的唯一标识符。

  • 基于资源:将数据和功能抽象成资源,并通过URI来唯一标识资源。例如,一个用户资源可以通过URL/users/{id}来访问,其中“{id}”表示该用户的唯一标识符。
  • 使用HTTP动词:使用HTTP动词来表示对资源的操作,如GET(获取资源)、POST(创建资源)、PUT(更新资源)和DELETE(删除资源)等。
  • 无状态:每个请求都包含足够的信息来完成请求,服务器不需要保存任何上下文信息。
  • 统一接口:使用统一的接口来简化客户端与服务器之间的交互,包括资源标识符、资源操作和响应消息的格式。

4.4 RequestHeader

作用:用于获取请求消息头。

位置:形参

属性:

  • value:提供消息头名称
  • required:是否必须有此消息头

使用很少

4.5 ModelAttribute

4.5.1 作用

作用:

该注解是 SpringMVC4.3 版本以后新加入的。它可以用于修饰方法和参数。

  • 出现在方法上,表示当前方法会在控制器的方法执行之前先执行。它可以修饰没有返回值的方法,也可以修饰有具体返回值的方法。
  • 出现在形参上,表示获取指定的数据给形参赋值。

属性:

  • value:用于获取数据的 key。key 可以是 POJO 的属性名称,也可以是 map 结构的 key。

应用场景:当表单提交数据不是完整的实体类数据时,保证没有提交数据的字段使用数据库对象原来的数据。

4.5.2 使用示例

① 基于 POJO 属性的基本使用
1
<a href="testModelAttribute?name=test">测试modelattribute</a>
1
2
3
4
5
6
7
8
9
10
@ModelAttribute
public void showUser(User user) {
System.out.println("执行了showUser方法" + user.getName());
}

@RequestMapping("/testModelAttribute")
public String testModelAttribute(User user) {
System.out.println("执行了控制器的方法" + user.getName());
return "success";
}

执行结果:

1
2
执行了showUser方法test // ModelAttribute修饰的方法先于控制器方法testModelAttribute执行
执行了控制器的方法test
② ModelAttribute 修饰方法带返回值

需求:修改用户信息(姓名和年龄),要求用户的密码不能修改

  • jsp
1
2
3
4
5
<form action="/updateUser" method="post">
用户名称:<input type="text" name="name" ><br/>
用户年龄:<input type="text" name="age" ><br/>
<input type="submit" value="保存">
</form>
  • 控制器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 返回值为User
@ModelAttribute
public User showModel(String name) {
// 模拟去数据库查询,通过用户名name查询
User user = findUserByName(name);
user.setName("Hongyi");
user.setAge(19);
user.setPassword("123456");
System.out.println("执行了showModel方法" + user);
// 返回user,作为updateUser的形参
return user;
}

@RequestMapping("/updateUser")
public String updateUser(User user) {
System.out.println("执行了控制器的方法,修改用户" + user);
return "success";
}

// 模拟数据库查询
private User findUserByName(String name) {
User user = new User(); // 模拟根据name找到了user
return user;
}
③ ModelAttribute 修饰方法不带返回值

4.6 SessionAttribute

作用:用于多次执行控制器方法间的参数共享。

位置:

属性:

  • value:用于指定存入的属性名称
  • type:用于指定存入的数据类型。

Model和ModelMap

1
2
3
4
5
6
7
8
9
// Model是一个接口
public interface Model{}
// ModelMap继承了LinkedHashMap
public class ModelMap extends LinkedhashMap<String,Object>{}

// ExtendedModelMap继承了ModelMap又实现了Model接口
public class ExtendedModelMap extends ModelMap implements Model{}
// BindingAwareModelMap继承了ExtendedModelMap
public class BindingAwareModelMap extends ExtendedModelMap{}

一般来说,可以用Model来接收各种类型的数据,这个时候的Model实际上是ModelMap。

ModelMap对象主要用于传递控制方法处理数据到结果页面(响应数据),也就是说我们把结果页面上需要的数据放到ModelMap对象中即可,他的作用类似于request对象的setAttribute方法的作用。

然后在jsp页面上可以通过el表达式语言${attributeName},来获取并展示ModelMap中的数据。

ModelMap本身不能设置页面跳转的url地址别名或者物理跳转地址,那么可以通过控制器方法的字符串返回值来设置跳转url地址别名或者物理跳转地址。或者使用ModelAndView,详见5.1.3节

5 响应数据与结果视图

5.1 返回值

5.1.1 字符串

控制器方法返回字符串可以指定逻辑视图名,通过视图解析器对象解析为物理视图地址。

例如:

1
2
3
4
5
6
// 指定逻辑视图名,经过视图解析器解析为jsp物理路径:/WEB-INF/pages/success.jsp
@RequestMapping("/testReturnString")
public String testReturnString() {
System.out.println("testReturnString 方法执行了");
return "success";
}

视图解析器在xml中配置,就是一个bean对象:

1
2
3
4
5
6
7
8
<!--配置视图解析器对象-->
<!--class:固定;id自取-->
<bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!--prefix:视图资源所在目录-->
<!--suffix:视图的后缀名-->
<property name="prefix" value="/WEB-INF/pages/"/>
<property name="suffix" value=".jsp"/>
</bean>

5.1.2 void

可以使用原生Servlet API来进行页面的跳转,此时控制器方法返回值为void

1
2
3
4
5
6
7
8
9
10
11
@RequestMapping("/testReturnVoid")
public void testReturnVoid(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 请求转发
request.getRequestDispatcher("/WEB-INF/pages/success.jsp").forward(request, response);
// 或者请求重定向
response.sendRedirect("testReturnString");
// 或者通过response指定响应结果
response.setCharacterEncoding("utf-8");
response.setContentType("application/json;charset=utf-8");
response.getWriter().write("json data");
}

5.1.3 ModelAndView

ModelAndView 是 SpringMVC 为我们提供的一个对象,该对象也可以用作控制器方法的返回值。

ModelAndView指模型和视图的集合,既包含模型又包含视图;ModelAndView的实例是开发者自己手动创建的,这也是和ModelMap主要不同点之一。

当控制器处理完请求时,通常会将包含视图名称或视图对象以及一些模型属性的ModelAndView对象返回到DispatcherServlet

类的成员:

1
2
3
4
5
6
7
8
9
10
public class ModelAndView {
@Nullable
private Object view; // 视图
@Nullable
private ModelMap model; // 模型,ModelMap继承自LinkedHashMap<String, Object>
@Nullable
private HttpStatus status;
private boolean cleared = false;
//...
}

代码示例

1
2
3
4
5
6
7
@RequestMapping("/testReturnModelAndView")
public ModelAndView testReturnModelAndView() {
ModelAndView mv = new ModelAndView();
mv.addObject("username", "张三"); // 模型属性
mv.setViewName("success"); // 设置逻辑视图名称
return mv;
}
1
2
3
4
5
6
7
8
9
10
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h3>操作成功</h3>
${username}
</body>
</html>

执行结果:

image-20230613165515029

5.2 请求转发与重定向

5.2.1 forward

控制器方法在提供了 String 类型的返回值之后,默认就是请求转发。

相当于:

1
2
3
4
@RequestMapping("/testForward")
public String testForward() {
return "forward:/WEB-INF/pages/success.jsp";
}

需要注意的是,如果用了 forward:则路径必须写成实际视图 url,不能写逻辑视图。

在原生Servlet API中,相当于:

1
request.getRequestDispatcher("url").forward(request,response);

使用请求转发,既可以转发到 jsp,也可以转发到其他的控制器方法。

5.2.2 redirect

控制器方法提供了一个 String 类型返回值之后,它需要在返回值里使用redirect:

例如:

1
2
3
4
@RequestMapping("/testRedirect")
public String testRedirect() {
return "redirect:testReturnModelAndView";
}

6 异常处理

7 拦截器

7.1 简介

Spring MVC 的处理器拦截器(interceptor)类似于 Servlet 开发中的过滤器 Filter,用于对处理器进行预处理和后处理。拦截器是 AOP 思想的具体应用。

拦截器和过滤器的区别:

  • 过滤器是 servlet 规范中的一部分,任何 java web 工程都可以使用。
  • 拦截器是 SpringMVC 框架自己的,只有使用了 SpringMVC 框架的工程才能用。
  • 过滤器在 url-pattern 中配置了/*之后,可以对所有要访问的资源拦截。
  • 拦截器它是只会拦截访问的控制器方法,如果访问的是 jsp,html,css,image 或者 js 是不会进行拦截的。

想自定义拦截器, 要求必须实现HandlerInterceptor接口。

7.2 自定义拦截器

7.2.1 实现接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 类实现HandlerInterceptor接口
public class HandlerInterceptorDemo1 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle拦截器拦截了");
return true;
}

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle方法执行了");
}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion方法执行了");
}
}

7.2.2 配置拦截器

springmvc.xml

1
2
3
4
5
6
7
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean id="handlerInterceptorDemo1"
class="com.hongyi.interceptor.HandlerInterceptorDemo1"/>
</mvc:interceptor>
</mvc:interceptors>

执行结果:

1
2
3
4
preHandle拦截器拦截了
执行了showModel方法User{name='null', age=19, password='123456'}
postHandle方法执行了
afterCompletion方法执行了

7.3 拦截器细节

7.3.1 放行

放行的含义是指,如果有下一个拦截器就执行下一个,如果该拦截器处于拦截器链的最后一个,则执行控制器中的方法。

1
2
3
4
5
6
7
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle拦截器拦截了");
// 当返回true,程序才能继续执行
// 当返回false,则不放行
return true;
}

7.3.2 方法说明

以下是HandlerInterceptor源码:

1
2
3
4
5
6
7
8
9
10
11
public interface HandlerInterceptor {
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return true;
}

default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
}

default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
}
}

方法 如何调用 何时调用 作用
preHandle 按拦截器定义顺序调用 只要配置了都会调用 如果程序员决定该拦截器对请求进行拦截处理后还要调用其他的拦截器,或者是业务处理器去进行处理,则返回 true。如果程序员决定不需要再调用其他的组件去处理请求,则返回 false。
postHandle 按拦截器定义逆序调用 在拦截器链内所有拦截器返成功调用 在业务处理器处理完请求后,但是 DispatcherServlet 向客户端返回响应前被调用,在该方法中对用户请求 request 进行处理。
afterCompletion 按拦截器定义逆序调用 只有 preHandle 返回 true 才调用 在 DispatcherServlet 完全处理完请求后被调用,可以在该方法中进行一些资源清理的操作。

7.3.3 拦截范围

作用路径(拦截范围)可以通过在springmvc配置文件中配置。

1
2
3
4
5
6
7
8
9
<!-- 配置拦截器的作用范围 -->
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**" /> <!-- 用于指定对拦截的 url -->
<mvc:exclude-mapping path="" /> <!-- 用于指定排除的 url-->
<bean id="handlerInterceptorDemo1"
class="com.itheima.web.interceptor.HandlerInterceptorDemo1"></bean>
</mvc:interceptor>
</mvc:interceptors>

7.3.4 多个拦截器的执行顺序

image-20230614160303916

7.4 正常流程测试

7.4.1 配置

1
2
3
4
5
6
7
8
9
10
11
12
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean id="handlerInterceptorDemo1"
class="com.hongyi.interceptor.HandlerInterceptorDemo1"/>
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean id="handlerInterceptorDemo2"
class="com.hongyi.interceptor.HandlerInterceptorDemo2"/>
</mvc:interceptor>
</mvc:interceptors>

7.4.2 拦截器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class HandlerInterceptorDemo1 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("拦截器1: preHandle拦截器拦截了");
return true;
}

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("拦截器1: postHandle方法执行了");
}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("拦截器1: afterCompletion方法执行了");
}
}

HandlerInterceptorDemo2代码类似。

执行结果:

1
2
3
4
5
6
7
拦截器1: preHandle拦截器拦截了
拦截器2: preHandle拦截器拦截了
执行了showModel方法User{name='null', age=19, password='123456'}
拦截器2: postHandle方法执行了
拦截器1: postHandle方法执行了
拦截器2: afterCompletion方法执行了
拦截器1: afterCompletion方法执行了

7.5 简单案例

需求:

1
2
3
4
5
6
7
8
1.有一个登录页面,需要写一个 controller 访问页面
2.登录页面有一提交表单的动作。需要在 controller 中处理。
2.1 判断用户名密码是否正确
2.2 如果正确,向 session 中写入用户信息
2.3 返回登录成功。
3.拦截用户请求,判断用户是否登录
3.1 如果用户已经登录。放行
3.2 如果用户未登录,跳转到登录页面

控制器代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//登陆页面
@RequestMapping("/login")
public String login(Model model) throws Exception{
return "login";
}

//登陆提交
//userid:用户账号,pwd:密码
@RequestMapping("/loginsubmit")
public String loginsubmit(HttpSession session,String userid,String pwd) throws Exception{
//向 session 记录用户身份信息
session.setAttribute("activeUser", userid);
return "redirect:/main.jsp";
}

//退出
@RequestMapping("/logout")
public String logout(HttpSession session) throws Exception{
//session 过期
session.invalidate();
return "redirect:index.jsp";
}

拦截器代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class LoginInterceptor implements HandlerInterceptor{
@Override
Public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
//如果是登录页面则放行
if(request.getRequestURI().indexOf("login.action")>=0){
return true;
}
HttpSession session = request.getSession();
//如果用户已登录也放行
if(session.getAttribute("user")!=null){
return true;
}
//用户没有登录挑战到登录页面
request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request,
response);
return false;
}
}