所有的web学习基于springboot项目,而不会去单独的使用spring来进行。
文章目录
- SpringMVC介绍
- 原理
- MVC模式
- 入门使用
- 导入依赖
- 编写controller类
- 详细介绍
- 注解详解
- @Controller
- @RequestMapping
- @ResponseBody
- @RestController
- @RequestParam
- @RequestBody
- @Request其他
- @DateTimeFormat
- 各类参数接收
- 简单参数
- 原始获取
- map
- 自动匹配
- 实体参数
- 简单实体
- 复杂实体
- 同属性名字的不同实体同时接收
- 数组
- 集合
- 日期时间类
- json
- 路径参数
- 扩展
- 统一返回类型
- 静态资源存放
SpringMVC介绍
原理
SpringMVC底层就是Servlet,SpringMVC就是对Servlet进行深层次的封装。
MVC模式
MVC分别是:模型model(javabean)、视图view(jsp/img)、控制器Controller(Action/servlet)。
入门使用
导入依赖
有2种方式,一般第一种
- 在springboot创建项目的时候可以勾选spring web
- 手动导入依赖
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
编写controller类
在类上添加注解
@RestController
新建一个方法,在方法上指定请求路径
@RequestMapping("/uri")
这样一个简单的controller就完成好了。
@RestController
public class HellowController {
@RequestMapping("/hello")
public String hello(String username,String password){
System.out.println("hello");
return "hello";
}
}
详细介绍
注解详解
@Controller
作用于:类
作用:该注解用来类上标识这是一个控制器组件类并创建这个类实例,告诉spring我是一个控制器。
不加这个
如果有controller就会显示了
@RequestMapping
作用于:类、方法
作用:用来指定请求路径。类设置的是这个类全局的请求路径,所以一个方法接收的uri为
类
m
a
p
p
i
n
g
+
方法
m
a
p
p
i
n
g
类mapping+方法mapping
类mapping+方法mapping
@ResponseBody
作用于:类
作用:将方法的返回值直接响应到浏览器
如果是数据,就直接用数据展示
如果是实体类/集合则会以json返回
先给结论,如果不加这个,会返回静态资源文件,就是resource下static的对应文件名的文件。如果加了这个就是返回数据。
因为这个,我们也可以通过一个没有ResponseBody的控制类,来进行资源跳转和资源的获取
首先让我们尝试不加这个注解
发送请求,发现没有找到
但是如果我们将返回值修改为一个我们准备的页面
这次发送直接返回的是index.html的页面
直接在浏览器返回也是
而且还必须要有.html,如果只是index会显示没有页面
接下来我们添加
重启发送请求,就只是在数据放在上面。
下面演示集合,返回json字符串
@GetMapping()
public Map getName(){
return Map.of("id",123,"name","张三");
}
@RestController
作用于:类
作用:这个注解是controller的升级,包括了controller和ResponseBody,并且建议以REST风格来书写代码
@RequestParam
作用于:方法参数
作用:获取url请求参数
可选值:
- required:表示是否一定需要
- defaultValue:默认值
- name:绑定到的请求参数
- value:别名name,书写更简洁,因为只有value的时候可以省略
@RequestBody
作用于:方法参数
作用:获取请求体的参数
可选值:
- required:表示是否一定需要
@Request其他
作用于:方法参数
作用:方法参数应绑定到web请求标头
其他:
- Attribute
- Header
@DateTimeFormat
作用于:方法参数
作用:声明字段或方法参数应格式化为日期或时间。
无论何时使用style或pattern属性,格式化java.util.Date值时都将使用JVM的默认时区
可选值:
- pattern:格式化的格式
- iso:格式化
- style:格式化字段或方法参数的样式模式
短日期、短时间默认为“SS”。如果希望根据默认样式以外的通用样式设置字段或方法参数的格式,请设置此属性 - fallbackPatterns:在主模式、iso或style属性解析失败时用作回退的一组自定义模式
各类参数接收
首先明确,参数的位置有2种,一种在url上一种在请求体上。
在请求体上的需要@RequestBody 而且最多只能出现一次,就是说需要一个实体接收全部的请求体参数。
这里先简单的做一个了解,后面在说匹配的问题。
打印出来的id就是
System.out.println("id = " + id);
简单参数
只推荐使用按名字匹配的,其他不推荐。
原始获取
public String getID(HttpServletRequest request){
String id = request.getParameter("id");
System.out.println("id = " + id);
return null;
}
map
@PostMapping()
public String getName(@RequestParam Map user) {
System.out.println("user = " + user);
return null;
}
结果
POST http://localhost/a?id=1&name=2
user = {id=1, name=2}
自动匹配
这是springboot的方式
会根据名字来进行匹配
public String getName(String id, @RequestParam("id") Integer i){
System.out.println("i = " + i);
System.out.println("id = " + id);
return null;
}
会发现都可以获取到id。
即使类型不一样,也会进行自动转换。
接下来我们将请求id设置为z
返回为400且控制台出现类型转换的错误
2023-07-05 13:59:09.160 WARN 19200 --- [p-nio-80-exec-2] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.method.annotation.MethodArgumentTypeMismatchException: Failed to convert value of type 'java.lang.String' to required type 'java.lang.Integer'; nested exception is java.lang.NumberFormatException: For input string: "z"]
如果对应不上,那么默认就是null
但是如果是基本数据类型对应不上如定义了int id但是没有给id
那么就会报错因为int类型不能为null
所以在参数定义上都用包装类
实体参数
简单实体
只需要保证请求的形参名和实习参数一致
record User(String name, Integer id){}
@PostMapping()
public String getName(User user){
System.out.println("user = " + user);
return null;
}
结果
###
POST http://localhost:80/a?id=1
user = User[name=null, id=1]
###
POST http://localhost:80/a?id=1&name=2
user = User[name=2, id=1]
复杂实体
复杂类型传参就用.来区分
这里本来也想用record的,但是record由于没有遵守bean标准,不提供setter和无参构造会导致初始化失败。而无法赋值。
所以通过案例也可以知道,这里推测是调用无参构造init然后通过setter赋值
@Data
static class User {
String name;
Integer id;
Address address;
}
@Data
static class Address {
Integer city;
Integer id;
}
@PostMapping()
public String getName(User user) {
System.out.println("user = " + user);
return null;
}
结果
POST http://localhost/a?id=1&name=2&address.city=4&address.id=5
user = ResponseBodyController.User(name=2, id=1, address=ResponseBodyController.Address(city=4, id=5))
前面提到map传参,那么这里可以用吗.
可以看到这里其实吧address.city当成key而不是一个实体类了。
POST http://localhost/a?id=1&name=2&address.city=4&address.id=5
user = {id=1, name=2, address.city=4, address.id=5}
同属性名字的不同实体同时接收
record User(String name, Integer id){}
record Student(String name, Integer id){}
@PostMapping()
public String getName(User user,Student student){
System.out.println("user = " + user);
System.out.println("student = " + student);
return null;
}
###
POST http://localhost:80/a?id=1
user = User[name=null, id=1]
student = Student[name=null, id=1]
###
POST http://localhost:80/a?id=1&name=2
user = User[name=2, id=1]
student = Student[name=2, id=1]
可以看到,两个对象的值都被赋上了,但是,大部分情况下,不同的对象的值一般都是不同的
数组
@PostMapping()
public String getName(Integer[] id) {
System.out.println("dis = " + Arrays.toString(id));
return null;
}
POST http://localhost/a?id=1&id=2
dis = [1, 2]
那么如果不是数组类型而传递了多个呢。
只有第一个有效
POST http://localhost/a?id=1&id=2
is = 1
集合
必须写来源在哪里,如RequestParam或者RequestBody
不然会出现
No primary or single unique constructor found for interface java.util.List
public String getName(@RequestParam List<Integer> id) {
System.out.println("id = " + id);
return null;
}
结果
POST http://localhost/a?id=1&id=2
id = [1, 2]
日期时间类
public String getName(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime time) {
System.out.println("time = " + time);
return null;
}
结果
POST http://localhost/a?time=2023-07-03 23:01:14
time = 2023-07-03T23:01:14
json
@PostMapping()
public String getName(@RequestBody User user) {
System.out.println("user = " + user);
return null;
}
结果
POST http://localhost/a
Content-Type: application/json
{
"name":"test",
"id": 1,
"address": {
"city": 1,
"id": 1
}
}
user = ResponseBodyController.User(name=test, id=1, address=ResponseBodyController.Address(city=1, id=1))
但是这里是可以用record的,所以路径解析和url请求解析是不一样的。
record User(String name, Integer id,Address address){}
record Address(Integer id){}
@PostMapping()
public String getName(@RequestBody User user) {
System.out.println("user = " + user);
return null;
}
POST http://localhost/a
Content-Type: application/json
{
"name":"test",
"id": 1,
"address": {
"id": 1
}
}
user = User[name=test, id=1, address=Address[id=1]]
路径参数
@PostMapping("{id}")
public String getName(@PathVariable Integer id) {
System.out.println("id = " + id);
return null;
}
POST http://localhost/a/1
id = 1
扩展
统一返回类型
因为返回的类型可能什么都有,所以我们需要一个统一的返回类型
一般有这3个属性
- code 响应码
- data 存放数据
- msg 消息
由于这个返回类只用一次,所以可以用record简化。
这里给我的做的一个参考,根据自己的业务修改
package com.yu.common;
/**
* 通用返回类
*
* @param code 200成功 500失败
* @param data 返回的数据
* @param msg 返回的信息
*/
public record R(Integer code, Object data, String msg) {
public static R ok(Object data) {
return new R(Code.SUCCESS.code(), data, "");
}
public static R ok() {
return new R(Code.SUCCESS.code(), null, "");
}
public static R ok(Code code){
return new R(code.code(),null,code.msg());
}
public static R error() {
return new R(Code.ERROR.code(), null, "");
}
public static R error(Code code) {
return new R(code.code(), null, code.msg());
}
}
枚举类
package com.yu.common;
/**
* 通用返回状态码类
*/
public enum Code {
SUCCESS(200,"成功"),
ERROR(500,"失败"),
NOT_FOUND(404,"未找到"),
NOT_LOGIN(401,"未登录"),
NOT_AUTH(403,"未授权"),
NOT_ALLOWED(405,"不允许的请求方式"),
PARAM_ERROR(400,"参数错误"),
SERVER_ERROR(500,"服务器错误"),
PASSWORD_ERROR(1001,"密码错误"),
NOT_NULL(1002,"不能为空"),
NOT_EXIST_STUDENTS(1003,"不存在该学生"),
;
private final int code;
private final String msg;
Code(int code, String msg) {
this.code = code;
this.msg = msg;
}
public static Code get(int code){
for (Code value : Code.values()) {
if (value.code == code){
return value;
}
}
return null;
}
public int code() {
return code;
}
public String msg() {
return msg;
}
}
静态资源存放
springboot的静态目录(html,css,js等)默认存放目录为classpath:/static、classpath:/public、classpath:/resources
一般放在static里面
访问直接localhost:port/相当于static/
如访问static/index/index.html
就可以通过localhost:port/index/index.html访问
当然前后端分离项目不会在static下存放