Отражение — единственный законный способ вызвать закрытый конструктор несвязанного класса. Но, конечно, не стоит каждый раз делать рефлективный призыв.
Решение 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 и не должен работать в других реализациях. Так что я бы определенно не рекомендовал эту альтернативу.
person
apangin
schedule
24.01.2019
javac -source 6 -target 6
добавляет синтетический конструктор, который принимает синтетический класс (похожий на анонимный внутренний класс, но не имеет абсолютно никаких конструкторов). Вы можете вызывать это из другого класса в том же пакете, но javac отсеивает синтетические методы. (Скомпилируйте второй класс против первого измененного, чтобы включить то, что будет синтетическим конструктором. Перекомпилируйте правильный первый класс. Затем запустите.) JDK 11, похоже, использует новую вещь гнезд, что неинтересно. - person Tom Hawtin - tackline   schedule 24.01.2019