2016-03-23 7 views
6

Ich möchte reflektierenden Zugriff auf java.lang.String 's Paket privaten Konstruktor erhalten.Können Lambda-Ausdrücke auf private Methoden von Klassen außerhalb ihres Geltungsbereichs zugreifen?

Nämlich dieses:

/* 
* Package private constructor which shares value array for speed. 
* this constructor is always expected to be called with share==true. 
* a separate constructor is needed because we already have a public 
* String(char[]) constructor that makes a copy of the given char[]. 
*/ 
String(char[] value, boolean share) { 
    // assert share : "unshared not supported"; 
    this.value = value; 
} 

Erstellen eines MethodHandle es einfach genug ist, und so ist es aufgerufen wird. Dasselbe gilt für die direkte Verwendung von Reflection.

Aber ich bin gespannt, ob es möglich ist, den Konstruktor über funktionale Schnittstellen direkt aufzurufen.

27602758 berührt ein ähnliches Problem, aber die angebotenen Lösungen scheinen in diesem Fall nicht zu funktionieren.

Der Testfall unten kompiliert ohne Probleme. Alles funktioniert, bis auf den eigentlichen Schnittstellenaufruf.

package test; 

import java.lang.invoke.CallSite; 
import java.lang.invoke.LambdaMetafactory; 
import java.lang.invoke.MethodHandle; 
import java.lang.invoke.MethodHandles; 
import java.lang.invoke.MethodHandles.Lookup; 
import java.lang.invoke.MethodType; 
import java.lang.reflect.Field; 

public class Test { 

    // Creates a new String that shares the supplied char[] 
    private static interface StringCreator { 

     public String create(char[] value, boolean shared); 
    } 

    // Creates a new conventional String 
    private static String create(char[] value, boolean shared) { 
     return String.valueOf(value); 
    } 

    public static void main(String[] args) throws Throwable { 
     // Reflectively generate a TRUSTED Lookup for the calling class 
     Lookup caller = MethodHandles.lookup(); 
     Field modes = Lookup.class.getDeclaredField("allowedModes"); 
     modes.setAccessible(true); 
     modes.setInt(caller, -1); // -1 == Lookup.TRUSTED 

     // create handle for #create() 
     MethodHandle conventional = caller.findStatic(
      Test.class, "create", MethodType.methodType(String.class, char[].class, boolean.class) 
     ); 
     StringCreator normal = getStringCreator(caller, conventional); 
     System.out.println(
      normal.create("foo".toCharArray(), true) 
     // prints "foo" 
     ); 

     // create handle for shared String constructor 
     MethodHandle constructor = caller.findConstructor(
      String.class, MethodType.methodType(void.class, char[].class, boolean.class) 
     ); 
     // test directly if the construcor is correctly accessed 
     char[] chars = "foo".toCharArray(); 
     String s = (String) constructor.invokeExact(chars, true); 
     chars[0] = 'b'; // modify array contents 
     chars[1] = 'a'; 
     chars[2] = 'r'; 
     System.out.println(
      s 
     // prints "bar" 
     ); 

     // generate interface for constructor 
     StringCreator shared = getStringCreator(caller, constructor); 
     System.out.println(
      shared.create("foo".toCharArray(), true) 
     // throws error 
     ); 
    } 

    // returns a StringCreator instance 
    private static StringCreator getStringCreator(Lookup caller, MethodHandle handle) throws Throwable { 
     CallSite callSite = LambdaMetafactory.metafactory(
      caller, 
      "create", 
      MethodType.methodType(StringCreator.class), 
      handle.type(), 
      handle, 
      handle.type() 
     ); 
     return (StringCreator) callSite.getTarget().invokeExact(); 
    } 
} 

specficially die Anweisung

shared.create("foo".toCharArray(), true) 

führt den folgenden Fehler:

