超实用的SpringBoot中全局异常处理器(供参考)

本文介绍了如何在SpringBoot应用中实现一个全局的异常处理器,提供详细的步骤和代码示例,帮助开发者优雅地处理各类异常情况。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.dao.DataAccessException;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.util.WebUtils;

import javax.security.auth.login.FailedLoginException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.io.BufferedReader;
import java.io.StringReader;
import java.util.List;
import java.util.Objects;

import static org.springframework.http.HttpHeaders.WWW_AUTHENTICATE;

/**
 * 全局异常处理类
 */
@Slf4j
@ControllerAdvice
@RestController
public class GlobalExceptionHandler implements ErrorController {

    private static final String PATH = "/error";
    private static final String JSON_ERROR_INFO = "JSON parse error:";
    private static final String SEMICOLON = ";";
    private static final char BLANK = ' ';
    private static final int CUT_LENGTH = 100;

    @RequestMapping(value = PATH, produces = {MediaType.APPLICATION_JSON_VALUE})
    @ResponseBody
    public ResponseEntity<Body<Void>> errorHandler(HttpServletResponse response, HttpServletRequest request) {
        Integer statusCode = (Integer) request.getAttribute(WebUtils.ERROR_STATUS_CODE_ATTRIBUTE);
        String message = String.valueOf(request.getAttribute(WebUtils.ERROR_MESSAGE_ATTRIBUTE));
        log.warn("[Handle_Error] - status:{}, message:{}", statusCode, message);
        response.setStatus(statusCode);
        return ResponseUtil.of(HttpStatus.valueOf(statusCode), message);
    }

    @Override
    public String getErrorPath() {
        return PATH;
    }

    @ExceptionHandler
    @ResponseBody
    public ResponseEntity<Body<Void>> exceptionHandler(MissingServletRequestParameterException e) {
        StringBuilder sb = new StringBuilder().append("未提供必选请求参数 '")
                .append(e.getParameterName()).append("'(").append(e.getParameterType()).append(")");
        log.warn("[Handle_MissingServletRequestParameterException] - {}", sb.toString());
        return ResponseUtil.badRequest(sb.toString());
    }

    @ExceptionHandler
    @ResponseBody
    public ResponseEntity<Body<Void>> exceptionHandler(IllegalArgumentException e) {
        log.warn("[Handle_IllegalArgumentException] - {}", e.getMessage());
        return ResponseUtil.badRequest(e.getMessage());
    }

    @ExceptionHandler
    @ResponseBody
    public ResponseEntity<Body<Void>> exceptionHandler(MethodArgumentTypeMismatchException e) {
        StringBuilder sb = new StringBuilder().append("参数 '").append(e.getName())
                .append("'(").append(e.getRequiredType().getSimpleName()).append(")类型不匹配: ").append(e.getValue());
        log.warn("[Handle_MethodArgumentTypeMismatchException] - {}", sb.toString());
        return ResponseUtil.badRequest(sb.toString());
    }

    @ExceptionHandler
    @ResponseBody
    public ResponseEntity<Body<Void>> exceptionHandler(MethodArgumentNotValidException e) {
        return ResponseUtil.badRequest(getErrorMsg(e.getBindingResult()));
    }

    @ExceptionHandler
    @ResponseBody
    public ResponseEntity<Body<Void>> exceptionHandler(BindException e) {
        return ResponseUtil.badRequest(getErrorMsg(e.getBindingResult()));
    }

    @ExceptionHandler
    @ResponseBody
    public ResponseEntity<Body<Void>> exceptionHandler(ConstraintViolationException e) {
        log.warn("[Handle_ConstraintViolationException] - {}", e.getMessage());

        final var violations = e.getConstraintViolations();
        if (CollectionUtils.isEmpty(violations)) {
            return ResponseUtil.badRequest("未知错误");
        }
        final var first = violations.stream().findFirst();
        final ConstraintViolation<?> violation = first.get();
        if (Objects.isNull(violation)) {

        }
        return ResponseUtil.badRequest(violation.getMessage());
    }

    @ExceptionHandler
    @ResponseBody
    public ResponseEntity<Body<Void>> exceptionHandler(ResourceNotFoundException e) {
        log.warn("[Handle_ResourceNotFoundException] - {}", e.getMessage());
        return ResponseUtil.serverError(e.getMessage());
    }

