В этой статье я расскажу, как создать простой кэш справочных данных в Java EE, используя одноэлементные EJB и Ehcache . Кеш самопроизвольно сбрасывается через определенный промежуток времени и может быть очищен «вручную» путем вызова конечной точки REST или метода MBean. Этот пост фактически основан на предыдущем посте Как создать и очистить кэш справочных данных с помощью одноэлементных EJB и MBeans ; единственное отличие состоит в том, что вместо хранения данных в ConcurrentHashMap<String, Object>я буду использовать кеш Ehcache, а кеш может обновляться с помощью средств Ehcache.
1. Кеш
Предполагалось, что это будет кэш только для чтения с возможностью очистки его извне. Я хотел, чтобы кэш был своего рода оберткой для службы, предоставляющей фактические справочные данные для приложения — стиль AOP с кодом
1.1. Интерфейс
@Local
public interface ReferenceDataCache {
/**
* Returns all reference data required in the application
*/
ReferenceData getReferenceData();
/**
* evict/flush all data from cache
*/
void evictAll();
}
Функциональность кэширования определяет два простых метода:
getReferenceData()— который кэширует справочные данные, собранные за кулисами из разных источниковevictAll()— метод вызывается для полной очистки кэша
1.2. Реализация
@ConcurrencyManagement(ConcurrencyManagementType.CONTAINER)
@Singleton
public class ReferenceDataCacheBean implements ReferenceDataCache {
private static final String ALL_REFERENCE_DATA_KEY = "ALL_REFERENCE_DATA";
private static final int CACHE_MINUTES_TO_LIVE = 100;
private CacheManager cacheManager;
private Cache refDataEHCache = null;
@EJB
ReferenceDataLogic referenceDataService;
@PostConstruct
public void initialize(){
cacheManager = CacheManager.getInstance();
CacheConfiguration cacheConfiguration = new CacheConfiguration("referenceDataCache", 1000);
cacheConfiguration.setTimeToLiveSeconds(CACHE_MINUTES_TO_LIVE * 60);
refDataEHCache = new Cache(cacheConfiguration );
cacheManager.addCache(refDataEHCache);
}
@Override
@Lock(LockType.READ)
public ReferenceData getReferenceData() {
Element element = refDataEHCache.get(ALL_REFERENCE_DATA_KEY);
if(element != null){
return (ReferenceData) element.getObjectValue();
} else {
ReferenceData referenceData = referenceDataLogic.getReferenceData();
refDataEHCache.putIfAbsent(new Element(ALL_REFERENCE_DATA_KEY, referenceData));
return referenceData;
}
}
@Override
public void evictAll() {
cacheManager.clearAll();
}
...........
}
Замечания:
@Singleton— вероятно, самая важная строка кода в этом классе. Эта аннотация указывает, что в приложении будет ровно один синглтон этого типа компонента. Этот бин может быть вызван одновременно несколькими потоками.
Давайте теперь разберем код на разные части:
1.2.1. Инициализация кэша
@PostConstructАннотаций используется на метод , который должен быть выполнен после инъекции зависимостей делается, чтобы выполнить инициализацию в нашем случае — это создать и инициализировать (Eh) кэш.
@PostConstruct
public void initialize(){
cacheManager = CacheManager.create();
CacheConfiguration cacheConfiguration = new CacheConfiguration("referenceDataCache", 1000);
cacheConfiguration.setTimeToLiveSeconds(CACHE_MINUTES_TO_LIVE * 60);
refDataEHCache = new Cache(cacheConfiguration );
cacheManager.addCache(refDataEHCache);
}
Примечание. С помощью этой аннотации можно аннотировать только один метод.
Все случаи использования Ehcache начинаются с создания a CacheManager, который является контейнером для Ehcaches, который поддерживает все аспекты их жизненного цикла. Я использую CacheManager.create()метод, который является фабричным методом для создания одноэлементного CacheManager с конфигурацией по умолчанию, или возвращаю его, если он существует:
cacheManager = CacheManager.create();
Затем я построил CacheConfigurationобъект, указав имя кэша («referenceDataCache») и количество максимального количества элементов в памяти ( maxEntriesLocalHeap), до того, как они будут выселены (0 == без ограничений), и, наконец, я установил значение по умолчанию. количество времени жизни для элемента с даты его создания:
CacheConfiguration cacheConfiguration = new CacheConfiguration("referenceDataCache", 1000);
cacheConfiguration.setTimeToLiveSeconds(CACHE_MINUTES_TO_LIVE * 60);
Теперь с помощью объекта CacheConfiguration я программно создаю свой кеш справочных данных и добавляю его в CacheManager. Обратите внимание, что кеши нельзя использовать, пока они не добавлены в CacheManager:
refDataEHCache = new Cache(cacheConfiguration ); cacheManager.addCache(refDataEHCache);
Примечание. Вы также можете создавать кэши декларативным способом: при создании CacheManager создаются кэши, найденные в конфигурации. Вы можете создать CacheManager, указав путь к файлу конфигурации, из конфигурации в classpath, из конфигурации в InputStream или путем использования файла ehcache.xml по умолчанию в вашем classpath. Взгляните на примеры кода Ehcache для получения дополнительной информации.
1.2.2. Получить данные из кеша
@Override
@Lock(LockType.READ)
public ReferenceData getReferenceData() {
Element element = refDataEHCache.get(ALL_REFERENCE_DATA_KEY);
if(element != null){
return (ReferenceData) element.getObjectValue();
} else {
ReferenceData referenceData = referenceDataLogic.getReferenceData();
refDataEHCache.put(new Element(ALL_REFERENCE_DATA_KEY, referenceData));
return referenceData;
}
}
Сначала я пытаюсь получить элемент из кэша, основываясь на его ключе, и, если он присутствует в cache ( ==null), то если он будет получен из класса обслуживания и помещен в кэш для будущих запросов.
Примечание
. @Lock(LockType.READ) Указывает тип блокировки параллелизма для одноэлементных компонентов с управляемым контейнером параллелизмом. Когда установлено значение LockType.READ, он применяет метод для разрешения полного одновременного доступа к нему (при условии, что блокировки записи не удерживаются). Это именно то, что я хотел, так как мне нужно только выполнять операции чтения. Другой более консервативный вариант @Lock(LockType.WRITE), который, кстати, является DEFAULT, обеспечивает исключительный доступ к экземпляру компонента. Это должно замедлить метод в среде с высокой степенью параллелизма …
1.2.3. Очистить кеш
@Override
public void evictAll() {
cacheManager.clearAll();
}
clearAll()Метод CacheManager очищает содержимое всех кэшей в CacheManager, но без удаления каких — либо тайников. Я просто использовал это здесь для простоты, и потому что у меня есть только один кеш, мне нужно обновить.
Примечание. Если у вас есть несколько кешей, то есть несколько кеш-имен, и вы хотите очистить только один, вам нужно использовать тот CacheManager.clearAllStartingWith(String prefix), который очищает содержимое всех кешей в CacheManager с именем, начинающимся с префикса, но не удаляя их.
2. Как вызвать сброс кеша
Вторая часть этого поста будет посвящена возможностям очистки кеша. Поскольку реализация кэша является корпоративным Java-бином, мы можем вызвать его из MBean или, почему нет, из веб-службы.
2.1. MBean
Если вы новичок в Java Management Extensions (JMX), который является технологией Java, которая предоставляет инструменты для управления и мониторинга приложений, системных объектов, устройств (например, принтеров) и сервис-ориентированных сетей. Эти ресурсы представлены объектами, называемыми MBeans (для Managed Bean) , я настоятельно рекомендую вам начать с этого учебного курса: Java Management Extensions (JMX)
2.1.1. Интерфейс
Представленный метод позволит только сброс кеша через JMX:
@MXBean
public interface CacheResetMXBean {
void resetReferenceDataCache();
}
«MXBean — это тип MBean, который ссылается только на предопределенный набор типов данных. Таким образом, вы можете быть уверены, что ваш MBean будет использоваться любым клиентом, включая удаленные клиенты, без каких-либо требований, чтобы клиент имел доступ к классам, зависящим от модели, представляющим типы ваших MBean. MXBeans предоставляют удобный способ связать связанные значения вместе, не требуя, чтобы клиенты были специально настроены для обработки пакетов ». [5]
2.1.2. Реализация
@Singleton
@Startup
public class CacheReset implements CacheResetMXBean {
private MBeanServer platformMBeanServer;
private ObjectName objectName = null;
@EJB
ReferenceDataCache referenceDataCache;
@PostConstruct
public void registerInJMX() {
try {
objectName = new ObjectName("org.codingpedia.simplecacheexample:type=CacheReset");
platformMBeanServer = ManagementFactory.getPlatformMBeanServer();
//unregister the mbean before registerting again
Set<ObjectName> existing = platformMBeanServer.queryNames(objectName, null);
if(existing.size() > 0){
platformMBeanServer.unregisterMBean(objectName);
}
platformMBeanServer.registerMBean(this, objectName);
} catch (Exception e) {
throw new IllegalStateException("Problem during registration of Monitoring into JMX:" + e);
}
}
@Override
public void resetReferenceDataCache() {
referenceDataCache.evictAll();
}
}
Замечания:
- как уже упоминалось, реализация вызывает только
evictAll()метод внедренного синглтон-компонента, описанный в предыдущем разделе - боб также определяется как
@Singleton @Startupаннотации вызывают боб быть обработаны с помощью контейнера при запуске приложения — нетерпеливый инициализации- Я снова использую
@PostConstructфункциональность. Здесь этот bean-компонент зарегистрирован в JMX, прежде чем проверять,ObjectNameиспользуется ли его для удаления, если это так…
2.2. Вызов службы отдыха
Я также встроил возможность очищать кеш, вызывая ресурс REST. Это происходит, когда вы выполняете HTTP POST в (rest-context) / reference-data / flush-cache:
@Path("/reference-data")
public class ReferenceDataResource {
@EJB
ReferenceDataCache referenceDataCache;
@POST
@Path("flush-cache")
public Response flushReferenceDataCache() {
referenceDataCache.evictAll();
return Response.status(Status.OK).entity("Cache successfully flushed").build();
}
@GET
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
public Response getReferenceData(@QueryParam("version") String version) {
ReferenceData referenceData = referenceDataCache.getReferenceData();
if(version!=null && version.equals(referenceData.getVersion())){
return Response.status(Status.NOT_MODIFIED).entity("Reference data was not modified").build();
} else {
return Response.status(Status.OK)
.entity(referenceData).build();
}
}
}
Обратите внимание на наличие параметра запроса версии в @GET getReferenceData(...) методе. Это представляет собой хэш справочных данных, и если он не был изменен, клиент получит статус HTTP 304 Not Modified . Это хороший способ сэкономить пропускную способность, особенно если у вас есть мобильные клиенты. См. Мой пост Tutorial — Проектирование и реализация REST API на Java с Джерси и Спрингом , где подробно обсуждается проектирование и реализация сервисов REST.
Примечание. В кластерной среде необходимо вызывать метод resetCache (…) для каждой JVM, в которой развернуто приложение, при изменении справочных данных.
Ну вот и все. В этом посте мы узнали, как с помощью Ehcache создать простой кэш справочных данных в Java EE. Конечно, вы можете легко расширить функциональность кэша, чтобы предложить более детальный доступ / очистку кешированных объектов. Не забудьте использовать LockType.WRITE для методов очистки в этом случае …
