最近在工作中遇到一个redis缓存中的hash key莫名其妙被删除的问题,我们用了J2Cache,二级缓存用的是redis。hash key莫名其妙被删除又没有日志,就想到做一个切面在调用redis删除hash key的方法的时候,打印日志,并且把调用链路打印出来。
但是这个类的对象不是spring ioc容器管辖的,所以没有办法使用spring AOP,只能用AspectJ。
AspectJ把切面代码织入目标代码有两种方式,一种是编译时织入,一种是类加载时织入
因为目标代码是第三方jar包的,所以这里只能选择类加载时编织(Load-time Weaving, LTW)
redis客服端使用的是lettuce,一开始我选择对io.lettuce.core.api.sync.RedisHashCommands#hdel这个方法打印日志,但是这是一个接口,而底层的具体的实现是一个代理类,切到这个代理类破费了一番功夫,最后执行还是报错:
java.lang.NoSuchMethodError:*.RedisDelCommandAop.aspectOf()L*/RedisDelCommandAop
实在没有办法放弃对RedisHashCommands进行切面,而改成对J2Cache中的二级缓存Level2Cache.evict方面进行切面。Level2Cache.evict切面成功后,对代理类切面也成功了(后面有详细叙述)。
普通类切面步骤有4步:
1 增加aspectJ依赖,这里以maven为例
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.19</version></dependency>
</dependencies>
2. 配置aop.xml
在 src/main/resources/META-INF
目录下创建并配置 aop.xml
文件:
<aspectj><weaver options="-verbose -showWeaveInfo"><!-- 目标类的包名,不确定直接用* --><include within="com.example..*"/></weaver><aspects><!-- 自定义切面类全路径 --><aspect name="com.example.MyAspect"/></aspects>
</aspectj>
3. 定义切面类
package com.example;import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;@Aspect
@Slf4j
public class MyAspect {@Before("execution(* com.example..*(..))")public void beforeMethod() {System.out.println("A method is about to be executed.");}// 切到某个接口下所有实现类的表达式@Around("execution(* net.oschina.j2cache.Level2Cache+.evict(..))")public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {Object[] args = joinPoint.getArgs();log.debug("evict方法入参:{}", Arrays.toString(args));StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();for (StackTraceElement element : stackTrace) {log.debug(element.getClassName() + "." + element.getMethodName());}return joinPoint.proceed();}
}
4. 运行时加入JVM参数,指向aspectjweaver的jar包,要确保路径正确
-javaagent:path/to/aspectjweaver.jar
代理类切面需要增加的步骤
-
表达式
@Around("execution(* com.sun.proxy.$Proxy*.hdel(..))")
-
aop文件中增加开启切代理类
<aspectj><weaver options="-verbose -showWeaveInfo -Xset:weaveJavaxPackages=true -Xset:weaveAllIo=true -Xset:weaveAllProxies=true"><!-- Include the packages to be woven --><include within="*"/></weaver><aspects><!-- Specify the aspects to be woven --><aspect name="*.RedisDelCommandAop"/></aspects> </aspectj>
-
启动类上加上注解,后面测试这个步骤不加也可以成功
@EnableAspectJAutoProxy
记录下我遇到的问题:
-
idea运行的时候配置JVM参数,选错了地方。
-
打印日志定位aspectJ相关问题
加入JVM参数,开启aspectJ日志打印:
-Dorg.aspectj.weaver.showWeaveInfo=true -Dorg.aspectj.weaver.verbose=true
如果配置正确,会打印如下日志:
如果切面类不正确上面日志最后一行就是error Cannot register,原因是切面类上忘记加注解:@Aspect
另外如果表达式不正确,没有切到切面,就不会打印下面的日志: