springboot入门学习笔记

在我们创建了一个Springboot项目之后,我们会看到有很多文件夹和文件

Springboot程序中各类文件的意义

一.pom.xml

在 Spring Boot 项目中,pom.xml(Project Object Model)文件是 Maven 构建工具的核心配置文件。起到项目信息定义,依赖管理,构建配置,项目继承,属性定义等作用。

Maven 是什么

Maven提供了一个标准化的方式来构建、管理和部署 Java 项目。它自动下载和管理项目所需的 Java 库和其他依赖。在Springboot中,

  • 通过 spring-boot-starter 依赖,轻松集成各种 Spring Boot 功能。
  • 自动化编译、测试、打包 Spring Boot 应用。
  • 统一管理 Spring Boot 及其相关依赖的版本。
  • 通过 parent POM 继承 Spring Boot 推荐的依赖版本。
  • 提供标准的目录结构,如 src/main/java, src/main/resources 等。
  • 自动运行单元测试和集成测试。
  • 管理本地和远程 Maven 仓库,确保依赖的可用性。

我们可以在idea的setting方法中配置maven

依赖是什么

依赖是项目运行或编译所需的外部代码库(JAR 文件)。不仅可以提供了必要的功能,还通过自动配置和版本管理大大简化了开发过程

  • Spring Boot 的"starter"依赖预配置了常用的库组合。
  • 例如,spring-boot-starter-web 包含了构建 web 应用所需的所有依赖。
  • 例如,添加 spring-boot-starter-data-jpa 来支持 JPA。

我们通过一个例子来看一下:

<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency>
</dependencies>

这个例子展示了如何在 pom.xml 文件中添加 Web 和 JPA 支持

注意:每一次引入新的依赖之后我们都需要点击右侧Maven的刷新功能刷新依赖,这样才能成功部署。

二.application.yml文件

application.yml 文件是 Spring Boot 应用程序中非常重要的配置文件。它用于配置应用程序的各种设置和属性。

我们来看个例子

spring:application:name: springboot3-learn
server:port: 8000

spring顶级配置节点,用于组织所有与 Spring 框架相关的配置。

application是 spring 下的一个子节点,专门用于配置应用程序级别的设置。

name: springboot3-learn设置了应用程序的名称。

  • 应用程序被命名为 "springboot3-learn"。
  • 这个名称可以在分布式系统、日志、监控等场景中用来识别该应用。

server是另一个顶级配置节点,用于配置嵌入式服务器的相关设置。

port: 8000设置了应用程序运行的端口号。

  • 应用程序将在 8000 端口上运行。
  • 这意味着你可以通过 http://localhost:8000 访问该应用。

三.Controller类

在 Spring Boot 中创建 Controller 类可以实现多种重要功能,它是构建 Web 应用和 RESTful API 的核心组件。

我们先来了解RESTful API

RESTful API(Representational State Transfer API)是一种软件架构风格,用于设计网络应用程序,特别是 Web 服务。它定义了一套规则和约束,用于创建可扩展、灵活和易于理解的 Web API。

HTTP 方法使用:

  • GET:获取资源
  • POST:创建新资源
  • PUT:更新现有资源
  • DELETE:删除资源
  • PATCH:部分更新资源

示例:

GET /users           # 获取所有用户
GET /users/123       # 获取特定用户
POST /users          # 创建新用户
PUT /users/123       # 更新特定用户
DELETE /users/123    # 删除特定用户

再来看controller类可以实现的功能

我们先看代码

