zoukankan      html  css  js  c++  java
  • 自定义全局异常处理器(Java)

    正常业务系统中,当前后端分离时,系统即使有未知异常,也要保证接口能返回错误提示,也需要根据业务规则制定相应的异常状态码和异常提示。所以需要一个全局异常处理器。相关代码:GitHub

    异常

    下面是 Java 异常继承图:

                         ┌───────────┐
                         │  Object   │
                         └───────────┘
                               ▲
                               │
                         ┌───────────┐
                         │ Throwable │
                         └───────────┘
                               ▲
                     ┌─────────┴─────────┐
                     │                   │
               ┌───────────┐       ┌───────────┐
               │   Error   │       │ Exception │
               └───────────┘       └───────────┘
                     ▲                   ▲
             ┌───────┘              ┌────┴──────────┐
             │                      │               │
    ┌─────────────────┐    ┌─────────────────┐┌───────────┐
    │OutOfMemoryError │... │RuntimeException ││IOException│...
    └─────────────────┘    └─────────────────┘└───────────┘
                                    ▲
                        ┌───────────┴─────────────┐
                        │                         │
             ┌─────────────────────┐ ┌─────────────────────────┐
             │NullPointerException │ │IllegalArgumentException │...
             └─────────────────────┘ └─────────────────────────┘
    

    根据编译时是否需要捕获,异常可以分为两类:1、写代码时,编译器规定必须捕获的异常,不捕获将报错;2、(抛出后)不必须捕获的异常,编译器对此类异常不做处理。

    • 必须捕获的异常:Exception 以及 Exception 除去 RuntimeException 的子类。

    • 不必须捕获的异常:Error 以及 Error 的子类;RuntimeException 以及 RuntimeException 的子类。

    必须捕获的异常:

        @GetMapping("/testThrowIOException")
        public ApiResponse<Void> testThrowIOException() {
    
            testThrowIOException(); // 将报错
            return ApiResponse.success();
        }
    
        private void throwIOException() throws IOException {
            System.out.println("testThrowIOException");
            throw new IOException();
        }
    

    不必须捕获的异常:

        @GetMapping("/testThrowRuntimeException")
        public ApiResponse<Void> testThrowRuntimeException() {
    
            throwRuntimeException(); // 不报错
            return ApiResponse.success();
        }
    
        private void throwRuntimeException() { // 无需 throws
            System.out.println("testThrowRuntimeException");
            throw new ArrayIndexOutOfBoundsException();
        }
    

    不过在运行时,任何异常都可以进行捕获处理,避免接口没有返回值的情况。

    抛异常

    常见异常处理方式有两种,1、捕获后处理,2、抛出。抛出也分为捕获后抛出和直接抛出。

    当本身没有异常,却使用 throws 抛出异常时,此时相当于没有抛异常(将拦截不到异常)。

        @GetMapping("/testThrowIOException2")
        public ApiResponse<Void> testThrowIOException2() throws IOException {
    
            throwIOException2();
            return ApiResponse.success();
        }
    
        private void throwIOException2() throws IOException {
            System.out.println("testThrowIOException");
        }
    

    打印异常

    打印异常可以使用 Logback 打印,其相关方法的使用: log.error(e.getMessage(), e); 相当于下面这两条语句:

    System.out.println(e.getMessage()); // 打印异常信息
    e.printStackTrace(); // 打印异常调用栈
    

    减少 NullPointException 的方式是设置默认值。

    测试 Error

    测试 StackOverflowError,设置栈的大小为 256K,IDEA(VM options): -Xss256k;命令行:java -Xss256k JavaVMStackSOF

    class JavaVMStackSOF {
        public int stackLength = 1;
    
        public void stackLeak() {
            stackLength++;
            stackLeak();
        }
    
        public static void main(String[] args) throws Throwable {
            JavaVMStackSOF oom = new JavaVMStackSOF();
            try {
                oom.stackLeak();
            } catch (Throwable e) {
                System.out.println("stack length:" + oom.stackLength);
                throw e;
            }
        }
    }
    
    stack length:1693
    Exception in thread "main" java.lang.StackOverflowError
    	at wang.depp.exception.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:10)
    	at wang.depp.exception.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:11)
    	...
    

    测试 OutOfMemoryError,设置 Java 堆的大小为 128M,IDEA(VM options):-Xms10M -Xmx10M;命令行:java -Xms10M -Xmx10M wang.depp.exception.HeapOOM(如果类中包含 package 路径,需 cd 到 java 目录后运行此命令)

    package wang.depp.exception;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class HeapOOM {
        static class OOMObject {
        }
    
        public static void main(String[] args) {
            List<OOMObject> list = new ArrayList<>();
            while (true) {
                list.add(new OOMObject());
            }
        }
    }
    
    Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    	at java.base/java.util.Arrays.copyOf(Arrays.java:3720)
    	at java.base/java.util.Arrays.copyOf(Arrays.java:3689)
    	...
    

    全局异常处理器

    自定义异常

    自定义异常从 RuntimeException 派生,构造方法使用 super(message);super(message, cause);。添加状态码和参数属性。

    public abstract class BaseException extends RuntimeException {
        private int code; // 状态码
        private String message;
        private Object[] args; // 参数
        private IResponseEnum responseEnum;
    
        public BaseException(IResponseEnum iResponseEnum, Object[] args, String message) {
            super(message);
            this.code = iResponseEnum.getCode();
            this.message = message;
            this.responseEnum = iResponseEnum;
            this.args = args;
        }
    
        public BaseException(IResponseEnum iResponseEnum, Object[] args, String message, Throwable cause) {
            super(message, cause);
            this.code = iResponseEnum.getCode();
            this.message = message;
            this.responseEnum = iResponseEnum;
            this.args = args;
        }
    
        public int getCode() {
            return this.code;
        }
    
        public String getMessage() {
            return this.message;
        }
    
        public Object[] getArgs() {
            return this.args;
        }
    
        public IResponseEnum getResponseEnum() {
            return this.responseEnum;
        }
    }
    

    当前服务的业务异常不用每个单独作为一个异常类,可通过 message 和 code 来做一个区分。

    public class LoanException extends BusinessException {
    
        public static LoanException INTERNAL_ERROR = new LoanException(ResponseEnum.SERVER_ERROR);
        public static LoanException REJECT = new LoanException(ResponseEnum.REJECT);
        public static LoanException BAND_FAIL = new LoanException(ResponseEnum.BAND_FAIL);
        public static LoanException FORBIDDEN = new LoanException(ResponseEnum.FORBIDDEN);
        public static LoanException DB_OPTIMISTIC_LOCK = new LoanException(ResponseEnum.DB_OPTIMISTIC_LOCK);
    
        public LoanException(IResponseEnum responseEnum) {
            super(responseEnum, null, responseEnum.getMessage());
        }
    
        public LoanException(IResponseEnum responseEnum, String message) {
            super(responseEnum, null, message);
        }
    
    }
    
        @GetMapping("/testLoanException")
        private ApiResponse<Void> testLoanException() {
            throw LoanException.REJECT;
        }
    

    为不同的业务错误场景设置相关枚举类型(状态码、错误提示)。为枚举添加可断言判断抛出异常功能。

    public interface Assert {
        BaseException newException(Object... var1);
    
        BaseException newException(Throwable var1, Object... var2);
    
        default void assertNotNull(Object obj) {
            if (obj == null) {
                throw this.newException((Object[])null);
            }
        }
    
        default void assertNotNull(Object obj, Object... args) {
            if (obj == null) {
                throw this.newException(args);
            }
        }
    
        default void assertTrue(boolean flag) {
            if (!flag) {
                throw this.newException((Object[])null);
            }
        }
    
        default void assertTrue(boolean flag, Object... args) {
            if (!flag) {
                throw this.newException((Object[])null);
            }
        }
    }
    
    public interface BusinessExceptionAssert extends IResponseEnum, Assert {
        default BaseException newException(Object... args) {
            String msg = MessageFormat.format(this.getMessage(), args);
            return new BusinessException(this, args, msg);
        }
    
        default BaseException newException(Throwable t, Object... args) {
            String msg = MessageFormat.format(this.getMessage(), args);
            return new BusinessException(this, args, msg, t);
        }
    }
    
    @Getter
    @AllArgsConstructor
    public enum ResponseEnum implements BusinessExceptionAssert {
    
        SUCCESS(111000,"success"),
        PARAM_VALID_ERROR(111001,"param check error."),
        SERVER_ERROR(111002,"server error."),
        LOGIN_ERROR(111003,"login error"),
        UNAUTHORIZED(111004, "unauthorized"),
        SERVICE_ERROR(111005,"service error."),
        FORBIDDEN(114003, "forbidden"),
        TIMEOUT(114000, "timeout"),
        REJECT(114001, "reject"),
        EMAIL_CONFLICT(114002, "email conflict"),
        EMAIL_VERIFY_FAIL(114004, "email verify fail"),
        DB_OPTIMISTIC_LOCK(114008, "update fail"),// 数据库乐观锁
        EMAIL_SEND_FAIL(114011, "email send fail"),
        DATA_NOT_FOUND(114012, "data not found"),
        LOGIN_TOKEN_VERIFY_FAIL(114014, "login token verify fail"),
        ;
    
        /**
         * 返回码
         */
        private int code;
        /**
         * 返回消息
         */
        private String message;
    
    }
    
        @GetMapping("/test")
        public ApiResponse<String> test(String value) {
            ResponseEnum.SERVICE_ERROR.assertNotNull(value);
            return ApiResponse.success("true");
        }
    

    全局异常管理器

    @Slf4j
    @ControllerAdvice
    public class GlobalExceptionHandler {
        /**
         * 生产环境
         */
        private final static String ENV_PROD = "production";
    
        /**
         * 当前环境
         */
        @Value("${env}")
        private String profile;
    
        /**
         * 业务异常
         *
         * @param e 异常
         * @return 异常结果
         */
        @ExceptionHandler(value = BusinessException.class)
        @ResponseBody
        public ApiResponse<String> handleBusinessException(BaseException e) {
            log.error(e.getMessage(), e);
            log.error("BusinessException");
            return ApiResponse.fail(e.getCode(), e.getMessage());
        }
    
        /**
         * 非错误编码类系统异常
         *
         * @param e 异常
         * @return 异常结果
         */
        @ExceptionHandler(value = SystemException.class)
        @ResponseBody
        public ApiResponse<String> handleBaseException(SystemException e) {
            return getServerErrorApiResponse(e);
        }
    
        /**
         * Controller 上一层相关异常
         *
         * @param e 异常
         * @return 异常结果
         */
        @ExceptionHandler({NoHandlerFoundException.class,
                HttpRequestMethodNotSupportedException.class,
                HttpMediaTypeNotSupportedException.class,
                MissingPathVariableException.class,
                MissingServletRequestParameterException.class,
                TypeMismatchException.class,
                HttpMessageNotReadableException.class,
                HttpMessageNotWritableException.class,
                // BindException.class,
                // MethodArgumentNotValidException.class
                HttpMediaTypeNotAcceptableException.class,
                ServletRequestBindingException.class,
                ConversionNotSupportedException.class,
                MissingServletRequestPartException.class,
                AsyncRequestTimeoutException.class
        })
        @ResponseBody
        public ApiResponse<String> handleServletException(Exception e) {
            return getServerErrorApiResponse(e);
        }
    
        /**
         * 未定义异常。相当于全局异常捕获处理器。
         *
         * @param e 异常
         * @return 异常结果
         */
        @ExceptionHandler(value = Exception.class)
        @ResponseBody
        public ApiResponse<String> handleException(Exception e) {
            return getServerErrorApiResponse(e);
        }
    
        private ApiResponse<String> getServerErrorApiResponse(Exception e) {
            int code = ResponseEnum.SERVER_ERROR.getCode();
            String productShowMessage = ResponseEnum.SERVER_ERROR.getMessage();
            if (ENV_PROD.equals(profile)) {
                return ApiResponse.fail(code, productShowMessage);
            }
            return ApiResponse.fail(code, e.getMessage());
        }
    }
    
    

    使用 @ControllerAdvice + @ExceptionHandler 实现对指定异常的捕获。此时运行时异常和 Error 也能被捕获。

    延伸阅读

  • 相关阅读:
    设计模式—享元模式
    设计模式—观察者模式
    设计模式—桥接模式
    设计模式—代理模式
    设计模式—装饰模式
    设计模式—单例模式
    设计模式—建造者模式
    设计模式—简单工厂
    源码解读—HashTable
    源码解读—HashMap
  • 原文地址:https://www.cnblogs.com/deppwang/p/13939528.html
Copyright © 2011-2022 走看看