In-depth understanding of Spring exception handling


1. Preface

I believe that every one of us encountered such problems in Spring MVC development: when our code runs normally, the returned data is in our expected format, such as json or xml, but once there is an exception (such as NPE or array cross-border, etc.), the returned content does contain the exception stack information of the server, thus causing the returned data not to be parsed normally by the client;
Obviously, these are not the results we want.

We know that a relatively common system involves control layer, service (business) layer, cache layer, storage layer and interface call, etc. Each link will inevitably encounter various unpredictable exceptions to be handled. If each step is tried try..catch separately, the system will be cluttered, with poor readability and high maintenance cost. The common way is to implement unified exception handling, thus decoupling all kinds of exceptions from each module.

2. Common global exception handling

There are three types of common global exception handling in Spring:

(1) note ExceptionHandler

(2) Inheriting HandlerExceptionResolver Interface

(3) note ControllerAdvice

In the following explanation, taking HTTP error codes: 400 (invalid request) and 500 (internal server error) as examples, first look at the test code and the return results without any processing, as follows:


Figure 1: Test Code

Figure 2: No Abnormal Error Returns

2.1 note ExceptionHandler

Annotation ExceptionHandler acts as a method. The simplest way to use it is to put it in the controller file. Detailed annotation definitions are not introduced. If there are multiple controller files in the project, ExceptionHandler exception handling can usually be implemented in the baseController, and each contoler inherits the basecontroller to achieve the purpose of unified exception handling. Because it is common, the simple code is as follows:

Figure 3: ExceptionHandler Usage in 3:Controller

When the exception is returned, the class name to which it belongs is added, which is convenient for everyone to remember and understand. Run to see the results:

Figure 4: Results after Adding ExceptionHandler

  • Advantages: ExceptionHandler is simple and easy to understand, and there is no defined method format for exception handling;
  • Disadvantages: Because ExceptionHandler only works on methods, for the case of multiple controllers, it is only one method. All controllers that need exception handling inherit this class. It is not very good to force them to find a father for things that are obviously irrelevant.

2.2 annotation ControllerAdvice

Although this is a ControllerAdvice note, it is actually a combination of it and an ExceptionHandler. As can be seen from the above, when @ExceptionHandler is used alone, it must be in a Controller. However, when it is used in combination with ControllerAdvice, there is no such restriction at all. In other words, the combination of the two achieves global exception capture processing.
Figure 5: Annotation ControllerAdvice Exception Handling Code

Before running, the ExceptionHandler in the previous Controller should be commented out. The test results are as follows:
Figure 6: Annotating ControllerAdvice Exception Handling Results

As can be seen from the above results, the exception handling has indeed been changed to the ExceptionHandlerAdvice class. This method integrates all exception handling into one place, removes the inheritance relationship in the Controller, and achieves the effect of global capture. It is recommended to use this method.

2.3 Implement HandlerExceptionResolver Interface

HandlerExceptionResolver itself is an interface within SpringMVC. There is only one method within it, resolveException. By implementing this interface, we can achieve the goal of global exception handling.
Figure 7: Implementing HandlerExceptionResolver Interface
Also before execution, comment out the exception handling of the above two methods, and the running results are as follows:
Figure 8: Running Results of Implementing HandlerExceptionResolver Interface

It can be seen that the exception handling of 500 has already taken effect, but the exception handling of 400 has not taken effect, and the return result before the root has no exception is the same. What is going on here? Not to say that can do global exception handling? Can’t want to know the reason of the problem, we can only dig into the root of the problem, dig into Spring’s ancestral graves, below we combine Spring’s source code debugging, to need the reason.

3. Source Code Analysis of Exception Handling in 3.Spring

As we all know, the first class to receive a request in Spring is DispatcherServlet, and the core method in this class is doDispatch. We can break points in this class and follow up exception handling step by step.

3.1 HandlerExceptionResolver Implements Class Processing Flow

Referring to the following follow-up steps, at the processHandlerException breakpoint, the tracking results are as follows:
Figure 9: ProcessHandlerException Breakpoint

It can be seen that at arrow [1] in the figure, handlerExceptionResolvers are traversed to handle the exception, while at arrow [2], handlerExceptionResolvers are seen to have 4 elements in total, and the last one is the exception handling class defined by the 2.3 method.

According to the above phenomena, it can be inferred from the current request query that the exception handling should have been handled in the first three exception handling, thus skipping our customized exception; With such speculation, we continue to follow up with F8, and we can trace that the exception was handled by the third one, namely DefaultHandlerException Resolver.

