Programmer Notes | Common Spring Exception Analysis and Handling

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:

(Figure 1: Test code)

(Figure 2: Error return without exception)

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:

(Figure 3: ExceptionHandler usage in Controller)

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

(Figure 4: Result after adding ExceptionHandler)

  • 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. All controllers that need exception handling inherit this class. It is not good to force them to find a father for obviously irrelevant things.

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.

(Figure 5: Annotating ControllerAdvice exception handling code)

Before running, you need to comment out the ExceptionHandler in the previous Controller. 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. 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.

(Figure 7: Implementing the HandlerExceptionResolver interface)

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

(Figure 8: The result of implementing the HandlerExceptionResolver interface)

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:

(Figure 9: processHandlerException breakpoint)

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.

At this point, the truth is revealed, and we can see that our custom class MyHandlerExceptionResolver can indeed handle exceptions globally, but for the exception of the query request, 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:

(Figure 10: Exception handling fully released operation result)

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.

(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 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.

(Figure 12: Two Exception Handling Caches)

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 traverse and search for processing in 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).

(Figure 13: Exception summary)

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. .

(Figure 14: Implementing the Ordered interface)

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:

(Figure 15: The result of implementing the Ordered interface)

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!

Reference link:

[1] http://www.cnblogs.com/fangjian0423/p/springMVC-request-mapping.html

[2]https://blog.csdn.net/mll999888/article/details/77621352

Author: Zhang Yuanhang Source: CreditEase Institute of Technology

{{o.name}}
{{m.name}}

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=324084737&siteId=291194637