@ControllerAdvice + @ExceptionHandler handles Controller layer exceptions globally

@ControllerAdvice + @ExceptionHandler handles Controller layer exceptions globally

SpringMVC important annotations (2) @ControllerAdvice

springMVC uses @ControllerAdvice to implement exception handling





zero, preface

For database-related Spring MVC projects, we usually configure the transaction in the Service layer. When the database operation fails, the Service layer throws a runtime exception, and the Spring transaction manager will roll back.

As a result, our Controller layer has to perform a try-catch Service layer exception, otherwise it will return some unfriendly error messages to the client. However, each method body of the Controller layer writes some templated try-catch code, which is ugly and difficult to maintain, especially when different exceptions in the Service layer need to be handled differently. For example the following Controller method code (very ugly and redundant):

/**
 * 手动处理 Service 层异常和数据校验异常的示例
 * @param dog
 * @param errors
 * @return
 */
@PostMapping(value = "")
AppResponse add(@Validated(Add.class) @RequestBody Dog dog, Errors errors){
    AppResponse resp = new AppResponse();
    try {
        // 数据校验
        BSUtil.controllerValidate(errors);

        // 执行业务
        Dog newDog = dogService.save(dog);

        // 返回数据
        resp.setData(newDog);

    }catch (BusinessException e){
        LOGGER.error(e.getMessage(), e);
        resp.setFail(e.getMessage());
    }catch (Exception e){
        LOGGER.error(e.getMessage(), e);
        resp.setFail("操作失败!");
    }
    return resp;
}
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

This article explains the use of @ControllerAdvice + @ExceptionHandler for global exception handling at the Controller layer. As long as it is properly designed, try-catch at the Controller layer is no longer necessary! Moreover, the exceptions annotated by the @Validated validator can also be handled together, and there is no need to manually judge the BindingResult/Errors of the binding validation result!

1. Advantages and disadvantages

  • Advantages: Unified processing of controller layer exceptions and data verification exceptions, reducing template code, reducing the amount of coding, and improving scalability and maintainability.
  • Disadvantages: It can only handle exceptions that are not caught (thrown out) by the Controller layer. For the exceptions of the Interceptor (interceptor) layer and the exception of the Spring framework layer, there is nothing that can be done.

2. Basic usage example

2.1 @ControllerAdvice annotation defines global exception handling class

@ControllerAdvice
public class GlobalExceptionHandler {
}
  
  
  • 1
  • 2
  • 3

Make sure that this GlobalExceptionHandler class can be scanned and loaded into the Spring container.

2.2 @ExceptionHandler annotation declares exception handling method

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)
    @ResponseBody
    String handleException(){
        return "Exception Deal!";
    }
}
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

The method handleException() will handle all exceptions thrown by the Controller layer and its subclasses , which is the most basic usage.

In the parameter list of the method annotated with @ExceptionHandler , many types of parameters can also be declared, see the documentation for details . Its prototype is as follows:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExceptionHandler {

    /**
     * Exceptions handled by the annotated method. If empty, will default to any
     * exceptions listed in the method argument list.
     */
    Class<? extends Throwable>[] value() default {};

}
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

If the exception type to be handled is not declared in the @ExceptionHandler annotation, it defaults to the exception type in the parameter list. So the above can also be written like this:

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler()
    @ResponseBody
    String handleException(Exception e){
        return "Exception Deal! " + e.getMessage();
    }
}
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

The parameter object is the exception object thrown by the Controller layer!

3. Handling business exceptions thrown on the Service layer

Sometimes in a complex business with database transactions, when there is inconsistent data, we directly throw the encapsulated business-level runtime exception, roll back the database transaction, and hope that the exception information can be returned and displayed. to users.

3.1 Code example

Encapsulated business exception class:

public class BusinessException extends RuntimeException {

    public BusinessException(String message){
        super(message);
    }
}
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

Service implementation class:

@Service
public class DogService {

    @Transactional
    public Dog update(Dog dog){

        // some database options

        // 模拟狗狗新名字与其他狗狗的名字冲突
        BSUtil.isTrue(false, "狗狗名字已经被使用了...");

        // update database dog info

        return dog;
    }

}
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

Among them, the auxiliary tool class BSUtil

public static void isTrue(boolean expression, String error){
    if(!expression) {
        throw new BusinessException(error);
    }
}
  
  
  • 1
  • 2
  • 3
  • 4
  • 5

Then, we should declare the business exception class in the GlobalExceptionHandler class, handle it accordingly, and then return it to the user. The code closer to the real project should look like this:

/**
 * Created by kinginblue on 2017/4/10.
 * @ControllerAdvice + @ExceptionHandler 实现全局的 Controller 层的异常处理
 */
@ControllerAdvice
public class GlobalExceptionHandler {

    private static final Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    /**
     * 处理所有不可知的异常
     * @param e
     * @return
     */
    @ExceptionHandler(Exception.class)
    @ResponseBody
    AppResponse handleException(Exception e){
        LOGGER.error(e.getMessage(), e);

        AppResponse response = new AppResponse();
        response.setFail("操作失败!");
        return response;
    }

