`@Use of ControllerAdvice 'and ` @ RestControllerAdvice'

@Use of ControllerAdvice and @ RestControllerAdvice

effect

In fact, the two annotations are the same, but one is for @ RestController and the other is for @ Controller. We will use @ RestController when the front and rear ends are separated. Therefore, the difference between the two annotations is that @ RestController is equivalent to adding the annotation @ RequestBody to the @ RequestMapping method in all @ controllers

It can be simply understood as an interceptor specially designed for @ RestController. If the scanned package is specified through the basePackages attribute, it means that the logic will be woven into the relevant scanned RestController, and the behavior will be determined according to the actual use time.

There are two common ways

  1. Add annotation @ RestControllerAdvice to the class that implements the org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice interface to specify the scanning package, and enter this logic after the scanned RestController executes the RequestMapping method we wrote, so that we can easily wrap the global return object class
  2. Used in conjunction with the annotation @ ExceptionHandler, define a class, add the annotation @ RestControllerAdvice to the class, then define a local method, annotate @ ExceptionHandler, and specify the exception class through the @ ExceptionHandler value attribute. If the scanned RestController method has an exception, it will be intercepted by the method identified by @ ExceptionHandler, This mechanism can be used for global exception handling.

Global object return class wrapper

step

  • Define global response object class
  • Consider that some methods may not need to be intercepted, and allow the user to skip
  • Define interception processing logic
  1. Define response object class

    package com.ddf.boot.common.core.response;
    
    import com.ddf.boot.common.core.exception200.BaseErrorCallbackCode;
    import com.ddf.boot.common.core.exception200.BusinessException;
    import java.util.Objects;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    import lombok.experimental.Accessors;
    import lombok.extern.slf4j.Slf4j;
    
    /**
     * Unified response content class
     *
     * @author dongfang.ding
     * @date 2019/6/27 11:17
     */
    @Data
    @NoArgsConstructor
    @Accessors(chain = true)
    @Slf4j
    public class ResponseData<T> {
    
        /**
         * Return message code
         */
        private String code;
        /**
         * Return message
         */
        private String message;
        /**
         * Error stack information
         */
        private String stack;
        /**
         * response time
         */
        private long timestamp;
        /**
         * Return data
         */
        private T data;
    
        /**
         * Extended field
         * For example, in some cases, the normal logic returns data
         * Under some exception logic, another set of data is returned
         */
        private Object extra;
    
    
        public ResponseData(String code, String message, String stack, long timestamp, T data) {
            this.code = code;
            this.message = message;
            this.stack = stack;
            this.timestamp = timestamp;
            this.data = data;
        }
    
        /**
         * Successfully returned data method
         *
         * @param data
         * @param <T>
         * @return
         */
        public static <T> ResponseData<T> success(T data) {
            return new ResponseData<>(BaseErrorCallbackCode.COMPLETE.getCode(),
                    BaseErrorCallbackCode.COMPLETE.getDescription(), "", System.currentTimeMillis(), data
            );
        }
    
        /**
         * Failed to return message method
         *
         * @param code
         * @param message
         * @param stack
         * @param <T>
         * @return
         */
        public static <T> ResponseData<T> failure(String code, String message, String stack) {
            return new ResponseData<>(code, message, stack, System.currentTimeMillis(), null);
        }
    
    
        /**
         * Judge whether the returned result is successful
         *
         * @return
         */
        public boolean isSuccess() {
            return Objects.equals(code, BaseErrorCallbackCode.COMPLETE.getCode());
        }
    
    
        /**
         * Get the return data. If the response code is unsuccessful, an exception will be thrown
         *
         * @return
         */
        public T requiredSuccess() {
            if (isSuccess()) {
                return data;
            }
            throw new BusinessException(code, message);
        }
    
    
        /**
         * Gets the returned data. If the response code is unsuccessful, the specified default value is returned
         *
         * @param defaultValue
         * @return
         */
        public T failureDefault(T defaultValue) {
            if (!isSuccess()) {
                return defaultValue;
            }
            return data;
        }
    
    }
    
  2. If you define an annotation to identify a method, the method will not be wrapped

    package com.ddf.boot.common.core.controllerwrapper;
    
    import java.lang.annotation.Documented;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * <p>Method of ignoring package identification < / P >
     *
     * @author Snowball
     * @version 1.0
     * @date 2021/08/26 20:19
     */
    @Target({ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface WrapperIgnore {
    }
    
  3. Define processing logic

    package com.ddf.boot.common.core.controllerwrapper;
    
    import com.ddf.boot.common.core.response.ResponseData;
    import java.lang.reflect.AnnotatedElement;
    import java.util.Arrays;
    import org.springframework.core.MethodParameter;
    import org.springframework.core.annotation.Order;
    import org.springframework.http.MediaType;
    import org.springframework.http.converter.HttpMessageConverter;
    import org.springframework.http.server.ServerHttpRequest;
    import org.springframework.http.server.ServerHttpResponse;
    import org.springframework.web.bind.annotation.DeleteMapping;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.PutMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestControllerAdvice;
    import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
    
    /**
     * Allows you to customize the response HttpMessageConverter after executing a @ ResponseBody or a ResponseEntity controller method, but before writing the body using a body.
     *
     * @author dongfang.ding
     * @date 2019/6/27 11:15
     */
    @RestControllerAdvice(basePackages = {"com"})
    @Order
    public class CommonResponseBodyAdvice implements ResponseBodyAdvice<Object> {
    
        private static final Class[] ANNOTATIONS = {
                RequestMapping.class, GetMapping.class, PostMapping.class, DeleteMapping.class, PutMapping.class
        };
    
        /**
         * @param returnType
         * @param converterType
         * @return
         */
        @Override
        public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
            if (returnType.getMethod() == null || returnType.getMethod().isAnnotationPresent(WrapperIgnore.class)) {
                return false;
            }
            AnnotatedElement element = returnType.getAnnotatedElement();
            return Arrays.stream(ANNOTATIONS).anyMatch(
                    annotation -> annotation.isAnnotation() && element.isAnnotationPresent(annotation));
        }
    
        @Override
        public ResponseData<Object> beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
                Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,
                ServerHttpResponse response) {
            return ResponseData.success(body);
        }
    }
    
  4. Special treatment

    In one case, the return value of the method is String. At this time, there may be problems with the packaging. You need to add a format converter, such as MappingJackson2HttpMessageConverter, and put the order at the top

    @Configuration
    public class CoreWebConfig implements WebMvcConfigurer {
         @Override
         public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
             converters.add(0, new MappingJackson2HttpMessageConverter());
         }
    }
    
  5. use

    1. Follow the rules for scanning packages defined above to create the Controller class and add the annotation @ RestController

    2. Define the annotations in the above interception rules on the method, such as GetMapping

    3. The method return value does not return the wrapper object defined above, but directly returns the original data type. When finally returning to the front end, it will be processed by the interception logic written above, and then packaged to complete the function of unified return object

    4. Of course, if it is spring cloud, a system based on Feign call mode, don't use this packaging method, otherwise it will be wrapped in two layers. However, the defined unified object can still be used. It also provides some static methods to use

