0%

SpringMVC

前置知识要求:

Java基础

JavaWeb

MySQL

Mybatis

Spring基本原理

  1. SpringMVC是一种基于Java实现MVC模型的轻量级Web框架
  2. 优点
    1. 使用简单,开发便捷(相比于Servlet)
    2. 灵活性强

image-20210804185234370

入门案例

首先我们通过一个入门案例来快速了解SpringMVC的魅力

实现步骤

我们分为以下几个步骤来实现这个案例

注意:所用版本分别为jdk8, tomcat7, SpringMVC 5.2.10

  1. 创建maven工程,IDEA中点击新建项目,生成器选择Maven Archtype,之后Archtype选择webapp
  2. 设置tomcat服务器,加载web工程(tomcat插件)
  3. 导入坐标(SpringMVC+Servlet) 注意:因为SpringMVC基于Servlet实现,所以需要导入Servlet依赖
  4. 定义处理请求的控制类(UserController)
  5. 编写SpringMVC配置类,加载处理请求的Bean
  6. 加载SpringMVC配置,并设置SpringMVC请求拦截的路径为全部路径

代码实现

创建Maven工程

设置tomcat服务器

在项目中的pom.xml添加上下面这串代码

1
2
3
4
5
6
7
8
9
10
11
12
13
<build>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.1</version>
<configuration>
<port>80</port>
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>

导入坐标

同样实在pom.xml配置如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
</dependencies>

注意版本问题

spring-webmvc坐标自动依赖spring相关坐标

定义控制类

在java中定义软件包,将控制类统一的放入controller包中

1
2
3
4
5
6
7
8
9
10
11
12
//定义表现层控制器bean
@Controller
public class UserController {
//设置映射路径为/save,即外部访问路径
@RequestMapping("/save")
//设置当前操作返回结果为指定json数据(本质上是一个字符串信息)
@ResponseBody
public String save(){
System.out.println("user save ...");
return "{'info':'springmvc'}";
}
}

对于SpringMVC而言,Controller方法返回值默认表示要跳转的页面,没有对应的页面就会报错。如果不想跳转页面而是

响应数据,那么就需要在方法上使用@ResponseBody注解。

配置SpringMVC

在源代码目录中创建config包统一管理配置类,新建一个SpringMVCConfig

1
2
3
4
5
//springmvc配置类,本质上还是一个spring配置类
@Configuration
@ComponentScan("love.jiahao.controller")
public class SpringMvcConfig {
}

加载SpringMVC配置

在config包中创建web容器的配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//web容器配置类
public class ServletContainersInitConfig extends AbstractDispatcherServletInitializer {
//加载springmvc配置类,产生springmvc容器(本质还是spring容器)
protected WebApplicationContext createServletApplicationContext() {
//初始化WebApplicationContext对象
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
//加载指定配置类
ctx.register(SpringMvcConfig.class);
return ctx;
}

//设置由springmvc控制器处理的请求映射路径
protected String[] getServletMappings() {
return new String[]{"/"};
}

//加载spring配置类
protected WebApplicationContext createRootApplicationContext() {
return null;
}
}

简化格式:(AbstractAnnotationConfigDispatcherServletInitializer继承自AbstractDispatcherServletInitializer,对其方法进行简化封装)

1
2
3
4
5
6
7
8
9
10
11
public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer{
protected Class<?>[] getServletConfigClasses() {
return new Class[]{SpringMvcConfig.class}
};
protected String[] getServletMappings() {
return new String[]{"/"};
}
protected Class<?>[] getRootConfigClasses() {
return new Class[]{SpringConfig.class};
}
}

到此案例就结束了,点击IDEA,编辑配置, 在命令行中添加tomcat7: run,随后即可执行

image-20231015140636797

分析案例类与注解

@Controller注解

  • 名称:@Controller
  • 类型:类注解
  • 位置:SpringMVC控制器类定义上方
  • 作用:设定SpringMVC的核心控制器bean
  • 范例
1
2
3
@Controller
public class UserController {
}

@RequestMapping注解

  • 名称:@RequestMapping
  • 类型:方法注解, 类注解
  • 位置:SpringMVC控制器方法或类定义上方
  • 作用:设置当前控制器方法请求访问路径
  • 范例
