一、需求背景
https://mp.csdn.net/mp_blog/creation/editor/140054265
上期,我们提到,使用自定义注解的方式实现接口入参校验,并抛出异常;
这期,我们对抛出的异常进行全局捕获处理,统一处理接口异常返回。
二、实现方案
@RestControllerAdvice是Spring框架提供的一个注解,用于定义全局异常处理器和全局数据绑定设置。它结合了@ControllerAdvice和@ResponseBody两个注解的功能。
@ControllerAdvice是一个用于定义全局控制器增强(即全局异常处理和全局数据绑定)的注解。通过使用@ControllerAdvice,我们可以将异常处理和数据绑定逻辑集中到一个类中,避免在每个控制器中重复编写相同的异常处理代码。
@ResponseBody是用于指示控制器方法返回的对象将被直接写入响应体中的注解。
@RestControllerAdvice注解标记的类将被视为全局异常处理器,并且异常处理方法的返回值将以JSON格式直接写入响应体中。
通过在@RestControllerAdvice类中定义异常处理方法,我们可以捕获和处理控制器中抛出的异常,提供自定义的异常处理逻辑,以及返回适当的响应给客户端。
这样可以统一处理应用程序中的异常情况,提高代码的可维护性和可读性。
三、编码实现
3.1 自定义异常
package com.saferycom.validate;
/**
* @author LH
*/
public class LhException extends RuntimeException{
String [] params;
public LhException() {
}
public LhException(String errCode) {
super(errCode);
}
public LhException(String errCode, String[] params) {
super(errCode);
this.params = params;
}
}
3.2 controller层代码,抛出自定义注解校验出的异常
package com.saferycom.validate;
import cn.hutool.core.collection.CollectionUtil;
import com.saferycom.domain.AjaxResult;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.validation.ConstraintViolation;
import javax.validation.Validator;
import java.util.Set;
/**
* @author LH
*/
@RestController
@RequestMapping("/test")
public class ValidateTestController {
@Resource
private Validator validator;
@PostMapping("/validate")
public AjaxResult validateTest(@RequestBody RegisterAddress address) {
// 校验
Set<ConstraintViolation<RegisterAddress>> violations = validator.validate(address);
// 遍历,抛出异常
if (CollectionUtil.isNotEmpty(violations)) {
violations.forEach(l -> {
throw new LhException(l.getMessage());
});
}
// 正常返回
return AjaxResult.success();
}
}
3.3 定义一个全局异常处理类,捕获各种类型的异常
根据处理的异常类型,可以根据实际需要新增
package com.saferycom.global;
import com.saferycom.domain.AjaxResult;
import com.saferycom.validate.LhException;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* 全局异常处理
*
* @author LH
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(LhException.class)
public AjaxResult handleLhException(LhException e) {
// 返回报错信息
return AjaxResult.error(HttpStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage());
}
}
3.4 自定义注解部分代码
自定义注解
package com.saferycom.validate;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
/**
* @author LH
*/
@Documented
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PccMatchValidator.class)
public @interface PccMatch {
// 省
String prov();
// 市
String city();
// 区
String country();
// 默认错误消息
String message() default "Invalid value for MyConstraint";
// 分组
Class<?>[] groups() default {};
// 负载
Class<? extends Payload>[] payload() default {};
}
自定义注解校验器
package com.saferycom.validate;
import org.springframework.beans.BeanWrapperImpl;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
/**
* @author LH
*/
public class PccMatchValidator implements ConstraintValidator<PccMatch, Object> {
private String prov;
private String city;
private String country;
@Override
public void initialize(PccMatch args) {
// 初始化逻辑
this.prov = args.prov();
this.city = args.city();
this.country = args.country();
}
@Override
public boolean isValid(Object obj, ConstraintValidatorContext context) {
// 结构体为空的默认返回true
if (obj == null) {
return true;
}
BeanWrapperImpl beanWrapper = new BeanWrapperImpl(obj);
String province = (String) beanWrapper.getPropertyValue(this.prov);
String city1 = (String) beanWrapper.getPropertyValue(this.city);
String country1 = (String) beanWrapper.getPropertyValue(this.country);
// todo 这里可以添加具体的验证逻辑,这里简单示例
if ("31000".equals(province) && "31100".equals(city1) && "31101".equals(country1)) {
return true;
}
// 根据需要,重新设置报错message
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate("省-市-区不匹配").addConstraintViolation();
return false;
}
}
3.5 启动服务测试返回
由此可见,被@RestControllerAdvice注解修饰的全局异常处理类,捕获了自定义注解校验抛出的异常,并将异常信息响应出去。
四、总结
全局的异常处理器,提供自定义的异常处理,返回适当的响应给客户端。统一处理应用程序的异常,提高代码的可维护性和可读性。
@RestControllerAdvice的使用还可以指定对应的注解、包,类。比如你需要返回自定义结果格式,可以指定@RestController层使用,如果你要指定类和包也可以。