    /**
     * 处理所有业务异常
     * @param e
     * @return
     */
    @ExceptionHandler(BusinessException.class)
    @ResponseBody
    AppResponse handleBusinessException(BusinessException e){
        LOGGER.error(e.getMessage(), e);

        AppResponse response = new AppResponse();
        response.setFail(e.getMessage());
        return response;
    }
}
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

The code of the Controller layer does not need to handle exceptions:

@RestController
@RequestMapping(value = "/dogs", consumes = {MediaType.APPLICATION_JSON_UTF8_VALUE})
public class DogController {

    @Autowired
    private DogService dogService;

    @PatchMapping(value = "")
    Dog update(@Validated(Update.class) @RequestBody Dog dog){
        return dogService.update(dog);
    }
}
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

3.2 Code Description

Logger does all exception logging.

@ExceptionHandler(BusinessException.class) declares the handling of the BusinessException business exception, and obtains the error prompt in the business exception, and returns it to the client after construction.

@ExceptionHandler (Exception.class) declares the handling of Exception, and plays a role in the bottom line. No matter what exception occurs in the code executed by the Controller layer that cannot be considered, it will return a unified error prompt to the client.

Remarks: The above GlobalExceptionHandler just returns Json to the client, and more room for play needs to be done according to the needs.

Fourth, handle the exception of Controller data binding and data verification

Annotation data validation rules on fields in the Dog class:

@Data
public class Dog {

    @NotNull(message = "{Dog.id.non}", groups = {Update.class})
    @Min(value = 1, message = "{Dog.age.lt1}", groups = {Update.class})
    private Long id;

    @NotBlank(message = "{Dog.name.non}", groups = {Add.class, Update.class})
    private String name;

    @Min(value = 1, message = "{Dog.age.lt1}", groups = {Add.class, Update.class})
    private Integer age;
}
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
说明:@NotNull@Min@NotBlank 这些注解的使用方法,不在本文范围内。如果不熟悉,请查找资料学习即可。

其他说明:
@Data 注解是 **Lombok** 项目的注解,可以使我们不用再在代码里手动加 getter & setter。EclipseIntelliJ IDEA 中使用时,还需要安装相关插件,这个步骤自行Google/Baidu 吧!

  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

For the usage of Lombok, see: Lombok of Java's strange tricks

For the Json interface of RESTFUL in SpringMVC, data binding and verification are as follows:

/**
 * 使用 GlobalExceptionHandler 全局处理 Controller 层异常的示例
 * @param dog
 * @return
 */
@PatchMapping(value = "")
AppResponse update(@Validated(Update.class) @RequestBody Dog dog){
    AppResponse resp = new AppResponse();

    // 执行业务
    Dog newDog = dogService.update(dog);

    // 返回数据
    resp.setData(newDog);

    return resp;
}
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

Implemented using @Validated + @RequestBodyannotations .

The Spring MVC framework throws an exception when an @Validated + @RequestBodyannotation but the bound data object is not followed by a type parameter declaration.ErrorsMethodArgumentNotValidException

Therefore, by adding the declaration and processing of MethodArgumentNotValidExceptionexceptions of data verification can be handled globally! The added code is as follows:

/**
 * Created by kinginblue on 2017/4/10.
 * @ControllerAdvice + @ExceptionHandler 实现全局的 Controller 层的异常处理
 */
@ControllerAdvice
public class GlobalExceptionHandler {

    private static final Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    /**
     * 处理所有不可知的异常
     * @param e
     * @return
     */
    @ExceptionHandler(Exception.class)
    @ResponseBody
    AppResponse handleException(Exception e){
        LOGGER.error(e.getMessage(), e);

        AppResponse response = new AppResponse();
        response.setFail("操作失败!");
        return response;
    }

    /**
     * 处理所有业务异常
     * @param e
     * @return
     */
    @ExceptionHandler(BusinessException.class)
    @ResponseBody
    AppResponse handleBusinessException(BusinessException e){
        LOGGER.error(e.getMessage(), e);

        AppResponse response = new AppResponse();
        response.setFail(e.getMessage());
        return response;
    }

    /**
     * 处理所有接口数据验证异常
     * @param e
     * @return
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseBody
    AppResponse handleMethodArgumentNotValidException(MethodArgumentNotValidException e){
        LOGGER.error(e.getMessage(), e);

        AppResponse response = new AppResponse();
        response.setFail(e.getBindingResult().getAllErrors().get(0).getDefaultMessage());
        return response;
    }
}
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54

Have you noticed that all the exception logging of the Controller layer is recorded in this GlobalExceptionHandler. In other words, the Controller layer does not need to manually record error logs.

V. Summary

This article mainly talks about the global unified handling of exceptions thrown at the Controller layer by the combination of @ControllerAdvice + @ExceptionHandler.

In fact, methods annotated with @ExceptionHandler can also declare many parameters, see the documentation for details.

@ControllerAdvice can also be used in combination with @InitBinder, @ModelAttribute and other annotations, and applied to all methods annotated with @RequestMapping, see Search Engine for details.

6. Appendix

The sample code for this article has been put on Github .






zero, preface

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325804734&siteId=291194637