Global exception handling in Spring (Practical)

I believe everyone has used try catch to handle exceptions when writing code, especially in the back-end interface called by the front-end. If we do not do exception handling, the back-end directly returns error information to the front-end, and the front-end directly displays the error information understood by programmers to users, which must cause a very bad user experience.

To prevent this from happening, we need to try catch in the back-end interface to handle exceptions and return more friendly error messages to users, such as server internal exceptions, verification exceptions, etc. It is troublesome for us to add try catch to the interface one by one. At this time, we can use the global exception handling mechanism.

@The ControllerAdvice annotation implements global exception handling

In Spring 3.2, @ ControllerAdvice and @ RestControllerAdvice annotations have been added. The functions of these two annotations are actually the same. The difference is like the difference between @ Controller and @ RestController.

@Role of ControllerAdvice:

  1. Global exception handling
  2. Global data binding
  3. Global data preprocessing

@ControllerAdvice usage:

  1. @The method of exception handler annotation: used to catch different types of exceptions thrown in the Controller, so as to achieve the purpose of global exception handling;
  2. @InitBinder annotation method: used for parsing the registered user-defined parameters in the request, so as to achieve the purpose of user-defined request parameter format;
  3. @Method annotated by ModelAttribute annotation: indicates that this method will be executed before the target Controller method is executed.

Specific usage of global exception handling:

Today, we mainly discuss @ ControllerAdvice combined with @ ExceptionHandler annotation for global exception handling.

As for other functions of @ ControllerAdvice, this article will not discuss them. Interested partners can learn by themselves.

Code implementation:

Create a GlobalExceptionHandler class and add the @ RestControllerAdvice annotation

/**
 * Global exception handler
 */
@RestControllerAdvice
public class GlobalExceptionHandler
{
    private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    /**
     * Basic anomaly
     */
    @ExceptionHandler(BaseException.class)
    public AjaxResult baseException(BaseException e)
    {
        return AjaxResult.error(e.getCode(),e.getDefaultMessage());
    }

    /**
     * Business exception
     */
    @ExceptionHandler(CustomException.class)
    public AjaxResult businessException(CustomException e)
    {
        if (StringUtils.isNull(e.getCode()))
        {
            return AjaxResult.error(e.getMessage());
        }
        return AjaxResult.error(e.getCode(), e.getMessage());
    }

    /**
     * Other exceptions
     */
    @ExceptionHandler(Exception.class)
    public AjaxResult handleException(Exception e)
    {
        log.error(e.getMessage(), e);
        return AjaxResult.error(HttpStatus.ERROR,"System internal error");
    }

    /**
     * Custom validation exception
     */
    @ExceptionHandler(BindException.class)
    public AjaxResult validatedBindException(BindException e)
    {
        log.error(e.getMessage(), e);
        String message = e.getAllErrors().get(0).getDefaultMessage();
        return AjaxResult.error(message);
    }

    /**
     * Permission exception
     */
    @ExceptionHandler(PreAuthorizeException.class)
    public AjaxResult preAuthorizeException(PreAuthorizeException e)
    {
        return AjaxResult.error("No permission, please contact the administrator for authorization");
    }
}

In this class, you can define multiple methods. Different methods deal with different exceptions. For example, you can also define methods that deal with null pointers, methods that deal with array bounds, and so on. Different exceptions in the Controller will enter the method corresponding to the exception in this class.

Take a look at our custom BaseException exception:

/**
 * Basic anomaly
 */
public class BaseException extends RuntimeException
{
    private static final long serialVersionUID = 1L;

    /**
     * Module
     */
    private String module;

    /**
     * Error code
     */
    private String code = HttpStatus.ERROR;

    /**
     * Parameter corresponding to error code
     */
    private Object[] args;

    /**
     * Error message
     */
    private String defaultMessage;

    public BaseException(String module, String code, Object[] args, String defaultMessage)
    {
        this.module = module;
        this.code = code;
        this.args = args;
        this.defaultMessage = defaultMessage;
    }

    public BaseException(String module, String code, Object[] args)
    {
        this(module, code, args, null);
    }

    public BaseException(String code, String defaultMessage)
    {
        this(null, code, null, defaultMessage);
    }

    public BaseException(String code, Object[] args)
    {
        this(null, code, args, null);
    }

    public BaseException(String defaultMessage)
    {
        this(null, HttpStatus.ERROR, null, defaultMessage);
    }

