Spring Boot3项目的常见通用整体架构-全局异常类

本文的核心重点就是讲解SpringBoot3分层架构中全局异常处理类的设计。

前文回顾:

上文中我们已经实现了SpringBoot项目的分层架构

Spring Boot3项目的常见通用整体架构

架构设计需要考虑诸多因素,合理的架构设计可以帮助项目的可维护性、扩展性和可测试性。以下是一些常见的最佳实践和设计建议,适合中大型项目或者复杂的 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,今天就到这~ 关注我,一起为程序员职业生涯储能续航。

  • 19
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值