Global exception handling

Before doing this, think about what problems need to be considered and solved?

  1. You should be able to throw your own system and customize status codes and exception messages
  2. In order to facilitate the management of status codes and messages, it is best to define classes for management. Considering more and more problems of subsequent status codes, it is necessary to use interfaces to define specifications, and implement classes to implement interfaces to ensure the maintenance of exception codes. Implementation classes can be enumerated, and each system or module can be defined separately
  3. Consider that variables may be required in messages, because exceptions need to support placeholders
  4. The exception information can include detailed exception stack messages and some exception messages, which are only exception messages. For example, the check error in the code needs to write out the specific conditions that are not met, but it is not convenient to return them to the user. Additional business messages need to be returned to hide the system information. Note that this and stack are two problems, not the same thing.
  5. The exception handling class is defined as an abstract class because the package object to be scanned by the application user cannot be determined. In addition, we have defined exception handling classes. What if the user wants to handle some additional logic or take over logic when an exception occurs? Therefore, an extension interface should be reserved for exception handling to allow the user to intervene in the exception handling logic.
  6. The exception returned to the front end also needs to be wrapped in a unified object, and the status code in the user-defined object is used to identify the exception. Is there any other information returned? It is necessary to consider returning the exception stack, so as to facilitate the test and joint commissioning to directly see the first on-site exception stack information, but it can not be returned in all environments, so it should be shielded in the production environment.

Start coding

Define the response class according to the above requirements, or the unified packaging object above

package com.ddf.boot.common.core.response;

import com.ddf.boot.common.core.exception200.BaseErrorCallbackCode;
import com.ddf.boot.common.core.exception200.BusinessException;
import java.util.Objects;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;

/**
 * Unified response content class
 *
 * @author dongfang.ding
 * @date 2019/6/27 11:17
 */
@Data
@NoArgsConstructor
@Accessors(chain = true)
@Slf4j
public class ResponseData<T> {

