Статьи

Использование SelfPopulationCache в Ehcache

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

В идеальном мире я бы ожидал, что кеш будет универсальным пулом. Я запрашиваю значение для ключа и хотел бы получить его, когда это возможно и максимально быстро, с помощью всего одной строки кода. Но в действительности я обычно вижу огромный код заполнения кэша при запуске приложения, потоки демонов, которые обновляют кэши в вечном цикле, и многочисленные «ifs» вокруг вызовов cache.get ().

Стремясь к идеальному миру, я обнаружил SelfPopulationCacheкласс в Ehcache. В этой статье я опишу, как с помощью класса SelfPopulationCache можно реализовать самосоздание с необязательным автоматическим обновлением. В некотором смысле этот пример является реализацией идей, упомянутых в документации Ehcache .

пример

Что вы увидите здесь:

  • Читатель, который выбирает объект из кэша 5 раз каждые 0,5 секунды.
  • Кэш за кулисами создаст новый объект, если такого объекта нет в кеше для запрошенного ключа.
  • Поток демона будет запускать обновление кэша каждые 2 секунды.

Самый важный класс в этом примере — ExampleCacheProvider.

package com.blogspot.mikler.java;

import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.constructs.blocking.SelfPopulatingCache;
import net.sf.ehcache.constructs.blocking.CacheEntryFactory;

public class ExampleCacheProvider {
private CacheManager cacheManager;
private CacheEntryFactory updatingFactory;
public SelfPopulatingCache selfPopulatingCache;

public ExampleCacheProvider() throws Exception {
cacheManager = CacheManager.create();
Ehcache originalCache = cacheManager.getCache("com.blogspot.mikler.java.cache");

final String cacheType = System.getProperty("com.blogspot.mikler.java.cache.factory");
if (cacheType == null || cacheType.equals("create")){
updatingFactory = new ExampleCacheEntryFactory();
} else {
updatingFactory = new ExampleUpdatingCacheEntryFactory();
}
selfPopulatingCache = new SelfPopulatingCache(originalCache, updatingFactory);
//chache refresh thread
Thread updatingThread = new Thread(){
public void run() {
super.run();
while (true){
System.out.println("!!!!! Doing refresh !!!!!");
selfPopulatingCache.refresh();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
System.out.println("Interrupted");
}
}
}
};
updatingThread.setDaemon(true);
updatingThread.start();

}

public Ehcache getCache(){
return selfPopulatingCache;
}
}

Он создает простой Ehcache (оригинальный кеш) и оборачивает его в SelfPopulationCache (selfPopulationCache). Мы можем использовать этот класс так же, как CacheManager, поэтому можно рассмотреть возможность расширения CacheManager, но для простоты здесь мы будем придерживаться такого рода источника кеша.
Во время переноса мы указываем updateFactory, для которого можно задать один из двух параметров: ExampleCacheEntryFactory (строка 19) и ExampleUpdatingCacheEntryFactory (строка 21). Для целей этого примера выбор между этими двумя опциями выполняется на основе значения системного свойства com.blogspot.mikler.java.cache.factory. Разница между этими двумя заключается в том, что ExampleCacheEntryFactory реализует интерфейс CacheEntryFactory, тогда как ExampleUpdatingCacheEntryFactory расширяет ExampleCacheEntryFactory и реализует UpdatingCacheEntryFactory. Разница между использованием каждого из этих вариантов объясняется позже. Между тем вот код обоих классов.

package com.blogspot.mikler.java;

import net.sf.ehcache.constructs.blocking.CacheEntryFactory;

public class ExampleCacheEntryFactory implements CacheEntryFactory {

public Object createEntry(Object key) throws Exception {
System.out.println("++++++creating entry for key = " + key);
return new StringBuffer(Long.toString(Math.round(100*Math.random())) + key+"0");
}
}

ExampleUpdatingCacheEntryFactory:

package com.blogspot.mikler.java;

import net.sf.ehcache.constructs.blocking.UpdatingCacheEntryFactory;

public class ExampleUpdatingCacheEntryFactory
extends ExampleCacheEntryFactory
implements UpdatingCacheEntryFactory
{
public void updateEntryValue(Object key, Object value) throws Exception {
System.out.println("~~~~~~UPDATING entry for key = " + key);
final StringBuffer stringBuffer = (StringBuffer) value;
stringBuffer.append(stringBuffer.length());
}

}

Как видно из этого примера, в качестве ключевой строки используется элемент кэша, а в качестве значения используется StringBuffer. В методе createEntry () в ExampleCacheEntryFactory StringBuffer создается с начальным случайным числом. В то время как в методе updateEntryValue () класса ExampleUpdatingCacheEntryFactory существующая длина StringBuffer добавляется к самому буферу.

И, наконец, вот наш основной класс Reader. Он извлекает наш обернутый кеш, получает из него значение для ключа «foo» и сохраняет его в конечной локальной переменной fooOriginalBuffer, которая никогда не изменяется поздно в коде Reader. Затем он начинает делать 5 итераций, получая значение для ключа «foo» из кэша, отображая отладочную информацию и статистику, и спя в течение одной секунды. Код прост как это.

