Global exception handling: Assert + enumeration + custom exception

Posted by keyont on Fri, 04 Mar 2022 21:25:25 +0100

A lot of exception handling is involved in the process of business development. Usually @ ControllerAdvice and @ ExceptionHandler are used to handle various exceptions, but the process of throwing exceptions still can not avoid a lot of if Else judged that I happened to see an article today( Why not recommend using try catch to handle exceptions? )This problem is elegantly solved in the way of user-defined Assert + enumeration + user-defined exception. Now we sort out the implementation ideas:

  1. The two most important messages of exceptions are code and message. In order to represent different business exception messages, enumeration is a good choice. Because different business exceptions have different division methods, we adopt the idea of interface oriented programming and take IResponseEnum as one of the combination elements of BaseException.
    public interface IResponseEnum {
        int getCode();
        String getMessage();
    }
  2. The global exception introduces IResponseEnum as its own attribute to ensure that all the exception information thrown is defined by the service itself.
    /**
     * @ClassName: BaseException
     * @Author: whp
     * @Description: Basic exception class
     * @Date: 2022/3/4 11:20
     * @Version: 1.0
     */
    @Getter
    public class BaseException extends RuntimeException{
        protected IResponseEnum responseEnum;
        protected Object[] args;
    
        public BaseException(IResponseEnum responseEnum){
            super(responseEnum.getMessage());
            this.responseEnum=responseEnum;
        }
    
        public BaseException(int code,String msg){
            super(msg);
            this.responseEnum=new IResponseEnum() {
                @Override
                public int getCode() {
                    return code;
                }
    
                @Override
                public String getMessage() {
                    return msg;
                }
            };
        }
    
        public BaseException(IResponseEnum responseEnum,Object[] args,String message){
            super(message);
            this.responseEnum=responseEnum;
            this.args=args;
        }
    
        public BaseException(IResponseEnum responseEnum,Object[] args,String message,Throwable cause){
            super(message,cause);
            this.responseEnum=responseEnum;
            this.args=args;
        }
    }
  3. Defining Assert has two functions: 1 > adding judgment logic, for example: judging whether the object is empty, and judging whether AssertTrue is true; 2> Throw a custom exception.
    /**
     * @ClassName: Assert
     * @Author: whp
     * @Description: Custom assertion
     * @Date: 2022/3/4 11:25
     * @Version: 1.0
     */
    public interface Assert {
    
        BaseException newException(Object ... args);
    
        BaseException newException(Throwable t,Object... args);
    
        default void assertNotNull(Object obj){
            if(obj==null){
                throw newException(obj);
            }
        }
    
        default void assertNotNull(Object obj,Object... args){
            if(obj==null){
                throw newException(args);
            }
        }
    }
  4. Define BusinessExceptionAssert and implement the Assert judgment logic and enumeration class to distinguish different business exception logic.
    /**
     * @ClassName: Assert
     * @Author: whp
     * @Description: Custom assertion
     * @Date: 2022/3/4 11:25
     * @Version: 1.0
     */
    public interface Assert {
    
        BaseException newException(Object ... args);
    
        BaseException newException(Throwable t,Object... args);
    
        default void assertNotNull(Object obj){
            if(obj==null){
                throw newException(obj);
            }
        }
    
        default void assertNotNull(Object obj,Object... args){
            if(obj==null){
                throw newException(args);
            }
        }
    }
    
  5. Use @ Controlleradvice to uniformly handle the exception classes thrown:
     */
    @ControllerAdvice
    @ResponseBody
    @Slf4j
    public class ExceptionHandlerAdvice {
    
        private static final String ENV_PROD="online";
    
        @Value("${spring.profiles.active}")
        private String profile;
    
        @ExceptionHandler(value = BaseException.class)
        public Response commonException(BaseException exception, WebRequest request){
            return new Response().failure().code(exception.getResponseEnum().getCode()).message(exception.getMessage());
        }
    
        /**
         * Controller Related abnormality of upper layer
         *
         * @param e abnormal
         * @return Abnormal result
         */
        @ExceptionHandler({
                NoHandlerFoundException.class,
                HttpRequestMethodNotSupportedException.class,
                HttpMediaTypeNotSupportedException.class,
                MissingPathVariableException.class,
                MissingServletRequestParameterException.class,
                TypeMismatchException.class,
                HttpMessageNotReadableException.class,
                HttpMessageNotWritableException.class,
                HttpMediaTypeNotAcceptableException.class,
                ServletRequestBindingException.class,
                ConversionNotSupportedException.class,
                MissingServletRequestPartException.class,
                AsyncRequestTimeoutException.class
        })
        @ResponseBody
        public Response handleServletException(Exception e) {
            log.error(e.getMessage(), e);
            int code = ServletResponseEnum.SEVER_EXCEPTION.getCode();
            try {
                ServletResponseEnum servletExceptionEnum = ServletResponseEnum.valueOf(e.getClass().getSimpleName());
                return new Response().failure().code(servletExceptionEnum.getCode()).message(servletExceptionEnum.getMessage());
            } catch (IllegalArgumentException e1) {
                log.error("class [{}] not defined in enum {}", e.getClass().getName(), ServletResponseEnum.class.getName());
            }
            return new Response().failure().code(code).message(ServletResponseEnum.SEVER_EXCEPTION.getMessage());
        }
    }

    Of course, the application of global exceptions should also be distinguished according to different services. For example, in the API layer, the exception result is to be returned to the user, and the exception information needs to be displayed as a user-friendly language. The call between services needs to enable the corresponding development to quickly identify the problem. For relevant code implementation, please refer to: GitHub - whpparper / spring boot study: enterprise spring boot framework

Problem thinking:

1. Global exceptions can be divided into exceptions before the business arrives at the interface (for example, NoHandlerFoundException status code corresponds to 404, HttpRequestMethodNotSupportedException status code corresponds to 405), parameter verification exceptions, custom business exceptions, etc. how all kinds of exceptions return to the client or the corresponding caller needs reasonable planning.

2. What is the purpose of the business exception definition constructor to carry the parameter throwable?

A: you can encapsulate unnecessary exceptions from the interface, and it is easy to track the initial problem point.

Topics: Spring Boot Design Pattern