java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > SpringBoot全局异常处理

SpringBoot中全局异常处理的5种实现方式小结

作者:风象南

在实际开发中,异常处理是一个非常重要的环节,合理的异常处理机制不仅能提高系统的健壮性,还能大大提升用户体验,下面我们就来看看SpringBoot中全局异常处理的5种实现方式吧

前言

在实际开发中,异常处理是一个非常重要的环节。合理的异常处理机制不仅能提高系统的健壮性,还能大大提升用户体验。本文将详细介绍SpringBoot中全局异常处理的几种实现方式。

为什么需要全局异常处理?

如果没有统一的异常处理机制,当系统发生异常时,可能会导致以下问题

下面,来看几种在SpringBoot中实现全局异常处理的方式。

方式一:@ControllerAdvice/@RestControllerAdvice + @ExceptionHandler

这是SpringBoot中最常用的全局异常处理方式。

首先,定义一个统一的返回结果类:

public class Result<T> {
    private Integer code;
    private String message;
    private T data;

    public static <T> Result<T> success(T data) {
        Result<T> result = new Result<>();
        result.setCode(200);
        result.setMessage("操作成功");
        result.setData(data);
        return result;
    }

    public static <T> Result<T> error(Integer code, String message) {
        Result<T> result = new Result<>();
        result.setCode(code);
        result.setMessage(message);
        return result;
    }

    // getter和setter方法省略
}

然后,定义自定义异常:

public class BusinessException extends RuntimeException {
    private Integer code;

    public BusinessException(String message) {
        super(message);
        this.code = 500;
    }

    public BusinessException(Integer code, String message) {
        super(message);
        this.code = code;
    }

    public Integer getCode() {
        return code;
    }
}

创建全局异常处理类:

import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.NoHandlerFoundException;

@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 处理自定义业务异常
     */
    @ExceptionHandler(BusinessException.class)
    public Result<Void> handleBusinessException(BusinessException e) {
        return Result.error(e.getCode(), e.getMessage());
    }

    /**
     * 处理参数校验异常
     */
    @ExceptionHandler(ConstraintViolationException.class)
    public Result<Void> handleValidationException(ConstraintViolationException e) {
        return Result.error(400, "参数校验失败:" + e.getMessage());
    }

    /**
     * 处理资源找不到异常
     */
    @ExceptionHandler(NoHandlerFoundException.class)
    public Result<Void> handleNotFoundException(NoHandlerFoundException e) {
        return Result.error(404, "请求的资源不存在");
    }

    /**
     * 处理其他所有未捕获的异常
     */
    @ExceptionHandler(Exception.class)
    public Result<Void> handleException(Exception e) {
        return Result.error(500, "服务器内部错误:" + e.getMessage());
    }
}

优点

方式二:实现HandlerExceptionResolver接口

HandlerExceptionResolver是Spring MVC中用于解析异常的接口,我们可以通过实现此接口来自定义异常处理逻辑。

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import com.fasterxml.jackson.databind.ObjectMapper;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

@Component
public class CustomExceptionResolver implements HandlerExceptionResolver {

    private final ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public ModelAndView resolveException(HttpServletRequest request, 
                                        HttpServletResponse response, 
                                        Object handler, Exception ex) {
        Result<?> result;
        
        // 处理不同类型的异常
        if (ex instanceof BusinessException) {
            BusinessException businessException = (BusinessException) ex;
            result = Result.error(businessException.getCode(), businessException.getMessage());
        } else {
            result = Result.error(500, "服务器内部错误:" + ex.getMessage());
        }
        
        // 设置响应类型和状态码
        response.setContentType("application/json;charset=UTF-8");
        response.setStatus(HttpServletResponse.SC_OK);
        
        try {
            // 将错误信息写入响应
            PrintWriter writer = response.getWriter();
            writer.write(objectMapper.writeValueAsString(result));
            writer.flush();
            writer.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        
        // 返回空的ModelAndView表示异常已经处理完成
        return new ModelAndView();
    }
}

优点

缺点

方式三:使用SimpleMappingExceptionResolver

SimpleMappingExceptionResolver是HandlerExceptionResolver的一个简单实现,适用于返回错误视图的场景。

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;

import java.util.Properties;

@Configuration
public class ExceptionConfig {

    @Bean
    public SimpleMappingExceptionResolver simpleMappingExceptionResolver() {
        SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver();
        
        // 设置默认错误页面
        resolver.setDefaultErrorView("error/default");
        
        // 设置异常映射
        Properties mappings = new Properties();
        mappings.setProperty(BusinessException.class.getName(), "error/business");
        mappings.setProperty(RuntimeException.class.getName(), "error/runtime");
        resolver.setExceptionMappings(mappings);
        
        // 设置异常属性名,默认为"exception"
        resolver.setExceptionAttribute("ex");
        
        return resolver;
    }
}

对应的错误页面模板(使用Thymeleaf):

<!-- templates/error/business.html -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>业务错误</title>
</head>
<body>
    <h1>业务错误</h1>
    <p th:text="${ex.message}">错误信息</p>
</body>
</html>

优点

缺点

方式四:自定义ErrorController

Spring Boot提供了BasicErrorController来处理应用中的错误,我们可以通过继承或替换它来自定义错误处理逻辑。

import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;

@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class CustomErrorController implements ErrorController {

    @RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE)
    @ResponseBody
    public ResponseEntity<Result<Void>> handleError(HttpServletRequest request) {
        HttpStatus status = getStatus(request);
        
        Integer statusCode = status.value();
        String message = "未知错误";
        
        switch (statusCode) {
            case 404:
                message = "请求的资源不存在";
                break;
            case 403:
                message = "没有权限访问该资源";
                break;
            case 500:
                message = "服务器内部错误";
                break;
            default:
                break;
        }
        
        return new ResponseEntity<>(Result.error(statusCode, message), status);
    }

