How to create a default constructor with Byte Buddy

I want to intercept some method calls on one of my classes, but these classes do not have a default constructor.

Given the following class, how would I configure Byte Buddy to create a public constructor with no arguments in order to be able to create the generated class?

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: A specific use case for this is to simplify unit test setup.
Currently, we always have to write something like this:

 @Test public void someTest() { // Given // When GetLoggedInUsersRequest request = new GetLoggedInUsersRequest(); setMessageForContext(request); // <-- always do this before calling handle sut.handle(request); // Then } 

I thought it would be nice to create a proxy in the @Before method, which automatically sets the context for you.

 @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 } 

I played a little, but, unfortunately, I did not work.

 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(); } 

Unfortunately, now I get (possibly because the ctor call is still not configured correctly)

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

Is this even the right approach?
Should I use bytes here or is there an easier / different way?

+6
source share
1 answer

You cannot define a constructor without a bytecode. This would be an abstract constructor, which is illegal in Java. I am going to add a more accurate javadoc description for a future version. Thank you for drawing this to my attention.

You need to define the super method call that is required for any constructor:

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

As for what you should use Byte Buddy here: I can't tell you from the little code that I saw. The question you should ask is: does my code simplify, given the amount of code and the complexity of following it? If Byte Buddy makes code easier to use (and starts), use it. If not, do not use it.

+3
source

All Articles