    /**
     * Return message code
     */
    private String code;
    /**
     * Return message
     */
    private String message;
    /**
     * Error stack information
     */
    private String stack;
    /**
     * response time
     */
    private long timestamp;
    /**
     * Return data
     */
    private T data;

    /**
     * Extended field
     * For example, in some cases, the normal logic returns data
     * Under some exception logic, another set of data is returned
     */
    private Object extra;


    public ResponseData(String code, String message, String stack, long timestamp, T data) {
        this.code = code;
        this.message = message;
        this.stack = stack;
        this.timestamp = timestamp;
        this.data = data;
    }

    /**
     * Successfully returned data method
     *
     * @param data
     * @param <T>
     * @return
     */
    public static <T> ResponseData<T> success(T data) {
        return new ResponseData<>(BaseErrorCallbackCode.COMPLETE.getCode(),
                BaseErrorCallbackCode.COMPLETE.getDescription(), "", System.currentTimeMillis(), data
        );
    }

    /**
     * Failed to return message method
     *
     * @param code
     * @param message
     * @param stack
     * @param <T>
     * @return
     */
    public static <T> ResponseData<T> failure(String code, String message, String stack) {
        return new ResponseData<>(code, message, stack, System.currentTimeMillis(), null);
    }


    /**
     * Judge whether the returned result is successful
     *
     * @return
     */
    public boolean isSuccess() {
        return Objects.equals(code, BaseErrorCallbackCode.COMPLETE.getCode());
    }


    /**
     * Get the return data. If the response code is unsuccessful, an exception will be thrown
     *
     * @return
     */
    public T requiredSuccess() {
        if (isSuccess()) {
            return data;
        }
        throw new BusinessException(code, message);
    }


    /**
     * Gets the returned data. If the response code is unsuccessful, the specified default value is returned
     *
     * @param defaultValue
     * @return
     */
    public T failureDefault(T defaultValue) {
        if (!isSuccess()) {
            return defaultValue;
        }
        return data;
    }
}

Define exception error code top-level interface specification

All error code definition classes must implement this interface to facilitate subsequent logic classes to code directly against the interface

package com.ddf.boot.common.core.exception200;

/**
 * <p>Exception message code unified interface < / P >
 *
 * @author dongfang.ding
 * @version 1.0
 * @date 2020/06/17 14:58
 */
public interface BaseCallbackCode {

    /**
     * Response status code
     *
     * @return
     */
    String getCode();

    /**
     * Response message
     *
     * @return
     */
    String getDescription();

    /**
     * Respond to the business message and finally return it to the user. If you need to hide the details of the system exception, remember to rewrite this method
     *
     * @return
     */
    default String getBizMessage() {
        return getDescription();
    }
}

A basic implementation example

package com.ddf.boot.common.core.exception200;

import lombok.Getter;

/**
 * <p>Exception definition enumeration < / P >
 *
 * @author dongfang.ding
 * @version 1.0
 * @date 2020/06/28 13:14
 */
public enum BaseErrorCallbackCode implements BaseCallbackCode {

    /**
     * Abnormal status code, sustainable supplement
     */
    DEMO_BLA_BLA("base_0001", "There is a problem inside the system, bla bla...", "The system is abnormal. Please confirm later"),

    TEST_FILL_EXCEPTION("base_0002", "Exception presentation with placeholder[{0}]"),

    TEST_FILL_BIZ_EXCEPTION("base_0003", "Exception presentation with placeholder[{0}],Client hide details", "Wrong report"),

    PAGE_NUM_NOT_ALLOW_NULL("base_0004", "The current number of pages cannot be empty"),

    PAGE_SIZE_NOT_ALLOW_NULL("base_0005", "Each page size cannot be empty"),

    COMPLETE("200", "Request succeeded"),

    BAD_REQUEST("400", "Error request"),

    UNAUTHORIZED("401", "Not certified"),

    ACCESS_FORBIDDEN("403", "Permission failed, access denied"),

    SERVER_ERROR("500", "Server exception"),

    ;

    /**
     * Exception code
     */
    @Getter
    private final String code;

    /**
     * Exception message
     */
    @Getter
    private final String description;

    /**
     * Exception message returned to user
     */
    @Getter
    private final String bizMessage;

    BaseErrorCallbackCode(String code, String description) {
        this.code = code;
        this.description = description;
        this.bizMessage = description;
    }

    BaseErrorCallbackCode(String code, String description, String bizMessage) {
        this.code = code;
        this.description = description;
        this.bizMessage = bizMessage;
    }
}

Define exception base class

