Java中如何统一处理异常

1、Java中异常相关概念

1.1异常类

  • Throwable类:Java中所有异常类的父类,它包含了最终要的两个类Exception和Error。

  • Error类:属于程序无法处理的错误,是JVM需要承担的,无法通过try-catch进行捕捉,例如系统崩溃、内存不足、堆栈溢出,编译器不会对这类异常进行检查,一旦发生就容易导致程序运行终止,仅靠程序本身无法恢复。

  • Exception:程序本身可以处理的异常,可以通过catch进行捕捉,也是我们需要处理的,以保证程序能够正常运行

    Exception又分为运行时异常(RunTimeException,又叫非受检查异常unchecked Exception)和非运行时异常(又叫受检查异常checked Exception)。

    运行时异常我们可处理可不处理,一般由程序逻辑错误引起,我们应该在编码时尽量避免这种错误,比如:NullPointException

    非运行时异常时Exception中除RunTimeException以外的异常,比如:IOException、SQLException等以及我们自定义的Exception异常,这种异常,Java编译器会强制要求我们处理

    @SneakyThrows注解:作用在方法上,加上以后可以对非运行时异常不进行处理

1.2异常的处理方式

  • try-catch:try中放可能发生异常的代码,如果发生异常,后面的代码不会再执行,直接进入catch,在catch中拿到异常对象,我们进行处理
  • try-catch-finally:finally是无论异常是否发生都会执行的,通常用来释放资源
  • try-finally:相当于没有捕捉异常
  • throws:在方法名后面进行抛出,表明该方法对此异常不进行处理,由调用者进行处理,谁用谁处理,调用者也可继续向上抛出。
  • throw:在方法内进行抛出,我们手动抛出一个异常对象

1.3注意事项

  • 对于非运行时异常,程序必须进行处理,用try-catch或throws都可以,在写代码时idea会提示
  • 对运行时异常,程序中没有处理,默认处理方法时throws
  • 子类重写父类的方法时,对抛出异常的规定:子类重写的方法,所抛出的异常类型不能大于父类异常的类型,可以是一样的类型或者是父类异常的子类

1.4自定义异常

  • 自定义异常类继承Exception或RunTimeException
  • 继承Exception属于非运行时异常
  • 继承RunTimeException属于运行时异常

2、配置全局异常处理

在项目中我们通常会写很多接口,各种各样的异常出现会让我们的返回结果很受影响,因为我们的接口都会写通用的返回格式,但是异常出现时返回的错误就和我们的返回格式产生分歧,所以为了保证这种情况不出现,我们就需要配置全局异常处理,在异常发生时也按照我们想要的返回格式。

核心:@RestControllerAdvice+@ExceptionHandler

2.1准备工作

常见的操作码

/**
 * 枚举了一些常用API操作码
 */
public enum ResultCode implements IErrorCode {
    SUCCESS(200, "操作成功"),
    FAILED(400, "操作失败"),
    VALIDATE_FAILED(404, "参数检验失败"),
    UNAUTHORIZED(401, "暂未登录或token已经过期"),
    FORBIDDEN(403, "没有相关权限");
    private int code;
    private String message;

    private ResultCode(int code, String message) {
        this.code = code;
        this.message = message;
    }

    public int getCode() {
        return code;
    }

    public String getMessage() {
        return message;
    }
}

封装API的错误码

/**
 * 封装API的错误码
 */
public interface IErrorCode {
    int getCode();

    String getMessage();
}

通用的返回体

import com.lcp.fitness.common.api.IErrorCode;
import com.lcp.fitness.common.api.ResultCode;
import lombok.Data;

import java.io.Serializable;

@Data
public class CommonResponse<T> implements Serializable {

    private int code;
    private String msg;
    private T data;
    private boolean success;