package net.chatmindai.springboot3learn.controller;import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.Map;/*** Controller demo* 这是一个演示用的控制器类** @author zk* @date 2024/10/04*/
@RestController // 表示这是一个RESTful Web服务的控制器,组合了@Controller和@ResponseBody
@RequestMapping("/demo") // 定义该控制器的基础URL路径
public class DemoController {/*** 处理GET请求的方法* @GetMapping是@RequestMapping(method = RequestMethod.GET)的简写* 其他HTTP方法还有:* @PostMapping - 处理POST请求* @PutMapping - 处理PUT请求* @DeleteMapping - 处理DELETE请求* @PatchMapping - 处理PATCH请求*/@GetMapping("/hello")public Object hello(){// 创建一个Map对象并初始化,使用Java 9引入的Map.of()方法Map<String,Object> map = new java.util.HashMap<>(Map.of("name", "chatmindai", "age", 18));// 向map中添加新的键值对map.put("introduction","we are chatmindai");// 直接返回map对象,Spring会自动将其转换为JSON格式// 如果想要更细粒度的控制,可以考虑使用ResponseEntity<>return map;}// 可以添加更多的方法来处理不同的请求// 例如:// @PostMapping("/create")// public ResponseEntity<?> createSomething(@RequestBody SomeDTO dto) { ... }// @PutMapping("/update/{id}")// public ResponseEntity<?> updateSomething(@PathVariable Long id, @RequestBody SomeDTO dto) { ... }// @DeleteMapping("/delete/{id}")// public ResponseEntity<?> deleteSomething(@PathVariable Long id) { ... }
}

我们来分析这段代码

@RestController
@RequestMapping("/demo")
  • @RestController: 表示这是一个 RESTful Web 服务的控制器,结合了 @Controller 和 @ResponseBody。
  • @RequestMapping("/demo"): 定义了该控制器的基础 URL 路径。所有方法的 URL 都会以 "/demo" 开头。
@GetMapping("/hello")
public Object hello() {
  • @GetMapping("/hello"): 表示这个方法处理 GET 请求,完整路径为 "/demo/hello"。
  • 方法返回 Object 类型,允许返回任何类型的对象。
  • public Object hello() 方法是控制器的核心部分,它定义了如何处理特定的 HTTP 请求。

方法实现

Map<String,Object> map = new java.util.HashMap<>(Map.of("name", "chatmindai", "age", 18));
map.put("introduction","we are chatmindai");
return map;

在这个方法中创建了一个Map对象并初值化,向map中添加键值对,直接返回object对象,Spring会自动将其转换为JSON格式。

这个controlledr控制器主要包括的功能

  1. 处理对 "/demo/hello" 的 GET 请求。
  2. 返回一个包含 "name"、"age" 和 "introduction" 信息的 JSON 对象。当客户端发送 GET 请求到 "/demo/hello" 时,会收到类似这样的 JSON 响应:
{"name": "chatmindai","age": 18,"introduction": "we are chatmindai"
}

四.DTO类

DTO 主要用于在不同层或组件之间传输数据,特别是在客户端和服务器之间,它封装了需要传输的数据,使数据传输更加高效和安全。

我们来通过例子去理解

我们先加入相关依赖validation和lombok包

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><scope>provided</scope>
</dependency>

 spring-boot-starter-validation:这是Spring Boot的验证启动器,主要用于数据验证。

 使用场景:验证用户输入,确保ApI接受的数据符合预期

Lombok是一个java库,用于减少样板代码,通过注解自动生成常用的Java代码,如getter、setter、构造函数等。

@Data
public class DemoDTO implements Serializable {@Schema(description = "名称", example = "张三")@NotBlank(message = "名称不能为空")@Length(min = 2, max = 50, message = "名称长度必须在2到50个字符之间")private String name;@Schema(description = "年龄", example = "20")@Min(value = 0, message = "年龄必须大于或等于0")@Max(value = 150, message = "年龄必须小于或等于150")private int age;@Schema(description = "邮箱", example = "zhangsan@example.com")@NotNull(message = "邮箱不能为空")@Email(message = "邮箱格式不正确")private String email;@Schema(description = "手机号", example = "13800138000")@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")private String phoneNumber;@Schema(description = "生日", example = "2000-01-01")@Past(message = "生日必须是过去的日期")private LocalDate birthDate;@Schema(description = "计划日期", example = "2025-10-05")@Future(message = "计划日期必须是将来的日期")private LocalDate planDate;@Schema(description = "分数", example = "90.5")@Positive(message = "分数必须为正数")private double score;@Schema(description = "兴趣爱好列表", example = "[\"篮球\", \"足球\", \"游泳\", \"阅读\", \"编程\"]")@Size(min = 1, max = 5, message = "兴趣爱好列表必须包含1到5项")private List<String> hobbies;@Schema(description = "是否同意服务条款", example = "true")@AssertTrue(message = "必须同意服务条款")private boolean agreeTerms;
}
  • @Data: Lombok 注解,自动生成 getter、setter、equals、hashCode 和 toString 方法。
  • implements Serializable: 使类可序列化,便于网络传输或持久化。 
  • @Schema 注解是 OpenAPI 3.0 规范一部分提供字段的描述、示例值等信息,使 API 更易于理解和使用。description 属性提供字段的详细说明, example 属性提供字段的示例值。

我们来通过一个例子去理解

@Schema(type = "string", format = "email", description = "用户的电子邮件地址")
@Email(message = "请提供有效的电子邮件地址")
private String email;

 生成的 OpenAPI 文档中,这个字段会被描述为:

email:type: stringformat: emaildescription: 用户的电子邮件地址

 在控制器中使用 DTO:

@Slf4j
@Tag(name = "演示用的控制器", description = "演示用的控制器")
@RestController
@RequestMapping("/demo")
public class DemoController {@Operation(summary = "返回一个简单的json")@GetMapping("/hello")public Object hello(){// ......return null;}@Operation(summary = "使用DemoDTO对象")@PostMapping("/demo2")public DemoDTO useDemoDTO(@Validated @RequestBody DemoDTO demoDTO) {log.info("入参为: {}", demoDTO);return demoDTO;}
}

 @Validated

  • 它会触发 DemoDTO 类中定义的所有验证注解(如 @NotNull, @Size 等)。

@RequestBody

  • 指示 Spring 将 HTTP 请求体反序列化到 DemoDTO 对象中。
  • 通常用于处理 JSON 或 XML 格式的请求数据。

DemoDTO demoDTO

  • 这是方法的参数,表示从请求体中解析出的 DemoDTO 对象。
  • 它包含了客户端在请求中发送的所有数据。

log.info("入参为: {}", demoDTO);

  • 这行代码使用 SLF4J 日志框架记录日志。
  • 它会打印接收到的 DemoDTO 对象的内容。
  • {} 是 SLF4J 的占位符,会被 demoDTO 的字符串表示替换。
  • 这对于调试和监控非常有用。

工作流程:

  1. 当服务器收到对应 URL 的 POST 请求时,Spring 会调用这个方法。
  2. 请求体中的 JSON 数据会被反序列化为 DemoDTO 对象。
  3. Spring 执行 @Validated 注解触发的验证。
  4. 如果验证通过,方法被执行,日志被记录。
  5. DemoDTO 对象被返回,并自动序列化为 JSON 响应。

五.引导类,springboot项目的入口

package com.example.springboot3learn;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class Springboot3LearnApplication {public static void main(String[] args) {SpringApplication.run(Springboot3LearnApplication.class, args);}}

AOP的使用

一,我们先引入对应的依赖
 

 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency>

我们通过代码和运行实例来详细理解AOP

二,创建切片类

@Pointcut("execution(* net.chatmindai.springboot3learn.controller..*.*(..))")
public void controllerPointcut() {}
  • 定义了一个切入点,匹配 net.chatmindai.springboot3learn.controller 包及其子包中所有类的所有方法。

切入点的含义:

  • 它匹配 net.chatmindai.springboot3learn.controller 包及其所有子包中的所有类的所有方法,不论方法的返回类型、名称或参数如何。

切入点的使用

  @Around("controllerPointcut()")public Object logAroundController(ProceedingJoinPoint joinPoint) throws Throwable {String methodName = joinPoint.getSignature().getName();String className = joinPoint.getTarget().getClass().getSimpleName();Object[] args = joinPoint.getArgs();// 记录方法调用信息和入参log.info("调用控制器方法: {}.{}", className, methodName);log.info("入参: {}", Arrays.toString(args));// 执行原方法Object result = joinPoint.proceed();// 记录出参log.info("出参: {}", result);return result;}/*** 前置通知,在方法执行前进行处理*/@Before("controllerPointcut()")public void logBeforeController(JoinPoint joinPoint) {String methodName = joinPoint.getSignature().getName();String className = joinPoint.getTarget().getClass().getSimpleName();Object[] args = joinPoint.getArgs();// 记录方法调用信息和入参log.info("Before调用控制器方法: {}.{}", className, methodName);log.info("Before入参: {}", Arrays.toString(args));}/*** 返回通知,在方法正常返回后进行处理*/@AfterReturning(pointcut = "controllerPointcut()", returning = "result")public void logAfterController(JoinPoint joinPoint, Object result) {String methodName = joinPoint.getSignature().getName();String className = joinPoint.getTarget().getClass().getSimpleName();// 记录方法执行完毕信息和出参log.info("AfterReturning控制器方法执行完毕: {}.{}", className, methodName);log.info("AfterReturning出参: {}", result);}
}

PS:环绕通知(Around Advice),它是 AOP 中最强大和灵活的通知类型。

环绕通知提供了一个强大的机制来增强控制器方法的功能。它主要用于全面的日志记录,捕获方法的执行上下文、入参和出参。这种方式不仅提高了代码的可维护性和可调试性,还为进一步的功能扩展(如性能监控、安全检查等)提供了基础。

@Around 注解指定这是一个环绕通知。

joinPoint.getSignature() 返回一个 Signature 对象,它代表了连接点(在这个场景中是方法)的签名。所以返回的是方法名UserDemoDTO

JoinPoint.getTarget().getClass().getSimpleName();返回的是切入的类名,返回的是DemoController

"controllerPointcut()" 引用了之前定义的切入点,指定了这个通知应用的范围。

返回类型是 Object,允许修改或替换原方法的返回值。

ProceedingJoinPoint 参数提供了访问和控制目标方法执行的能力

继承了Throwable在 @Around 通知中,通常使用 Throwable 来捕获和处理可能发生的任何异常。

前置通知(Before Advice),它在目标方法执行之前运行。

前置通知提供了一种简洁有效的方式来记录控制器方法的调用信息。它主要用于在方法执行前进行日志记录,提供了valuable的调试和监控信息。这种方式增强了代码的可追踪性和可维护性,同时保持了较低的复杂度和性能开销

@Before 注解指定这是一个前置通知。

"controllerPointcut()" 引用了之前定义的切入点,指定了这个通知应用的范围。

JoinPoint 参数提供了访问被拦截方法信息的能力。

返回通知(AfterReturning Advice),它在目标方法成功执行并返回结果后运行。

返回通知提供了一种有效的方式来记录控制器方法的执行结果。它增强了应用程序的可观察性,提供了valuable的调试和监控信息。

@AfterReturning 注解指定这是一个返回通知。

pointcut = "controllerPointcut()" 指定了这个通知应用的切入点。

returning = "result" 指定了用于接收方法返回值的参数名。

JoinPoint 参数提供了访问被拦截方法信息的能力。

Object result 参数用于接收原方法的返回值。

在Apifox中发送 我们来看在日志中是怎么体现的

2024-10-26T15:21:05.447+08:00  INFO 49440 --- [springboot3-learn] [nio-8000-exec-1] c.e.springboot3learn.aspect.LogAspect    : 调用控制器方法: DemoController.useDemoDTO
2024-10-26T15:21:05.451+08:00  INFO 49440 --- [springboot3-learn] [nio-8000-exec-1] c.e.springboot3learn.aspect.LogAspect    : 入参: [DemoDTO(name=张三, age=20, email=zhangsan@example.com, phoneNumber=13800138000, birthDate=2000-01-01, planDate=2025-10-05, score=90.5, hobbies=[篮球, 足球, 游泳, 阅读, 编程], agreeTerms=true)]
2024-10-26T15:21:05.451+08:00  INFO 49440 --- [springboot3-learn] [nio-8000-exec-1] c.e.springboot3learn.aspect.LogAspect    : Before调用控制器方法: DemoController.useDemoDTO
2024-10-26T15:21:05.452+08:00  INFO 49440 --- [springboot3-learn] [nio-8000-exec-1] c.e.springboot3learn.aspect.LogAspect    : Before入参: [DemoDTO(name=张三, age=20, email=zhangsan@example.com, phoneNumber=13800138000, birthDate=2000-01-01, planDate=2025-10-05, score=90.5, hobbies=[篮球, 足球, 游泳, 阅读, 编程], agreeTerms=true)]
2024-10-26T15:21:05.452+08:00  INFO 49440 --- [springboot3-learn] [nio-8000-exec-1] c.e.s.controller.DemoController          : 入参为: DemoDTO(name=张三, age=20, email=zhangsan@example.com, phoneNumber=13800138000, birthDate=2000-01-01, planDate=2025-10-05, score=90.5, hobbies=[篮球, 足球, 游泳, 阅读, 编程], agreeTerms=true)
2024-10-26T15:21:05.452+08:00  INFO 49440 --- [springboot3-learn] [nio-8000-exec-1] c.e.springboot3learn.aspect.LogAspect    : AfterReturning控制器方法执行完毕: DemoController.useDemoDTO
2024-10-26T15:21:05.452+08:00  INFO 49440 --- [springboot3-learn] [nio-8000-exec-1] c.e.springboot3learn.aspect.LogAspect    : AfterReturning出参: DemoDTO(name=张三, age=20, email=zhangsan@example.com, phoneNumber=13800138000, birthDate=2000-01-01, planDate=2025-10-05, score=90.5, hobbies=[篮球, 足球, 游泳, 阅读, 编程], agreeTerms=true)
2024-10-26T15:21:05.452+08:00  INFO 49440 --- [springboot3-learn] [nio-8000-exec-1] c.e.springboot3learn.aspect.LogAspect    : 出参: DemoDTO(name=张三, age=20, email=zhangsan@example.com, phoneNumber=13800138000, birthDate=2000-01-01, planDate=2025-10-05, score=90.5, hobbies=[篮球, 足球, 游泳, 阅读, 编程], agreeTerms=true)

我们再来分析一下三种环绕的区别:

环绕通知,前置通知和返回通知的区别

主要区别:

  1. 执行时机:

    • 环绕通知:方法执行前后。
    • 前置通知:仅在方法执行前。
    • 返回通知:仅在方法成功返回后。
  2. 控制能力:

    • 环绕通知:可以完全控制方法的执行。
    • 前置通知和返回通知:不能控制方法的执行流程。
  3. 异常处理:

    • 环绕通知:可以处理方法执行期间的异常。
    • 前置通知:不涉及异常处理。
    • 返回通知:只在方法成功执行时触发,不处理异常。
  4. 返回值处理:

    • 环绕通知:可以修改返回值。
    • 前置通知:无法访问返回值。
    • 返回通知:可以访问但不能修改返回值。
  5. 复杂性:

    • 环绕通知:最复杂但最灵活。
    • 前置通知和返回通知:相对简单,职责单一。

使用注解进行开发

添加一个注解类LogInfo

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** LogInfo 注解* 用于标记需要记录日志的方法,并提供方法的描述信息** @author zk* @date 2024/10/05*/
@Target(ElementType.METHOD) // 指定该注解只能应用于方法
@Retention(RetentionPolicy.RUNTIME) // 指定该注解在运行时可以通过反射获取
public @interface LogInfo {/*** 方法描述* * @return 返回描述该方法功能的字符串*/String value() default "";//它定义了一个名为 value 的属性,这个属性可以在使用注解时被赋值。
}

 

修改切面类的代码

@Around("controllerPointcut()")
public Object logAroundController(ProceedingJoinPoint joinPoint) throws Throwable {MethodSignature signature = (MethodSignature) joinPoint.getSignature();LogInfo logInfo = signature.getMethod().getAnnotation(LogInfo.class);// 如果方法没有 @LogInfo 注解,直接执行方法并返回结果if (logInfo == null) {return joinPoint.proceed();}String methodDescription = logInfo.value();String methodName = signature.getName();String className = joinPoint.getTarget().getClass().getSimpleName();Object[] args = joinPoint.getArgs();log.info("执行方法: {}.{} - {}", className, methodName, methodDescription);log.info("入参: {}", Arrays.toString(args));Object result = joinPoint.proceed();log.info("方法返回: {}.{} - {}", className, methodName, methodDescription);log.info("出参: {}", result);return result;
}

这个方法将返回的 Signature 对象强制转换为 MethodSignatureMethodSignature 提供了更多与方法相关的具体信息。

 其他获取类名和方法名的原理和上面一样

给Controller添加注解,用来获取用户信息

@LogInfo("获取用户信息")

我们来看日志,也同样获取了整个流程

2024-10-26T20:40:04.682+08:00  INFO 49288 --- [springboot3-learn] [nio-8000-exec-1] c.e.springboot3learn.aspect.LogAspect    : 执行方法: DemoController.useDemoDTO - 获取用户信息
2024-10-26T20:40:04.686+08:00  INFO 49288 --- [springboot3-learn] [nio-8000-exec-1] c.e.springboot3learn.aspect.LogAspect    : 入参: [DemoDTO(name=张三, age=20, email=zhangsan@example.com, phoneNumber=13800138000, birthDate=2000-01-01, planDate=2025-10-05, score=90.5, hobbies=[篮球, 足球, 游泳, 阅读, 编程], agreeTerms=true)]
2024-10-26T20:40:04.686+08:00  INFO 49288 --- [springboot3-learn] [nio-8000-exec-1] c.e.springboot3learn.aspect.LogAspect    : Before调用控制器方法: DemoController.useDemoDTO
2024-10-26T20:40:04.686+08:00  INFO 49288 --- [springboot3-learn] [nio-8000-exec-1] c.e.springboot3learn.aspect.LogAspect    : Before入参: [DemoDTO(name=张三, age=20, email=zhangsan@example.com, phoneNumber=13800138000, birthDate=2000-01-01, planDate=2025-10-05, score=90.5, hobbies=[篮球, 足球, 游泳, 阅读, 编程], agreeTerms=true)]
2024-10-26T20:40:04.687+08:00  INFO 49288 --- [springboot3-learn] [nio-8000-exec-1] c.e.s.controller.DemoController          : 入参为: DemoDTO(name=张三, age=20, email=zhangsan@example.com, phoneNumber=13800138000, birthDate=2000-01-01, planDate=2025-10-05, score=90.5, hobbies=[篮球, 足球, 游泳, 阅读, 编程], agreeTerms=true)
2024-10-26T20:40:04.687+08:00  INFO 49288 --- [springboot3-learn] [nio-8000-exec-1] c.e.springboot3learn.aspect.LogAspect    : AfterReturning控制器方法执行完毕: DemoController.useDemoDTO
2024-10-26T20:40:04.687+08:00  INFO 49288 --- [springboot3-learn] [nio-8000-exec-1] c.e.springboot3learn.aspect.LogAspect    : AfterReturning出参: DemoDTO(name=张三, age=20, email=zhangsan@example.com, phoneNumber=13800138000, birthDate=2000-01-01, planDate=2025-10-05, score=90.5, hobbies=[篮球, 足球, 游泳, 阅读, 编程], agreeTerms=true)
2024-10-26T20:40:04.687+08:00  INFO 49288 --- [springboot3-learn] [nio-8000-exec-1] c.e.springboot3learn.aspect.LogAspect    : 方法返回: DemoController.useDemoDTO - 获取用户信息
2024-10-26T20:40:04.687+08:00  INFO 49288 --- [springboot3-learn] [nio-8000-exec-1] c.e.springboot3learn.aspect.LogAspect    : 出参: DemoDTO(name=张三, age=20, email=zhangsan@example.com, phoneNumber=13800138000, birthDate=2000-01-01, planDate=2025-10-05, score=90.5, hobbies=[篮球, 足球, 游泳, 阅读, 编程], agreeTerms=true)

依赖注入与控制反转

 

使用全局异常处理器进行异常处理

我们再来思考一个问题,我们在Apifox中发送请求,所示的结果如下

将入参中的name设置为空字符串,会返回这个

 

 所以我们可以试着使用异常处理器对此进行处理

创建CommonResult类

这个类用于包装所有的接口出参,将出参信息结构化,使前端方便处理

import java.io.Serializable;@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class CommonResult<T> implements Serializable {private static final long serialVersionUID = 1L;private T data;public static <T> CommonResult<T> success(T data) {return success(data, "操作成功");}public static <T> CommonResult<T> success(T data, String message) {return CommonResult.<T>builder().code(200).message(message).data(data).build();}public static <T> CommonResult<T> error(int code, String message) {return CommonResult.<T>builder().code(code).message(message).build();}public static <T> CommonResult<T> error(String message) {return error(500, message);}public static <T> CommonResult<T> any(int code, String message, T data) {return CommonResult.<T>builder().code(code).message(message).data(data).build();}
}

导入 Serializable 接口,使类可序列化

@Data: Lombok 注解,自动生成 getter、setter、toString 等方法。

@AllArgsConstructor: 生成包含所有字段的构造函数。

@NoArgsConstructor: 生成无参构造函数。

@Builder: 启用 Builder 模式。

<T>: 泛型参数,允许结果包含任意类型的数据。

implements Serializable: 使类可序列化。

 这段代码中的方法实现

success(T data): 创建成功响应,默认消息。

success(T data, String message): 创建成功响应,自定义消息。

error(int code, String message): 创建错误响应,自定义状态码和消息。

error(String message): 创建错误响应,默认状态码 500。

any(int code, String message, T data): 创建完全自定义的响应

这段代码定义了一个通用的结果类 CommonResult<T>,用于统一封装 API 响应的格式。

添加异常处理类

import com.example.springboot3learn.entity1.CommonResult;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;import lombok.extern.slf4j.Slf4j;/*** 全局异常处理器* 用于统一处理应用中抛出的异常,并返回标准化的错误响应*/
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {/*** 处理所有未被特定处理器捕获的异常** @param ex 捕获到的异常* @param request 当前的web请求* @return 包含错误信息的ResponseEntity*/@ExceptionHandler(Exception.class)public ResponseEntity<CommonResult<String>> handleAllExceptions(Exception ex, WebRequest request) {// 记录异常日志log.error("发生未处理的异常", ex);// 创建错误响应CommonResult<String> result = CommonResult.error(HttpStatus.INTERNAL_SERVER_ERROR.value(),"发生未处理的异常: " + ex.getMessage());// 返回HTTP 500 内部服务器错误状态码return new ResponseEntity<>(result, HttpStatus.INTERNAL_SERVER_ERROR);}/*** 处理所有RuntimeException及其子类的异常** @param ex 捕获到的RuntimeException* @param request 当前的web请求* @return 包含错误信息的ResponseEntity*/@ExceptionHandler(RuntimeException.class)public ResponseEntity<CommonResult<String>> handleRuntimeException(RuntimeException ex, WebRequest request) {// 记录运行时异常日志log.error("发生运行时异常", ex);// 创建错误响应CommonResult<String> result = CommonResult.error(HttpStatus.BAD_REQUEST.value(),"发生运行时异常: " + ex.getMessage());// 返回HTTP 400 错误请求状态码return new ResponseEntity<>(result, HttpStatus.BAD_REQUEST);}
}

@ControllerAdvice这个注解标记 GlobalExceptionHandler 类为全局异常处理器,Spring 会自动扫描并注册这个类,使其能够处理来自所有 Controller 的异常。

@ExceptionHandler(Exception.class): 指定此方法处理所有 Exception 类型的异常。

异常处理过程: 当应用中抛出异常时:

a. Spring 拦截这个异常。

b. 寻找能处理这个异常的 @ExceptionHandler 方法。

c. 调用匹配的处理方法。

d. 处理方法生成并返回一个 ResponseEntity

e. Spring 将这个 ResponseEntity 转换为 HTTP 响应返回给客户端。

  1. 具体工作流程:

    • 如果抛出 RuntimeException

      • handleRuntimeException 方法被调用。
      • 记录错误日志。
      • 创建一个 CommonResult 对象,包含错误信息。
      • 返回一个带有 400 状态码的 ResponseEntity
    • 如果抛出其他类型的 Exception

      • handleAllExceptions 方法被调用。
      • 记录错误日志。
      • 创建一个 CommonResult 对象,包含错误信息。
      • 返回一个带有 500 状态码的 ResponseEntity

我们来看此时APifox接受的响应结果,冒号后面即为ex.getmessage的内容

对入参校验抛出的异常进行解析

 在日志中可以看到处理的日志,它输出了异常的类型 MethodArgumentNotValidException

2024-10-26T21:34:56.669+08:00 ERROR 51280 --- [springboot3-learn] [nio-8000-exec-1] c.e.s.exception.GlobalExceptionHandler   : 发生未处理的异常

为了返回更清晰的响应,我们进一步改善,我们给异常处理类添加新的代码

/*** 处理参数校验失败的异常** @param ex 捕获到的MethodArgumentNotValidException* @param request 当前的web请求* @return 包含错误信息的ResponseEntity*/@ExceptionHandler(MethodArgumentNotValidException.class)public ResponseEntity<CommonResult<Map<String, String>>> handleValidationExceptions(MethodArgumentNotValidException ex, WebRequest request) {Map<String, String> errors = new HashMap<>();ex.getBindingResult().getFieldErrors().forEach(error -> errors.put(error.getField(), error.getDefaultMessage()));log.warn("参数校验失败", ex);CommonResult<Map<String, String>> result = CommonResult.error(HttpStatus.BAD_REQUEST.value(),"参数校验失败");result.setData(errors);return new ResponseEntity<>(result, HttpStatus.BAD_REQUEST);}

 加入了这段代码后的异常处理类,有了哪些改进?

原来的类主要处理一般的 Exception 和 RuntimeException。
现在增加了对 MethodArgumentNotValidException 的专门处理,这是一种更具体的异常类型。

对于参数校验失败,不再只是返回一个通用的错误消息。
现在能够提供每个失败字段的具体错误信息,大大提高了错误反馈的精确度。

使用 Map<String, String> 来存储和返回错误信息,每个字段的错误都能被清晰地表示。
这种结构化的方式使得前端或API消费者更容易解析和处理错误。

客户端可以准确知道哪些字段没有通过验证,以及具体的原因。
这有助于用户界面的快速反馈和表单验证的实现。

我们来看响应结果

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/57855.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

OpenCV中的图像通道合并

在计算机视觉和图像处理领域&#xff0c;OpenCV是一个强大的工具库&#xff0c;它提供了从基本操作到复杂算法的广泛功能。今天&#xff0c;我们将通过一个简单的示例来探索OpenCV中的图像通道处理&#xff0c;特别是如何操作和理解BGR与RGB颜色空间的差异。 Lena图像&#xf…

四,Linux基础环境搭建(CentOS7)- 安装Zookeeper

Linux基础环境搭建&#xff08;CentOS7&#xff09;-安装Zookeeper 大家注意以下的环境搭建版本号&#xff0c;如果版本不匹配有可能出现问题&#xff01; 一、Zookeeper下载及安装 ZooKeeper是一个开源的分布式协调服务&#xff0c;由雅虎创建&#xff0c;是Google Chubby的…

解决 IntelliJ IDEA 中 Tomcat 日志乱码问题的详细指南

目录 前言1. 分析问题原因2. 解决方案2.1 修改 IntelliJ IDEA 的 JVM 选项2.2 配置 Tomcat 实例的 VM 选项2.2.1 设置 Tomcat 的 VM 选项2.2.2 添加环境变量 3. 进一步优化3.1 修改 Tomcat 的 logging.properties3.2 修改操作系统默认编码3.2.1 Windows 系统3.2.2 Linux 和 mac…

2024mathorcup大数据竞赛B题【电商品类货量预测及品类分仓规划】思路详解

问题 1&#xff1a;建立货量预测模型&#xff0c;对该仓储网络 350 个品类未来 3 个月&#xff08;7-9月&#xff09;每个月的库存量及销量进行预测&#xff0c;其中库存量根据历史每月数据预测月均库存量即可&#xff0c;填写表 1 的预测结果并放在正文中&#xff0c;并将完整…

强化学习数学原理学习(一)

前言 总之开始学! 正文 先从一些concept开始吧,有一个脉络比较好 state 首先是就是状态和状态空间,显而易见,不多说了 action 同理,动作和动作空间 state transition 状态转换,不多说 policy 策略,不多说 reward 奖励,不多说 MDP(马尔科夫) 这里需要注意到就是这个是无…

Java-图书管理系统

我的个人主页 欢迎来到我的Java图书管理系统&#xff0c;接下来让我们一同探索如何书写图书管理系统吧&#xff01; 1管理端和用户端 2建立相关的三个包&#xff08;book、operation、user&#xff09; 3建立程序入口Main类 4程序运行 1.首先图书馆管理系统分为管理员端和…

通过Docker Compose构建自己的Java项目

通过Docker Compose构建自己的Java项目 前置条件 安装了Docker,未安装的请移步:CentOS7 / CentOS8 安装 Docker-ce安装了Docker-Compose,未安装的请移步:在CentOS7、CentOS8系统下安装Docker Compose1. 配置阿里云镜像仓库 为了提高Docker镜像的下载速度,我们可以配置阿…

代码随想录算法训练营第46期Day42

leetcode.518.零钱兑换 class Solution { public: //求装满背包有几种方法&#xff0c;公式都是&#xff1a;dp[j] dp[j - nums[i]]; // 如果求组合数就是外层for循环遍历物品&#xff0c;内层for遍历背包。 // 如果求排列数就是外层for遍历背包&#xff0c;内层for循环遍历物…

Detecting Holes in Point Set Surfaces 论文阅读

下载链接 Detecting Holes in Point Set Surfaces 摘要 3D 数据采集过程&#xff08;例如激光范围扫描&#xff09;产生的重要物体模型通常包含由于遮挡、反射或透明度而产生的孔洞。本文的目标就是在点集表面上检测存在的孔洞。对于每个点&#xff0c;将多个标准组合成一个综…

【机器学习】股票数据爬取与展示分析

数据爬取 一、爬取原理二、代码实践2.1 股票列表获取2.1.1 确定待爬取网页2.1.2 向网页发送请求获取页面响应2.1.3 文本转换成JSON2.1.4 将数据保存到csv文件中2.2 股票数据获取 三、结果分析 一、爬取原理 本文中主要使用的就是Python的request库&#xff0c;这个库基于HTTP请…

Task :prepareKotlinBuildScriptModel UP-TO-DATE,编译卡在这里不动或报错

这里写自定义目录标题 原因方案其他思路 原因 一般来说&#xff0c;当编译到这个task之后&#xff0c;后续是要进行一些资源的下载的&#xff0c;如果你卡在这边不动的话&#xff0c;很有可能就是你的IDE目前没有办法进行下载。 方案 开关一下IDE内部的代理&#xff0c;或者…

Jetpack架构组件_LiveData组件

1.LiveData初识 LiveData:ViewModel管理要展示的数据&#xff08;VM层类似于原MVP中的P层&#xff09;&#xff0c;处理业务逻辑&#xff0c;比如调用服务器的登陆接口业务。通过LiveData观察者模式&#xff0c;只要数据的值发生了改变&#xff0c;就会自动通知VIEW层&#xf…

【Spring】详解SpringMVC,一篇文章带你快速入门

目录 一、初始MVC 二、SpringMVC 三、Spring MVC的运用 ⭕RequestMapping ⭕传递参数 1、传递单个参数 2、传递多个参数 3、参数重命名 4、传递数组与集合 5、获取路径参数 6、传递JSON数据 7、上传文件 一、初始MVC MVC&#xff08;Model-View-Controller&#…

在不能联网的电脑上安装库(PyEMD为例)

1、查看PyEMD需要什么依赖 需要numpy、pathos、scipy、tqdm依赖&#xff0c;我电脑上有了numpy, scipy&#xff0c;以另外两个为例 2、查看依赖的依赖 查看依赖是否还要依赖 可以看到pathos还要这四个依赖&#xff0c;以此类推&#xff0c;看还要哪些依赖&#xff0c;直至req…

Mac 使用脚本批量导入 Apple 歌曲

最近呢&#xff0c;买了一个 iPad&#xff0c;虽然家里笔记本台式都有&#xff0c;显示器都是 2个&#xff0c;比较方便看代码&#xff08;边打游戏边追剧&#xff09;。 但是在床上拿笔记本始终还是不方便&#xff0c;手机在家看还是小了点&#xff0c;自从有 iPad 之后&…

【Java】java 集合框架(详解)

&#x1f4c3;个人主页&#xff1a;island1314 ⛺️ 欢迎关注&#xff1a;&#x1f44d;点赞 &#x1f442;&#x1f3fd;留言 &#x1f60d;收藏 &#x1f49e; &#x1f49e; &#x1f49e; 1. 概述 &#x1f680; &#x1f525; Java集合框架 提供了一系列用于存储和操作…

实现uniapp天地图边界范围覆盖

在uniapp中&#xff0c;难免会遇到使用地图展示的功能&#xff0c;但是百度谷歌这些收费的显然对于大部分开源节流的开发者是不愿意接受的&#xff0c;所以天地图则是最佳选择。 此篇文章&#xff0c;详细的实现地图展示功能&#xff0c;并且可以自定义容器宽高&#xff0c;还可…

java项目之电影评论网站(springboot)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的电影评论网站。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 电影评论网站的主要使用者管…

UE5 源码学习 初始化

跟着 https://www.cnblogs.com/timlly/p/13877623.html 学习 入口函数 UnrealEngine\Engine\Source\Runtime\Launch\Private\Windows\LaunchWindows.cpp WinMain 入口 int32 WINAPI WinMain(_In_ HINSTANCE hInInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ char* p…

8.three.js相机详解

8.three.js相机详解 1、 认识相机 在Threejs中相机的表示是THREE.Camera&#xff0c;它是相机的抽象基类&#xff0c;其子类有两种相机&#xff0c;分别是正投影相机THREE.OrthographicCamera和透视投影相机THREE.PerspectiveCamera&#xff1a; 正投影和透视投影的区别是&am…