Este es mi código:
class A {
public static void doIt() {
//Class currentClass = new Object() { }.getClass().getEnclosingClass();
//Class currentClass = MethodHandles.lookup().lookupClass();
String currentClass = Thread.currentThread().getStackTrace()[1].getClassName();
System.out.println("CALLING CLASS:" + currentClass);
}
}
class B extends A { }
public class NewMain {
public static void main(String[] args) {
A.doIt();
B.doIt();
}
}
Como se puede ver doIt
método puede ser llamado por A
y B
clases. En doIt
quiero saber qué clase se utilizó el método de llamada ( A
o B
). ¿Es posible? Tres soluciones que probé no funcionó - que siempre dice A
la clase.
Al principio, pensé que esto es imposible, ya que el compilador Java puede averiguar qué método será llamado y emiten la misma instrucción.
Resulta que se graba en realidad la forma en que la clase se llama.
Entonces, la pregunta ahora es:
- ¿Cómo podemos llegar al lugar donde se llama al método?
- ¿Cómo podemos utilizar esta información para obtener la forma en que el método se llama?
La primera de ellas es fácil: Usamos un StackWalker, lo que nos puede dar el índice de código de bytes .
Ahora sólo tenemos que analizar la clase, vistazo a la instrucción en la que el índice de código de bytes, y encontrar la manera se llama a este método.
Solía ASM para eso, pero podría ser la herramienta equivocada aquí.
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.lang.StackWalker.StackFrame;
import java.util.Set;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import static org.objectweb.asm.Opcodes.*;
class CallingClassVisitor extends ClassVisitor {
private final StackFrame where;
String ownerClass = null;
public CallingClassVisitor(StackFrame where) {
// We need a backing ClassWriter, so the Label is resolved.
super(ASM8, new ClassWriter(0));
this.where = where;
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature,
String[] exceptions) {
MethodVisitor parent = super.visitMethod(access, name, descriptor, signature, exceptions);
if (name.equals(where.getMethodName()) && descriptor.equals(where.getDescriptor())) {
return new CallingMethodVisitor(where, parent);
} else {
return parent;
}
}
class CallingMethodVisitor extends MethodVisitor {
private final StackFrame where;
public CallingMethodVisitor(StackFrame where, MethodVisitor parent) {
super(ASM8, parent);
this.where = where;
}
@Override
public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
Label lbl = new Label();
visitLabel(lbl);
if (lbl.getOffset() == where.getByteCodeIndex()) {
ownerClass = owner;
}
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
}
}
public String getOwnerClass() {
return ownerClass;
}
}
class A {
static final StackWalker SW = StackWalker.getInstance(Set.of(StackWalker.Option.RETAIN_CLASS_REFERENCE));
public static void doIt() {
StackFrame sf = SW.walk(s -> s.skip(1).findFirst()).orElseThrow();
InputStream source = sf.getDeclaringClass().getClassLoader()
.getResourceAsStream(sf.getClassName().replace('.', '/') + ".class");
try {
CallingClassVisitor ccv = new CallingClassVisitor(sf);
new ClassReader(source).accept(ccv, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
String how = ccv.getOwnerClass();
System.out.println(how);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
}
class B extends A { }
public class NewMain {
public static void main(String[] args) {
A.doIt();
B.doIt();
}
}
Al final, no estoy seguro de si su requerimiento vale la pena el esfuerzo.