elegant method of SpringBoot global exception handling

created at 12-26-2021 views: 113

SpringBoot global exception preparation

Development preparation

Environmental requirements * JDK: 1.8 * SpringBoot: 1.5.17.RELEASE

First of all, there are related dependencies of Maven:

<properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.17.RELEASE</version>
        <relativePath />
    </parent>
    <dependencies>
        <!-- Spring Boot Web dependence core -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- Spring Boot Test dependence -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.41</version>
        </dependency>
    </dependencies>

There is basically no need to change the configuration file, and the handling of global exceptions only needs to be implemented in the code.

Code

The SpringBoot project already has a certain amount of exception handling, but it may not be suitable for our developers, so we need to catch and handle these exceptions uniformly. There is a ControllerAdvice annotation in SpringBoot. Using this annotation indicates that the global exception capture is enabled. We only need to use the ExceptionHandler annotation in a custom method and define the type of the exception to be captured to unify these captured exceptions. deal with.

Let's see how this annotation is used based on the example below.

Sample code:

@ControllerAdvice
public class MyExceptionHandler {

     @ExceptionHandler(value =Exception.class)
  public String exceptionHandler(Exception e){
   System.out.println("Unknown exception! The reason is: "+e);
         return e.getMessage();
     }
}

In the above example, we perform simple secondary processing on the caught exception and return the exception information. Although this can let us know the cause of the exception, in many cases, it may not be humane enough and not suitable for us. Requirements. Then we can use custom exception classes and enumeration classes to achieve the kind of data we want.

Custom basic interface class

First define a basic interface class, and the custom error description enumeration class needs to implement this interface. code show as below:

public interface BaseErrorInfoInterface {
     /** error code*/
   String getResultCode();

  /** wrong description*/
   String getResultMsg();
}

Custom enumeration class

Then we are here to customize an enumeration class and implement the interface. code show as below:

public enum CommonEnum implements BaseErrorInfoInterface {
  // Data operation error definition
  SUCCESS("200", "Success!"),
  BODY_NOT_MATCH("400","The requested data format does not match!"),
  SIGNATURE_NOT_MATCH("401","The requested digital signature does not match!"),
  NOT_FOUND("404", "The resource was not found!"),
  INTERNAL_SERVER_ERROR("500", "Server internal error!"),
  SERVER_BUSY("503","The server is busy, please try again later!")
  ;

  /** error code */
  private String resultCode;

  /** wrong description */
  private String resultMsg;

  CommonEnum(String resultCode, String resultMsg) {
   this.resultCode = resultCode;
   this.resultMsg = resultMsg;
  }

  @Override
  public String getResultCode() {
   return resultCode;
  }

  @Override
  public String getResultMsg() {
   return resultMsg;
  }

}

Custom exception class

Then we are customizing an exception class to handle business exceptions that occur to us. code show as below:

public class BizException extends RuntimeException {

 private static final long serialVersionUID = 1L;

 /**
  * error code
  */
 protected String errorCode;
 /**
  * Error message
  */
 protected String errorMsg;

 public BizException() {
  super();
 }

 public BizException(BaseErrorInfoInterface errorInfoInterface) {
  super(errorInfoInterface.getResultCode());
  this.errorCode = errorInfoInterface.getResultCode();
  this.errorMsg = errorInfoInterface.getResultMsg();
 }

 public BizException(BaseErrorInfoInterface errorInfoInterface, Throwable cause) {
  super(errorInfoInterface.getResultCode(), cause);
  this.errorCode = errorInfoInterface.getResultCode();
  this.errorMsg = errorInfoInterface.getResultMsg();
 }

 public BizException(String errorMsg) {
  super(errorMsg);
  this.errorMsg = errorMsg;
 }

 public BizException(String errorCode, String errorMsg) {
  super(errorCode);
  this.errorCode = errorCode;
  this.errorMsg = errorMsg;
 }

 public BizException(String errorCode, String errorMsg, Throwable cause) {
  super(errorCode, cause);
  this.errorCode = errorCode;
  this.errorMsg = errorMsg;
 }


 public String getErrorCode() {
  return errorCode;
 }

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

 public String getErrorMsg() {
  return errorMsg;
 }

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