1
2
3
4
@RequestMapping("/save")
public void save(){
System.out.println("user save ...");
}

注意:其实@RequestMapping注解还可以写到类上面,代表统一的一个请求前缀

@ResponseBody注解

  • 名称:@ResponseBody
  • 类型:方法注解
  • 位置:SpringMVC控制器方法定义上方
  • 作用:设置当前控制器方法响应内容为当前返回值,无需解析(也就是只返回数据)
  • 范例
1
2
3
4
5
6
@RequestMapping("/save")
@ResponseBody
public String save(){
System.out.println("user save ...");
return "{'info':'springmvc'}";
}

AbstractDispatcherServletInitializer类

  • AbstractDispatcherServletInitializer类是SpringMVC提供的快速初始化Web3.0容器的抽象类

  • AbstractDispatcherServletInitializer提供三个接口方法供用户实现

    • createServletApplicationContext()方法,创建Servlet容器时,加载SpringMVC对应的bean并放入WebApplicationContext对象范围中,而WebApplicationContext的作用范围为ServletContext范围,即整个web容器范围。
    1
    2
    3
    4
    5
    6
    //加载springmvc配置类,产生springmvc容器(本质还是spring容器)
    protected WebApplicationContext createServletApplicationContext() {
    AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
    ctx.register(SpringMvcConfig.class);
    return ctx;
    }
    • getServletMappings()方法,设定SpringMVC对应的请求映射路径,设置为/表示拦截所有请求,任意请求都将转入到SpringMVC进行处理。
    1
    2
    3
    4
    //设置由springmvc控制器处理的请求映射路径
    protected String[] getServletMappings() {
    return new String[]{"/"};
    }
    • createRootApplicationContext()方法,如果创建Servlet容器时需要加载非SpringMVC对应的bean,使用当前方法进行,使用方式同createServletApplicationContext()
    1
    2
    3
    4
    //加载spring配置类
    protected WebApplicationContext createRootApplicationContext() {
    return null;
    }

案例工作流程分析

初始化流程

  1. 服务器启动,执行ServletContainersInitConfig类,初始化web容器
  2. 执行createServletApplicationContext方法,创建WebApplicationContext对象
  3. 加载SpringMVCConfig配置类
  4. 执行@ComponentScan加载对应的Bean
  5. 加载UserController,每个@RequestMapping的名称对应一个具体的方法
  6. 执行getServletMappings方法,定义所有的请求都通过SpringMVC

image-20231015141733430

单次请求流程

  1. 发送请求localhost/save
  2. web容器发现所有请求都经过SpringMVC,将请求交给SpringMVC处理
  3. 解析请求路径/save
  4. 由/save匹配执行对应的方法save()
  5. 执行save()
  6. 检测到有@ResponseBody直接将save()方法的返回值作为响应求体返回给请求方

image-20231015141803959

问题思考

因为各个bean的功能不同,我们怎么避免Spring错误的加载到SpringMVC的bean呢?

解决思路

  • SpringMVC相关的bean在控制层,也就是controller中
  • Spring所控制的bean在业务层service
  • 解决方法
    • 1:Spring加载的bean设定扫描范围为love.jiahao,排除掉controller包内的bean
    • 2:Spring加载的bean设定扫描范围为精准范围,例如service包等
    • 3:不区分Spring与SpringMVC的环境,加载到同一个环境中

方案一实现

在我们的Spring配置类中

1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration
@ComponentScan(value = "com.itheima",
// 排除扫描路径中加载的bean,需要指定类别(type)与具体项(classes)
// 对应的由includeFilters为加载指定的bean
excludeFilters = @ComponentScan.Filter(
// 依据注解的方式进行过滤
type = FilterType.ANNOTATION,
// 过滤带有Controller注解的类
classes = Controller.class
)
)
public class SpringConfig {
}

方案二实现

在Spring配置类中@ComponentScan(“love.jiahao.service”)

在SpringMVC配置类中@ComponentScan(“love.jiahao.controller”)

请求与响应

请求参数

