Добавить поле в прокси-класс, созданный с помощью Javassist

Я создаю класс Proxy, используя Javassist ProxyFactory со следующим кодом:

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

Проблема в том, что мне также нужно добавить поле в класс. Но если я сделаю CtClass proxy = ClassPool.getDefault().get(clazz.getName()); это дает NotFoundException

Как я могу добавить поле класса, созданного с помощью createClass? Есть ли лучший способ сделать то, что я пытаюсь сделать?

1 ответ

Решение

Это основано на вашем ответе на мой комментарий.

Вы действительно можете использовать MyCustomInterface и ваш proxyClass для создания своего рода миксина в Java. Но вам все равно придется привести из класса прокси к MyCustomInterface чтобы иметь возможность вызывать методы.

Давайте начнем.

Создание вашего прокси

Сначала вы создаете свой прокси, который вы уже делали:

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

Обработчик метода: магия

Прокси Javassist позволяют вам добавить MethodHandler. Он в основном действует как InvocationHandler в обычном Java Proxy, что означает, что он работает как метод-перехватчик.

Обработчик метода будет вашим миксином! Сначала вы создаете новый MethodHandler с настраиваемым полем, которое вы действительно хотите добавить в класс, вместе с объектом сущности, который вы начали проксировать:

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

(*) Обратите внимание, что даже если я скажу в комментарии для внутренней обработки вызова, вы можете получить реализацию интерфейса в другом месте, которое не является вашим обработчиком метода, и просто вызвать его отсюда.

Собираем все вместе

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

Теперь вы должны быть в состоянии сделать ((MyCustomInterface)proxyEntity).someMethodFromTheInterface() и пусть это будет обработано вашим CustomMethodHandler

Подводя итоги

  • Вы создаете прокси с помощью Proxy Factory из javassist
  • Вы создаете свой собственный класс MethodHandler, который может получать вашу прокси-сущность и поле, с которым вы хотите работать
  • Вы связываете methodHandler с вашим прокси, чтобы вы могли делегировать реализацию интерфейса

Имейте в виду, что эти подходы не идеальны, так как код в классе Entity не может ссылаться на интерфейс, пока вы сначала не создадите прокси.

Если что-то не очень понятно для вас, просто прокомментируйте, и я сделаю все возможное, чтобы разъяснить вам.

Другие вопросы по тегам