Мой план - написать структуру кеширования на основе аннотаций, которая кэширует возвращаемые значения методов. Когда метод вызывается в первый раз с определенным параметром, кеш должен хранить возвращаемое значение метода. Когда тот же метод вызывается второй раз с тем же параметром, тогда метод должен возвращать ранее вычисленный результат из кеша и больше не выполнять свой код. Мои аннотации выглядят так:

@Cached(cacheProvider = HashMapCacheProvider.class)
public Product getProduct(String productId){    
    // Scraping the product from a website ...
    return product;
}

На данный момент мой маленький фреймворк уже работает нормально. Я использую Javassist для создания прокси-объектов классов, содержащих аннотированные методы. Чтобы создать новый кешированный объект, я использую этот код:

public static <T> T newCachedInstance(Class<T> clazz)
    throws InstantiationException, IllegalAccessException {

    ProxyFactory factory = new ProxyFactory();
    factory.setSuperclass(clazz);
    factory.setFilter(new MethodFilter() {
        public boolean isHandled(Method m) {
            // ignore finalize()
            return !m.getName().equals("finalize");
        }
    });

    Class<T> c = factory.createClass();

    T proxy = c.newInstance();
    ((ProxyObject) proxy).setHandler(new CachedMethodHandler());
    return proxy;
}

Проблема в том, что я могу просто создавать новые кешированные объекты этим методом, а не конструктором их классов. Поэтому я ищу решение прикрепить уже существующие объекты к механизму кеширования.

И вот мой вопрос: можно ли прикрепить прокси к существующему объекту? Насколько я понимаю, это невозможно без обновления всех существующих ссылок на этот объект.

Другой мой подход - использовать манипуляции с байт-кодом для управления кодом аннотированных методов следующим образом:

public class Hello {
    public void say() {
         System.out.println("Hello");
    }
}

public class Test {
    public static void main(String[] args) throws Exception {
        ClassPool cp = ClassPool.getDefault();
        CtClass cc = cp.get("Hello");
        CtMethod m = cc.getDeclaredMethod("say");
        m.insertBefore("{ System.out.println(\"Hello.say():\"); }");
        Class c = cc.toClass();
        Hello h = (Hello)c.newInstance();
        h.say();
    }
}

Есть ли у вас другие идеи? Как лучше всего манипулировать методами существующих объектов?

6
eztam 17 Янв 2014 в 20:06

3 ответа

Лучший ответ

В виртуальных машинах Java по умолчанию каждый экземпляр объекта хранится в куче, где хранятся его данные поля вместе со ссылкой на его Class (и небольшую область, используемую для сборки мусора). Вы в основном спрашиваете, можете ли вы переопределить эту ссылку, чтобы указать на другой Class, что невозможно по умолчанию.

Однако вы можете гипотетически использовать sun.misc.Unsafe для переопределения этой ссылки с помощью подкласса до тех пор, пока этот подкласс не вводит новые поля. Однако результат этого не определен, и я бы не рекомендовал экспериментировать с этим, поскольку пользователи вашего фреймворка могут столкнуться с очень тонкими ошибками. Кроме того, иерархия пакетов sun не предназначена для публичного использования, что может нарушить совместимость.

Другой способ предложит Attach API. Вы можете переопределить классы во время выполнения с помощью агента Java. Однако это повлияет на все экземпляры класса, но с вашей целью это будет иметь смысл.

Другая возможность - использовать что-то вроде AspectJ для переопределения классов перед запуском.

В противном случае вам нужно вернуть новый экземпляр, который работает как прокси для кеша, поскольку вы, по-видимому, уже это делаете. Это абсолютно нормально, и это приложение используется основными фреймворками, такими как Hibernate. Обратите внимание, что javassist значительно медленнее, чем, например, cglib, потому что он читает файлы классов напрямую, а не использует отражающий доступ, чтобы избежать загрузки класса. Это может снизить производительность при использовании кеша.

2
Rafael Winterhalter 18 Янв 2014 в 10:31

https://github.com/verhas/djcproxy утверждает, что это делает. К сожалению, в нем задокументирован недостаток конструкции, делающий его небезопасным для потоков.

https://javax0.wordpress.com/2016/ 02/03 / create-proxy-object-using-djcproxy / говорит:

Реализация имеет несколько потоков (*), например, поздние экземпляры прокси метода не имеют никакого преимущества, но в то же время могут повредить в случае многопоточного выполнения прокси.

(*) недостатки

ИЗМЕНИТЬ Кроме того, djcproxy работает медленно.

1
18446744073709551615 21 Июн 2016 в 11:41

Если вы можете заменить все ссылки на объект, который вы хотите сделать проксированным, в частности, если объект уже существует, но есть только одна ссылка на него, вы можете сделать следующее:

  1. получить класс объекта и создать прокси-класс с помощью Javassist;
  2. создать экземпляр этого класса (предполагается, что существует конструктор без аргументов)
  3. скопируйте все поля в этот новый экземпляр
  4. заменить исходную ссылку (ссылки) на ссылку (ссылки) на новый объект

Если есть конструктор копирования, вы можете использовать его для создания прокси-копии существующего объекта. (Вам нужно будет определить это как X$Proxy(X x) {super(x);})

1
18446744073709551615 20 Июн 2016 в 14:32