2016-06-14 6 views
18

Da Java 8-Schnittstellen Standardmethoden haben können. mit Reflexion zum Beispiel auf einem Proxy- Ich weiß, wie die Methode explizit von der Implementierung Methode aufzurufen, das heißt (siehe Explicitly calling a default method in Java)Wie explizit die Standardmethode von einem dynamischen Proxy aufrufen?

Aber wie ich explizit die Standard-Methode aufrufen?

Beispiel:

interface ExampleMixin { 

    String getText(); 

    default void printInfo(){ 
    System.out.println(getText()); 
    } 
} 

class Example { 

    public static void main(String... args) throws Exception { 

    Object target = new Object(); 

    Map<String, BiFunction<Object, Object[], Object>> behavior = new HashMap<>(); 

    ExampleMixin dynamic = 
      (ExampleMixin) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),new Class[]{ExampleMixin.class}, (Object proxy, Method method, Object[] arguments) -> { 

       //custom mixin behavior 
       if(behavior.containsKey(method.getName())) { 
        return behavior.get(method.getName()).apply(target, arguments); 
       //default mixin behavior 
       } else if (method.isDefault()) { 
        //this block throws java.lang.IllegalAccessException: no private access for invokespecial 
        return MethodHandles.lookup() 
             .in(method.getDeclaringClass()) 
             .unreflectSpecial(method, method.getDeclaringClass()) 
             .bindTo(target) 
             .invokeWithArguments(); 
       //no mixin behavior 
       } else if (ExampleMixin.class == method.getDeclaringClass()) { 
        throw new UnsupportedOperationException(method.getName() + " is not supported"); 
       //base class behavior 
       } else{ 
        return method.invoke(target, arguments); 
       } 
      }); 

    //define behavior for abstract method getText() 
    behavior.put("getText", (o, a) -> o.toString() + " myText"); 

    System.out.println(dynamic.getClass()); 
    System.out.println(dynamic.toString()); 
    System.out.println(dynamic.getText()); 

    //print info should by default implementation 
    dynamic.printInfo(); 
    } 
} 

Edit: Ich weiß, eine ähnliche Frage in How do I invoke Java 8 default methods refletively gefragt wurde, aber das hat mein Problem aus zwei Gründen nicht gelöst:

  • das Problem beschrieben in dieser Frage auf, wie man es über Reflexion im Allgemeinen - so dass keine Unterscheidung zwischen d Efault und Override-Methode wurde gemacht - und das ist einfach, Sie brauchen nur eine Instanz.
  • eine der Antworten - mit Methode behandelt - funktioniert nur mit bösen Hack (imho) wie Ändern von Zugriffsmodifizierer auf Felder der Suchklasse, die die gleiche Kategorie von "Lösungen" ist wie folgt: Change private static final field using Java reflection: es ist gut zu weiß, es ist möglich, aber ich würde es nicht in der Produktion verwenden - Ich bin auf der Suche nach einem "offiziellen" Weg, es zu tun.

Die IllegalAccessException geworfen in unreflectSpecial

Caused by: java.lang.IllegalAccessException: no private access for invokespecial: interface example.ExampleMixin, from example.ExampleMixin/package 
at java.lang.invoke.MemberName.makeAccessException(MemberName.java:852) 
at java.lang.invoke.MethodHandles$Lookup.checkSpecialCaller(MethodHandles.java:1568) 
at java.lang.invoke.MethodHandles$Lookup.unreflectSpecial(MethodHandles.java:1227) 
at example.Example.lambda$main$0(Example.java:30) 
at example.Example$$Lambda$1/1342443276.invoke(Unknown Source) 
+1

Ist es nicht ein Duplikat dieser http://stackoverflow.com/questions/22614746/how-do-i-invoke-java-8-default-methods-refletively –

+2

„* Ich bin auf der Suche für einen "offiziellen" Weg, es zu tun * "Ich mag mich irren, aber ich fürchte, dass Sie offiziell nicht in der Lage sein sollten, die Methode vom Supertyp aufzurufen, wenn Ihr Subtyp es überschrieben hat. Nehmen wir an, Ihr Supertyp hat eine 'acceptSquare' Methode, die beliebige Squares akzeptieren kann, aber Ihr Subtyp ist * spezialisiert * nur mit roten Quadraten, also überschreibt er es entsprechend um Test für Farbe hinzuzufügen (danach ruft er' super.addSquare' auf). Es könnte also eine große Sicherheitslücke sein, jemandem zu erlauben, von außerhalb der Implementierung von einem Supertyp (sogar durch Reflektion) einer solchen Methode aus aufzurufen. – Pshemo

