2016-06-14 38 views
18

Poiché le interfacce Java 8 potrebbero avere metodi predefiniti. So come richiamare il metodo in modo esplicito dal metodo di applicazione, vale a dire (vedi Explicitly calling a default method in Java)Come richiamare esplicitamente il metodo predefinito da un proxy dinamico?

Ma come faccio a esplicitamente invoco il metodo predefinito utilizzando la riflessione, per esempio su un proxy?

Esempio:

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: So che una domanda simile è stato chiesto in How do I invoke Java 8 default methods refletively, ma questo non ha risolto il mio problema per due motivi:

  • il problema descritto in quella domanda mirava a come richiamarlo tramite la riflessione in generale - quindi nessuna distinzione tra È stato eseguito il metodo efault e overriden - e questo è semplice, hai solo bisogno di un'istanza.
  • una delle risposte - utilizzando il metodo handle - funziona solo con brutto hack (imho) come modificare i modificatori di accesso ai campi della classe di ricerca, che è la stessa categoria di "soluzioni" come questa: Change private static final field using Java reflection: è buono per so che è possibile, ma non lo userei in produzione - Sto cercando un modo "ufficiale" per farlo.

Il IllegalAccessException è gettato 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

non è un duplicato di questo http://stackoverflow.com/questions/22614746/how-do-i-invoke-java-8-default-methods-refletively –

+2

"* sto cercando per un modo "ufficiale" di farlo * "Potrei sbagliarmi, ma temo che ufficialmente non si debba essere in grado di invocare il metodo da supertipo se il sottotipo lo ha sovrascritto. Diciamo che il tuo supertipo ha il metodo 'acceptSquare' che può accettare qualsiasi Quadrato, ma il tuo sottotipo è * specializzato * nella gestione dei soli quadrati rossi, quindi lo sostituisce di conseguenza per aggiungere un test per il colore (dopodiché richiama' super.addSquare'). Quindi permettere a qualcuno di invocare dall'esterno l'implementazione da supertipo (anche tramite riflessione) di tale metodo potrebbe essere un grosso buco di sicurezza. – Pshemo

+0

Che ne dici di mixin - l'aggiunta di funzionalità a una classe esistente utilizzando un proxy dinamico? Ad esempio, ho un'istanza e voglio aggiungere funzionalità aggiuntive "aggiungendo" interfacce con metodi predefiniti all'istanza in fase di runtime. Deve esserci un modo –

risposta

4

Se si utilizza una classe impl concreto come lookupClass e chiamante per l'invokeSpecial si deve richiamare correttamente l'implementazione predefinita di l'interfaccia (non è necessario alcun trucco per l'accesso privato):

Example target = new Example(); 
... 

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

Questo ovviamente funziona solo se si ha un riferimento a un oggetto concreto che implementa l'interfaccia.

Modifica: questa soluzione funzionerà solo se la classe in questione (Esempio nel codice sopra), è privata accessibile dal codice del chiamante, ad es. una classe interiore anonima.

L'implementazione corrente della classe MethodHandles/Lookup non consentirà di chiamare invokeSpecial su qualsiasi classe che non è privata accessibile dalla classe del chiamante corrente. Sono disponibili vari work-around, ma tutti richiedono l'uso della reflection per rendere accessibili costruttori/metodi, che probabilmente falliranno nel caso in cui sia installato un SecurityManager.

+1

hm, questo funziona anche con classi anonime ... ma dato, I don ' Se conosco le interfacce prima (cioè perché è un parametro), potrei creare istanze di classi anonime in modo dinamico? (il proxy sembra non funzionare, la generazione del codice byte non è nemmeno un'opzione) –

+0

Cosa significa "ex" in questo esempio? Deve essere un'istanza di 'Esempio' qui, quindi perché non usare solo l'istanza' target'? Oltre a ciò, ha la stessa restrizione dell'uso di 'ExampleMixin' direttamente; funziona solo se la classe specificata ha una relazione di classe interna con il codice circostante (il codice che richiama 'lookup()'). – Holger

+0

Questo è stato un errore, ex dovrebbe essere effettivamente sostituito dal bersaglio. –

1

Usa:

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

Ho provato che mi ha dato 'Caused by: java.lang.IllegalAccessException: nessun accesso privato per invokespecial: interface example.IExample, da example.IExample/package' –

1

Se tutto ciò che si ha è un'interfaccia e tutto ciò a cui si ha accesso è un oggetto di classe è un'interfaccia che estende l'interfaccia di base e si desidera chiamare il metodo predefinito senza un'istanza reale di una classe che implementa l'interfaccia , è possibile:

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

Creare un'istanza dell'interfaccia e quindi costruire MethodHandles.Ricerca utilizzando la riflessione:

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

e quindi utilizzare tale lookupConstructor per creare una nuova istanza della vostra interfaccia che permetterà l'accesso privato a invokespecial. Quindi invoca il metodo sul proxy falso target creato in precedenza.

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

MethodHandles.Lookup fornisce "accesso illecito", come riportato sopra . – user1135940

Problemi correlati