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

Я создаю прокси-класс с помощью 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? Есть ли лучший способ сделать то, что я пытаюсь сделать?


person Fernando    schedule 08.12.2013    source источник
comment
Похоже, он не предназначен для управления этими прокси-классами. В этом поле не будет кода. Если вам нужен нетривиальный класс, используйте фабрику классов.   -  person Holger    schedule 09.12.2013
comment
Будет ли поле использоваться методами, представленными в вашем MyCustomInterface?   -  person pabrantes    schedule 10.12.2013
comment
Именно это поле будет использоваться методами в MyCustomInterface. AFAIK java не позволяет объявлять поле экземпляра в интерфейсах.   -  person Fernando    schedule 12.12.2013


Ответы (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, что означает, что он работает как перехватчик метода.

Обработчик метода будет вашим миксином! Сначала вы создаете новый 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 не может ссылаться на интерфейс, если вы сначала не создадите прокси.

Если вам что-то не совсем понятно, просто прокомментируйте, и я постараюсь уточнить вас.

person pabrantes    schedule 12.12.2013
comment
Большое спасибо! Это была отличная идея .... Просто упомяну, что MethodHandler - это интерфейс, а не класс, поэтому мне пришлось реализовать MethodHandler (не расширяет) - person Fernando; 17.12.2013
comment
Привет, Фернандо, рад, что смог помочь! Извините за несоответствие MethodHandler, я писал это из головы, компилятор тут не помог ;-) Я отредактирую сообщение, чтобы исправить эту деталь. - person pabrantes; 17.12.2013
comment
Если бы вы могли писать все это просто из головы, вы гений (надеюсь, не сумасшедший) - person Fernando; 17.12.2013
comment
@Fernando: Я не гений, и хотя некоторые называют меня сумасшедшим, я обычно не согласен, мне нравится видеть себя творческим человеком ;-) В своей работе я уже создал инструменты, которые полагаются на Javassist, и я также делаю много JVM voodoo, так что такие вещи, о которых вы спрашиваете, являются частью моей повседневной работы ... Вот почему это было не в моей голове. - person pabrantes; 18.12.2013
comment
@pabrantes: У меня одна проблема с Hibernate Envers, у меня одна база данных со старым регистром, поэтому, когда я пытаюсь просмотреть свой журнал, он генерирует ошибку, потому что не нашел регистр, но отлаживаю в Eclipse внутри этих объектов, Я нашел обработчик метода и внутри поля id с длинным номером id, поэтому я хочу знать, возможно ли получить это поле? Я получил класс обработчика в одном объекте. Но не знаю, как получить поле id. Я попытался поразмыслить, но мне это не помогло. - person Diego Macario; 12.04.2014
comment
@DiegoMacario: Здравствуйте, Диего, может быть, вам стоит попробовать опубликовать вопрос, чтобы получить лучшую помощь. Насколько я понял, у вас есть объект, реализующий MethodHandler и имеющий поле, к которому вы хотите получить доступ, не так ли? Путем отражения вы должны иметь возможность получить его, помните, что вы не хотите проходить через MethodHandler, а скорее через класс объекта (выполните getClass ()), и имейте в виду, что при отражении вам может потребоваться беспокоиться о getFields vs getDeclaredFields и иерархия, если существует. - person pabrantes; 14.04.2014
comment
@pabrantes спасибо за вашу поддержку, да решил мою проблему. Это произошло потому, что Hibernate Envers был реализован в базе данных со старыми регистрами. Но не так, как вы предложили, потому что я могу получить доступ к прокси-объекту путем отражения, я не знал почему, потому что итерация в полях, методах и классах, но ничего, как я хотел. По этой ссылке я ответил [ссылка] (stackoverflow.com/questions/22896124/). Обригадо! - person Diego Macario; 14.04.2014