 public String getMessage() {
  return errorMsg;
 }

 @Override
 public Throwable fillInStackTrace() {
  return this;
 }

}

Custom data format

By the way, we define the data transmission format. code show as below:

public class ResultBody {
 /**
  * Response code
  */
 private String code;

 /**
  * Response message
  */
 private String message;

 /**
  * Response results
  */
 private Object result;

 public ResultBody() {
 }

 public ResultBody(BaseErrorInfoInterface errorInfo) {
  this.code = errorInfo.getResultCode();
  this.message = errorInfo.getResultMsg();
 }

 public String getCode() {
  return code;
 }

 public void setCode(String code) {
  this.code = code;
 }

 public String getMessage() {
  return message;
 }

 public void setMessage(String message) {
  this.message = message;
 }

 public Object getResult() {
  return result;
 }

 public void setResult(Object result) {
  this.result = result;
 }

 /**
  * success
  *
  * @return
  */
 public static ResultBody success() {
  return success(null);
 }

 /**
  * success
  * @param data
  * @return
  */
 public static ResultBody success(Object data) {
  ResultBody rb = new ResultBody();
  rb.setCode(CommonEnum.SUCCESS.getResultCode());
  rb.setMessage(CommonEnum.SUCCESS.getResultMsg());
  rb.setResult(data);
  return rb;
 }

 /**
  * Fail
  */
 public static ResultBody error(BaseErrorInfoInterface errorInfo) {
  ResultBody rb = new ResultBody();
  rb.setCode(errorInfo.getResultCode());
  rb.setMessage(errorInfo.getResultMsg());
  rb.setResult(null);
  return rb;
 }

 /**
  * Fail
  */
 public static ResultBody error(String code, String message) {
  ResultBody rb = new ResultBody();
  rb.setCode(code);
  rb.setMessage(message);
  rb.setResult(null);
  return rb;
 }

 /**
  * Fail
  */
 public static ResultBody error( String message) {
  ResultBody rb = new ResultBody();
  rb.setCode("-1");
  rb.setMessage(message);
  rb.setResult(null);
  return rb;
 }

 @Override
 public String toString() {
  return JSONObject.toJSONString(this);
 }

}

Custom global exception handling class

Finally, we are going to write a custom global exception handling class. code show as below:

@ControllerAdvice
public class GlobalExceptionHandler {
 private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

 /**
  * Handle custom business exceptions
  * @param req
  * @param e
  * @return
  */
    @ExceptionHandler(value = BizException.class)
    @ResponseBody
 public ResultBody bizExceptionHandler(HttpServletRequest req, BizException e){
     logger.error("A business exception occurred! The reason is: {}",e.getErrorMsg());
     return ResultBody.error(e.getErrorCode(),e.getErrorMsg());
    }

 /**
  * Handle the exception of a null pointer
  * @param req
  * @param e
  * @return
  */
 @ExceptionHandler(value =NullPointerException.class)
 @ResponseBody
 public ResultBody exceptionHandler(HttpServletRequest req, NullPointerException e){
  logger.error("Null pointer exception occurred! The reason is:",e);
  return ResultBody.error(CommonEnum.BODY_NOT_MATCH);
 }


    /**
        * Handling other exceptions
     * @param req
     * @param e
     * @return
     */
    @ExceptionHandler(value =Exception.class)
 @ResponseBody
 public ResultBody exceptionHandler(HttpServletRequest req, Exception e){
     logger.error("Unknown exception! The reason is:",e);
        return ResultBody.error(CommonEnum.INTERNAL_SERVER_ERROR);
    }
}

Because here we are only for the implementation and testing of global exception handling, so here we only need to add an entity class and a control layer class.

Entity class

It is also a universal user table (▽)

code show as below:

public class User implements Serializable{
  private static final long serialVersionUID = 1L;
  /** Numbering */
   private int id;
   /** Name */
   private String name;
   /** age */
   private int age;

   public User(){
   }

  public int getId() {
   return id;
  }

  public void setId(int id) {
   this.id = id;
  }

  public String getName() {
   return name;
  }

  public void setName(String name) {
   this.name = name;
  }

  public int getAge() {
   return age;
  }

