Cagatay Cagatay - 1 month ago 9
Java Question

How can I make delegate class non-public when delegating methods of an interface in another package?

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?

Answer

The delegate type needs to be visible to the class that is invoking it. You have only two possibilities:

  1. Create a type in the same package as the interceptor. Make sure you are injecting the generated class in the interceptor's class loader, a package-private type is only visible to classes of the same package in the same class loader. This way, you can however only implement public interfaces.
  2. 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.)

Comments