Common Spring Exception Analysis and Handling

Introduction: This article mainly introduces three types of common global exception handling in SpringMVC, finds problems in debugging, and then leads to the Spring source code to explore the reasons, and finally solve the problem.

I. Introduction

I believe that each of us has encountered such a problem in the development of SpringMVC: when our code runs normally, the returned data is in the format we expect, such as json or xml, but once an exception occurs (such as: NPE or array out of bounds) etc.), the returned content is indeed the exception stack information of the server, so that the returned data cannot be parsed normally by the client; obviously, these are not the results we want.

We know that a relatively common system will involve the control layer, service (business) layer, cache layer, storage layer, and interface calls, etc. Each link will inevitably encounter various unpredictable exceptions that need to be handled. If each step is individually try..catch, the system will be very messy, the readability is poor, and the maintenance cost is high; the common way is to implement unified exception handling, so as to decouple various exceptions from each module;

2. Common global exception handling

There are three common global exception handling in Spring:

(1) Annotate ExceptionHandler

(2) Inherit the HandlerExceptionResolver interface

(3) Annotate ControllerAdvice

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

2.1 Annotating ExceptionHandler
The object of the annotation ExceptionHandler is a method. The easiest way to use it is to put it in the controller file. The detailed annotation definition will not be introduced. If there are multiple controller files in the project, the exception handling of ExceptionHandler can usually be implemented in the baseController, and each contoller inherits the basecontroller to achieve the purpose of unified exception handling. Because it is relatively common, the simple code is as follows:

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

Advantages: ExceptionHandler is simple and easy to understand, and there is no limited method format for exception handling;
Disadvantages: Since ExceptionHandler only acts on methods, in the case of multiple controllers, it is only for one method, and all controllers that need exception handling inherit this class. It is not good to forcibly associate things that are obviously irrelevant.
2.2 Annotating ControllerAdvice
Although the ControllerAdvice annotation is used here, it is actually a combination of it and ExceptionHandler. As you can see above, when using @ExceptionHandler alone, it must be in a Controller, but 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 catching processing.

Before running, you need to comment out the ExceptionHandler in the previous Controller. The test results are as follows:

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. This method is recommended;

2.3 Implement the HandlerExceptionResolver interface
HandlerExceptionResolver itself is the interface inside SpringMVC, and there is only one method inside resolveException. By implementing this interface, we can achieve the purpose of global exception handling.

Also before execution, comment out the exception handling of the above two methods, and the results are as follows:

It can be seen that the exception handling of 500 has taken effect, but the exception handling of 400 has not taken effect, and the return result is the same before the root has no exception. What's going on here? Doesn't it mean that global exception handling can be done? There is no way to know the cause of the problem. We can only get to the bottom of it and go to Spring's ancestral grave. Let's combine Spring's source code debugging to find the reason.

3. Source code analysis of exception handling in Spring
As we all know, the first class that receives 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 with exception handling step by step.

3.1 HandlerExceptionResolver implementation class processing flow
Referring to the following follow-up steps, at the processHandlerException breakpoint, the traced results are as follows:

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

The current request query request, according to the above phenomenon, it can be inferred that the exception handling should be handled in the first 3 exception handling, thus skipping our custom exception; with such a guess, we continue to follow up with F8, It can be traced that the exception is handled by the third one, DefaultHandlerExceptionResolver.

DefaultHandlerExceptionResolver: SpringMVC is equipped with DefaultHandlerExceptionResolver by default. The doResolveException method of this class mainly handles some special exceptions and converts such exceptions into corresponding response status codes. The exception triggered by the query request is MissingServletRequestParameterException, which happens to be the exception targeted by DefaultHandlerExceptionResolver, so it will be caught by exception in this class.
Now that the truth is revealed, we can see that our custom class MyHandlerExceptionResolver can indeed handle exceptions globally, but for the exceptions of query requests, the DefaultHandlerExceptionResolver is inserted in the middle, so the processing of the MyHandlerExceptionResolver class is skipped, thus A return result of 400 appears. For the calc request, there is no blocking in the middle, so the expected effect is achieved.

3.2 Processing sequence of three types of exceptions
So far, we have introduced three types of global exception handling. According to the above analysis, it can be seen that the way to implement the HandlerExceptionResolver interface is to be processed last, so who is the order of @ExceptionHandler and @ControllerAdvice? Open all three types of exception handling (commented out before), run it to see the effect:

It can be seen from the phenomenon that the individual @ExceptionHandle exception handling in the Controller ranks first, and @ControllerAdvice ranks second. Rigorous children's shoes can write a Controller02, copy the query and calc, and do not need exception handling, so when the method of c02 is requested, the class name of the exception capture is the class of @ControllerAdvice.

The above are the conclusions we got based on the phenomenon. Let's go to the Spring source code to find "evidence". In Figure 9, there are 4 types of handlers in handlerExceptionResolvers, and the processing of @ExceptionHandler and @ControllerAdvice is in the first ExceptionHandlerExceptionResolver (you can know it by following the breakpoint). Continue to follow up until you enter the doResolveHandlerMethodException method of the ExceptionHandlerExceptionResolver class, where HandlerMethod is the method by which Spring maps HTTP requests to the specified Controller, and Exception is the exception that needs to be caught; continue to follow up and see what is done using these two parameters what's up.

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 suspicious; secondly, the former is obviously used as a key through the class to get a processor (resolver) in the code, which coincides with the @ExceptionHandler processing rules in the Controller; finally, the two The processing order of each Cache is also in line with the previous conclusions. As I guessed before, Spring does give priority to finding the corresponding ExceptionHandler based on the Controller class name. If it is not found, then @ControllerAdvice exception handling is performed.

If you are interested, you can continue to dig into the source code of Spring. Here is a brief summary of ExceptionHandlerExceptionResolver:

The exceptionHandlerCache contains the ExceptionHandler exception handling in the Controller. When processing, the Controller is obtained through the HandlerMethod, and then the exception handling method is found. It should be noted that the value is put during the exception handling process;
The exceptionHandlerAdviceCache is initialized when the project starts. The general idea is to find a bean annotated with @ControllerAdvice, so as to cache the ExceptionHandler in the bean, and it needs to be aligned and traversed to find processing during exception processing, so as to achieve the purpose of global processing.

3.3 Turn over the salted fish
So much has been introduced, just draw a picture to summarize. The blue part is the 3 types of exception handlers added by Spring by default, and the yellow part is the exception handler we added and the position and order in which it is called. To see where there is still unclear, look back (ResponseStatusExceptionResolver is for the @ResponseStatus annotation, and will not be described in detail here).

If it is necessary to process MyHandlerExceptionResolver in advance, even before ExceptionHandlerExceptionResolver, can it be done? The answer is yes. In Spring, if you want to handle the MyHandlerExceptionResolver exception in advance, you need to implement an Ordered interface and implement the getOrder method inside. Return -1 here and put it on the top. This time, the salted fish can finally turn over. .

Run it to see if the result is as expected, and remind us that all three exception handlers are valid, as shown in the following figure:

4. Summary
This article mainly introduces three types of common global exception handling in SpringMVC, finds problems in debugging, and then leads to the Spring source code to explore the reasons, and finally solve the problem, I hope everyone can gain something. Of course, the Spring exception handling class is not only introduced, please explore by yourself if you are interested!

Related Articles

Explore More Special Offers

  1. Short Message Service(SMS) & Mail Service

    50,000 email package starts as low as USD 1.99, 120 short messages start at only USD 1.00

phone Contact Us