2015-07-07 16 views
6

Voglio intercettare alcune chiamate di metodo su una delle mie classi, ma quelle classi non hanno un costruttore predefinito.Come creare un costruttore predefinito con Byte Buddy

Data la classe seguente, come configurare Byte Buddy per creare anche un costruttore pubblico senza argomenti per essere in grado di creare la classe generata?

public class GetLoggedInUsersSaga extends AbstractSpaceSingleEventSaga { 
    private final UserSessionRepository userSessionRepository; 

    @Inject 
    public GetLoggedInUsersSaga(final UserSessionRepository userSessionRepository) { 
     this.userSessionRepository = userSessionRepository; 
    } 

    @StartsSaga 
    public void handle(final GetLoggedInUsersRequest request) { 
     // this is the method in want to intercept 
    } 
} 

EDIT: Il caso d'uso concreto per questo è di semplificare l'installazione unit test.
Attualmente abbiamo sempre di scrivere qualcosa del genere:

@Test 
public void someTest() { 
    // Given 

    // When 
    GetLoggedInUsersRequest request = new GetLoggedInUsersRequest(); 
    setMessageForContext(request); // <-- always do this before calling handle 
    sut.handle(request); 

    // Then 
} 

ho pensato che sarebbe stato bello creare un proxy nel metodo @Before che imposta automaticamente il contesto per voi.

@Before 
public void before() { 
    sut = new GetLoggedInUsersSaga(someDependency); 
    sut = intercept(sut); 
} 

@Test 
public void someTest() { 
    // Given 

    // When 
    GetLoggedInUsersRequest request = new GetLoggedInUsersRequest(); 
    sut.handle(request); 

    // Then 
} 

ho giocato un po 'intorno, ma purtroppo mi hanno dato che funziona ..

public <SAGA extends Saga> SAGA intercept(final SAGA sagaUnderTest) throws NoSuchMethodException, IllegalAccessException, InstantiationException { 
    return (SAGA) new ByteBuddy() 
      .subclass(sagaUnderTest.getClass()) 
      .defineConstructor(Collections.<Class<?>>emptyList(), Visibility.PUBLIC) 
      .intercept(MethodCall.invokeSuper()) 
      .method(ElementMatchers.isAnnotatedWith(StartsSaga.class)) 
      .intercept(
        MethodDelegation.to(
          new Object() { 
           @RuntimeType 
           public Object intercept(
             @SuperCall Callable<?> c, 
             @Origin Method m, 
             @AllArguments Object[] a) throws Exception { 
            setMessageForContext((Message) a[0]); 
            return c.call(); 
           } 
          })) 
      .make() 
      .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER) 
      .getLoaded() 
      .newInstance(); 
} 

Purtroppo ora ottengo (probabilmente perché l'invocazione ctor non è ancora impostato correttamente)

java.lang.IllegalStateException: Cannot invoke public com.frequentis.ps.account.service.audit.GetLoggedInUsersSaga$ByteBuddy$zSZuwhtR() as a super method 

È questo l'approccio corretto?
Devo anche usare l'amico del byte qui o c'è un modo più semplice/alternativo?

risposta

3

Non è possibile definire un costruttore senza alcun codice byte. Questo sarebbe un costruttore astratto che cosa è illegale in Java. Ho intenzione di aggiungere una descrizione più precisa alla javadoc per una versione futura. Grazie per avermelo fatto notare.

è necessario definire una chiamata di metodo eccellente che è richiesto per qualsiasi costruttore:

DynamicType.Builder builder = ... 
builder = builder 
    .defineConstructor(Collections.<Class<?>>emptyList(), Visibility.PUBLIC) 
    .intercept(MethodCall 
       .invoke(superClass.getDeclaredConstructor()) 
       .onSuper()) 

Quanto a wheather si dovrebbe usare Byte amici qui: non posso dire dal po 'di codice che ho visto. La domanda che dovresti fare: rende il mio codice più semplice, considerando sia la quantità di codice che la complessità di seguirlo? Se Byte Buddy rende il tuo codice più facile da usare (ed eseguire), usalo. In caso contrario, non usarlo.

+0

Grazie per la risposta, ho modificato la mia domanda per mostrare anche il caso concreto di utilizzo per questo. Forse hai ancora un paio di minuti per spiegare cosa sto facendo male. – leozilla

+0

Jikes. Dovrei leggere il mio * javadoc *. È necessario specificare il costruttore da chiamare e fornire argomenti se richiesti. In questo modo, Byte Buddy non ha bisogno di indovinare. Puoi anche dare un'occhiata al 'MethodCallTest' per gli esempi. –

+0

Non capisco. Hai la possibilità di chiamare il classloader predefinito se i classloader bytebuddy non sono adatti. A mio avviso, non dovrebbe esserci assolutamente alcun motivo per cui non può semplicemente scrivere la sua stessa classe, prendendo di mira lo stesso pacchetto in cui vuole costruire una nuova classe usando quei metodi, e solo fare in modo che la sua nuova classe li chiami. Quindi potrebbe costruire un costruttore nella sua nuova classe e fare ogni genere di cose divertenti. Lo scopo di un codegen dovrebbe essere la flessibilità se hai bisogno di cambiare classe, ma non dovrebbe essere limitato così tanto che non può fare default java thing.s. – Xype