    public CommonResponse(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public CommonResponse(int code, String msg, T data, boolean success) {
        this.code = code;
        this.msg = msg;
        this.data = data;
        this.success = success;
    }


    //失败返回结果
    public static <T> CommonResponse fail() {
        return new CommonResponse(ResultCode.FAILED.getCode(), ResultCode.FAILED.getMessage(), null, false);
    }

    //失败返回结果
    public static <T> CommonResponse fail(String msg) {
        return new CommonResponse(ResultCode.FAILED.getCode(), msg, null, false);
    }

    //失败返回结果
    public static <T> CommonResponse fail(IErrorCode errorCode) {
        return new CommonResponse(errorCode.getCode(), errorCode.getMessage(), null, false);
    }

    //失败返回结果
    public static <T> CommonResponse fail(IErrorCode errorCode, String msg) {
        return new CommonResponse(errorCode.getCode(), msg, null, false);
    }

    //失败返回结果
    public static <T> CommonResponse fail(int code, String msg) {
        return new CommonResponse(code, msg, null, false);
    }

    //成功返回结果
    public static <T> CommonResponse success() {
        return new CommonResponse(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(), null, true);
    }

    //成功返回结果
    public static <T> CommonResponse success(T data) {
        return new CommonResponse(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(), data, true);
    }

    //成功返回结果
    public static <T> CommonResponse success(String msg, T data) {
        return new CommonResponse(ResultCode.SUCCESS.getCode(), msg, data, true);
    }


    /**
     * 参数验证失败返回结果
     */
    public static <T> CommonResponse<T> validateFailed() {
        return fail(ResultCode.VALIDATE_FAILED);
    }

    /**
     * 参数验证失败返回结果
     * @param message 提示信息
     */
    public static <T> CommonResponse<T> validateFailed(String message) {
        return new CommonResponse<T>(ResultCode.VALIDATE_FAILED.getCode(), message, null, false);
    }

    /**
     * 未登录返回结果
     */
    public static <T> CommonResponse<T> unauthorized(T data) {
        return new CommonResponse<T>(ResultCode.UNAUTHORIZED.getCode(), ResultCode.UNAUTHORIZED.getMessage(), data, false);
    }

    /**
     * 未授权返回结果
     */
    public static <T> CommonResponse<T> forbidden(T data) {
        return new CommonResponse<T>(ResultCode.FORBIDDEN.getCode(), ResultCode.FORBIDDEN.getMessage(), data, false);
    }

2.2全局异常处理实现

自定义我们的异常类

import com.lcp.fitness.common.api.IErrorCode;

/**
 * 自定义API异常
 */
public class ApiException extends RuntimeException {
    private IErrorCode errorCode;

    public ApiException(IErrorCode errorCode) {
        super(errorCode.getMessage());
        this.errorCode = errorCode;
    }

    public ApiException(String message) {
        super(message);
    }

    public ApiException(Throwable cause) {
        super(cause);
    }

    public ApiException(String message, Throwable cause) {
        super(message, cause);
    }

    public IErrorCode getErrorCode() {
        return errorCode;
    }
}

全局异常处理:

这里可以使用@RestControllerAdvice+@ExceptionHandler或者@ControllerAdvice+@ExceptionHandler+@ResponseBody,都是可以的,@RestControllerAdvice=@ControllerAdvice+@ResponseBody。

import com.lcp.fitness.utils.CommonResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

/**
 * 全局异常处理
 */
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    @ExceptionHandler(value = ApiException.class)
    public CommonResponse handle(ApiException e) {
        log.error(e.getMessage());
        return CommonResponse.fail(e.getMessage());
    }

    @ExceptionHandler(value = Exception.class)
    public CommonResponse exception(Exception e) {
        log.error(e.getMessage(), e);
        return CommonResponse.fail(e.getMessage());
    }

    /**
     * springsecurity权限认证失败返回
     * @param e
     * @return
     */
    @ExceptionHandler(value = AccessDeniedException.class)
    public CommonResponse accessDeniedException(AccessDeniedException e) {
        log.error(e.getMessage());
        return CommonResponse.fail("用户无权限访问");
    }

}

这样在我们某个接口再有运行时异常时,就不会有奇奇怪怪的格式了,我们希望即使有错误也都是我们定义好的这种格式
在这里插入图片描述
如果没有处理过的话就是像这样的报错,和我们想要的格式完全不一样,前端也不好处理


用了全局的异常处理我们就可以随心所欲了,可以完全按照我们的格式返回错误码和错误信息。
在这里插入图片描述

2.3特殊情况filter中的异常如何捕捉

从我们全局异常的注解名字@RestControllerAdvice我们也可以看出,他是针对controller层做了切面处理,也就是说如果异常最终出现在了controller层中,我们可以进行处理,但是我就遇到了一种特殊情况,请求以后代码报错了,但是我的接口没有返回任何信息

在这里插入图片描述
我的后台日志也收到了请求,并打印了错误
在这里插入图片描述
这里是因为该异常没有经过controller,在filter中就失败返回了,所以最终返回类型是void,这是我在认证token的过滤器中,尽管抛出了异常,但是接口的返回结果仍然是空的,这也证实了**@RestControllerAdvice注解只在controller层起到了作用这一点,一旦我们的异常没有到达controller就结束,全局异常的配置是没有任何作用的。**

import com.lcp.fitness.common.component.RedisCache;
import com.lcp.fitness.dto.LoginUser;
import com.lcp.fitness.utils.JwtTokenUtil;
import io.jsonwebtoken.Claims;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Objects;

@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    @Autowired
    private RedisCache redisCache;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        //获取token
        String token = request.getHeader("Authorization");
        if (!StringUtils.hasText(token)) {
            //放行
            filterChain.doFilter(request, response);
            return;
        }
        //解析token
        String userId = null;
        try {
            Claims claims = JwtTokenUtil.parseJWT(token);
            userId = claims.getSubject();
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("token无效");
        }
        //从redis中获取用户信息
        String redisKey = "login:" + userId;
        LoginUser loginUser = redisCache.getCacheObject(redisKey);
        if(Objects.isNull(loginUser)){
            throw new RuntimeException("用户未登录");
        }
        //存入SecurityContextHolder
        //TODO 获取权限信息封装到Authentication中
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(loginUser,null,null);
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        //放行
        filterChain.doFilter(request, response);
    }
}