This exception base class should be able to store placeholder information, custom exception status codes, and quickly use custom exception classes to convert to exceptions

package com.ddf.boot.common.core.exception200;

import java.text.MessageFormat;
import lombok.Getter;
import org.springframework.context.MessageSource;

/**
 * <p>Benchmark exception class < / P >
 *
 * @author dongfang.ding
 * @version 1.0
 * @date 2020/06/17 15:55
 */
public abstract class BaseException extends RuntimeException {

    /**
     * Exception code
     */
    @Getter
    private String code;

    /**
     * Exception message
     */
    @Getter
    private String description;

    /**
     * Some messages need to provide placeholders. You want to fill in data at run time. Here, you can pass in the parameters corresponding to the placeholders
     * Note that the format parameter uses {@ link MessageSource}, so please note that the original booth parameter needs to use {0} {1}
     */
    @Getter
    private Object[] params;

    /**
     * A class that holds information about business exceptions
     */
    @Getter
    private BaseCallbackCode baseCallbackCode;


    /**
     * Used to wrap other exceptions to convert to custom exceptions
     *
     * @param throwable
     */
    public BaseException(Throwable throwable) {
        super(throwable);
        initCallback(defaultCallback());
    }

    /**
     * It is recommended to use a set of system exceptions customized by the system, and pass in exception error code classes
     *
     * @param baseCallbackCode
     */
    public BaseException(BaseCallbackCode baseCallbackCode) {
        super(baseCallbackCode.getDescription());
        initCallback(baseCallbackCode);
    }

    /**
     * The same as above, but an additional message placeholder is provided. The message in baseCallbackCode contains placeholders, which are used as the final exception message after formatting parameters
     * The placeholder string adopts the angular notation of {0} {1}
     *
     * @param baseCallbackCode
     * @param params
     */
    public BaseException(BaseCallbackCode baseCallbackCode, Object... params) {
        super(MessageFormat.format(baseCallbackCode.getDescription(), params));
        initCallback(baseCallbackCode, params);
    }

    /**
     * Simply throw message exceptions
     *
     * @param description
     */
    public BaseException(String description) {
        super(description);
        initCallback(defaultCallback());
    }

    /**
     * The error code definition system defined by the system is not followed, but the error code and message system are used
     *
     * @param code
     * @param description
     */
    public BaseException(String code, String description) {
        super(description);
        initCallback(code, description);
    }

    /**
     * Same as above, but supports placeholders
     *
     * @param code
     * @param description
     * @param params
     */
    public BaseException(String code, String description, Object... params) {
        super(MessageFormat.format(description, params));
        initCallback(code, description, params);
    }


    private void initCallback(BaseCallbackCode baseCallbackCode, Object... params) {
        this.baseCallbackCode = baseCallbackCode;
        initCallback(
                baseCallbackCode.getCode() == null ? defaultCallback().getCode() : baseCallbackCode.getCode(),
                baseCallbackCode.getDescription()
        );
    }


    /**
     * Initialization status code
     *
     * @param code
     * @param description
     */
    private void initCallback(String code, String description, Object... params) {
        this.code = code == null ? defaultCallback().getCode() : code;
        this.params = params;
        this.description = MessageFormat.format(description, params);
    }


    /**
     * Current exception default response status code
     *
     * @return
     */
    public abstract BaseCallbackCode defaultCallback();
}

Implementation classes are provided for thought reference only to address system business exceptions

package com.ddf.boot.common.core.exception200;

/**
 * <p>General business exception. Only the predefined message status code constructor < / P >
 *
 * @author dongfang.ding
 * @version 1.0
 * @date 2020/06/28 15:13
 */
public class BusinessException extends BaseException {

    /**
     * @param baseCallbackCode
     */
    public BusinessException(BaseCallbackCode baseCallbackCode) {
        super(baseCallbackCode);
    }


    public BusinessException(String description) {
        super(description);
    }

    public BusinessException(String code, String description) {
        super(code, description);
    }

    public BusinessException(String code, String description, Object... params) {
        super(code, description, params);
    }

    /**
     * A message placeholder method is provided. The message in baseCallbackCode contains placeholders, which are used as the final exception message after formatting parameters
     *
     * @param baseCallbackCode
     * @param params
     */
    public BusinessException(BaseCallbackCode baseCallbackCode, Object... params) {
        super(baseCallbackCode, params);
    }

    /**
     * Current exception default response status code
     *
     * @return
     */
    @Override
    public BaseCallbackCode defaultCallback() {
        return BaseErrorCallbackCode.SERVER_ERROR;
    }
}

Define extension points