    public String getModule()
    {
        return module;
    }

    public String getCode()
    {
        return code;
    }

    public Object[] getArgs()
    {
        return args;
    }

    public String getDefaultMessage()
    {
        return defaultMessage;
    }
}

Then post the code of AjaxResult.java (from the open source project ruoyi management system):

public class AjaxResult extends HashMap<String, Object>
{
    private static final long serialVersionUID = 1L;

    /** Status code */
    public static final String CODE_TAG = "code";

    /** Return content */
    public static final String MSG_TAG = "msg";

    /** data object */
    public static final String DATA_TAG = "data";

    /**
     * Initialize a newly created Ajax result object to represent an empty message.
     */
    public AjaxResult()
    {
    }

    /**
     * Initialize a newly created Ajax result object
     *
     * @param code Status code
     * @param msg Return content
     */
    public AjaxResult(String code, String msg)
    {
        super.put(CODE_TAG, code);
        super.put(MSG_TAG, msg);
    }

    /**
     * Initialize a newly created Ajax result object
     *
     * @param code Status code
     * @param msg Return content
     * @param data data object
     */
    public AjaxResult(String code, String msg, Object data)
    {
        super.put(CODE_TAG, code);
        super.put(MSG_TAG, msg);
        if (StringUtils.isNotNull(data))
        {
            super.put(DATA_TAG, data);
        }
    }

    /**
     * Convenient chain call
     *
     * @param key
     * @param value
     * @return
     */
    @Override
    public AjaxResult put(String key, Object value)
    {
        super.put(key, value);
        return this;
    }

    /**
     * Return success message
     *
     * @return Success message
     */
    public static AjaxResult success()
    {
        return AjaxResult.success("Operation succeeded");
    }

    /**
     * Return success data
     *
     * @return Success message
     */
    public static AjaxResult success(Object data)
    {
        return AjaxResult.success("Operation succeeded", data);
    }

    /**
     * Return success message
     *
     * @param msg Return content
     * @return Success message
     */
    public static AjaxResult success(String msg)
    {
        return AjaxResult.success(msg, null);
    }

    /**
     * Return success message
     *
     * @param msg Return content
     * @param data data object
     * @return Success message
     */
    public static AjaxResult success(String msg, Object data)
    {
        return new AjaxResult(HttpStatus.SUCCESS, msg, data);
    }

    /**
     * Return error message
     *
     * @return
     */
    public static AjaxResult error()
    {
        return AjaxResult.error("operation failed");
    }

    /**
     * Return error message
     *
     * @param msg Return content
     * @return Warning message
     */
    public static AjaxResult error(String msg)
    {
        return AjaxResult.error(HttpStatus.ERROR, msg);
    }

    /**
     * Return error message
     *
     * @param msg Return content
     * @param data data object
     * @return Warning message
     */
    public static AjaxResult error(String msg, Object data)
    {
        return new AjaxResult(HttpStatus.ERROR, msg, data);
    }

    /**
     * Return error message
     *
     * @param code Status code
     * @param msg Return content
     * @return Warning message
     */
    public static AjaxResult error(String code, String msg)
    {
        return new AjaxResult(code, msg, null);
    }

    /**
     * Return error message
     *
     * @param code Status code
     * @param msg Return content
     * @return Warning message
     */
    public static AjaxResult error(String code, String msg, Object data)
    {
        return new AjaxResult(code, msg, data);
    }
}

summary

In addition to using @ ControllerAdvice, you can also implement the global exception handling mechanism by implementing the HandlerExceptionResolver interface class. I won't narrate here. Interested partners will check it by themselves.

Based on the @ ControllerAdvice implementation, when we write code, we will throw exceptions in the Service layer to the Controller layer instead of trying to catch directly. If you try catch yourself, you won't enter the GlobalExceptionHandler class again.

Of course, in addition to returning the standard error information to the user, you can also do other work in the GlobalExceptionHandler method, such as exception statistics.

Another point I would like to write:

In the service method, if exceptions are caught and processed, the transaction will not be rolled back and @ Transactional will become invalid

Or add a throw new RuntimeException() statement at the end of the catch statement to allow aop to catch exceptions and then roll back.

Or add the following code in the catch statement to manually rollback:

TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

Tags: Java Spring Exception

Posted on Mon, 29 Nov 2021 17:18:36 -0500 by runfastrick