Exception in thread "main" java.lang.IllegalAccessError: tried to access method java.lang.String.<init>([CZ)V from class test.Test$$Lambda$2/989110044 at test.Test.main(Test.java:59) 

Warum wird dieser Fehler noch geworfen, trotz Zugang angeblich gewährt werden?

Kann jemand eine Erklärung dafür finden, warum die generierte Schnittstelle keinen Zugriff auf eine Methode hat, auf die alle Komponenten zugreifen können?

Gibt es eine Lösung oder eine praktikable Alternative, die tatsächlich für diesen speziellen Anwendungsfall funktioniert, ohne auf reine Reflexion oder MethodHandles zurückzugreifen?

Weil ich ratlos bin.

+1

Kann nicht Ihre eigentliche Frage beantworten endgültig , aber reine Reflexion ist hier nicht schrecklich. Methode/Konstruktor/etc. sind im Wesentlichen Proxy-Klassen um einen Accessor mit generiertem Bytecode. Sie zu erstellen ist eine Schwergewichtsoperation, aber eine Invokation ist es nicht. Der 'boolean' wird eingerahmt, aber ansonsten ist er ungefähr so ​​wie 'new', zumindest in der Sun/OpenJDK-Implementierung. Reflection umgeht den Zugriffsfehler, indem der Accessor so generiert wird, als befände er sich innerhalb des Bereichs, zu dem das Member gehört, was LambdaMetaFactory hier nicht tut. – Radiodef

+0

@Radiodef Vielen Dank für die Details. Wenn keine Lambda-Lösung gefunden werden kann, verwende ich einfach einen MethodHandle. Ich bin meistens neugierig, warum sich der Test so verhält wie er. –

Antwort

2

Das Problem ist, dass Sie das Lookup-Objekt überschreiben vertraut werden, so dass ihr Zugang zu einem private Verfahren von String wird das Lookup-Verfahren und Lambda-Meta-Fabrik passieren, aber es ist immer noch auf Ihre Test Klasse gebunden wie die Klasse ist, die erstellt Das Lookup-Objekt über MethodHandles.lookup() und die generierte Klasse wird im selben Kontext leben. Die JVM ist ziemlich großzügig in Bezug auf die Erreichbarkeit, wenn es um diese generierten Klassen geht, aber anscheinend wird der Zugriff auf ein private Mitglied der Bootstrap-Klasse java.lang.String von einer Klasse, die im Kontext Ihrer Anwendungsklasse lebt, nicht akzeptiert.

Sie können ein Nachschlageobjekt erhalten, das in einem geeigneten Kontext über z.B. MethodHandles.lookup() .in(String.class) (und dann patchen es private oder "vertrauenswürdigen" Zugriff), aber dann werden Sie ein weiteres Problem bekommen: eine Klasse im Kontext von java.lang.String (oder nur im Bootstrap Loader-Kontext) hat keinen Zugriff auf Ihre benutzerdefinierte interface StringCreator und kann es nicht implementieren.

Die einzige Lösung ist ein Lookup-Objekt lebt im Zusammenhang mit String und einen des bestehenden generischen interface s, zugänglich von dem Bootstrap Class Loader Implementierung zu verwenden:

import java.lang.invoke.*; 
import java.lang.invoke.MethodHandles.Lookup; 
import java.lang.reflect.Field; 
import java.util.function.BiFunction; 

public class Test { 
    public static void main(String[] args) throws Throwable { 
     // Reflectively generate a TRUSTED Lookup for the String class 
     Lookup caller = MethodHandles.lookup().in(String.class); 
     Field modes = Lookup.class.getDeclaredField("allowedModes"); 
     modes.setAccessible(true); 
     modes.setInt(caller, -1); // -1 == Lookup.TRUSTED 
     // create handle for shared String constructor 
     MethodHandle constructor = caller.findConstructor(
      String.class, MethodType.methodType(void.class, char[].class, boolean.class) 
     ); 
     // generate interface implementation for constructor 
     BiFunction<char[],Boolean,String> shared=getStringCreator(caller, constructor); 

     // test if the construcor is correctly accessed 
     char[] chars = "foo".toCharArray(); 
     String s = shared.apply(chars, true); 
     chars[0] = 'b'; chars[1] = 'a'; chars[2] = 'r';// modify array contents 
     System.out.println(s); // prints "bar" 
     chars[0] = '1'; chars[1] = '2'; chars[2] = '3'; 
     System.out.println(s); // prints "123" 
    } 
    private static BiFunction<char[],Boolean,String> getStringCreator(
      Lookup caller, MethodHandle handle) throws Throwable { 
     CallSite callSite = LambdaMetafactory.metafactory(
      caller, 
      "apply", 
      MethodType.methodType(BiFunction.class), 
      handle.type().generic(), 
      handle, 
      handle.type() 
     ); 
     return (BiFunction) callSite.getTarget().invokeExact(); 
    } 
} 
+0

danke für die detaillierte Erklärung und für eine funktionierende Lösung! Ich kann mir nicht vorstellen, dass es einen Weg gibt, solch ein Nachschlagen über Klassen außerhalb des Bootstrap-Klassenladeprogramms zu lernen. –

Verwandte Themen