package com.blogspot.mikler.java;

import net.sf.ehcache.Ehcache;

public class Reader {
private ExampleCacheProvider exampleCacheProvider;

public Reader(ExampleCacheProvider exampleCacheProvider) {
this.exampleCacheProvider = exampleCacheProvider;
}

public void run(){
Ehcache cache = exampleCacheProvider.getCache();
final StringBuffer fooOriginalBuffer = (StringBuffer) cache.get("foo").getValue();
for (int i = 0; i < 5; i++){
System.out.println("----------------------------------");
System.out.println("Starting iteration " + i);
StringBuffer fooBuffer = (StringBuffer) cache.get("foo").getValue();
System.out.println("fooBuffer. = " + fooBuffer.toString());
System.out.println("fooOriginalBuffer = " + fooOriginalBuffer.toString());
System.out.println("cache.getSize() = " + cache.getSize());
System.out.println("----------------------------------");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("Interrupted");
}
}
}

public static void main(String[] args) throws Exception {
ExampleCacheProvider cacheProvider = new ExampleCacheProvider();
Reader reader = new Reader(cacheProvider);
reader.run();
}
}

Давайте запустим его с обеими опциями updateFactory.
Результат запуска Reader с ExampleCacheEntryFactory (-Dcom.blogspot.mikler.java.cache.factory = create)

!!!!! Doing refresh !!!!!
++++++creating entry for key = foo
----------------------------------
Starting iteration 0
fooBuffer. = 35foo0
fooOriginalBuffer = 35foo0
cache.getSize() = 1
----------------------------------
----------------------------------
Starting iteration 1
fooBuffer. = 35foo0
fooOriginalBuffer = 35foo0
cache.getSize() = 1
----------------------------------
!!!!! Doing refresh !!!!!
++++++creating entry for key = foo
----------------------------------
Starting iteration 2
fooBuffer. = 36foo0
fooOriginalBuffer = 35foo0
cache.getSize() = 1
----------------------------------
----------------------------------
Starting iteration 3
fooBuffer. = 36foo0
fooOriginalBuffer = 35foo0
cache.getSize() = 1
----------------------------------
!!!!! Doing refresh !!!!!
++++++creating entry for key = foo
----------------------------------
Starting iteration 4
fooBuffer. = 51foo0
fooOriginalBuffer = 35foo0
cache.getSize() = 1
----------------------------------

А вот как выглядит вывод при запуске Reader с ExampleUpdatingCacheEntryFactory как updateFactory (-Dcom.blogspot.mikler.java.cache.factory = update)

!!!!! Doing refresh !!!!!
++++++creating entry for key = foo
----------------------------------
Starting iteration 0
fooBuffer. = 19foo0
fooOriginalBuffer = 19foo0
cache.getSize() = 1
----------------------------------
----------------------------------
Starting iteration 1
fooBuffer. = 19foo0
fooOriginalBuffer = 19foo0
cache.getSize() = 1
----------------------------------
!!!!! Doing refresh !!!!!
~~~~~~UPDATING entry for key = foo
----------------------------------
Starting iteration 2
fooBuffer. = 19foo06
fooOriginalBuffer = 19foo06
cache.getSize() = 1
----------------------------------
----------------------------------
Starting iteration 3
fooBuffer. = 19foo06
fooOriginalBuffer = 19foo06
cache.getSize() = 1
----------------------------------
!!!!! Doing refresh !!!!!
~~~~~~UPDATING entry for key = foo
----------------------------------
Starting iteration 4
fooBuffer. = 19foo067
fooOriginalBuffer = 19foo067
cache.getSize() = 1
----------------------------------

Вывод

Как вы можете видеть, результат совершенно другой, и вот некоторые выводы, к которым мы пришли, проанализировав его:

  • При попытке получить объект из кэша, которого там нет, не возникает исключение NullPointerException. Он создается в обоих случаях.
  • Если updateFactory является экземпляром CacheEntryFactory (в нашем случае ExampleCacheEntryFactory), когда cache.refresh () вызывается, каждый объект в кэше воссоздается.
  • Также, когда updateFactory является экземпляром CacheEntryFactory, конечная переменная fooOriginalBuffer не обновляется.
  • Между тем, в случае, когда updateFactory является экземпляром CacheEntryFactory (в нашем случае UpdatingCacheEntryFactory) при вызове cache.refresh () каждый объект в кэше обновляется, а не воссоздается.
  • И конечное значение переменной fooOriginalBuffer также обновляется. (На самом деле эта переменная сама передается в метод updateEntryValue () ExampleUpdatingCacheEntryFactory)

Помимо приведенного выше заключения, можно заметить, что использование SelfPopulationCache уменьшает разброс и запутывание (см. Мою
статью « WTF is AOP »).

Как обычно, вы можете получить исходный код для этого поста, запустив

svn co http://miklerjava.googlecode.com/svn/trunk/samples/SelfUpdatingEhCache SelfUpdatingEhCache