0、测试环境
我们简化开发,创建一个简单的环境(因为没有其它包比如 service、dao,所以这里不用 Spring 容器,只用 SpringMVC 容器):
Servelet 容器配置:
package com.lyh.config;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
/**
* Servlet 容器配置类
*/
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[]{"/"};
}
}
注意:有了这个就不需要 web.xml 了,不然启动 tomcat 会报错!!!
SpringMVC 配置:
package com.lyh.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan("com.lyh.controller") // 扫描
public class SpringMvcConfig {
}
UserController:
package com.lyh.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class UserController {
@RequestMapping("/save")
@ResponseBody
public String save(){
return "{'module':'user save'}";
}
@RequestMapping("/delete")
@ResponseBody
public String delete(){
return "{'module':'user delete'}";
}
}
BookController:
package com.lyh.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class BookController {
@RequestMapping("/save")
@ResponseBody
public String save(){
return "{'module':'book save'}";
}
}
1、请求映射路径
在上面的环境中,我们可看到,对于不同 controller 类中存在有相同映射路径(/save),这是我们开发中经常会遇到的问题,也就是不同开发人员设置了相同的映射路径该怎么办?
解决办法:设置模块名作为请求路径前缀。
这样,我们就解决了不同业务模块请求路径冲突的问题了,但是我们发现,如果这个每个业务模块下面的方法前面都加一个( /module )是不是太冗余了。所以,SpringMVC 还能帮我们继续简化开发( 直接在 Controller 类上面定义前缀):
这里我们就可以总结一下注解 @RequestMapping 的用法了:
名称 | 类型 | 位置 | 作用 |
@RequestMapping | 类注解 / 方法注解 | 控制器类 / 控制器方法上面 | 设置当前控制器方法请求访问路径,如果加在控制器类上则代表统一访问路径前缀 |
2、请求方式
2.1、Get 请求
Get 请求非常简单,我们只需要在访问路径后面跟上一个 ?参数名=参数值 即可,如果是多个参数,直接 ?参数名1=参数值1&参数名2=参数值2 即可。
注意:我们之前在学习 Servlet 的时候,Get 和 Post 请求是区分的,但是在 SpringMVC 中,我们并不需要在方法中分开处理不同类型的请求。
2.2、Post 请求
这里用 Postman 来进行模拟发送 Post 请求:
注意:Post 请求发表单的时候,我们请求体中用的应该是 x-www-from-urlencoded 。
from-date 区别于 x-www-from-urlencoded 不同的是,它不仅可以发表单,还可以发送文件:
2.3、Post 请求中文乱码问题
中文乱码的问题我们之前在学习 Servlet 的时候是通过过滤器来实现的,在 SpringMVC 这里是一样的。我们不需要在负责响应的方法里去写,而是直接去 Servlet 容器配置类里重写一下 getServletFilters 方法即可:
注意:这里的过滤器只对 Post 请求有效,Get 请求设置过滤器要复杂一些。
2.4、请求参数
参数种类:
- 普通参数
- POJO
- 嵌套POJO
- 数组
- 集合
2.4.1、客户端和服务端参数名不一致问题
也就是客户端请求参数和我们服务端 Controller 类下面的方法形参名不一致,这种问题很常见,解决办法也很简单,直接通过 @RequestParam("参数名") 把客户端的参数名和我们服务端方法的形参绑定在一起:
但是需要注意的是:设置了这个注解就相当于指定了客户端的参数名,也就是说客户端要想再用 name 来给服务端传递参数就不行了!
2.4.2、POJO 类型参数
我们先创建一个包 domain 并创建一个简单的 User 类:
package com.lyh.domain;
public class User {
private String name;
private int age;
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
在 Controller 中添加一个用 Pojo 作为参数传递的方法:
@RequestMapping("/saveByPojo")
@ResponseBody
public String saveByPojo(User user){
System.out.println("普通参数传递 name = " + user.getName() + " , age = " + user.getAge());
return "{'module':'user save'}";
}
注意:在Spring MVC中,不允许一个Controller下的不同方法具有相同的映射(我原本想着方法重载的,但是发现不行)。
运行结果和之前的普通参数传递是一致的,而我们并没有做额外的操作,只是简单的修改了参数类型,SpringMVC 会帮我们自动通过对象的参数名去找到我们的方法形参。所以,这就要求我们 POJO 的参数名必须和方法形参名一致!
2.4.3、嵌套 POJO
嵌套 POJO 也就是一个对象的参数是另一个对象。
我们再创建一个 Address 类(两个属性:province 和 city,这里省略代码)并添加给 User 类。
2.4.4、数组类型
同样非常简单,在控制器类下面添加一个方法:
发送 Post 请求:
这样,我们就通过多个相同参数名的方式实现了数组的传递。
2.4.5、集合类型
我们试着用和数组一样的方式去给集合传递参数:
在控制器类下创建一个方法:
Post 请求测试:
运行结果:
可以看到,服务器报了一个构造器的错误,说明服务器在尝试调用 List 的构造方法,但是我们知道 List 是一个接口,根本没有构造器。这也说明,我们上一节在用 Pojo 作为参数传递的时候,SpringMVC 底层其实是会调用形参的那个 Pojo 对象的构造器,然后通过 set 方法进行属性注入。
解决的办法也很简单,直接通过@RequestParam 注解把我们的请求参数当做集合的元素传递给集合(如果形参前面有 @RequestParam 注解,SpringMVC 会自动识别,而不会把它当做 Pojo 通过构造器创建空对象,然后通过 set 注入属性):
运行结果:
这样就没问题了。
2.5、JSON 传参
我们实际开发用到 JSON 的情况会很多很多,所以这里需要重点学习 JSON 的传参方法。
首先我们需要导入 json 转换格式的依赖,方便之后把不同的数据类型转为 json:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
</dependency>
还需要注意的是,使用 Postman 发送 json 格式的数据需要使用:
2.5.1、json 数组
1、给SpringMVC配置类添加注解 @EnablewebMvc
我们需要把 json 转为对象,必须给控制器类添加这个注解:
@Configuration
@ComponentScan("com.lyh.controller") // 扫描
@EnableWebMvc // 开启 json 转对象的功能(这只是这个注解的功能之一)
public class SpringMvcConfig {
}
名称 | 类型 | 位置 | 作用 |
---|---|---|---|
@EnableWebMvc | 配置类注解 | SpringMVC配置类上方 | 开启SpringMVC多项辅助功能(比如这里的json格式自动转换) |
2、给服务端方法形参前添加注解 @RequestBody
区别于之前我们同查询参数给 List 集合传递元素使用的注解 @RequestParam:
- @RequestBody 主要用于处理请求体中的数据,比如这里传递 json 数据用的就是 http 请求体中的 raw 来传递的。
- @RequestParam 主要用于处理查询参数中的数据。
什么是查询参数?
查询参数(Query Parameters)是在HTTP请求的URL中用于传递附加信息的键值对。它们通常出现在URL的“?”之后,并以“&”符号分隔多个参数。
名称 | 类型 | 位置 | 作用 |
---|---|---|---|
@RequestBody | 形参注解 | SpringMVC控制器方法形参定义前面 | 将请求体中的数据参数传递给控制器方法形参 |
@RequestMapping("/listParamForJson")
@ResponseBody
public String listParamForJson(@RequestBody List<String> likes){
System.out.println("json数据传递"+likes.toString());
return "{'module':'list common for json param'}";
}
发送测试:
运行结果:
2.5.2、json 对象
@RequestMapping("/pojoParamForJson")
@ResponseBody
public String pojoParamForJson(@RequestBody User user){
System.out.println("json对象传递"+user.toString());
return "{'module':'pojo for json param'}";
}
2.5.3、json 对象数组
这里同样需要对形参添加一个注解 @RequestBody 来把
@RequestMapping("/listPojoParamForJson")
@ResponseBody
public String listPojoParamForJson(@RequestBody List<User> users){
System.out.println("json对象数组传递");
for (User user : users)
System.out.println(user.toString());
return "{'module':'list pojo for json param'}";
}
对于 json 对象数组数据的发送,Postman 会自动帮我们设置 Content-type 格式为 application/json:
测试结果:
2.6、日期类型参数传递
常见的日类型格式:
- 2024-04-14
- 2024/04/14(这种格式 SpringMVC 可以直接处理)
- 04/14/2024
对于另外两种类型的日期格式,SpringMVC 要求通过 @DateTimeFormat 注解的 pattern 属性来指定日期的格式:
@RequestMapping("/dateParam")
@ResponseBody
public String dateParam(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date date){
System.out.println("日期类型参数传递" + date.toString());
return "{'module':'date param'}";
}
测试:
运行结果:
2.7、类型转换器
上面的日期格式转换其实也是依赖于上面我们在 SpringMVC 配置类上开启的注解:@EnableWebMvc 。
而这些所有的格式转换全部依赖于底层的 Converter 接口:
这个接口提供了大量的数据格式转换,其中一部分数据转换是默认开启的,但是大部分是不默认开启的,所以一定要记得通过注解 @EnableWebMvc 来开启类型转换!
3、响应
3.1、响应界面
在 webapp 下创建一个文件 index.jsp
在控制器类中设置响应方法并返回响应页面的路径:
@Controller
public class BookController {
@RequestMapping("/index")
public String jumpToIndex(){
return "index.jsp";
}
}
注意:这里我把 BookController 上面的路径前缀(/book)注解 @RequestMapping 给去掉了,不然到时候会尝试访问 /book/index.jsp 而不是 /index.jsp,会报错。
3.2、响应文本
响应文本的时候为了避免 SpringMVC 把我们的响应文本解读为资源路径需要加一个注解:@ResponseBody
@RequestMapping("/text")
@ResponseBody
public String reposeText(){
return "Hello Spring";
}
运行结果:
其实响应文本的本质还是返回一个页面:
@RequestMapping("/text")
@ResponseBody
public String reposeText(){
return "<h1>Hello Spring</h1>";
}
测试结果:
3.3、响应 json 对象
响应 json 对象很简单,直接返回一个对象即可,我们之前导入的依赖 jackson 会自动帮我们把 Java 对象转为 json:
@RequestMapping("/jsonPojo")
@ResponseBody
public User reposeJsonPojo(){
return new User("谢永强",25);
}
测试结果:
3.4、响应 json 对象集合
同样很简单,和响应对象一样,只不过是把对象放进了集合。同样,一定要导入 Jackson 依赖。
@RequestMapping("/jsonList")
@ResponseBody
public List<User> reposeJsonList(){
List<User> list = new ArrayList<User>();
list.add(new User("谢永强", 25));
list.add(new User("王小蒙", 25));
return list;
}
测试结果:
名称 | 类型 | 位置 | 作用 |
---|---|---|---|
@ResponseBody | 方法注解 | 控制器方法上方 | 将当前控制器方法返回值违响应体 |
如果该控制器方法返回值是 String 类型的文本,@ResponseBody 会把它转为响应体而不是把该文本当做资源路径;如果是对象类型,则把它转为我们浏览器可识别的数据类型,比如把 Java 对象转为 json。
3.5、类型转换器 HttpMessageConverter
这里的转换用的是并不是我们前面说的 Converter 接口,而是 HttpMessageConverter 接口,这个接口有一个实现类叫做 MappingJackson2HttpMessageConverter ,所以只要我们导入了 Jackson 包,它就可以帮我们把对象转为 json 格式。
这也是我之前疑惑的地方:为什么不用 alibaba 的 fastjson 呢,原来是因为人家 SpringMVC 底层用的是 Jackson。