Now to deal with the extension point problem, there are the following problems mentioned earlier

  1. Return error stack information to the front end? Simply use the configuration class. It is returned by default, but you can specify that the matching profile is not returned
  2. How to let an external take over exception handling? Provide an interface. If there is an implementation, it will be delegated to the user-defined implementation for processing. However, a strategy is provided. If the implementer returns null after taking over, it only means that it is listening and does not want to take over the exception system. After processing, continue to follow the exception logic defined by the system, otherwise the user's return value will be returned directly
  1. Attribute class

    package com.ddf.boot.common.core.config;
    
    import java.util.List;
    import lombok.Getter;
    import lombok.Setter;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.stereotype.Component;
    
    /**
     * Store some global user-defined attributes and decide whether they can be configured as needed
     *
     * @author dongfang.ding on 2019/1/25
     */
    @Component
    @ConfigurationProperties(prefix = "customs.global-properties")
    @Getter
    @Setter
    public class GlobalProperties {
    
        /**
         * The default exception handling class will return a trace field to return the current error stack information, which is convenient for viewing errors during debugging, and provide the information specified by this parameter
         * profile This field is not returned, such as production environment
         */
        private List<String> ignoreErrorTraceProfile;
    
    }
    
    
  2. Abnormal nozzle interface

    package com.ddf.boot.common.core.exception200;
    
    import com.ddf.boot.common.core.response.ResponseData;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    /**
     * <p>Expose the captured exception and allow the implementer to customize the return data according to the exception < / P >
     *
     * @author dongfang.ding
     * @version 1.0
     * @date 2020/06/28 10:33
     */
    public interface ExceptionHandlerMapping {
    
        /**
         * The caught exception returns data to the implementer's custom implementation
         *
         * @param exception
         * @return If the current exception is not the type you want to handle, please return {@ code null}
         * @see AbstractExceptionHandler#handlerException(Exception, HttpServletRequest, HttpServletResponse)
         */
        ResponseData<?> handlerException(Exception exception);
    }
    

Exception handling parent class

The reason why it is defined as an abstract class here is that it cannot solve the scanning rules of the application. Therefore, only define logic, inherit it by the application, and then call the parent class method directly.

package com.ddf.boot.common.core.exception200;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.net.NetUtil;
import com.ddf.boot.common.core.config.GlobalProperties;
import com.ddf.boot.common.core.helper.EnvironmentHelper;
import com.ddf.boot.common.core.helper.SpringContextHolder;
import com.ddf.boot.common.core.logaccess.AccessLogAspect;
import com.ddf.boot.common.core.response.ResponseData;
import com.ddf.boot.common.core.util.WebUtil;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.http.HttpStatus;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * <p>description</p >
 * <p>
 * <p>
 * FIXME In this way, the basePackages of the system user cannot be determined, so com is used temporarily
 * <p>
 * You can also define this class as an abstract ordinary class, which is no longer managed by spring. Then, the application user defines its own interception rules, inherits this class, and uses the logic of the parent class. Only logic is provided here
 * This can at least ensure that the exception handling mechanism of multiple modules can be managed uniformly in the microservice project
 *
 * @author dongfang.ding
 * @version 1.0
 * @date 2020/06/28 10:20
 */
@Slf4j
public abstract class AbstractExceptionHandler {

    @Autowired
    private GlobalProperties globalProperties;
    @Autowired(required = false)
    private ExceptionHandlerMapping exceptionHandlerMapping;
    @Autowired
    private EnvironmentHelper environmentHelper;
    @Autowired(required = false)
    private MessageSource messageSource;

