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.rhkb.cn/news/457483.html

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

相关文章

S-Function

目录 S-Function介绍 生成S-Function的三种常用手段 使用手写S-函数合并定制代码 使用S-Function Builder块合并定制代码 使用代码继承工具合并定制代码 S-Function介绍 我们可以使用S-Function扩展Simulink对仿真和代码生成的支持。例如&#xff0c;可以使用它们&#xf…

ELK之路第一步——Elasticsearch集群的搭建以及踩坑记录

elasticSearch集群 前言一、架构二、下载三、虚拟机相关设置3.1 创建es用户3.2 为建es用户赋权sudo3.3 更换es目录所属用户 四、Elasticsearch配置文件修改4.1 修改elasticsearch.yml4.2 修改jvm.options4.3 修改jdk路径 五、启动六、启动报错七、可视化界面cerebro 前言 Elk&…

二进制方式部署k8s集群

目标任务: 1、Kubernetes集群部署架构规划 2、部署Etcd数据库集群 3、在Node节点安装Docker 4、部署Flannel网络插件 5、在Master节点部署组件(api-server,schduler,controller-manager) 6、在Node节点部署组件(kubelet,kube-proxy) 7、查看集群状态 8、运行⼀个测…

【有啥问啥】DINO:一种改进的去噪锚框的端到端目标检测器

DINO&#xff1a;一种改进的去噪锚框的端到端目标检测器 在目标检测领域&#xff0c;DINO&#xff08;DETR with Improved DeNoising Anchor Boxes for End-to-End Object Detection&#xff09;是一种创新的端到端目标检测模型&#xff0c;旨在解决传统目标检测算法中的一些关…

基于Multisim的音频放大电路设计与仿真

基本设计要求&#xff1a;设计并仿真实现一个音频功率放大器。功率放大器的电源电压为&#xff0b;5V&#xff08;电路其他部分的电源电压不限&#xff09;&#xff0c;负载为8Ω电阻。具体要求如下&#xff1a;1&#xff09;3dB通频带为300&#xff5e;3400Hz&#xff0c;输出…

AI智能爆发:从自动驾驶到智能家居,科技如何改变我们的日常?

内容概要 在这个瞬息万变的时代&#xff0c;AI智能以其惊人的速度崛起&#xff0c;正在以前所未有的方式改变我们的生活。从自动驾驶到智能家居&#xff0c;这一系列创新为我们的日常生活注入了新的活力和便利。从交通安全到居家体验&#xff0c;这些科技不仅仅是工具&#xf…

【Visual Studio】下载安装 Visual Studio Community 并配置 C++ 桌面开发环境的图文教程

引言 Visual Studio 是一个面向 .NET 和 C 开发人员的综合性 Windows 版 IDE&#xff0c;可用于构建 Web、云、桌面、移动应用、服务和游戏。 安装步骤 访问 Visual Studio 的官方下载页面&#xff1a; https://visualstudio.microsoft.com/zh-hans/downloads/运行已下载的 V…

【数据结构与算法】第4课—数据结构单链表OJ练习题

文章目录 1. 移除链表元素2. 反转链表3. 找链表中间节点4. 合并两个有序的链表5. 分割链表6. 链表的回文结构7. 相交链表8. 判断环形链表9. 返回环形链表的入环节点10. 随机链表的复制 1. 移除链表元素 题目 思路 #include <stdio.h> #include <stdlib.h> #include…

【功能安全】技术安全概念TSC

目录 01 TSC定义 02 TSC注意事项 03 TSC案例 01 TSC定义 所处位置 TSC:Technical safety concept技术安全概念 TSR:Technical safety requirement技术安全需求 在系统开发阶段属于安全活动4-6 系统层产品开发示例 TSC目的

传输层UDP

再谈端口号 端口号&#xff1a;标识了主机上进行通信的不同的应用程序 在TCP/IP 协议中我们用“源IP”"源端口号" “目的IP”“目的端口号” “协议号”五元组来标识一个通信 用netstat -n 查看 查看网络信息&#xff0c;我们有两种命令查看网络通信1.用netsta…

