@The ControllerAdvice and @ ExceptionHandler annotations handle global exceptions

Posted by gary00ie on Fri, 26 Nov 2021 20:06:04 +0100

Preface: @ ControllerAdvice, many beginners may not have heard of this annotation. In fact, this is a very useful solution. As the name suggests, this is an enhanced Controller. It is generally used in conjunction with @ ExceptionHandler to handle global exceptions.

1, @ ControllerAdvice

@ControllerAdvice is a new annotation provided by Spring 3.2. It is a controller enhancer that can logically process the methods using the @ RequestMapping annotation in the controller.

Using this annotation, three functions can be realized:

  • Global exception handling

  • Global data binding

  • Global data preprocessing

The flexible use of these three functions can help us simplify a lot of work. It should be noted that this is a function provided by spring MVC and can be used directly in Spring Boot. Only global exception handling is introduced here. If other functions are needed, you can access the reference link.

Global exception handling

Use @ ControllerAdvice and @ ExceptionHandler to realize global exception handling. You only need to define the class and add the annotation. The definition method is as follows:

@ControllerAdvice
public class MyGlobalExceptionHandler 
{
    @ExceptionHandler(Exception.class)
    public ModelAndView customException(Exception e) 
    {
        ModelAndView mv = new ModelAndView();
        mv.addObject("message", e.getMessage());
        mv.setViewName("myerror");
        return mv;
    }
}

In this class, multiple methods can be defined. Different methods deal with different exceptions, such as methods that deal with null pointers and methods that deal with array out of bounds. You can also deal with all exception information in one method directly like the above code.

@The ExceptionHandler annotation is used to indicate the exception handling type. That is, if NullpointerException is specified here, the array out of bounds exception will not be entered into this method.

2, Why do you want to handle and return exceptions in the Controller layer

I wonder if you have noticed how to deal with the exception thrown when writing the Controller layer interface. Is your first reaction thinking of using a try catch to catch the exception?

However, this method is only suitable for checking exceptions that the compiler actively prompts, because you can't pass the compilation check without try catch, so you can actively catch exceptions and handle them. But what happens if there is a runtime exception and you don't have time to think about handling it? Let's take a look at the following example without handling runtime exceptions:

@RestController
public class ExceptionRest {
    @GetMapping("getNullPointerException")
    public Map<String,Object> getNullPointerException(){
        throw new NullPointerException("A null pointer exception occurred");
    }
}

In the maven based spring MVC project, after the above code is started using tomcat, the browser side initiates the following request:

http://localhost:8080/zxtest/getNullPointerException

The results obtained after the visit are as follows: the error message received by the browser:

  As you can see, we threw a null pointer exception at the Controller interface layer and did not catch it. As a result, the exception stack will be returned to the front-end browser, causing a very bad experience for users.

In addition, the front end can see the server and middleware types used by the background system, the framework information and class information from the error message. Even if the back end throws SQL exceptions, you can also see the specific query parameter information of SQL exceptions. This is a medium risk security vulnerability and must be repaired.

3, Use @ ExceptionHandler and @ ControllerAdvice to achieve unified processing

When such runtime exceptions occur, perhaps the simplest method we can think of is to add exception handling to the code that may throw exceptions, as shown below:

@RestController
public class ExceptionRest {
    private Logger log = LoggerFactory.getLogger(ExceptionRest.class);
    @GetMapping("getNullPointerException")
    public Map<String,Object> getNullPointerException(){
        Map<String,Object> returnMap = new HashMap<String,Object>();
        try{
            throw new NullPointerException("A null pointer exception occurred");
        }catch(NullPointerException e){
            log.error("A null pointer exception occurred",e);
            returnMap.put("success",false);
            returnMap.put("mesg","An exception occurred in the request. Please try again later");
        }
        return returnMap;
    }
}

Because we manually add processing to the place where the exception is thrown and properly return the content returned to the front end when the exception occurs, when we launch the same request in the browser again, we get the following content:

{
success: false,
mesg: "An exception occurred in the request. Please try again later"
}

It seems that the problem has been solved, but can you ensure that you can catch and handle exceptions in all possible places? Can you make sure the rest of the team does the same?

Obviously, you need a unified exception capture and handling scheme.