    @RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
    public String handleErrorHtml(HttpServletRequest request, Map<String, Object> model) {
        HttpStatus status = getStatus(request);
        
        // 添加错误信息到模型
        model.put("status", status.value());
        model.put("message", status.getReasonPhrase());
        
        // 返回错误页面
        return "error/error";
    }

    private HttpStatus getStatus(HttpServletRequest request) {
        Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
        if (statusCode == null) {
            return HttpStatus.INTERNAL_SERVER_ERROR;
        }
        try {
            return HttpStatus.valueOf(statusCode);
        } catch (Exception ex) {
            return HttpStatus.INTERNAL_SERVER_ERROR;
        }
    }
}

优点

缺点

方式五:使用错误页面模板

Spring Boot支持通过静态HTML页面或模板来展示特定状态码的错误。只需要在templates/error/目录下创建对应状态码的页面即可。

目录结构:

src/
  main/
    resources/
      templates/
        error/
          404.html
          500.html
          error.html  # 默认错误页面

例如,404.html的内容:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>页面不存在</title>
</head>
<body>
    <h1>404 - 页面不存在</h1>
    <p>您请求的页面不存在,请检查URL是否正确。</p>
    <a href="/" rel="external nofollow" >返回首页</a>
</body>
</html>

优点

缺点

实战示例:完整的异常处理体系

下面提供一个完整的异常处理体系示例,组合了多种方式:

首先,创建异常体系:

// 基础异常类
public abstract class BaseException extends RuntimeException {
    private final int code;

    public BaseException(int code, String message) {
        super(message);
        this.code = code;
    }

    public int getCode() {
        return code;
    }
}

// 业务异常
public class BusinessException extends BaseException {
    public BusinessException(String message) {
        super(400, message);
    }

    public BusinessException(int code, String message) {
        super(code, message);
    }
}

// 系统异常
public class SystemException extends BaseException {
    public SystemException(String message) {
        super(500, message);
    }

    public SystemException(int code, String message) {
        super(code, message);
    }
}

// 权限异常
public class PermissionException extends BaseException {
    public PermissionException(String message) {
        super(403, message);
    }
}

创建全局异常处理器:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.NoHandlerFoundException;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

@RestControllerAdvice
public class GlobalExceptionHandler {
    private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    /**
     * 处理自定义基础异常
     */
    @ExceptionHandler(BaseException.class)
    public Result<?> handleBaseException(BaseException e) {
        logger.error("业务异常:{}", e.getMessage());
        return Result.error(e.getCode(), e.getMessage());
    }

    /**
     * 处理参数校验异常(@Valid)
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Result<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
        String errorMsg = fieldErrors.stream()
                .map(error -> error.getField() + ": " + error.getDefaultMessage())
                .collect(Collectors.joining(", "));
        logger.error("参数校验错误:{}", errorMsg);
        return Result.error(400, "参数校验错误: " + errorMsg);
    }

    /**
     * 处理所有其他异常
     */
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public Result<?> handleException(Exception e) {
        logger.error("系统异常:", e);
        return Result.error(500, "服务器内部错误,请联系管理员");
    }
}

使用示例:

@RestController
@RequestMapping("/api")
public class UserController {

    @GetMapping("/{id}")
    public Result<User> getUser(@PathVariable Long id) {
        if (id <= 0) {
            throw new BusinessException("用户ID必须大于0");
        }
        
        if (id > 100) {
            throw new SystemException("系统维护中");
        }
        
        if (id == 10) {
            throw new PermissionException("没有权限查看此用户");
        }
        
        // 模拟查询用户
        User user = new User(id, "用户" + id, "user" + id + "@example.com");
        return Result.success(user);
    }
}

各方式对比与使用建议

实现方式适用场景灵活性复杂度
@ControllerAdvice + @ExceptionHandlerRESTful API、前后端分离项目
HandlerExceptionResolver需要精细控制异常处理过程的场景
SimpleMappingExceptionResolver传统Web应用,需要返回错误页面
自定义ErrorController需要自定义错误页面和错误响应的场景
错误页面模板简单的Web应用,只需自定义错误页面

建议

最佳实践总结

结语

在Spring Boot应用中,全局异常处理是提高系统健壮性和用户体验的重要环节。通过本文介绍的几种实现方式,开发者可以根据实际需求选择合适的实现方案。在实际项目中,往往需要结合多种方式,构建一个完整的异常处理体系。

以上就是SpringBoot中全局异常处理的5种实现方式小结的详细内容,更多关于SpringBoot全局异常处理的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文