使用@Aspect和@Before注解以及全局异常拦截
记录:390
场景:使用aspectjweaver包的@Aspect和@Before注解拦截自定义注解@VerifyCity。自定义注解@VerifyCity作用在Controller的方法上。实现在执行Controller的方法前,先拦截注解校验必要参数。使用@RestControllerAdvice和@ExceptionHandler注解拦截异常,实现优雅返回异常信息。
版本:JDK 1.8,SpringBoot 2.6.3
AOP:Aspect Oriented Programming,面向切面编程。
AOP:将业务逻辑的各个部分进行隔离,使开发人员在编写业务逻辑时可以专心于核心业务,从而提高了开发效率。
AOP:取横向抽取机制,取代了传统纵向继承体系的重复性代码,主要应用在事务处理、日志管理、权限控制、异常处理等方面。
1.自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface VerifyCity {
String value() default "";
}
2.使用@Aspect和@Before实现拦截自定义注解
2.1代码
@Aspect
@Order(0)
@Component
@Slf4j
public class VerifyCityAspect {
@Before("@annotation(verifyCity)")
public void beforeVerify(VerifyCity verifyCity) {
RequestAttributes reqAttributes = RequestContextHolder.currentRequestAttributes();
HttpServletRequest httpReq = ((ServletRequestAttributes) reqAttributes).getRequest();
String httpCityName = httpReq.getHeader("cityName");
String annotationCityName = verifyCity.value();
log.info("从http请求头中获取的城市名称,cityName=" + httpCityName);
log.info("从Controller的方法上的@VerifyCity注解获取的城市名称,cityName=" + annotationCityName);
if (!StringUtils.equals(httpCityName, annotationCityName)) {
log.info("校验失败,不执行Controller.");
throw new VerifyException("校验城市编码失败.");
}
log.info("校验成功,执行Controller.");
}
}
2.2解析
@Aspect,标记实现AOP。
@Before("@annotation(verifyCity)"),标记拦截注解@VerifyCity。
StringUtils.equals(httpCityName, annotationCityName),比对从请求头中获取的cityName的值与从注解@VerifyCity中获取的值,相等则验证通过,否则抛出一个异常,结束程序。
throw new VerifyException("校验城市编码失败."),抛出定义异常,结束本次请求,也就是不会执行@VerifyCity标记的Controller的方法了。
3.自定义异常
自定义VerifyException继承RuntimeException。
public class VerifyException extends RuntimeException {
private String errorCode = "";
public VerifyException(String message) {
super(message);
this.errorCode = "500";
}
public String getErrorCode() {
return errorCode;
}
}
4.自定义全局拦截器
4.1代码
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(value = VerifyException.class)
public String bizExceptionHandler(HttpServletRequest req, VerifyException e) {
String errMsg = "异常编码: " + e.getErrorCode() + ";异常信息: " + e.getMessage();
log.error(errMsg);
return errMsg;
}
}
4.2解析
@RestControllerAdvice,标记全局异常。
@ExceptionHandler(value = VerifyException.class),指定拦截的异常类型。
返回值类型String,抛出异常后,会组装成字符串信息返回,而不是一连串报错异常。
5.使用自定义注解
在Controller类使用注解@VerifyCity("Hangzhou")。
@Slf4j
@RestController
@RequestMapping("/hub/example/city")
public class CityController {
@VerifyCity("Hangzhou")
@PostMapping("/queryCityByCityCode")
public Object queryCityByCityCode(String cityCode) {
log.info("入参cityCode = "+cityCode);
return "执行成功";
}
}
6.使用Postman测试
6.1请求地址与入参
地址:http://127.0.0.1:18200/hub-200-base/hub/example/city/queryCityByCityCode
入参:cityCode=310001
请求头:cityName=Hangzhou
返回值:执行成功
6.2优雅拦截异常返回
返回值:异常编码: 500;异常信息: 校验城市编码失败.
6.3不使用全局拦截异常返回
把VerifyCityAspect中抛出异常改为:
throw new RuntimeException("校验城市编码失败.");
不优雅返回值:
{
"timestamp": "2023-03-23T23:03:4127+08:00",
"status": 500,
"error": "Internal Server Error",
"path": "/hub-200-base/hub/example/city/queryCityByCityCode"
}
或者
java.lang.RuntimeException: 校验城市编码失败.
at com.hub.example.aspect.VerifyCityAspect.beforeVerify(VerifyCityAspect.java:37) ~[classes/:na]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_181]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_181]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_181]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_181]
7.核心依赖包
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.19</version>
</dependency>
以上,感谢。
2023年3月23日