普通类型参数

直接在控制层的方法中,参数输入对应的参数类型和名称即可

如果参数名称和请求的一致,可以直接写

不一致,则需要进行映射,如:请求的参数是userName,接收的是name,需要进行@RequestParam(“userName”) String name

例如:

1
2
3
4
5
6
7
@RequestMapping("/commonParam")
@ResponseBody
public String commonParam(@RequestParam("userName")String name ,int age){
System.out.println("普通参数传递 name ==> "+name);
System.out.println("普通参数传递 age ==> "+age);
return "{'module':'common param'}";
}

中文乱码解决

  • Get请求乱码解决

tomcat 8.5版本之后GET请求就不再出现中文乱码问题,但是我们使用的是tomcat7插件,所以会出现GET请求中文乱码问

题。

解决:在pom.xml添加tomcat7插件处配置UTF-8字符集,解决GET请求中文乱码问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<build>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.1</version>
<configuration>
<port>80</port><!--tomcat端口号-->
<path>/</path> <!--虚拟目录-->
<uriEncoding>UTF-8</uriEncoding><!--访问路径编解码字符集-->
</configuration>
</plugin>
</plugins>
</build>
  • Post请求乱码解决

解决:在加载SpringMVC配置的配置类中指定字符过滤器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
protected Class<?>[] getRootConfigClasses() {
return new Class[0];
}

protected Class<?>[] getServletConfigClasses() {
return new Class[]{SpringMvcConfig.class};
}

protected String[] getServletMappings() {
return new String[]{"/"};
}

//乱码处理
@Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter filter = new CharacterEncodingFilter();
filter.setEncoding("UTF-8");
return new Filter[]{filter};
}
}

实体类接收参数

如果请求参数名和对象的属性名完全相同,我们定义一个POJO实体类,即可接收参数

例如请求参数为name和age,这时我们定义实体类来接收参数

1
2
3
4
5
@Data
public class User{
private String name;
private int age;
}

控制层:

1
2
3
4
5
6
@RequestMapping("/pojoParam")
@ResponseBody
public String pojoParam(User user){
System.out.println("pojo参数传递 user ==> "+user);
return "{'module':'pojo param'}";
}

请求参数key的名称要和POJO中属性的名称一致,否则无法封装

===嵌套实体类参数===

  • POJO对象中包含POJO对象
1
2
3
4
5
6
7
8
9
10
11
@Data
public class User {
private String name;
private int age;
private Address address;
}
@Data
public class Address {
private String province;
private String city;
}
  • 嵌套POJO参数:请求参数名与形参对象属性名相同,按照对象层次结构关系即可接收嵌套POJO属性参数

image-20210805105505625

1
2
3
4
5
6
7
//嵌套POJO参数:嵌套属性按照层次结构设定名称即可完成参数传递
@RequestMapping("/pojoContainPojoParam")
@ResponseBody
public String pojoContainPojoParam(User user){
System.out.println("pojo嵌套pojo参数传递 user ==> "+user);
return "{'module':'pojo contain pojo param'}";
}

请求参数key的名称要和POJO中属性的名称一致,否则无法封装。

数组与集合参数

  • 数组参数:请求参数名与形参对象属性名相同且请求参数为多个,定义数组类型即可接收参数

image-20210805105825688

1
2
3
4
5
6
7
//数组参数:同名请求参数可以直接映射到对应名称的形参数组对象中
@RequestMapping("/arrayParam")
@ResponseBody
public String arrayParam(String[] likes){
System.out.println("数组参数传递 likes ==> "+ Arrays.toString(likes));
return "{'module':'array param'}";
}
  • 集合保存普通参数:请求参数名与形参集合对象名相同且请求参数为多个,**@RequestParam绑定参数关系**

image-20210805105957957

1
2
3
4
5
6
7
//集合参数:同名请求参数可以使用@RequestParam注解映射到对应名称的集合对象中作为数据
@RequestMapping("/listParam")
@ResponseBody
public String listParam(@RequestParam List<String> likes){
System.out.println("集合参数传递 likes ==> "+ likes);
return "{'module':'list param'}";
}

JSON参数

