В этой статье я расскажу, как создать простой кэш справочных данных в Java EE, используя одноэлементные EJB и Ehcache . Кеш самопроизвольно сбрасывается через определенный промежуток времени и может быть очищен «вручную» путем вызова конечной точки REST или метода MBean. Этот пост фактически основан на предыдущем посте Как создать и очистить кэш справочных данных с помощью одноэлементных EJB и MBeans ; единственное отличие состоит в том, что вместо хранения данных в ConcurrentHashMap<String, Object> я буду использовать кеш Ehcache, и кеш может обновляться с помощью средств Ehcache.
1. Кеш
Предполагалось, что это будет кэш только для чтения с возможностью очистки его извне. Я хотел, чтобы кэш был своего рода оберткой для службы, предоставляющей фактические справочные данные для приложения — стиль AOP с кодом!
1.1. Интерфейс
Простой интерфейс для справочных данных
|
01
02
03
04
05
06
07
08
09
10
11
12
13
|
@Localpublic interface ReferenceDataCache { /** * Returns all reference data required in the application */ ReferenceData getReferenceData(); /** * evict/flush all data from cache */ void evictAll();} |
Функциональность кэширования определяет два простых метода:
-
getReferenceData()— который кэширует справочные данные, собранные за кулисами из разных источников -
evictAll()— метод, вызываемый для полной очистки кэша
1.2. Реализация
Простая реализация кэша справочных данных с Ehcache
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
@ConcurrencyManagement(ConcurrencyManagementType.CONTAINER)@Singletonpublic 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) кэша.
Инициализация Ehcache
|
01
02
03
04
05
06
07
08
09
10
11
|
@PostConstructpublic 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 начинаются с создания CacheManager , который является контейнером для Ehcache , поддерживающим все аспекты их жизненного цикла. Я использую метод CacheManager.create() , который является фабричным методом для создания одноэлементного CacheManager с конфигурацией по умолчанию или возвращает его, если он существует:
|
1
|
cacheManager = CacheManager.create(); |
Затем я построил объект CacheConfiguration имя кэша («referenceDataCache») и количество максимального количества элементов в памяти ( maxEntriesLocalHeap ) до их выселения (0 == без ограничений), и, наконец, я установил количество времени по умолчанию для элемента с даты его создания:
|
1
|
CacheConfiguration cacheConfiguration = new CacheConfiguration("referenceDataCache", 1000); cacheConfiguration.setTimeToLiveSeconds(CACHE_MINUTES_TO_LIVE * 60); |
Теперь с помощью объекта CacheConfiguration я программно создаю свой кеш справочных данных и добавляю его в CacheManager. Обратите внимание, что кеши нельзя использовать, пока они не добавлены в CacheManager:
|
1
|
refDataEHCache = new Cache(cacheConfiguration ); cacheManager.addCache(refDataEHCache); |
Примечание. Вы также можете создавать кэши декларативным способом: при создании CacheManager создаются кэши, найденные в конфигурации. Вы можете создать CacheManager, указав путь к файлу конфигурации, из конфигурации в classpath, из конфигурации в InputStream или в файле ehcache.xml по умолчанию в вашем classpath. Взгляните на примеры кода Ehcache для получения дополнительной информации.
1.2.2. Получить данные из кеша
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
@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; } } |
Сначала я пытаюсь получить элемент из кэша, основываясь на его ключе, и если он присутствует в кэше ( ==null ), то если он будет получен из класса обслуживания и помещен в кэш для будущих запросов.
Замечания:
@Lock(LockType.READ) указывает тип блокировки параллелизма для одноэлементных компонентов с параллелизмом, управляемым контейнером. Когда установлено значение LockType.READ , он применяет метод для разрешения полного одновременного доступа к нему (при условии, что блокировки записи не удерживаются). Это именно то, что я хотел, так как мне нужно только выполнять операции чтения. Другой более консервативный параметр @Lock(LockType.WRITE) , который, @Lock(LockType.WRITE) , является DEFAULT, обеспечивает исключительный доступ к экземпляру компонента. Это должно замедлить метод в среде с высокой степенью параллелизма …
1.2.3. Очистить кеш
Очистить кэш
|
1
|
@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:
|
1
|
@MXBean public interface CacheResetMXBean { void resetReferenceDataCache(); } |
«MXBean — это тип MBean, который ссылается только на предопределенный набор типов данных. Таким образом, вы можете быть уверены, что ваш MBean будет использоваться любым клиентом, включая удаленные клиенты, без каких-либо требований, чтобы клиент имел доступ к классам, зависящим от модели, представляющим типы ваших MBean. MXBeans предоставляют удобный способ связать связанные значения вместе, не требуя, чтобы клиенты были специально настроены для обработки пакетов ». [5]
2.1.2. Реализация
Реализация CacheReset MxBean
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
@Singleton@Startuppublic 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функциональность@PostConstruct. Здесь этот bean-компонент зарегистрирован в JMX, прежде чем проверять, используется лиObjectNameдля его удаления, если это так…
2.2. Вызов службы отдыха
Я также встроил возможность очищать кеш, вызывая ресурс REST. Это происходит, когда вы выполняете HTTP POST в (rest-context) / reference-data / flush-cache:
Запустить обновление кэша через ресурс REST
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
@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 для методов очистки в этом случае …
| Ссылка: | Как создать и очистить кэш справочных данных с помощью одноэлементных EJB, Ehcache и MBeans от нашего партнера по JCG Адриана Матея в блоге Codingpedia.org . |