    /**
     * Handle exception classes. Some exception classes need special handling. Judge whether it is the expected exception type according to the current exception,
     * In this way, only one method can be used for processing. Otherwise, there are too many methods, which looks a little messy, and it is not easy to do some general processing
     *
     * @param exception
     * @return
     */
    @ExceptionHandler(value = Exception.class)
    @ResponseBody
    public ResponseData<?> handlerException(Exception exception, HttpServletRequest httpServletRequest,
            HttpServletResponse response) {
        // This optional log processor will print the exception log in case of exception. If it has been handled, do not print it again here
        if (!SpringContextHolder.containsBeanType(AccessLogAspect.class)) {
            log.error("Abnormal information: ", exception);
        }
        // Whether to return the current error stack information, which is returned by default, but provides hidden information in some environments
        boolean ignoreErrorStack = false;
        List<String> ignoreErrorTraceProfile = globalProperties.getIgnoreErrorTraceProfile();
        if (CollectionUtil.isNotEmpty(ignoreErrorTraceProfile) && environmentHelper.checkIsExistOr(
                ignoreErrorTraceProfile)) {
            ignoreErrorStack = true;
        }

        // The extension implementation class is allowed to take over exception handling. You can implement additional handling in some exception cases at the business level, but remember to return null if you do not take over exception handling
        if (exceptionHandlerMapping != null) {
            ResponseData<?> responseData = exceptionHandlerMapping.handlerException(exception);
            if (responseData != null) {
                if (ignoreErrorStack) {
                    responseData.setStack(null);
                }
                return responseData;
            }
        }

        String exceptionCode;
        String message;
        if (exception instanceof BaseException) {
            BaseException baseException = (BaseException) exception;
            exceptionCode = baseException.getCode();
            // Parse the exception class message code and format the resource file according to the current Local
            Locale locale = httpServletRequest.getLocale();
            String description = baseException.getDescription();
            if (Objects.nonNull(baseException.getBaseCallbackCode())) {
                description = baseException.getBaseCallbackCode().getBizMessage();
            }
            // The use of the resource file is not defined. The exception message is used directly. It is defined that the i18n resource file will be used according to the exception status code
            message = messageSource.getMessage(baseException.getCode(), baseException.getParams(), description, locale);
        } else if (exception instanceof IllegalArgumentException) {
            exceptionCode = BaseErrorCallbackCode.BAD_REQUEST.getCode();
            message = exception.getMessage();
        } else if (exception instanceof MethodArgumentNotValidException) {
            exceptionCode = BaseErrorCallbackCode.BAD_REQUEST.getCode();
            MethodArgumentNotValidException exception1 = (MethodArgumentNotValidException) exception;
            message = exception1.getBindingResult().getAllErrors().stream().map(ObjectError::getDefaultMessage).collect(
                    Collectors.joining(";"));
        } else {
            exceptionCode = BaseErrorCallbackCode.SERVER_ERROR.getCode();
            message = exception.getMessage();
        }

        if (globalProperties.isExceptionCodeToResponseStatus()) {
            String numberRegex = "\\d+";
            // There may be problems that exceed the maximum value of int, regardless for the time being
            if (exceptionCode.matches(numberRegex)) {
                response.setStatus(Integer.parseInt(exceptionCode));
            } else {
                response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
            }
        }
        // Additional service messages
        String extraServerMessage = String.format("[%s:%s]", environmentHelper.getApplicationName(),
                NetUtil.getLocalhostStr()
        );
        return ResponseData.failure(exceptionCode, message,
                ignoreErrorStack ? "" : extraServerMessage + ":" + ExceptionUtils.getStackTrace(exception)
        );
    }


    /**
     * Parsing business exception messages
     *
     * @param exception
     * @return
     */
    public static String resolveExceptionMessage(Exception exception) {
        try {
            if (exception instanceof BaseException) {
                BaseException baseException = (BaseException) exception;
                // Parse the exception class message code and format the resource file according to the current Local
                Locale locale = WebUtil.getCurRequest().getLocale();
                String description = baseException.getDescription();
                if (Objects.nonNull(baseException.getBaseCallbackCode())) {
                    description = baseException.getBaseCallbackCode().getBizMessage();
                }
                // The use of the resource file is not defined. The exception message is used directly. It is defined that the i18n resource file will be used according to the exception status code
                return SpringContextHolder.getBean(MessageSource.class).getMessage(baseException.getCode(),
                        baseException.getParams(), description, locale);
            }
            return exception.getMessage();
        } catch (Exception e) {
            log.error("Failed to parse exception message, Original exception message = {}", exception, e);
            return exception.getMessage();
        }
    }
}