力扣刷题(sql)--零散知识点(1)

通过一段时间的刷题&#xff0c;感觉自己的sql能力逐渐上去&#xff0c;所以不会像前三道题一样讲那么详细了&#xff0c;这里主要会讲到一些特殊的知识点和方法。另外&#xff0c;我的建议是做完一个题有好的想法赶紧记录下来&#xff0c;不要想着最后汇总&#xff0c;不然会懒…

通过cv库智能切片 把不同的分镜切出来 自媒体抖音快手混剪

用 手机自动化脚本&#xff0c;从自媒体上获取视频&#xff0c;一个商品对应几百个视频&#xff0c;我们把这几百个视频下载下来&#xff0c;进行分镜 视频切片&#xff0c;从自媒体上下载视频&#xff0c;通过cv库用直方图识别每个镜头进行切片。 下载多个图片进行视频的伪原…

香橙派5(RK3588)使用npu加速yolov5推理的部署过程

香橙派5使用npu加速yolov5推理的部署过程 硬件环境 部署过程 模型训练(x86主机) 在带nvidia显卡(最好)的主机上进行yolo的配置与训练, 获取最终的best.pt模型文件, 详见另一篇文档 模型转换(x86主机) 下载airockchip提供的yolov5(从pt到onnx) 一定要下这个版本的yolov5, …

sass软件登录设定——未来之窗行业应用跨平台架构

一、saas软件开发中登录设计 以为大公司为参考思迅在登录时候需要录入商户号 二、独立商户商户好处 1.每个店铺的账户是独立的&#xff0c;保护商户职员账户信息的相对安全。 2.不同店铺可以试用相同用户名

LDR6020:为VR串流线方案注入高效能与稳定性

随着虚拟现实&#xff08;VR&#xff09;技术的不断发展&#xff0c;VR设备已经成为连接用户与沉浸式体验的重要桥梁。而VR串流线&#xff0c;作为这一技术的重要组成部分&#xff0c;更是承担着传输高质量图像、音频及数据的重任。在这个过程中&#xff0c;一款功能强大、性能…

【计网】从零开始认识IP协议 --- 认识网络层,认识IP报头结构

从零开始认识IP协议 1 网络层协议1.1 初步认识IP协议1.2 初步理解IP地址 2 IP协议报头3 初步理解网段划分 1 网络层协议 1.1 初步认识IP协议 我们已经熟悉了传输层中的UDP和TCP协议&#xff0c;接下来我们来接触网络层的协议&#xff1a; 网络层在计算机网络中的意义主要体现…

寻找大自然的颜色

走在停停&#xff0c;停停走走&#xff0c;恍惚间一天过去了&#xff0c;转瞬间一年过去了&#xff0c;身边的一切在变化又不在变化&#xff0c;生活是自己的又不是自己的。 今天是个特殊的日子&#xff0c;其实前几天对我而言就算特殊的日子了&#xff0c;一个心里暗暗等待着却…

Maven项目管理工具-初始+环境配置

1. Maven的概念 1.1. 什么是Maven Maven是跨平台的项目管理工具。主要服务于基于Java平台的项目构建&#xff0c;依赖管理和项目信息管理。 理想的项目构建&#xff1a;高度自动化&#xff0c;跨平台&#xff0c;可重用的组件&#xff0c;标准化的流程 maven能够自动下载依…

python项目实战——多线程爬虫

多线程爬虫 文章目录 多线程爬虫概念并行并发Python多线程用途threading模块小知识----函数体内pass的用处1. **占位符**2. **控制结构**3. **定义接口**总结 代码解读单线程--串行多线程--并行查看当前程序的线程让主函数等待子线程结束&#xff0c;再运行---.join()join()方法…

C# 串口通信教程

串口通信&#xff08;Serial Communication&#xff09;是一种用于设备之间数据传输的常见方法&#xff0c;通常用于与外部硬件设备&#xff08;如传感器、机器人、微控制器&#xff09;进行通信。在 C# 中&#xff0c;System.IO.Ports 命名空间提供了与串口设备交互的功能&…