Use @ ExceptionHandler

After spring 3.2, spring MVC introduced the exception handler processing method to make the exception handling more simple and accurate. The only thing you need to do is create a new Controller, and then add two annotations to complete the capture and processing of all exceptions in the Controller layer.

3.1 basic use

Create a new Controller as follows:

@ControllerAdvice
public class ExceptionConfigController {
    @ExceptionHandler
    public ModelAndView exceptionHandler(Exception e){
        ModelAndView mv = new ModelAndView(new MappingJackson2JsonView());
        mv.addObject("success",false);
        mv.addObject("mesg","An exception occurred in the request. Please try again later");
        return mv;
    }
}

In the above code, we added @ ControllerAdvice annotation to the class to indicate that it is an enhanced controller, and then created an ExceptionHandler method that returns ModelAndView object, and added @ ExceptionHandler annotation to indicate that it is an exception handling method, and then wrote specific exception handling and return parameter logic in the method, It's really convenient to finish all the work in this way.

After the browser initiates the call, we return the following results:

{
success: false,
mesg: "An exception occurred in the request. Please try again later"
}

3.2 specific exception handling

Compared with HandlerExceptionResolver, @ ExceptionHandler is more flexible to handle different exceptions separately. Moreover, when the thrown exception is a subclass of the specified exception, it can still be caught and handled.

We change the code of the lower controller layer as follows:

@RestController
public class ExceptionController {

    @GetMapping("getNullPointerException")
    public Map<String, Object> getNullPointerException() {
        throw new NullPointerException("A null pointer exception occurred");
    }

    @GetMapping("getClassCastException")
    public Map<String, Object> getClassCastException() {
        throw new ClassCastException("A type conversion exception occurred");
    }

    @GetMapping("getIOException")
    public Map<String, Object> getIOException() throws IOException {
        throw new IOException("There it is IO abnormal");
    }
}

It is known that both NullPointerException and ClassCastException inherit RuntimeException, while both RuntimeException and IOException inherit Exception.

We do this in the ExceptionConfigController:

@ControllerAdvice
public class ExceptionConfigController {
    // It is specially used to catch and handle null pointer exceptions in the Controller layer
    @ExceptionHandler(NullPointerException.class)
    public ModelAndView nullPointerExceptionHandler(NullPointerException e){
        ModelAndView mv = new ModelAndView(new MappingJackson2JsonView());
        mv.addObject("success",false);
        mv.addObject("mesg","A null pointer exception occurred in the request. Please try again later");
        return mv;
    }

    // Specifically used to catch and handle runtime exceptions in the Controller layer
    @ExceptionHandler(RuntimeException.class)
    public ModelAndView runtimeExceptionHandler(RuntimeException e){
        ModelAndView mv = new ModelAndView(new MappingJackson2JsonView());
        mv.addObject("success",false);
        mv.addObject("mesg","A runtime exception occurred in the request. Please try again later");
        return mv;
    }

    // It is specially used to catch and handle exceptions in the Controller layer
    @ExceptionHandler(Exception.class)
    public ModelAndView exceptionHandler(Exception e){
        ModelAndView mv = new ModelAndView(new MappingJackson2JsonView());
        mv.addObject("success",false);
        mv.addObject("mesg","An exception occurred in the request. Please try again later");
        return mv;
    }
}

that

  • When we throw NullPointerException in the Controller layer, it will be processed by nullPointerExceptionHandler and then intercepted.

    {
    success: false,
    mesg: "A null pointer exception occurred in the request. Please try again later"
    }
    
  • When we throw ClassCastException in the Controller layer, it will be processed by runtimeExceptionHandler and then intercepted.

    {
    success: false,
    mesg: "A runtime exception occurred in the request. Please try again later"
    }
    
  • When we throw IOException in the Controller layer, it will be processed by the exceptionHandler and then intercepted.

    {
    success: false,
    mesg: "An exception occurred in the request. Please try again later"
    }

summary

The Controller layer exception handling provided by spring MVC is really convenient, especially @ ExceptionHandler, which is recommended.

Reference link:

Unified exception handling and return of Controller layer

Three usage scenarios of @ ControllerAdvice annotation in spring MVC!