SpringBoot exception handling? Just use these two!

In daily projects, we will inevitably encounter system errors. If the system exception is not handled, Springboot itself will return the error exception as an interface request by default.

 @GetMapping("/testNorError")
 public void testNorError() {
     try {
         throw new MyException(6000, "My error");
     }catch (Exception e){
         throw new MyException(5000, "My package exception", e);
     }
 }
Copy Code

As can be seen from the above figure, when Springboot does not handle the exception, it returns the wrong stack directly as the response data. This is not friendly to users, and may cause potential security risks due to the leakage of system stack information. Therefore, it is very necessary to build a complete exception handling mechanism to maintain the robustness of the system.

General exception handling

To quickly build an exception handling mechanism, you need to consider how to catch and handle exceptions? The most convenient way is to use the @ExceptionHandler annotation to achieve.

 @ExceptionHandler(MyException. class)
     protected ResponseEntity<Object> handleException(Exception ex) {
         LOGGER.error("Failed to execute, handleException:{}", ex.getMessage(), ex);
         return new ResponseEntity<>(new ResultDTO(). fail(ResultCodeEnum. ERROR_SERVER), HttpStatus. OK);
     }
Copy Code

By adding the above exception handling code in the Controller, Springboot can convert the relevant error information into the unified error handling of the system, thereby avoiding stack exposure. (The ResultDTO here is a custom JSON structure in the system, which can be modified according to your own business.)

However, @ExceptionHandler itself has a drawback, that is, the scope of its action must be Controller, which means that as many Controllers as there are, your exception handling code will have to be rewritten many times, which is undoubtedly inefficient. In order to reduce repetitive code redundancy, @ControllerAdvance has entered our field of vision.

 @ControllerAdvice
 @Slf4j
 public class ExtGlobalExceptionHandler {
     
     @ExceptionHandler(Exception. class)
     protected ResponseEntity<Object> handleException(Exception ex) {
         LOGGER.error("Failed to execute, handleException:{}", ex.getMessage(), ex);
         return new ResponseEntity<>(new ResultDTO(). fail(ResultCodeEnum. ERROR_SERVER), HttpStatus. OK);
     }
 }
Copy Code

To put it simply, @ControllerAdvance is a global processing annotation. The code in it will take effect for all Controllers. It usually handles exceptions with @ExceptionHandler. From this, it is possible to handle global exceptions by writing only one exception handling method. .

As for how @ControllerAdvance and @ExceptionHandler realize this magical function, due to space limitations, a separate article will be considered in the future to introduce in detail. (In fact, according to the name, it is not difficult to infer that ControllerAdvance is a dynamic proxy for Controller objects.)

Personalized exception handling

With @ControllerAdvance and ExceptionHandler, almost 80% of the error handling problems faced by projects can be solved. However, think about it. If multiple groups of people maintain and iterate a system at the same time in a project (cost reduction and efficiency increase, everyone understands), the error reports that each group of people need to pay attention to will naturally be different. If people in group A only pay attention to error A, and people in group B only pay attention to error B, then this general exception solution cannot be distinguished.

For this situation, we have to invite another big guy, he is: AOP, there are many implementation methods and frameworks for dynamic proxy, here we directly use SpringBoot’s own AOP framework by default:

 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-aop</artifactId>
     <version>2.1.11.RELEASE</version>
 </dependency>
Copy Code

No matter what AOP implementation framework you choose, the following two steps are indispensable to adopt AOP coding:

1. Define cut point and execution timing (where to enhance)

2. Define the notification (how to enhance it)

Define pointcut and execution timing

For the AOP framework that comes with Springboot, there are five execution times:

Enhancement Timing Enhancement Type Similarities and Differences
@After Post enhancement Call the enhancement method after the target method is executed
@Before Pre-enhancement Call the enhancement method before the target method is executed
@AfterReturning Return enhancement Call the enhanced method after the target method executes return before returning the result, if there is an exception, it will not be executed
@AfterThrowing Exception Enhancement The execution of the target method generates an exception to call the enhanced method. It should be noted that the exception will still be thrown up after processing and will not be caught.
@Around Surround enhancement Surround enhancement includes the first four enhancements, through a certain try-catch processing, the surround type Any of the above enhancements can be substituted.