对于JSON类型的参数,我们想要接收,首先我们需要在项目中导入JSON数据转换的相关坐标

1
2
3
4
5
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
</dependency>

其次要开启一个配置,在SpringMVCConfig中加入注解@EnableWebMvc,该注解功能强大,该注解整合了多个功能,此处仅使用其中一部分功能,即json数据进行自动类型转换

1
2
3
4
5
6
@Configuration
@ComponentScan("love.jiahao.controller")
//开启json数据类型自动转换
@EnableWebMvc
public class SpringMvcConfig {
}

所用到的注解解释

  • 名称:@EnableWebMvc
  • 类型:==配置类注解==
  • 位置:SpringMVC配置类定义上方
  • 作用:开启SpringMVC多项辅助功能
  • 范例:
1
2
3
4
5
@Configuration
@ComponentScan("com.itheima.controller")
@EnableWebMvc
public class SpringMvcConfig {
}

  • 名称:@RequestBody
  • 类型:==形参注解==
  • 位置:SpringMVC控制器方法形参定义前面
  • 作用:将请求中请求体所包含的数据传递给请求参数,此注解一个处理器方法只能使用一次
  • 范例:
1
2
3
4
5
6
@RequestMapping("/listParamForJson")
@ResponseBody
public String listParamForJson(@RequestBody List<String> likes){
System.out.println("list common(json)参数传递 list ==> "+likes);
return "{'module':'list common for json param'}";
}

JSON普通数组

例如: [“”, “”, “”]

image-20210805110937684

在Controller中编写方法接收json参数

1
2
3
4
5
6
7
@RequestMapping("/listParamForJson")
@ResponseBody
// 使用@RequestBody注解将外部传递的json数组数据映射到形参的集合对象中作为数据
public String listParamForJson(@RequestBody List<String> likes){
System.out.println("list common(json)参数传递 list ==> "+likes);
return "{'module':'list common for json param'}";
}

JSON对象

例如: {“name”: “张三”, “age”: 18}

image-20231015145842077

  1. 定义实体类进行接收
1
2
3
4
5
@Data
public class User{
private String name;
private Integer age;
}
  1. 控制层编写方法
1
2
3
4
5
6
7
@RequestMapping("/pojoParamForJson")
@ResponseBody
// 使用@RequestBody注解将外部传递的json数据映射到形参的实体类对象中,要求属性名称一一对应
public String pojoParamForJson(@RequestBody User user){
System.out.println("pojo(json)参数传递 user ==> "+user);
return "{'module':'pojo for json param'}";
}

JSON对象数组

例如: [{“name”: “张三”, “age”: 18}, {“name”: “李四”, “age”: 20}]

image-20231015150143560

控制层编写方法

1
2
3
4
5
6
@RequestMapping("/listPojoParamForJson")
@ResponseBody
public String listPojoParamForJson(@RequestBody List<User> list){
System.out.println("list pojo(json)参数传递 list ==> "+list);
return "{'module':'list pojo for json param'}";
}

POJO集合参数:json数组数据与集合泛型属性名相同,定义List类型形参即可接收参数

@RequestBody与@RequestParam区别

  • 区别
    @RequestParam用于接收url地址传参,表单传参【application/x-www-form-urlencoded】
    @RequestBody用于接收json数据【application/json】
  • 应用
    后期开发中,发送json格式数据为主,@RequestBody应用较广
    如果发送非json格式数据,选用@RequestParam接收请求参数

日期类型参数

我们知道,平常使用的日期都由差异,但是对于程序来说,就要接收不同的日期类型,所以我们需要对此进行格式化

例如:接收到2088-08-18, 2088/08/18, 08/18/2088 需要我们将其分别接收

  • 接收形参时,根据不同的日期格式设置不同的接收方式

image-20231015150515988

  • 控制层实现