SpringMVC is equipped with DefaultHandlerException Resolver by default. The doResolveException method of this class mainly handles some special exceptions and converts these exceptions into corresponding response state codes. However, the exception triggered by the query request is missingserviletrequestparameterexception, which happens to be the exception targeted by defaulthandlerexceptionresolver, so it will be caught by the exception in this class.

When the truth is revealed, we can see that our custom class MyHandlerExceptionResolver can indeed handle exceptions globally, except that the exception requested by query was inserted by DefaultHandlerException Resolver in the middle, so the handling of MyHandlerExceptionResolver class was skipped, resulting in a 400 return result. For calc request, there is no block in the middle, so the expected effect is achieved.

3.2 Processing Sequence of Three Types of Exceptions

Here we have introduced 3 types of global exception handling. According to the above analysis, the way to implement the HandlerExceptionResolver interface is the last processing. Then, who comes first and who comes after @ExceptionHandler and @ControllerAdvice?
Turn on all three types of exception handling (previously commented out) and run it to see the effect:
Fig. 10: results of exception handling full release operation

It can be seen from the phenomenon that @ExceptionHandle exception handling is ranked first in the Controller and @ControllerAdvice is ranked second. Strict children’s shoes can write Controller02, copy query and calc, exception handling is not needed, so when requesting c02 method, the class name of exception capture belongs to the class of @ControllerAdvice.

These are all the conclusions we have drawn from the phenomenon. Let’s go to Spring source code to find “evidence”. In Figure 9, there are 4 types of processors in handlerExceptionResolvers, while the processing of @ExceptionHandler and @ControllerAdvice is in the first Exception Handler Exception Resolver (the breakpoint can be learned by following up before). Continue to follow up until entering the DoresolveHandlerMethodException method of the ExceptionHandlerException Resolver class, where HandlerMethod is Spring’s method to map HTTP requests to the specified Controller, and Exception is the exception that needs to be caught; Continue to follow up and see what has been done with these two parameters.

Figure 11: DoresolvehandlerMethodException Breakpoint

Continue to follow up the getExceptionHandlerMethod method and find that there are two variables that may be the key to the problem: exceptionHandlerCache and exceptionHandlerAdviceCache. First of all, the variable names of the two are very doubtful. Secondly, in code, the former obviously uses the class as the key to get a resolver, which exactly matches the @ExceptionHandler processing rules in the Controller. Finally, the processing order of these two Cache is consistent with the previous conclusions. As previously speculated, Spring does give priority to finding the corresponding ExceptionHandler according to the Controller class name. If it is not found, the @ControllerAdvice exception will be processed.
Figure 12: Two Exception Handling Cache

* * If you are interested, you can continue to dig deep into Spring’s source code, which is targeted at
Exceptionhandlerexceptionresolver to make a brief summary: * *

  • ExceptionHandlerCache contains ExceptionHandler exception handling in the Controller. During handling, the Controller is obtained through HandlerMethod, and then an exception handling method is found. It should be noted that it is put value during exception handling.
  • ExceptionHandlerAdviceCache is initialized when the project is started. The general idea is to find a bean with @ControllerAdvice annotation so as to c ache the ExceptionHandler in the bean. During exception handling, it is necessary to align and traverse the lookup processing, thus achieving the purpose of global processing.

3.3 turnaround of salted fish

After introducing so much, simply draw a picture to summarize it. The blue part is the 3 types of exception handlers added by Spring by default, and the yellow part is the exception handlers we added and the location and order in which they are called. Look at what is still unclear, and turn back (Response StatesException Resolver is a comment on @ Response Stateus, which will not be described in detail here).
Figure 13: Anomaly Summary

If it is necessary to handle MyHandlerExceptionResolver in advance, even before Exception Handler Exception Resolver, can it be done? The answer is yes. If you want to advance the handling of MyHandlerExceptionResolver exception in Spring, you need to implement another Ordered interface to implement the getOrder method inside. Here you return -1 and put it on the top. This time the salted fish can finally turn over.
Figure 14: Implementing Ordered Interface

Run to see if the results are in line with expectations, and remind us that all three exception handling are effective, as shown in the following figure:
Figure 15: Implementation of Ordered Interface Operation Results

4. Summary

This article mainly introduces three kinds of common global exception handling in SpringMVC, finds problems in debugging, and then causes to go to Spring source code to explore the reasons, finally solves the problems, hoping that everyone can get something. Of course, Spring exception handling classes do not only introduce these, but also those who are interested in children’s shoes, please explore them yourself!

Reference link:

[1] …

[2] …

Author: Zhang YuanhangYixin Institute of Technology