Spring boot series elegant handling of unified exception handling and unified result return

Keywords: Java Spring JSON SpringBoot Mybatis

Spring boot series (x) unified exception handling and unified result return

Previous recommendation

Spring boot series (I) idea new spring boot project

Introduction to SpringBoot series (2)

Spring boot series (3) configuration file details

Spring boot series (IV) detailed explanation of web static resource configuration

Spring boot series (V) Mybatis integrated full detailed version

Spring boot series (6) integrated tymeleaf detailed Edition

Spring boot series (7) integration interface document swagger, use, test

Spring boot series (8) minutes learn spring boot's multiple cross domain solutions

Spring boot series (9) correct posture for single and multi file upload

Catalog

introduction:

  in the process of daily development, it is inevitable that some programs will throw exceptions for some reasons, and these exceptions are generally handled by try, catch or throw, throws. This method is also cumbersome for programmers and not very friendly for customers, so we hope that it can not only facilitate programmers to write code, but also improve the user experience without too many exceptions. At this time, global exception handling is very important and convenient, which is a good choice.

1. Global exception capture and handling

  because the current mainstream projects are front-end and back-end separation, our exception handling is also described according to front-end and back-end separation.

  Springboot also provides good support for exception handling. It provides an @ ControllerAdvice annotation and an @ ExceptionHandler annotation. The former is used to enable global exception capture, and the latter is used to indicate which exceptions are caught and handle those exceptions.

@ControllerAdvice
public class MyExceptionHandler {

    @ExceptionHandler(value =Exception.class)
	public String exceptionHandler(Exception e){
		System.out.println("An exception occurred"+e);
       	return e.getMessage();
    }
}

  the above code means that as long as there is an exception in the code running process, it will be caught and output the exception. Then we write a random code that will generate exceptions, and the exceptions tested are like this.

  this is not good for our front-end and back-end separation. The only interaction after the front-end and back-end separation is json. We also want to turn the backend exception into json and return it to the front-end for processing. Let's take a look at unified result processing.

2. Unified result return and unified exception

code:

public class Result<T> {
    //Success or not
    private Boolean success;
    //Status code
    private Integer code;
    //Tips
    private String msg;
    //data
    private T data;
    public Result() {

    }
    //Customize the construction method of returned results
    public Result(Boolean success,Integer code, String msg,T data) {
        this.success = success;
        this.code = code;
        this.msg = msg;
        this.data = data;
    }
    //Custom exception returned results
    public static Result defineError(DefinitionException de){
        Result result = new Result();
        result.setSuccess(false);
        result.setCode(de.getErrorCode());
        result.setMsg(de.getErrorMsg());
        result.setData(null);
        return result;
    }
    //Results returned by other exception handling methods
    public static Result otherError(ErrorEnum errorEnum){
        Result result = new Result();
        result.setMsg(errorEnum.getErrorMsg());
        result.setCode(errorEnum.getErrorCode());
        result.setSuccess(false);
        result.setData(null);
        return result;
    }
}

Note: get and set methods are omitted. In addition, a custom enumeration is included in the method. The code is as follows:

public enum ErrorEnum {
	// Data operation error definition
	SUCCESS(200, "nice"),
	NO_PERMISSION(403,"You don't have permission"),
	NO_AUTH(401,"Can you log in first"),
	NOT_FOUND(404, "The resource was not found!"),
	INTERNAL_SERVER_ERROR(500, "The server is running"),
	;

	/** Error code */
	private Integer errorCode;

	/** error message */
	private String errorMsg;

	ErrorEnum(Integer errorCode, String errorMsg) {
		this.errorCode = errorCode;
		this.errorMsg = errorMsg;
	}

    public Integer getErrorCode() {
        return errorCode;
    }

    public String getErrorMsg() {
        return errorMsg;
    }
}

  note: enumeration class defines common error codes and error prompt information. Here we have defined a unified result return, in which the static method is used to convert to the specified format of exception return when the program is abnormal.

  then we need to customize the exception handling class. The code is as follows:

public class DefinitionException extends RuntimeException{

    protected Integer errorCode;
    protected String errorMsg;

    public DefinitionException(){

    }
    public DefinitionException(Integer errorCode, String errorMsg) {
        this.errorCode = errorCode;
        this.errorMsg = errorMsg;
    }

    public Integer getErrorCode() {
        return errorCode;
    }

    public void setErrorCode(Integer errorCode) {
        this.errorCode = errorCode;
    }

    public String getErrorMsg() {
        return errorMsg;
    }

    public void setErrorMsg(String errorMsg) {
        this.errorMsg = errorMsg;
    }
}

It contains error status code and error prompt information. Then we can customize a global exception handling class to handle all kinds of exceptions, including our own defined Exceptions and internal exceptions. This can simplify a lot of code, without using try and catch for each exception.

@ControllerAdvice
public class GlobalExceptionHandler {

    /**
     * Handling custom exceptions
     *
     */
    @ExceptionHandler(value = DefinitionException.class)
    @ResponseBody
    public Result bizExceptionHandler(DefinitionException e) {
        return Result.defineError(e);
    }

    /**
     * Handle other exceptions
     *
     */
    @ExceptionHandler(value = Exception.class)
    @ResponseBody
    public Result exceptionHandler( Exception e) {
        return Result.otherError(ErrorEnum.INTERNAL_SERVER_ERROR);
    }
}

  note: each method is annotated with a @ ResponseBody annotation, which is used to parse the object into json and facilitate the interaction between the front and back ends. You can also use @ ResponseBody to put it on the exception class.

3. controller code test and results

  controller code:

@RestController
@RequestMapping("/result")
public class ResultController {
    @GetMapping("/getStudent")
    public Result getStudent(){
        Student student = new Student();
        student.setAge(21);
        student.setId(111);
        student.setName("Learning notes");
        Result result = new Result();
        result.setCode(200);
        result.setSuccess(true);
        result.setData(student);
        result.setMsg("Student list information");
        return result;
    }
    @RequestMapping("/getDeException")
    public Result DeException(){
        throw new DefinitionException(400,"I made a mistake");
    }
    @RequestMapping("/getException")
    public Result Exception(){
        Result result = new Result();
        int a=1/0;
        return result;
    }
}

  the Student class is the one that has been used before. Contains three properties. The get and set methods are omitted.

public class Student  {
    /**
    * Unique id
    */
    private Integer id;
    /**
    * Full name
    */
    private String name;
    /**
    * Age
    */
    private Integer age;

}

  then start the project to test one by one. First, test the normal data without exception. Browser input: localhost:8095/result/getStudent

  you can see that the data is returned to the json string normally. No exception. Then we test the second custom exception handling interface. Browser input localhost:8095/result/getDeException.

  you can see that the custom exception was caught and returned a json string. Finally, let's test other exceptions. Browser input: localhost:8095/result/getException

  here we have handled the exception and returned to the front end correctly.
  as mentioned here, there are many ways to test the interface. You can use postman or the interface test tool provided by idea.

However, you may find a problem. This method can't handle 404 exceptions and can't catch them. What should I do?

4. 404 exception special handling.

  by default, SpringBoot does not throw 404 exceptions, so @ ControllerAdvice cannot catch 404 exceptions either. We can use the following configuration to enable this annotation to catch 404 exceptions.

spring.mvc.throw-exception-if-no-handler-found=true
spring.resources.add-mappings=false

The first one is to throw an exception directly when 404 exception is found. The second sentence turns off the default static resource path mapping. In this way, 404 errors can also be caught, but this configuration will cause problems in your static resource access, that is, it is not suitable for the situation where the front and back ends are not separated.

5. Summary

  this article explains how to handle and capture global exceptions and how to customize exceptions. It also explains the return format of unified results, and the 404, not found exceptions specially handled, which are returned as unified results. If you think this article is useful, like it!

Posted by countcet on Sun, 26 Apr 2020 06:25:32 -0700