1
2
3
4
5
6
7
8
9
10
@RequestMapping("/dataParam")
@ResponseBody
public String dataParam(Date date,
@DateTimeFormat(pattern="yyyy-MM-dd") Date date1,
@DateTimeFormat(pattern="yyyy/MM/dd HH:mm:ss") Date date2){
System.out.println("参数传递 date ==> "+date);
System.out.println("参数传递 date1(yyyy-MM-dd) ==> "+date1);
System.out.println("参数传递 date2(yyyy/MM/dd HH:mm:ss) ==> "+date2);
return "{'module':'data param'}";
}
  • @DateTimeFormat注解介绍

    • 名称:@DateTimeFormat

    • 类型:==形参注解==

    • 位置:SpringMVC控制器方法形参前面

    • 作用:设定日期时间型数据格式

    • 属性:pattern:指定日期时间格式字符串

    • 工作原理
      • 其内部依赖Converter接口
1
2
3
4
public interface Converter<S, T> {
@Nullable
T convert(S var1);
}

传递日期类型参数必须在配置类上使用@EnableWebMvc注解。其功能之一:根据类型匹配对应的类型转换器。

响应

响应页面[了解]

控制层

1
2
3
4
5
6
7
8
9
10
11
@Controller
public class UserController {

//响应页面/跳转页面
//返回值为String类型,设置返回值为页面名称,即可实现页面跳转
@RequestMapping("/toJumpPage")
public String toJumpPage(){
System.out.println("跳转页面");
return "page.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>
<h2>Hello Spring MVC!</h2>
</body>
</html>

文本数据[了解]

1
2
3
4
5
6
7
8
//响应文本数据
//返回值为String类型,设置返回值为任意字符串信息,即可实现返回指定字符串信息,需要依赖@ResponseBody注解
@RequestMapping("/toText")
@ResponseBody
public String toText(){
System.out.println("返回纯文本数据");
return "response text";
}

JSON数据[重点]

1
2
3
4
5
6
7
8
9
10
11
//响应POJO对象
//返回值为实体类对象,设置返回值为实体类类型,即可实现返回对应对象的json数据,需要依赖@ResponseBody注解和@EnableWebMvc注解
@RequestMapping("/toJsonPOJO")
@ResponseBody
public User toJsonPOJO(){
System.out.println("返回json对象数据");
User user = new User();
user.setName("itcast");
user.setAge(15);
return user;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//响应POJO集合对象
//返回值为集合对象,设置返回值为集合类型,即可实现返回对应集合的json数组数据,需要依赖@ResponseBody注解和@EnableWebMvc注解
@RequestMapping("/toJsonList")
@ResponseBody
public List<User> toJsonList(){
System.out.println("返回json集合数据");
User user1 = new User();
user1.setName("传智播客");
user1.setAge(15);

User user2 = new User();
user2.setName("黑马程序员");
user2.setAge(12);

List<User> userList = new ArrayList<User>();
userList.add(user1);
userList.add(user2);

return userList;
}

==注意:需要添加jackson-databind依赖以及在SpringMvcConfig配置类上添加@EnableWebMvc

REST风格

REST(Representational State Transfer),表现形式状态转换,其实就是根据请求方式不同,进行不同的操作

优点:

  • 隐藏资源的访问行为,无法通过地址得知对资源是何种操作
  • 书写简化

表现形式:

上述行为是约定方式,约定不是规范,可以打破,所以称REST风格,而不是REST规范

描述模块的名称通常使用复数,也就是加s的格式描述,表示此类资源,而非单个资源,例如:users、books、accounts……


快速入门:

做法:我们只需要把原来的@RequestMapping切换成对应的请求方式的注解(例如: GetMapping, PostMapping等),就可以设置当前路径的请求方式

1
2
3
4
5
6
@GetMapping("/toText")	// 整合了RequestMapping(value = "/toText", method = RequestMethod.POST)
@ResponseBody
public String toText(){
System.out.println("返回纯文本数据");
return "response text";
}

注解解释:

@PathVariable

  • 名称:@PathVariable
  • 类型:形参注解
  • 位置:SpringMVC控制器方法形参定义前面
  • 作用:绑定路径参数与处理器方法形参间的关系,要求路径参数名与形参名一一对应
1
2
3
4
5
6
@GetMapping("/users/{id}")
@ResponseBody
public String toText(@PathVariable Integer id){
log.info("查询用户: {}", id) // 需要Slf4j注解
return user;
}