JVM INVOKESPECIAL частный конструктор с ASM
Я использую ASM для генерации некоторого байт-кода и динамического выполнения. Но есть случай, когда мне нужно вызвать приватный конструктор, но я не могу понять, как. Я знаю, что можно вызвать закрытое конструктор через отражение (setAccessible), но как я могу сделать это непосредственно в байт-код / JVM?
mv.visitMethodInsn(
INVOKESPECIAL, target.byteCodeName(), "<init>", "()V", false
)
Когда этот код выполняется JVM, он генерирует java.lang.IllegalAccessError.
1 ответ
Отражение - единственный законный способ вызвать частный конструктор несвязанного класса. Но, конечно, это не очень хорошая идея, чтобы каждый раз вызывать рефлексивный вызов.
Решение invokedynamic
, Это позволяет связать сайт вызова с конструктором (полученным с помощью отражения) только один раз, а затем вызвать его без дополнительных затрат. Вот пример.
import org.objectweb.asm.*;
import java.lang.invoke.*;
import java.lang.reflect.Constructor;
import static org.objectweb.asm.Opcodes.*;
public class InvokeGenerator extends ClassLoader {
private static Class<?> generate() {
ClassWriter cv = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
cv.visit(V1_7, ACC_PUBLIC, "InvokeImpl", null, "java/lang/Object", null);
MethodVisitor mv = cv.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
// Generate INVOKEDYNAMIC instead of NEW+INVOKESPECIAL.
// This will instantiate the target class by calling its private constructor.
// Bootstrap method is called just once to link this call site.
mv.visitInvokeDynamicInsn("invoke", "()LInvokeGenerator$Target;",
new Handle(H_INVOKESTATIC, "InvokeGenerator", "bootstrap", "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;", false));
// Here we have newly constructed instance of InvokeGenerator.Target
mv.visitInsn(POP);
mv.visitInsn(RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
cv.visitEnd();
byte[] classData = cv.toByteArray();
return new InvokeGenerator().defineClass(null, classData, 0, classData.length);
}
public static void main(String[] args) throws Exception {
Class<?> cls = generate();
cls.newInstance();
}
public static CallSite bootstrap(MethodHandles.Lookup lookup, String name, MethodType type) throws Exception {
// Derive the constructor signature from the signature of this INVOKEDYNAMIC
Constructor c = type.returnType().getDeclaredConstructor(type.parameterArray());
c.setAccessible(true);
// Convert Constructor to MethodHandle which will serve as a target of INVOKEDYNAMIC
MethodHandle mh = lookup.unreflectConstructor(c);
return new ConstantCallSite(mh);
}
public static class Target {
private Target() {
System.out.println("Private constructor called");
}
}
}
До JDK 9 был альтернативный грязный взлом. Если вы унаследовали свой сгенерированный класс от sun.reflect.MagicAccessorImpl
JVM будет пропускать проверки доступа и разрешать вызов любого частного метода или конструктора. Но инкапсуляция частных API в JDK 9 затруднила выполнение этого трюка. Более того, MagicAccessorImpl
является специфическим для HotSpot JVM и не должен работать на других реализациях. Так что я бы определенно не рекомендовал эту альтернативу.