Reputation: 1218
I am looking at some ByteBuddy code from someone else. He uses ByteBuddy to generate runtime subclasses which are used as proxies to implement some management code of his runtime into specific objects.
Class<? extends T> newSubClass = new ByteBuddy(ClassFileVersion.ofThisVm())
.subclass(classType)
.defineField("_core", Object.class, Visibility.PUBLIC) //<---
.method(ElementMatchers.isDeclaredBy(classType))
.intercept(InvocationHandlerAdapter.of((proxy, method, m_args) -> {
//TODO: Need to replace core with _core as core is a function argument and will make it bound
return proxyHandler(core, method, m_args); //<--
}))
.make()
.load(roleType.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
.getLoaded();
T proxy = ReflectionHelper.newInstance(newSubClass, args);
newSubClass.getField("_core").set(proxy, core);
In order to not bind the core
object directly into the lambda I want to use the new defined field _core
so I can reuse the generated class (and not regenerate it for every call of the function).
Is there a way to achieve this?
Thanks in advance.
Upvotes: 1
Views: 1644
Reputation: 43972
You can define custom constructors just as you define methods. One important point for defining a constructor is that you require another constructor call as its first instruction. You can invoke a constructor using MethodCall::invoke
which you can combine with FieldAccessor::ofField
.
This way, you can define your class similar to the following:
new ByteBuddy(ClassFileVersion.ofThisVm())
.subclass(classType, ConstructorStrategy.Default.NO_CONSTRUCTORS)
.defineConstructor(Visibility.PUBLIC)
.withParameter(InvocationHandler.class)
.intercept(MethodCall.invoke(classType.getDeclaredConstructor())
.andThen(FieldAccessor.ofField("_core").setsArgumentAt(0)))
.defineField("_core", InvocationHandler.class, Visibility.PUBLIC)
.method(ElementMatchers.isDeclaredBy(classType))
.intercept(InvocationHandlerAdapter.toField("_core"))
.make();
This way, you can set a custom InvocationHandler
per instance. If you want to only store the state in the _core
field and access this field from your interceptor, have a look at MethodDelegation
:
new ByteBuddy(ClassFileVersion.ofThisVm())
.subclass(classType, ConstructorStrategy.Default.NO_CONSTRUCTORS)
.defineConstructor(Visibility.PUBLIC)
.withParameter(Object.class)
.intercept(MethodCall.invoke(classType.getDeclaredConstructor())
.andThen(FieldAccessor.ofField("_core").setsArgumentAt(0)))
.defineField("_core", Object.class, Visibility.PUBLIC)
.method(ElementMatchers.isDeclaredBy(classType))
.intercept(MethodDelegation.to(MyHandler.class))
.make();
public class MyHandler {
@RuntimeType
public static Object intercept(@FieldValue("_core") Object value) { ... }
}
Other annotations you might need are @This
, @AllArguments
, @Origin
and @SuperCall
. The less you need, the more efficient your proxy will be. Especially @AllArguments
is expensive due to its allocation requirements.
Note that in this case, your field is only set after the super constructor call. Also, you assume that there is a default constructor in the super type. Alternatively, you can implement a custom ConstructorStrategy
.
As for caching, have a look at Byte Buddy's TypeCache
.
Upvotes: 2