After understanding the execution timing of SpringBoot’s dynamic proxy, we also need to know how it defines the pointcut. There are two main ways in which the framework defines pointcuts:

  • Pointcut expressions
  • Notes

note

We first introduce the correct way to open annotations. To implement your own AOP through annotations, you first need to define a new annotation. Here I simply define an annotation:

 @Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})
 @Retention(RetentionPolicy. RUNTIME)
 @Documented
 public @interface MyAnnotation {
     
     String SERVER_NAME() default "";
 ?
     String action() default "";
 }
Copy Code

After defining the annotation, define the annotation as the input parameter of the method, and mark the variable name of the annotation through @annotation(), so that the function of annotation AOP can be realized.

 // where annotations are processed
 @Around(value = "@annotation(name)")
 public <T> T test(ProceedingJoinPoint point, MyAnnotation name) throws Throwable {
     String serverName = name. SERVER_NAME();
     //handle the exception
     return handlerRpcException(point, serverName);
 }
 ?
 //Specific code execution
 @MyAnnotation(SERVER_NAME = "Downstream System", action = "Operation Processing")
 public <T> T testFunction() {
     return (T) new ResultDTO<>(). success(Boolean. TRUE);
 }
Copy Code

pointcut expression

Springboot’s AOP also provides a very powerful way to implement dynamic agent point-cutting annotations, that is, point-cutting expressions. The basic pattern is as follows:

 execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
Copy Code

Note that parameters with question marks such as modifiers-pattern?, declaring-type-pattern?, and throws-pattern? are optional. Next, let’s introduce the meaning of the above parameters one by one:

  • modifiers-pattern? : Modifier matching, which mainly indicates which kind of cut point is public/private/protected/default.
  • ret-type-pattern: As the name suggests, it refers to the type of return value, such as: void/Boolean/String, etc.
  • declaring-type-pattern? : This refers to the classpath of the enhanced method and attribute, such as com.example.demo.service.aop.MyAspect, etc.
  • name-pattern(param-pattern) : This is a relatively key parameter, referring to the enhanced method name and its corresponding parameter type.
  • throws-pattern: Throw-pattern sees the meaning of the word, and you can know the type of exception thrown by the method it refers to.

In addition to understanding the basic matching meaning of the above expressions, there are several special wildcard symbols to mention:

***** : Match any number of characters .. : Match any number of repetitions of characters, such as matching any number of subpackages in the type pattern; and matching any number of parameters in the method parameter pattern (0 one or more parameters) + : matches the specified type and its subtypes; can only be placed as a suffix after the type pattern

Maybe the above code and introduction make you look confused, it doesn’t matter, you can simply look at the meaning of the following two expressions, and you will roughly understand their meaning:

 // 1. Represents [all parameters] method [under any class] [any name] with [any return value] and prefix [com.example.demo.rpc]
 execution(* com.example.demo.rpc.*.*(..))
 ?
 // 2. Represents the [arbitrary name] method [taking String as the last input parameter] [return value is Boolean] and is located in [com.example.demo.rpc and its subpackages] [arbitrary name]
 execution(Boolean com.example.demo.rpc..*(.., String))
Copy Code

With the help of aspect expressions, we can define our cut points freely and flexibly, so as to realize our exception handling through AOP

@Pointcut("execution(Boolean com.example.demo.rpc..*(.., String)) || execution(another expression)")
private void PointCutOfAnno() {
}

@Around(value = "PointCutOfAnno()")
public <T> T testForAOP(ProceedingJoinPoint point) throws Throwable {
    // Handle the corresponding exception
    return handlerRpcException(point, serverName);
}
Copy Code

Summary

This article introduces two writing methods for exception handling under Springboot:

1. The general exception handling method realized by @ControllerAdvance and @ExceptionHandler

Second, the personalized exception handling mechanism realized by means of AOP.

In fact, the implementation ideas of the two are essentially the same. Through the dynamic proxy of the execution code, the error is packaged to achieve the effect of not leaking the exception. In actual business scenarios, Method 1 can cover almost 80% of exception handling scenarios. The second scheme is mainly aimed at the situation that needs to be personalized in a system, and can be selected according to specific business needs.