解决filter中不起作用,我们没有办法改变@RestControllerAdvice注解的作用域,我的解决思路是将filter中的异常扔到controller层中,为此需要定义一个controller,专门用来接收这些特殊情况的异常。

import com.lcp.fitness.common.exception.ApiException;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;

/**
 * 全局异常处理-filter中的异常处理(全局异常只能处理controller层的异常,而filter中的异常捕捉不到,
 * 所以需要将filter中的异常全部重定向到该controller中,以实现全局异常的统一处理格式)
 */
@RestController
@RequestMapping("/exception")
public class ExceptionController {

    @RequestMapping("/handler")
    public void exception(HttpServletRequest request) {
        String msg = (String) request.getAttribute("msg");
        throw new ApiException(msg);
    }

}

在filter中,将原来throw抛出异常的代码改成下面的代码,使用重定向将异常信息转到controller层中

request.setAttribute("msg", "token无效");
request.getRequestDispatcher("/exception/handler").forward(request, response);

然后在filter中发生异常时我们的接口返回了通用的格式,但是后台日志又报了另外的错误
在这里插入图片描述

出现这个问题的原因就是在发生异常时我们进行了重定向,但是因为filter中的代码并没有结束,依然在向下执行,所以这里我们在重定向后filter中的代码理应结束了,我们加上一个return就好了。

request.setAttribute("msg", "token无效");
request.getRequestDispatcher("/exception/handler").forward(request, response);
return;
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值