Application access usage

  1. Inherit abstract processing classes and define interception rules

    package com.ddf.boot.quick.common.exception;
    
    import com.ddf.boot.common.core.exception200.AbstractExceptionHandler;
    import com.ddf.boot.common.core.response.ResponseData;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.bind.annotation.ResponseBody;
    import org.springframework.web.bind.annotation.RestControllerAdvice;
    
    /**
     * Exception handling by integrating the exception logic of the general package
     * <p>
     * Because the general package cannot determine the package, an abstract class is provided to let the user determine the package scope and then inherit the logic
     *
     * @author dongfang.ding
     * @date 2020/11/22 0022 22:10
     */
    @RestControllerAdvice(basePackages = "com.ddf.boot.quick")
    public class BootExceptionAdvice extends AbstractExceptionHandler {
    
        @ExceptionHandler(value = Exception.class)
        @ResponseBody
        @Override
        public ResponseData<?> handlerException(Exception exception, HttpServletRequest httpServletRequest,
                HttpServletResponse response) {
            return super.handlerException(exception, httpServletRequest, response);
        }
    }
    
  2. Implement the exception code interface and define your own exception code

    package com.ddf.boot.quick.common.exception;
    
    import com.ddf.boot.common.core.exception200.BaseCallbackCode;
    import lombok.Getter;
    
    /**
     * <p>description</p >
     *
     * @author Snowball
     * @version 1.0
     * @date 2020/10/13 16:59
     */
    public enum BizCode implements BaseCallbackCode {
        /**
         * abnormal
         */
        TEST_SIMPLE_BIZ_MESSAGE("10014", "Basic anomaly"),
        TEST_BIZ_MESSAGE("10015", "boot-quick Presentation business exception", "Lala Lala, please try again"),
        TEST_FILL_EXCEPTION("10016", "Exception presentation with placeholder[{0}]"),
        TEST_FILL_BIZ_EXCEPTION("10017", "Exception presentation with placeholder[{0}],Client hide details", "Wrong report")
    
        ;
    
        BizCode(String code, String description) {
            this.code = code;
            this.description = description;
            this.bizMessage = description;
        }
    
        BizCode(String code, String description, String bizMessage) {
            this.code = code;
            this.description = description;
            this.bizMessage = bizMessage;
        }
    
        @Getter
        private final String code;
    
        @Getter
        private final String description;
    
        @Getter
        private final String bizMessage;
    }
    
  3. use

    The following demonstration shows that the stack of the returned field is the actual error stack, which is optional and not returned to the front end.

    1. Instead of following the defined exception error code system, use the error message directly

      throw new BusinessException("Exception presentation");
      

      Return results

      {
        "code": "500",
        "message": "Exception presentation",
        "stack": "[boot-quick:127.0.0.1]:com.ddf.boot.common.core.exception200.BusinessException: Exception presentation\n\tat Omit others.........",
        "timestamp": 1630587288896,
        "data": null,
        "extra": null,
        "success": false
      }
      
    2. The exception system is a basic exception, which returns an error code and a defined message

      throw new BusinessException(BizCode.TEST_SIMPLE_BIZ_MESSAGE);
      

      Return results

      {
        "code": "10014",
        "message": "Basic anomaly",
        "stack": "[boot-quick:127.0.0.1]:com.ddf.boot.common.core.exception200.BusinessException: Basic anomaly\n\tat com.ddf.boot.quick.controller.features.QuickStartController.simpleBizException1(QuickStartController.java:95)\n\tat ellipsis...............",
        "timestamp": 1630587899925,
        "data": null,
        "extra": null,
        "success": false
      }
      
      
    3. Exception system hides system exceptions and returns user non sensitive information

      throw new BusinessException(BizCode.TEST_BIZ_MESSAGE);
      

      The returned result shows that the error message of the background log stack is a boot quick demonstration business exception, but lalalalalalalalalalalalalalalalalalalalalalalalalalalalalalalalalalalalalalalalalalalalalalalalalalalalalalalalalalalalala

      {
        "code": "10015",
        "message": "Lala Lala, please try again",
        "stack": "[boot-quick:127.0.0.1]:com.ddf.boot.common.core.exception200.BusinessException: boot-quick Presentation business exception\n\tat com.ddf.boot.quick.controller.features.QuickStartController.simpleBizException2(QuickStartController.java:105)\n\tat ellipsis...............",
        "timestamp": 1630659452294,
        "data": null,
        "extra": null,
        "success": false
      }
      
    4. The demonstration defines a placeholder in the exception message, which is automatically filled when an exception is thrown

      throw new BusinessException(BizCode.TEST_FILL_EXCEPTION, System.currentTimeMillis());
      

      Return results

      {
        "code": "10016",
        "message": "Exception presentation with placeholder[1,630,660,463,298]",
        "stack": "[boot-quick:127.0.0.1]:com.ddf.boot.common.core.exception200.BusinessException: Exception presentation with placeholder[1,630,660,463,298]\n\tat com.ddf.boot.quick.controller.features.QuickStartController.fillBizException(QuickStartController.java:115)\n\tat ellipsis............",
        "timestamp": 1630660463594,
        "data": null,
        "extra": null,
        "success": false
      }
      
    5. The demonstration defines an exception message with a placeholder, which is automatically filled when an exception is thrown, but returns to the user to hide the details

      throw new BusinessException(BizCode.TEST_FILL_BIZ_EXCEPTION, System.currentTimeMillis());
      
      {
        "code": "10017",
        "message": "Wrong report",
        "stack": "[boot-quick:127.0.0.1]:com.ddf.boot.common.core.exception200.BusinessException: Exception presentation with placeholder[1,630,660,776,819],Client hide details\n\tat com.ddf.boot.quick.controller.features.QuickStartController.fillBizException1(QuickStartController.java:125)\n\tat ellipsis...........",
        "timestamp": 1630660777008,
        "data": null,
        "extra": null,
        "success": false
      }
      

    Auxiliary tools

    Define an assertion help class that can throw an exception when some conditions are not met

    package com.ddf.boot.common.core.util;
    
    import com.ddf.boot.common.core.exception200.BadRequestException;
    import com.ddf.boot.common.core.exception200.BaseCallbackCode;
    import com.ddf.boot.common.core.exception200.BaseErrorCallbackCode;
    import com.ddf.boot.common.core.exception200.BusinessException;
    import java.text.MessageFormat;
    import java.util.Iterator;
    import java.util.Objects;
    import java.util.Set;
    import javax.validation.ConstraintViolation;
    import javax.validation.Validation;
    import javax.validation.Validator;
    import org.springframework.lang.NonNull;
    
    /**
     * <p>Provide assertions and throw system custom exception information < / P >
     *
     * @author dongfang.ding
     * @version 1.0
     * @date 2020/10/23 18:38
     */
    public class PreconditionUtil {
    
        /**
         * Validator instances can be pooled and shared by the implementation.
         * This thing is not cached. When it comes to concurrency, tomcat threads will be created and blocked, which greatly affects qps
         */
        private static final Validator VALIDATOR = Validation.buildDefaultValidatorFactory()
                .getValidator();
    
        /**
         * Check parameters
         *
         * @param expression
         * @param message
         */
        public static void checkArgument(boolean expression, String message) {
            if (!expression) {
                throw new BusinessException(message);
            }
        }
    
        /**
         * Check parameters
         *
         * @param expression
         * @param code
         * @param message
         */
        public static void checkArgument(boolean expression, String code, String message) {
            if (!expression) {
                throw new BusinessException(code, message);
            }
        }
    
        /**
         * Check parameters
         *
         * @param expression
         * @param callbackCode
         */
        public static void checkArgument(boolean expression, BaseCallbackCode callbackCode) {
            if (!expression) {
                throw new BusinessException(callbackCode);
            }
        }
    
        /**
         * The verification parameter throws an external incoming runtime exception
         *
         * @param expression
         * @param exception
         */
        public static void checkArgument(boolean expression, RuntimeException exception) {
            if (!expression) {
                throw exception;
            }
        }
    
        /**
         * Check the parameters and format the placeholder message
         *
         * @param expression
         * @param callbackCode
         * @param args
         */
        static void checkArgumentAndFormat(boolean expression, @NonNull BaseCallbackCode callbackCode, Object... args) {
            checkArgument(expression, callbackCode.getCode(), MessageFormat.format(callbackCode.getDescription(), args));
        }
    
    
        /**
         * Check parameters
         *
         * @param expression
         * @param message
         */
        public static void checkBadRequest(boolean expression, String message) {
            if (!expression) {
                throw new BusinessException("400", message);
            }
        }
    
        /**
         * Provide a manual required parameter verification
         *
         * @param request
         */
        public static <T> void requiredParamCheck(T request) {
            PreconditionUtil.checkArgument(
                    Objects.nonNull(request), BaseErrorCallbackCode.BAD_REQUEST
            );
            Set<ConstraintViolation<T>> constraintViolations = VALIDATOR.validate(request);
            if (constraintViolations.size() == 0) {
                return;
            }
            Iterator<ConstraintViolation<T>> iterator = constraintViolations.iterator();
            throw new BadRequestException(iterator.next().getMessage());
        }
    }
    
    

    use

    PreconditionUtil.checkArgument(1 != 1, BizCode.TEST_BIZ_MESSAGE);
    // Equivalent to
    if (1 != 1) {
      throw new BusinessException(BizCode.TEST_BIZ_MESSAGE);
    }
    
    
    PreconditionUtil.checkArgument(1 != 1, BizCode.TEST_FILL_BIZ_EXCEPTION, System.currentTimeMillis());
    // Equivalent to
    if (1 != 1) {
      throw new BusinessException(BizCode.TEST_FILL_BIZ_EXCEPTION, System.currentTimeMillis());
    }
    
    

Tags: Spring Spring Boot

Posted on Fri, 03 Sep 2021 21:23:40 -0400 by tisa