本文的核心重点就是讲解SpringBoot3分层架构中全局异常处理类的设计。
前文回顾:
上文中我们已经实现了SpringBoot项目的分层架构
架构设计需要考虑诸多因素,合理的架构设计可以帮助项目的可维护性、扩展性和可测试性。以下是一些常见的最佳实践和设计建议,适合中大型项目或者复杂的 Spring Boot 3 项目
一、 分层架构设计
典型的分层架构可以帮助将关注点分离,推荐使用三层或四层架构:
- Controller 层:处理 HTTP 请求,执行输入验证,调用 Service 层。
- Service 层:包含业务逻辑,负责处理数据交互。
- Repository 层:负责数据访问操作,连接数据库。
- DTO(Data Transfer Object)层:用于在不同层之间传输数据,避免暴露数据库实体。
二、模块化设计
随着项目规模扩大,考虑将项目拆分为多个模块。这可以通过 Maven/Gradle 的多模块(multi-module)项目来实现:
- Domain 模块:包含领域对象(实体类)和业务规则。
- Service 模块:包含业务逻辑层。
- Web 模块:用于处理 API、Controller 等与外部交互的部分。
- Repository 模块:负责数据库交互。
这样可以确保不同的模块具有明确的职责,项目更加可扩展。
三、 项目结构示例
以下是一个推荐的 Spring Boot 项目结构示例:
src
├── main
│ ├── java
│ │ └── com.example.project
│ │ ├── controller // Controller 层,处理请求
│ │ ├── service // Service 层,业务逻辑
│ │ ├── repository // Repository 层,数据持久化
│ │ ├── dto // DTO 类,数据传输对象
│ │ ├── entity // 实体类,映射数据库
│ │ ├── exception // 自定义异常处理
│ │ ├── utils // 通用工具类
│ │ ├── filter // 过滤类
│ │ └── config // 配置类(如 Security, DataSource 等)
│ └── resources
│ ├── application.properties // 应用程序配置文件
│ └── static // 静态资源(前端文件)
└── test // 单元测试和集成测试
四、 分层依赖与解耦
- Service 层与 Repository 层解耦:使用接口来定义服务的契约,而不是直接依赖具体的实现类。例如,定义
UserRepository
接口,然后在 Service 层中依赖接口而不是具体实现。 - DTO 与实体类解耦:通过 DTO 将实体类和前端返回的数据解耦,避免直接暴露数据库的结构。这就涉及到日常开发中所说的DO、VO、DTO、BO各种O。我认为最好的方式按照请求过程来区分,入参UserRequest、出参UserResponse、中间数据转换状态UserDTO、数据临时状态UserDO。
五、异常处理
- 全局异常处理:
- 通过
@ControllerAdvice
和@ExceptionHandler
实现全局异常处理,确保统一的错误响应格式
- 通过
本文的核心重点就是讲解全局异常处理类的设计。 在 Spring Boot 项目中,通常会在项目中封装异常处理类。以下是自定义异常、全局异常处理器和统一的错误响应格式。
5.1、统一错误响应类
定义一个统一的 ErrorResponse
类,用于封装异常处理的响应数据。
package com.javastudy.springboot3framework.myapp.exception;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
/**
* @author zhizhou 2024/9/10 13:28
*/
@AllArgsConstructor
@NoArgsConstructor
@Data
public class ErrorResponse {
private LocalDateTime timestamp; //时间戳
private String message;//消息
private String details; //详细信息
private int status;//状态
private int code; //错误码
public ErrorResponse(String message, String details, int status, int code) {
this.timestamp = LocalDateTime.now();
this.message = message;
this.details = details;
this.status = status;
this.code = code;
}
}
5.2、 全局异常处理类
使用 @ControllerAdvice
和 @ExceptionHandler
注解来实现全局异常处理器,统一处理应用程序中的异常。
package com.javastudy.springboot3framework.myapp.exception;
import lombok.extern.slf4j.Slf4j;
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;
/**
* @author zhizhou 2024/9/10 23:28
*/
@Slf4j
@ControllerAdvice
public class GlobalException3Handler {
// 处理资源未找到异常
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorResponse> handleResourceNotFoundException(ResourceNotFoundException ex, WebRequest request) {
ErrorResponse errorResponse = new ErrorResponse(
ex.getMessage(),
request.getDescription(false),
HttpStatus.NOT_FOUND.value(),
ErrorCode.RESOURCE_NOT_FOUND.getCode()
);
return new ResponseEntity<>(errorResponse, HttpStatus.NOT_FOUND);
}
// 处理无效请求参数异常
@ExceptionHandler(InvalidRequestException.class)
public ResponseEntity<ErrorResponse> handleInvalidRequestException(InvalidRequestException ex, WebRequest request) {
ErrorResponse errorResponse = new ErrorResponse(
ex.getMessage(),
request.getDescription(false),
HttpStatus.BAD_REQUEST.value(),
ErrorCode.INVALID_INPUT.getCode()
);
return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);
}
// 处理操作失败异常
@ExceptionHandler(OperationFailedException.class)
public ResponseEntity<ErrorResponse> handleOperationFailedException(OperationFailedException ex, WebRequest request) {
ErrorResponse errorResponse = new ErrorResponse(
ex.getMessage(),
request.getDescription(false),
HttpStatus.INTERNAL_SERVER_ERROR.value(),
ErrorCode.OPERATION_FAILED.getCode()
);
return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR);
}
// 处理所有其他异常
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGlobalException(Exception ex, WebRequest request) {
ErrorResponse errorResponse = new ErrorResponse(
"Internal Server Error",
request.getDescription(false),
HttpStatus.INTERNAL_SERVER_ERROR.value(),
ErrorCode.INTERNAL_SERVER_ERROR.getCode()
);
return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR);
}
//异常-未寻址到资源
public static class ResourceNotFoundException extends RuntimeException {
public ResourceNotFoundException(String message) {
super(message);
}
}
//异常-请求参数无效
public static class InvalidRequestException extends RuntimeException {
public InvalidRequestException(String message) {
super(message);
}
}
//异常-操作失败
public static class OperationFailedException extends RuntimeException {
public OperationFailedException(String message) {
super(message);
}
}
}
5.3、使用枚举定义错误码(自定义常规的以及特殊业务错误代码)
可以通过枚举来管理错误码和消息:
public enum ErrorCode {
RESOURCE_NOT_FOUND(40401, "资源不存在"),
INVALID_INPUT(40001, "入参不合法"),
OPERATION_FAILED(50001, "操作失败"),
INTERNAL_SERVER_ERROR(50000, "内部服务错误");
private final int code;
private final String message;
ErrorCode(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
}
总结
到此为止,异常类中 完成了 自定义异常类、全局异常处理器、统一错误响应类、错误码管理等通用的异常处理机制。以上方案经过时间,方案相对通用可复制到自己的项目中。
OK,今天就到这~ 关注我,一起为程序员职业生涯储能续航。