  public void setAge(int age) {
   this.age = age;
  }

  public String toString() {
   return JSONObject.toJSONString(this);
  }
}

Controller control layer

The control layer is also relatively simple here, using the CRUD function implemented in the Restful style. The difference is that I deliberately made some exceptions here so that these exceptions can be caught and processed. Among these exceptions, there are custom exceptions thrown, there are also null pointer exceptions, and of course there are also unpredictable exceptions (here I use type conversion exceptions instead), then after we finish writing the code, take a look at these exceptions Can it be captured and processed successfully?

code show as below:

@RestController
@RequestMapping(value = "/api")
public class UserRestController {

 @PostMapping("/user")
    public boolean insert(@RequestBody User user) {
     System.out.println("Start adding...");
     //If the name is empty, manually throw a custom exception!
        if(user.getName()==null){
            throw new BizException("-1","User name cannot be empty!");
        }
        return true;
    }

    @PutMapping("/user")
    public boolean update(@RequestBody User user) {
     System.out.println("Start update...");
       //This deliberately caused a null pointer exception and did not handle it
        String str=null;
        str.equals("111");
        return true;
    }

    @DeleteMapping("/user")
    public boolean delete(@RequestBody User user) {
        System.out.println("Start deleting...");
        //An exception is deliberately caused here and will not be processed
        Integer.parseInt("abc123");
        return true;
    }

    @GetMapping("/user")
    public List<User> findByUser(User user) {
     System.out.println("Start query...");
        List<User> userList =new ArrayList<>();
        User user2=new User();
        user2.setId(1L);
        user2.setName("xuwujing");
        user2.setAge(18);
        userList.add(user2);
        return userList;
    }

}

App entrance

It is basically the same as a normal SpringBoot project.

code show as below:

@SpringBootApplication
public class App
{
     public static void main( String[] args)
     {
   SpringApplication.run(App.class, args);
   System.out.println("The program is running...");
     }
}

function test

After we successfully started the program, we used the Postman tool to test the interface.

First, make a query to see if the program is running normally, and use the GET method to make a request.

GET http://localhost:8181/api/user returns parameters: {"id":1,"name":"xuwujing","age":18}

You can see that the program returns normally, and it is not affected by the custom global exception.

Then we will test whether the custom exception can be correctly caught and handled.

Use POST to make a request

POST http://localhost:8181/api/user

Body parameters are:{"id":1,"age":18}

Return parameters are:{"code":"-1","message ":"User name cannot be empty!","result":null}

It can be seen that the exception we throw is encapsulated in data, and then the exception is returned.

Then we will test whether the null pointer exception can be correctly caught and handled. In the custom global exception, in addition to defining the null pointer exception handling, we also define one of the highest-level Exception exceptions. After a null pointer exception occurs here, which one should it use first? Here we will test it.

Use the PUT method to make a request.

PUT http://localhost:8181/api/user

Body parameters are: {"id":1,"age":18}

The return parameters are: {"code":"400","message":"The requested data format does not match!","result":null}

We can see that here is indeed the exception care that returns a null pointer, and we can conclude that the global exception handling gives priority to subclass exceptions.

Then we are trying to unspecify the handling of the exception to see if the exception can be caught.

Use the DELETE method to make a request.

DELETE http://localhost:8181/api/user

The body parameter is: {"id":1}

The return parameters are: {"code":"500","message":"Server internal error!","result":null}

Here you can see that it uses the Exception method of exception handling in our custom global exception handling class. At this point, the test is over. By the way, in addition to the above-mentioned data format, the self-defined global exception handling can also handle page jumps. You only need to fill in the jump path in the return processing of the new exception method and do not use ResponseBody annotation is sufficient.

Careful students may find that the ControllerAdvice annotation is used in the GlobalExceptionHandler class instead of the RestControllerAdvice annotation. If the RestControllerAdvice annotation is used, it will automatically convert the data into JSON format. Controlleris similar toRestController`, so we can perform flexible selection processing after using global exception handling.

summary

Regarding SpringBoot's elegant global exception handling method, that's it. If you don't understand, you can communicate in the comment area. If there is anything wrong, please correct me!

created at:12-26-2021
edited at: 12-26-2021: