Add field to Proxy class created with Javassist

2.7k views Asked by At

I am creating a Proxy class using Javassist ProxyFactory with the following code:

ProxyFactory factory = new ProxyFactory();
factory.setSuperclass(entity.getClass());
factory.setInterfaces(new Class[] { MyCustomInterface.class });
.....
Class clazz = factory.createClass();
Object result = clazz.newInstance();

The problem is that I also need to add a field to the class. But if I do CtClass proxy = ClassPool.getDefault().get(clazz.getName()); it gaves a NotFoundException

How can I add a field the class created with createClass? Is there a better way to do what I am trying to do?

1

There are 1 answers

7
pabrantes On BEST ANSWER

This is based in your reply to my comment.

You can indeed use MyCustomInterface and your proxyClass to create sort of a mixin in Java. But you'll still have to cast from proxy class to MyCustomInterface to be able to call the methods.

Let's get started.

Creating your proxy

First you create your proxy has you already were doing:

 // this is the code you've already posted
 ProxyFactory factory = new ProxyFactory();
 factory.setSuperclass(entity.getClass());
 factory.setInterfaces(new Class[] { MyCustomInterface.class });

Method handler: Doing the magic

Javassist proxies allow you to add a MethodHandler. It basically acts has a an InvocationHandler would in a regular Java Proxy, that means it works as a method interceptor.

The method handler will be your mixin! First you create a new MethodHandler with the custom field you actually want to add to the class, along with the entity object you've started proxying:

  public class CustomMethodHandler implements MethodHandler {

    private MyEntity objectBeingProxied;
    private MyFieldType myCustomField;

    public CustomMethodHandler(MyEntity entity) {
       this.objectBeingProxied = entity;
    }

    // code here with the implementation of MyCustomInterface
    // handling the entity and your customField

    public Object invoke(Object self, Method method, Method proceed, Object[] args) throws Throwable {
          String methodName = method.getName();

          if(methodNameFromMyCustomInterface(methodName)) {
            // handle methodCall internally: 
            // you can either do it by reflection
            // or if needed if/then/else to dispatch
            // to the correct method (*) 
          }else {
             // it's just a method from entity let them
             // go. Notice we're using proceed not method!

             proceed.invoke(objectBeingProxied,args);
          }
    }
  }

(*) Notice that even if I say in the comment to handle the call internally you can have the interface implementation in another place that isn't your method handler and just call it from here.

Getting everything together

ProxyFactory factory = new ProxyFactory();
factory.setSuperclass(entity.getClass());
factory.setInterfaces(new Class[] { MyCustomInterface.class });
Class cls = factory.createClass();

// bind your newly methodHandler to your proxy
((javassist.util.proxy.Proxy) cls).setHandler(new CustomMethodHandler(entity));
EntityClass proxyEntity = cls.newInstance();

You should now be able to do ((MyCustomInterface)proxyEntity).someMethodFromTheInterface() and let it be handled by your CustomMethodHandler

Summing up

  • You create a proxy using Proxy Factory from javassist
  • You create your own MethodHandler class that can receive your proxied entity and the field you want to operate
  • You bind the methodHandler to your proxy so you can delegate interface implementation

Keep in mind that these approach isn't perfect, being one of the short comings the code in Entity class cannot refer to the interface unless you first create the proxy.

If anything wasn't very clear for you, just comment and I'll do my best to clarify you.