    @ExceptionHandler
    @ResponseBody
    public ResponseEntity<Body<Void>> exceptionHandler(HttpMessageNotReadableException e) {
        log.error("[Handle_HttpMessageNotReadableException] - {}", e.getMessage());
        String jsonErrorMsg;
        if (e.getLocalizedMessage().contains(JSON_ERROR_INFO) &&
                Objects.nonNull(jsonErrorMsg =
                        dealWithJsonExceptionError(e.getLocalizedMessage()))) {
            return ResponseUtil.badRequest("格式转换错误,请检查" + jsonErrorMsg + "字段");

        }
        return ResponseUtil.serverError("消息不可读:" + StringUtils.substring(e.getMessage(), 0, CUT_LENGTH));
    }

    @ExceptionHandler
    @ResponseBody
    public ResponseEntity<Body<Void>> exceptionHandler(FailedLoginException e) {
        String msg = e.getMessage();
        log.warn("[Handle_FailedLoginException] - {}", msg);
        return ResponseUtil.badRequest("登录失败:" + msg);
    }

    @ExceptionHandler
    @ResponseBody
    public ResponseEntity<Body<Void>> exceptionHandler(HttpServletResponse response, AccessDeniedException e) {
        log.warn("[Handle_AccessDeniedException] - {}", e.getMessage());
        StringBuilder wwwAuthenticateBuilder = new StringBuilder();
        wwwAuthenticateBuilder.append("Bearer").append(" ")
                .append("realm").append("=").append("xiaohoucode.com").append(", ")
                .append("error").append("=").append("insufficient_scope");
        response.setHeader(WWW_AUTHENTICATE, wwwAuthenticateBuilder.toString());
        return ResponseUtil.forbidden(e.getMessage());
    }

    @ExceptionHandler
    @ResponseBody
    public ResponseEntity<Body<Void>> exceptionHandler(DataAccessException e) {
        log.warn("[Handle_DataAccessException] - 数据访问异常 {}", ExceptionUtils.getStackTrace(e));
        return ResponseUtil.serverError("数据访问异常,请联系技术支持!");
    }

    @ExceptionHandler
    @ResponseBody
    public ResponseEntity<Body<Void>> exceptionHandler(MyAppCustomException e) {
        log.error("[Handle_MyAppCustomException] - {}", getSimpleStackTrace(e));
        return ResponseUtil.serverError(StringUtils.substring(e.getMessage(), 0, CUT_LENGTH));
    }

    @ExceptionHandler
    @ResponseBody
    public ResponseEntity<Body<Void>> exceptionHandler(Exception e) {
        log.error("[Handle_Exception] - {}", ExceptionUtils.getStackTrace(e));
        return ResponseUtil.serverError("抱歉,系统发生意外了。["
                + StringUtils.substring(e.getMessage(), 0, CUT_LENGTH) + "]");
    }


    private String getErrorMsg(BindingResult bindingResult) {
        final var defaultMsg = "未知错误";
        List<FieldError> fieldErrors = bindingResult.getFieldErrors();
        if (CollectionUtils.isEmpty(fieldErrors)) {
            return defaultMsg;
        }
        FieldError error = fieldErrors.get(0);
        if (Objects.isNull(error)) {
            return defaultMsg;
        }
        log.warn("Binding error - {}: {}", error.getField(), error.getDefaultMessage());
        return error.getDefaultMessage();
    }

    /**
     * 针对性处理JsonException,友好返回异常信息
     *
     * @param errorMsg
     * @return
     */
    private String dealWithJsonExceptionError(String errorMsg) {
        int lastSemicolon = errorMsg.lastIndexOf(SEMICOLON);
        if (lastSemicolon != -1) {
            int startIndex = lastSemicolon - 1;
            while (startIndex > 0 && errorMsg.charAt(startIndex) != BLANK) {
                startIndex--;
            }
            return errorMsg.substring(++startIndex, lastSemicolon);
        }

        return null;
    }

    private String getSimpleStackTrace(Exception e) {
        StringBuilder simpleStackTraceBuilder = new StringBuilder();
        try (BufferedReader bufferedReader = new BufferedReader(new StringReader(
                ExceptionUtils.getStackTrace(e)))) {
            String line = bufferedReader.readLine();
            simpleStackTraceBuilder.append(line).append("\n");
            while ((line = bufferedReader.readLine()) != null) {
                if (StringUtils.contains(line, "com.tal.")) {
                    simpleStackTraceBuilder.append(line).append("\n");
                }
            }
        } catch (Exception ex) {
            // do nothing
        }
        return simpleStackTraceBuilder.toString();
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值