В этой статье я расскажу, как создать простой кэш справочных данных в 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
, который является контейнером для Ehcache
s, который поддерживает все аспекты их жизненного цикла. Я использую 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
для методов очистки в этом случае …