@Around踩坑记录
先上结论:
如果你在定义切面的时候打算使用环绕通知➕定义注解的方式,那么在进行*
@Around*("(@annotation(costTrace))")
类似这样的定义的时候,”costTrace“一定要与你定义切面中入参的参数名字一致,而不是参数类型(你定义的注解名字)一致.
1. 背景
在完善自己暑期实习项目的过程中,发现自己在业务逻辑中花了大量的篇幅去打一些和方法耗时计算相关的日志,于是在优化的过程中,第一步便是利用切面编程的思想将这些计算耗时的日志代码通过切面的方式,悄无声息的侵入,把代码篇幅都留给业务逻辑,而不是这些千篇一律的日志打印。
在这里,我通过传统的 (自定义注解 ➕ spring AOP 切面)的方式进行,以下是我的初始版本⬇️
1.1 引入依赖与定义配置
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
spring的xml配置文件中定义:
<aop:config proxy-target-class="true"/>
<aop:aspectj-autoproxy/>
1.2 自定义注解
/**
* @author kaihua
* @describe : 被该注解标记,将被进行代价计算与日志打印
* @date 2023/7/4
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD })
public @interface CostTrace {
String value() default "";
}
1.3 定义切面
/**
* @author kaihua
* @describe
* @date 2023/7/4
*/
@Aspect
@Component
public class CostTraceAspect {
/**
* CostTrace 切面
* @param joinPoint : 切点
* @param costTrace : 方法注解
* @return : 返回值
*/
@Around("(@annotation(CostTrace))")
public Object handleJoinPoint(ProceedingJoinPoint joinPoint, CostTrace costTrace) {
//1. 获取切入点方法信息
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String className = methodSignature.getDeclaringType().getName();
String methodName = methodSignature.getName();
String costTraceInformation = costTrace.value();
Object[] args = joinPoint.getArgs();
String requestStr = JSON.toJSONString(args);
//2. 定义方法开始时间
long begin = System.currentTimeMillis();
Object result = null;
try {
//3. 执行方法
result = joinPoint.proceed(args);
//4.1 方法执行完毕之后打印日志
LoggerManager.info(
String.format(
"the method %s of class %s execution successful, the input parameters are %s, the total time consumption is %dms, the other information: %s"
, methodName
, className
, requestStr
, System.currentTimeMillis() - begin
, costTraceInformation)
,""
,"100");
} catch (Throwable e) {
e.printStackTrace();
//4.2 方法若执行失败打印日志
LoggerManager.error(
String.format(
"the method %s of class %s execution error, the input parameters are %s, the other information: %s"
, methodName
, className
, requestStr
, costTraceInformation)
,""
,e);
}
return result;
}
}
2. 问题的出现
在预发环境部署的过程中出现了如下报错
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dependencyTypeHolder' defined in URL [jar:file:/home/admin/taefileserver/target/taefileserver.war/WEB-INF/lib/ateye-client-2.3.8.jar!/ateye-scene.xml]: BeanPostProcessor before instantiation of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.transaction.config.internalTransactionAdvisor': Cannot resolve reference to bean 'org.springframework.transaction.annotation.AnnotationTransactionAttributeSource#0' while setting bean property 'transactionAttributeSource'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.transaction.annotation.AnnotationTransactionAttributeSource#0': BeanPostProcessor before instantiation of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor#0': Cannot resolve reference to bean 'cursitor-pointcut' while setting bean property 'pointcut'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'cursitor-pointcut' defined in class path resource [spring-cursitor.xml]: BeanPostProcessor before instantiation of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor#1': Cannot resolve reference to bean 'jmonitor-pointcut' while setting bean property 'pointcut'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jmonitor-pointcut' defined in class path resource [jmonitor-spring.xml]: Initialization of bean failed; nested exception is java.lang.IllegalArgumentException: error at ::0 formal unbound in pointcut
报错非常长,但是通过控制变量法(通过部署前后的日志变化比较,不推荐,,最笨的方法)以及读取日志内容(推荐,但是有时候难以排查问题)发现,日志中pointcut的字眼,并且报错的本质原因是bean容器初始化失败,那必定是某个bean的定义出现了问题。
结合自己新添加的代码,发现只有自己定义的切面被标注了@Component,便把问题锁定在切面中了,但是这段代码乍一看,并没有什么错误,就是一个非常简单的环绕通知定义。
3. 问题的排查
通过复制日志报错搜索解决方案显然不太可行,后面专门去搜索了@Around配合注解如何去进行定义,终于在廖雪峰老师的网站看到了答案.
👉 https://www.liaoxuefeng.com/wiki/1252599548343744/1310052317134882
我又重新尝试修改了自己定义的切面的代码,进行部署,终于成功了!!
但是我这里还是不明白,为什么不能够使用注解类型,非要去使用对应的注解参数名字呢?
在一番搜索和chatGpt battle大战之后,大概搞清楚了原因:
**“在使用 @annotation 拦截注解时,需要获取到该注解的属性值,而注解的属性值是在注解对象中定义的,而不是在注解类型中定义的。因此,在 @annotation 中传入注解对象,可以获取到注解的属性值。”**
4. 解决方法的证实
我将切面的@Around定义分别换为了两个版本,结果都可以部署成功,基本可以证明了这个做法的正确性。
// version 1
@Around("(@annotation(costTrace))")
public Object handleJoinPoint(ProceedingJoinPoint joinPoint, CostTrace costTrace)
// version 2
@Around("(@annotation(cos))")
public Object handleJoinPoint(ProceedingJoinPoint joinPoint, CostTrace cos)
5. 小结
虽然这个只是一个非常非常非常小的排查问题案例,但是还是不由得产生非常多的感悟:
“作为一个臭写代码的程序员,我们所谓的学习,只是在学习其他人定义的规则,最典型的就是开发过程中的各种框架,我们在不断的学习别人定下来的规则。虽然这个现实是绝望的,但是我们也要从中有所收获,我们今天了解了这个规则,并不应该仅仅停留于学习到了这个规则,更可贵的是要去尝试去思考规则制定者在制定规则时候的思考”