SpringBoot全局异常

Posted by     "zengchengjie" on Friday, December 6, 2024

在Spring Boot中,优雅地处理全局异常可以通过定义一个全局异常处理器来实现。全局异常处理器可以帮助你集中处理应用程序中的所有异常,并返回统一的响应格式。以下是如何实现全局异常处理器的步骤:

  1. 创建自定义异常类(可选):如果你有特定的业务异常,可以创建自定义异常类。

  2. 创建全局异常处理器:使用@ControllerAdvice注解来定义一个全局异常处理器类。

  3. 定义异常处理方法:在全局异常处理器类中,使用@ExceptionHandler注解来定义处理特定异常的方法。

下面是一个简单的示例:

自定义异常类(可选)

public class CustomException extends RuntimeException {
    private int errorCode;

    public CustomException(String message, int errorCode) {
        super(message);
        this.errorCode = errorCode;
    }

    public int getErrorCode() {
        return errorCode;
    }
}

全局异常处理器

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;

@ControllerAdvice
public class GlobalExceptionHandler {

    // 处理自定义异常
    @ExceptionHandler(CustomException.class)
    public ResponseEntity<?> handleCustomException(CustomException ex, WebRequest request) {
        ApiError apiError = new ApiError(HttpStatus.BAD_REQUEST, ex.getMessage(), ex.getErrorCode());
        return new ResponseEntity<>(apiError, HttpStatus.BAD_REQUEST);
    }

    // 处理其他异常
    @ExceptionHandler(Exception.class)
    public ResponseEntity<?> handleGlobalException(Exception ex, WebRequest request) {
        ApiError apiError = new ApiError(HttpStatus.INTERNAL_SERVER_ERROR, ex.getMessage(), 500);
        return new ResponseEntity<>(apiError, HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

class ApiError {
    private HttpStatus status;
    private String message;
    private int errorCode;

    public ApiError(HttpStatus status, String message, int errorCode) {
        this.status = status;
        this.message = message;
        this.errorCode = errorCode;
    }

    // Getters and setters
    public HttpStatus getStatus() {
        return status;
    }

    public void setStatus(HttpStatus status) {
        this.status = status;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public int getErrorCode() {
        return errorCode;
    }

    public void setErrorCode(int errorCode) {
        this.errorCode = errorCode;
    }
}

使用自定义异常

在你的服务或控制器中,你可以抛出自定义异常:

@Service
public class SomeService {

    public void someMethod() {
        // 某些业务逻辑
        if (someCondition) {
            throw new CustomException("Something went wrong", 400);
        }
    }
}

通过这种方式,你可以集中管理所有的异常处理逻辑,并且可以确保返回给客户端的错误信息格式一致。

在 Spring Boot 应用程序中,全局异常处理是非常重要的一个方面。它可以帮助你集中管理和统一处理应用程序中的各种异常,从而提高代码的可维护性和用户体验。以下是一些关于 Spring Boot 全局异常处理的技巧和知识:

1. 使用 @ControllerAdvice 注解

@ControllerAdvice 注解用于定义全局异常处理器类。它可以捕获所有控制器中的异常,并提供统一的错误响应。

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleException(Exception ex) {
        ErrorResponse error = new ErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR.value(), "Internal Server Error", ex.getMessage());
        return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
    }

    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleResourceNotFoundException(ResourceNotFoundException ex) {
        ErrorResponse error = new ErrorResponse(HttpStatus.NOT_FOUND.value(), "Resource Not Found", ex.getMessage());
        return new ResponseEntity<>(error, HttpStatus.NOT_FOUND);
    }

    // 可以添加更多的 @ExceptionHandler 方法来处理特定的异常
}

2. 自定义异常类

为了更好地管理不同的异常情况,可以创建自定义异常类。这些异常类可以继承自 RuntimeException 或其他异常类。

public class ResourceNotFoundException extends RuntimeException {
    public ResourceNotFoundException(String message) {
        super(message);
    }
}

public class InvalidRequestException extends RuntimeException {
    public InvalidRequestException(String message) {
        super(message);
    }
}

3. 统一的错误响应格式

定义一个统一的错误响应格式,以便在发生异常时返回一致的错误信息。

public class ErrorResponse {
    private int status;
    private String message;
    private String details;

    public ErrorResponse(int status, String message, String details) {
        this.status = status;
        this.message = message;
        this.details = details;
    }

    // Getters and Setters
}

4. 使用 @ResponseStatus 注解

可以在自定义异常类上使用 @ResponseStatus 注解,指定异常对应的 HTTP 状态码。

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {
    public ResourceNotFoundException(String message) {
        super(message);
    }
}

5. 日志记录

在全局异常处理器中记录异常信息,有助于后续的问题排查和调试。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice
public class GlobalExceptionHandler {

    private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleException(Exception ex) {
        logger.error("Internal Server Error: {}", ex.getMessage(), ex);
        ErrorResponse error = new ErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR.value(), "Internal Server Error", ex.getMessage());
        return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
    }

    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleResourceNotFoundException(ResourceNotFoundException ex) {
        logger.warn("Resource Not Found: {}", ex.getMessage());
        ErrorResponse error = new ErrorResponse(HttpStatus.NOT_FOUND.value(), "Resource Not Found", ex.getMessage());
        return new ResponseEntity<>(error, HttpStatus.NOT_FOUND);
    }
}

6. 响应体的自定义

可以根据不同的客户端需求,自定义响应体的格式。例如,对于 API 客户端,可以返回 JSON 格式的错误信息;对于 Web 客户端,可以返回 HTML 页面。

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.servlet.ModelAndView;

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleException(Exception ex) {
        ErrorResponse error = new ErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR.value(), "Internal Server Error", ex.getMessage());
        return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
    }

    @ExceptionHandler(ResourceNotFoundException.class)
    public ModelAndView handleResourceNotFoundException(ResourceNotFoundException ex) {
        ModelAndView modelAndView = new ModelAndView("error");
        modelAndView.addObject("message", "Resource Not Found");
        modelAndView.addObject("details", ex.getMessage());
        return modelAndView;
    }
}

7. 异常处理的优先级

Spring Boot 会按照方法签名的特异性来决定哪个 @ExceptionHandler 方法应该被调用。因此,更具体的异常处理方法会优先于更通用的异常处理方法。

8. 使用 AOP 切面

除了 @ControllerAdvice,还可以使用 AOP 切面来实现全局异常处理。这种方式更加灵活,可以应用于更广泛的场景。

import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class GlobalExceptionAspect {

    @AfterThrowing(pointcut = "execution(* com.ctsi..*(..))", throwing = "ex")
    public void handleException(Exception ex) {
        // 处理异常
    }
}

9. 集成第三方库

可以集成第三方库,如 HystrixResilience4j 等,来增强异常处理的能力,实现熔断、重试等功能。

10. 测试全局异常处理

使用 JUnit 和 Mockito 等测试框架,编写单元测试来验证全局异常处理的正确性。

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.web.servlet.MockMvc;

import static org.mockito.Mockito.doThrow;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@WebMvcTest
public class GlobalExceptionHandlerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private SomeService someService;

    @Test
    public void testHandleException() throws Exception {
        doThrow(new RuntimeException("Test exception")).when(someService).someMethod();

        mockMvc.perform(get("/some-endpoint"))
                .andExpect(status().isInternalServerError());
    }
}

通过以上技巧和知识,你可以有效地管理和处理 Spring Boot 应用程序中的各种异常,提升应用的健壮性和用户体验。