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?
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
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. –
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