+0

Wie wäre es mit Mixins - Hinzufügen von Funktionalität zu einer bestehenden Klasse mit einem dynamischen Proxy? d. h. ich habe eine Instanz und möchte zusätzliche Funktionalität hinzufügen, indem ich Interfaces mit Standardmethoden zur Laufzeit zur Instanz hinzufüge. Es muss einen Weg geben –

Antwort

4

Wenn Sie eine konkrete impl Klasse als lookupClass und Anrufer für die invokespecial verwenden sollte es korrekt die Standardimplementierung aufrufen die Schnittstelle (kein Hack für privaten Zugriff erforderlich):

Example target = new Example(); 
... 

Class targetClass = target.getClass(); 
return MethodHandles.lookup() 
        .in(targetClass) 
        .unreflectSpecial(method, targetClass) 
        .bindTo(target) 
        .invokeWithArguments(); 

Dies funktioniert natürlich nur, wenn Sie einen Verweis auf ein konkretes Objekt haben, das die Schnittstelle implementiert.

Edit: Diese Lösung wird nur funktionieren, wenn die betreffende Klasse (Beispiel im obigen Code), ist privat aus dem Aufrufer-Code, z. eine anonyme innere Klasse.

Die aktuelle Implementierung der MethodHandles/Lookup-Klasse wird nicht ermöglichen, InvokeSpecial für jede Klasse aufzurufen, die nicht von der aktuellen Aufruferklasse aus zugänglich ist. Es gibt verschiedene Workarounds, aber alle erfordern die Verwendung von Reflektion, um Konstruktoren/Methoden zugänglich zu machen, die wahrscheinlich fehlschlagen, falls ein SecurityManager installiert wird.

+1

hm, das funktioniert auch mit anonymen Klassen ... aber gegeben, ich don ' Ich kenne die Schnittstellen vorher (weil es ein Parameter ist), könnte ich Instanzen von anonymen Klassen dynamisch erstellen? (Proxy scheint nicht zu funktionieren, Byte-Code-Generierung ist auch keine Option) –

+0

Was ist 'ex' in diesem Beispiel? Es muss hier eine Instanz von 'Example' sein, also warum nicht einfach die 'target'-Instanz verwenden? Abgesehen davon hat es die gleiche Einschränkung wie die direkte Verwendung von 'ExampleMixin'; es funktioniert nur, wenn die angegebene Klasse eine innere Klassenbeziehung mit dem umgebenden Code hat (der Code, der 'lookup()' aufruft). – Holger

+0

Das war ein Fehler, ex sollte in der Tat durch Ziel ersetzt werden. –

1

Verwendung:

Object result = MethodHandles.lookup() 
    .in(method.getDeclaringClass()) 
    .unreflectSpecial(method, method.getDeclaringClass()) 
    .bindTo(target) 
    .invokeWithArguments(); 
+2

Ich habe versucht das, gab mir 'verursacht durch: java.lang.IllegalAccessException: kein privater Zugriff für Invokespecial: Schnittstelle example.IExample, aus example.IExample/package' –

1

Wenn alles, was Sie haben, eine Schnittstelle ist und alles, was Sie zugreifen können, ist ein Klassenobjekt eine Schnittstelle, die Ihre Basisschnittstelle erweitert, und Sie möchten die Standardmethode ohne eine reale Instanz einer Klasse aufrufen, die die Schnittstelle implementiert Sie können:

Object target = Proxy.newProxyInstance(classLoader, 
     new Class[]{exampleInterface}, (Object p, Method m, Object[] a) -> null); 

Erstellen Sie eine Instanz der Schnittstelle, und erstellen Sie die MethodHandles.Lookup mit Reflexion:

Constructor<MethodHandles.Lookup> lookupConstructor = 
    MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, Integer.TYPE); 
if (!lookupConstructor.isAccessible()) { 
    lookupConstructor.setAccessible(true); 
} 

und verwenden Sie dann, dass lookupConstructor eine neue Instanz der Schnittstelle zu schaffen, die einen privaten Zugang zum invokespecial ermöglichen. Dann rufen Sie die Methode auf dem falschen Proxy target auf, den Sie früher machten.

lookupConstructor.newInstance(exampleInterface, 
     MethodHandles.Lookup.PRIVATE) 
     .unreflectSpecial(method, declaringClass) 
     .bindTo(target) 
     .invokeWithArguments(args); 
+0

MethodHandles.Lookup gibt "illegalen reflective access", wie oben berichtet . – user1135940

Verwandte Themen