Reputation: 1372
In my library, I'm generating implementations of client-provided interfaces (annotated with custom directives from the library). I use MethodDelegation
to intercept interface methods and forward them to an instance of a delegate class defined in a library package:
package library.pkg;
class ImplBase { }
public class ImplDelegate {
final ImplContext context;
ImplDelegate(ImplContext ctx) {
this.context = ctx;
}
public void impl(
@CustomName String name,
@CustomTags String[] tags,
@AllArguments Object[] args) {
// do things here
}
}
static <T> T implClient(Class<T> clientType) {
MethodDelegation delegation = MethodDelegation
.to(new ImplDelegate(new ImplContext(clientType)))
.filter(not(isDeclaredBy(Object.class)))
.appendParameterBinder(ParameterBinders.CustomTags.binder)
.appendParameterBinder(ParameterBinders.CustomName.binder);
Class<? extends ImplBase> implClass =
new ByteBuddy()
.subclass(ImplBase.class)
.name(String.format("%s$Impl$%d", clientType.getName(), id++))
.implement(clientType)
.method(isDeclaredBy(clientType).and(isVirtual()).and(returns(VOID)))
.intercept(delegation)
.make()
.load(clientType.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
.getLoaded();
return clientType.cast(implClass.newInstance());
}
// In client code, get an instance of the interface and use it.
package client.pkg;
interface Client {
void operationA(String p1, long p2);
void operationB(String... p1);
}
Client client = implClient(Client.class);
client.operationA("A", 1);
This works, but it exposes ImplDelegate
as a public type from the library; I'd rather have it stay package-private. One way of doing this would be to generate a public subclass of ImplDelegate
in the library package at runtime that proxies all package-private methods with public bridge methods and use that as the delegate. I've looked at TypeProxy
but I'm not familiar enough with ByteBuddy yet to see if the auxiliary type mechanism is a good fit for this.
Is there a way to generate runtime proxies that implement bridge methods in a way so I can hide delegate implementations?
Upvotes: 1
Views: 252
Reputation: 44042
The delegate type needs to be visible to the class that is invoking it. You have only two possibilities:
At runtime, subclass your interceptor and make sure all interceptor methods are public. Byte Buddy, by default, generates a public subclass:
Object delegate = new ByteBuddy()
.subclass(ImplDelegate.class)
.make()
.load(ImplDelegate.class.getClassLoader())
.getLoaded()
.newInstance();
The above type will be public such that you can now delegate to this instance, even if ImplDelegate
is package-private. Note however, that this only affects compile-time visibility, at runtime, the subclass of ImplDelegate
is visible to any type. (The constructor does however remain package-private, even for the subclass.)
Upvotes: 1