最近在工作中遇到了@Transactional和@Async注解不生效
发现原因是this方法调用导致AOP失效
于是在想问一个问题,我们能不能够像idea搜索方法引用那样直接找出来,这些存在的问题呢?
首先,我们一开始需要扫描类中的被Transactional或者Async注解的类方法,于是我们需要扫描我们项目底下的编译目录,遍历每个方法是否已经注解了上面这些方法。
Class<?> clazz = Class.forName(className);
try {
for (Method method : clazz.getDeclaredMethods()) {
spanAnnotation.forEach(annotation->{
if (method.isAnnotationPresent(annotation)) {
methods.add(clazz.getName()+"#" + method.getName());
}
});
}
} catch (Throwable e) {
e.printStackTrace();
}
随后如何在项目中寻找方法的引用呢?
ASM
ASM可以在方法体中找到对应的方法引用
class MethodReferenceClassVisitor extends ClassVisitor {
private final String methodName;
private final String className;
private final HashSet<String> referencedBy = new HashSet<>();
public MethodReferenceClassVisitor(String methodName, String className) {
super(Opcodes.ASM6);
this.methodName = methodName;
this.className = className;
}
public HashSet<String> getReferencedBy() {
return referencedBy;
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
return new MethodReferenceMethodVisitor(className + "#" + name, methodName, referencedBy);
}
}
class MethodReferenceMethodVisitor extends MethodVisitor {
private final String selfMethod;
private final String methodName;
private final HashSet<String> referencedBy;
public String localVariables=null;
public int Line;
public MethodReferenceMethodVisitor(String name, String methodName, HashSet<String> referencedBy) {
super(Opcodes.ASM6);
this.selfMethod = name;
this.methodName = methodName;
this.referencedBy = referencedBy;
}
@Override
public void visitVarInsn(int opcode, int var) {
if (opcode == Opcodes.ALOAD && var == 0) {
localVariables= Opcodes.ALOAD+"-"+var;
}
super.visitVarInsn(opcode, var);
}
@Override
public void visitTypeInsn(int opcode, String type) {
super.visitTypeInsn(opcode, type);
}
@Override
public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
owner = owner.replaceAll("/", ".");
if ((owner +"#"+ name).equals(methodName)) {
if((Opcodes.ALOAD +"-0").equals(localVariables)){
referencedBy.add(selfMethod+":"+Line);
}
}
}
@Override
public void visitLineNumber(int line, Label start) {
super.visitLineNumber(line, start);
Line=line;
}
}
简单来说就是构建一个ClassVisitor重载对应方法,在读取Class的时候ClassReader会调用ClassVisitor的visitMethod方法,随后调用MethodVisitor加载方法体中的内容
visitMethodInsn可以读取到方法名参数返回
visitLineNumber可以读取到引用的行数
visitVarInsn可以读取到方法当前是否使用对象
不过基于以上这些还是无法判断出对象引用还是this方法引用
下面附上代码
项目为jdk11内置asm,如果jdk不对,需要自己引入asm包
<dependencies>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-model</artifactId>
<version>3.8.1</version>
</dependency>
</dependencies>
import jdk.internal.org.objectweb.asm.*;
import org.apache.maven.model.Model;
import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Collectors;
public class SpanAnno {
static List<String> classes = new LinkedList<>();
static HashMap<String, ClassReader> classReaders = new HashMap<>();
static List<String> methods = new LinkedList<>();
static List<String> errors = new LinkedList<>();
static String[] spanStr = new String[]{
"org.springframework.scheduling.annotation.Async",
"org.springframework.transaction.annotation.Transactional",
};
static List<Class<? extends Annotation>> spanAnnotation = new ArrayList<>(spanStr.length);
static {
for (String s : spanStr) {
try {
spanAnnotation.add((Class<? extends Annotation>) Class.forName(s));
} catch (ClassNotFoundException e) {
}
}
}
public static void main(String[] args) throws Exception {
URL resource = ClassLoader.getSystemResource("");
File pomFile = new File(resource.getPath().replaceAll("/target", "").replaceAll("/classes", "") + "pom.xml");
MavenXpp3Reader reader = new MavenXpp3Reader();
Model model = reader.read(new FileInputStream(pomFile));
List<File> Dirs = new ArrayList<>();
model.getDependencies().forEach(f -> {
File file1 = new File(f.getArtifactId() + "/target/classes");
if (!file1.exists()) {
return;
}
Dirs.add(file1);
});
Dirs.add(new File(resource.getPath()));
for (File dir : Dirs) {
try {
String basePath = dir.getAbsolutePath();
List<File> files = Files.walk(Paths.get(basePath))
.filter(Files::isRegularFile)
.filter(file -> file.toString().endsWith(".class"))
.map(Path::toFile)
.collect(Collectors.toList());
for (File file : files) {
String className = file.getAbsolutePath().replace(basePath, "")
.replaceAll("^[/\\\\]+|[/\\\\]+$", "")
.replaceAll("[/\\\\]", ".");
className = className.substring(0, className.lastIndexOf("."));
classes.add(className);
try {
Class<?> clazz = Class.forName(className);
try {
for (Method method : clazz.getDeclaredMethods()) {
spanAnnotation.forEach(annotation -> {
if (method.isAnnotationPresent(annotation)) {
methods.add(clazz.getName() + "#" + method.getName());
}
});
}
} catch (Throwable e) {
e.printStackTrace();
}
} catch (Throwable e) {
e.printStackTrace();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
MethodReferenceFinder finder = new MethodReferenceFinder();
System.out.println(new Date());
methods.forEach(m -> {
classes.forEach(c -> {
try {
finder.findReferences(c, m);
HashSet<String> references = finder.getReferences(c);
for (String reference : references) {
if (reference.split("#")[0].equals(m.split("#")[0])) {
errors.add(m + " -> " + reference);
}
}
} catch (IOException e) {
e.printStackTrace();
}
});
});
errors.forEach(System.err::println);
}
static class MethodReferenceFinder {
private final Map<String, HashSet<String>> references = new HashMap<>();
public void findReferences(String className, String methodName) throws IOException {
ClassReader classReader;
if (classReaders.containsKey(className)) {
classReader = classReaders.get(className);
} else {
classReader = new ClassReader(className);
classReaders.put(className, classReader);
}
MethodReferenceClassVisitor classVisitor = new MethodReferenceClassVisitor(methodName, className);
classReader.accept(classVisitor, Opcodes.ASM6);
references.put(className, classVisitor.getReferencedBy());
}
public HashSet<String> getReferences(String className) {
return references.get(className);
}
class MethodReferenceClassVisitor extends ClassVisitor {
private final String methodName;
private final String className;
private final HashSet<String> referencedBy = new HashSet<>();
public MethodReferenceClassVisitor(String methodName, String className) {
super(Opcodes.ASM6);
this.methodName = methodName;
this.className = className;
}
public HashSet<String> getReferencedBy() {
return referencedBy;
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
return new MethodReferenceMethodVisitor(className + "#" + name, methodName, referencedBy);
}
}
class MethodReferenceMethodVisitor extends MethodVisitor {
private final String selfMethod;
private final String methodName;
private final HashSet<String> referencedBy;
public String localVariables = null;
public int Line;
public MethodReferenceMethodVisitor(String name, String methodName, HashSet<String> referencedBy) {
super(Opcodes.ASM6);
this.selfMethod = name;
this.methodName = methodName;
this.referencedBy = referencedBy;
}
@Override
public void visitVarInsn(int opcode, int var) {
if (opcode == Opcodes.ALOAD && var == 0) {
localVariables = Opcodes.ALOAD + "-" + var;
}
super.visitVarInsn(opcode, var);
}
@Override
public void visitTypeInsn(int opcode, String type) {
super.visitTypeInsn(opcode, type);
}
@Override
public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
owner = owner.replaceAll("/", ".");
if ((owner + "#" + name).equals(methodName)) {
if ((Opcodes.ALOAD + "-0").equals(localVariables)) {
referencedBy.add(selfMethod + ":" + Line);
}
}
}
@Override
public void visitLineNumber(int line, Label start) {
super.visitLineNumber(line, start);
Line = line;
}
}
}
}