SpringMVC底层就是Servlet,SpringMVC就是对Servlet进行深层次的封装。
MVC分别是:模型model(javabean)、视图view(jsp/img)、控制器Controller(Action/servlet)。
有2种方式,一般第一种
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
在类上添加注解
@RestController
新建一个方法,在方法上指定请求路径
@RequestMapping("/uri")
这样一个简单的controller就完成好了。
@RestController
public class HellowController {
@RequestMapping("/hello")
public String hello(String username,String password){
System.out.println("hello");
return "hello";
}
}
作用于:类
作用:该注解用来类上标识这是一个控制器组件类并创建这个类实例,告诉spring我是一个控制器。
不加这个
如果有controller就会显示了
作用于:类、方法
作用:用来指定请求路径。类设置的是这个类全局的请求路径,所以一个方法接收的uri为 类 m a p p i n g + 方法 m a p p i n g 类mapping+方法mapping 类mapping+方法mapping
作用于:类
作用:将方法的返回值直接响应到浏览器
如果是数据,就直接用数据展示
如果是实体类/集合则会以json返回
先给结论,如果不加这个,会返回静态资源文件,会与视图服务器设定的前后缀拼接返回给客户端定位资源,就是resource下static的对应文件名的文件。如果加了这个就是返回数据。
因为这个,我们也可以通过一个没有ResponseBody的控制类,来进行资源跳转和资源的获取
首先让我们尝试不加这个注解
发送请求,发现没有找到
但是如果我们将返回值修改为一个我们准备的页面
这次发送直接返回的是index.html的页面
直接在浏览器返回也是
而且还必须要有.html,如果只是index会显示没有页面
接下来我们添加
重启发送请求,就只是在数据放在上面。
下面演示集合,返回json字符串
@GetMapping()
public Map getName(){
return Map.of("id",123,"name","张三");
}
作用于:类
作用:这个注解是controller的升级,包括了controller和ResponseBody,并且建议以REST风格来书写代码
作用于:方法参数
作用:获取url请求参数
可选值:
作用于:方法参数
作用:获取请求体的参数
可选值:
作用于:方法参数
作用:方法参数应绑定到web请求标头
其他:
作用于:方法参数
作用:声明字段或方法参数应格式化为日期或时间。
无论何时使用style或pattern属性,格式化java.util.Date值时都将使用JVM的默认时区
可选值:
首先明确,参数的位置有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;
}
@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
@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个属性
由于这个返回类只用一次,所以可以用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下存放