В одном из моих проектов у меня было требование загружать справочные данные из нескольких источников в среде Java EE 6 WebLogic с EclipseLink в качестве платформы ORM . Поскольку я не мог найти аннотацию в мире Java EE, сравнимую со сладким @Cacheable из Spring YET, мне пришлось написать свое «собственное» решение для кэширования. Хотя справочные данные практически не меняются с течением времени, еще одним требованием была возможность очистки кеша от внешних источников. Так что здесь идет …
1. Кеш
Предполагалось, что это будет кэш только для чтения с возможностью очистки его извне. Я хотел, чтобы кэш был своего рода оберткой для службы, предоставляющей фактические справочные данные для приложения — стиль AOP с кодом!
1.1. Интерфейс
Простой интерфейс кеша для справочных данных
01
02
03
04
05
06
07
08
09
10
11
12
13
|
@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. Реализация
Простая реализация кэша справочных данных с @Singleton
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
|
@ConcurrencyManagement (ConcurrencyManagementType.CONTAINER) @Singleton public class ReferenceDataCacheBean implements ReferenceDataCache { private static final String ALL_REFERENCE_DATA_KEY = "ALL_REFERENCE_DATA" ; private ConcurrentHashMap<String, Object> refDataCache = null ; @EJB ReferenceDataService referenceDataService; @PostConstruct public void initialize(){ this .refDataCache = new ConcurrentHashMap<>(); } @Override @Lock (LockType.READ) public ReferenceData getReferenceData() { if (refDataCache.containsKey(ALL_REFERENCE_DATA_KEY)){ return refDataCache.get(ALL_REFERENCE_DATA_KEY); } else { ReferenceData referenceData = referenceDataService.getReferenceData(); refDataCache.put(ALL_REFERENCE_DATA_KEY, referenceData); return referenceData; } } @Override public void evictAll() { refDataCache.clear(); } .......... } |
Замечания:
-
@Singleton
— наверное, самая важная строка кода в этом классе. Эта аннотация указывает, что в приложении будет ровно один синглтон этого типа компонента. Этот бин может быть вызван одновременно несколькими потоками. Он также поставляется с аннотацией@PostConstruct
. Эта аннотация используется для метода, который должен быть выполнен после внедрения зависимости для выполнения любой инициализации — в нашем случае это инициализация «кэша» (карта хеша) -
@ConcurrencyManagement(ConcurrencyManagementType.CONTAINER)
объявляет тип управления параллелизмом одноэлементного сессионного компонента. По умолчанию установлено значение «Container
. Я использую его здесь только для того, чтобы подчеркнуть его существование. Другой параметрConcurrencyManagementType.BEAN
указывает, что разработчик компонента отвечает за управление одновременным доступом к экземпляру компонента. - фактический «кеш» — это
ConcurrentHashMap
который имеет ключи на основеString
и хранитObject
s. Это держится в памяти из-за синглтоновой природы бобов -
@EJB
ReferenceDataService
— это@Stateless
@EJB
который за кулисами собирает справочные данные из разных источников - Реализация метода getReferenceData () очень проста: он проверяет, есть ли в
ConcurrentHashMap
запись с ключом String, указанным как константа «ALL_REFERENCE_DATA
». Если это так, это будет извлечено из памяти, в противном случае будет загружен компонент службы. -
@Lock(LockType.READ)
указывает тип блокировки параллелизма для одноэлементных компонентов с параллелизмом, управляемым контейнером. Когда установлено значениеLockType.READ
, он применяет метод для разрешения полного одновременного доступа к нему (при условии, что блокировки записи не удерживаются). Это именно то, что я хотел, так как мне нужно только выполнять операции чтения. Другой более консервативный параметр@Lock(LockType.WRITE)
, который,@Lock(LockType.WRITE)
, является DEFAULT, обеспечивает исключительный доступ к экземпляру компонента. Это должно замедлить метод в среде с высокой степенью параллелизма … - метод
evictAll()
просто удаляет все элементы из хэш-карты.
2. Очистка кеша
Вторая часть этого поста будет посвящена возможностям очистки кеша. Поскольку реализация кэша является корпоративным Java-бином, мы можем вызвать его из MBean или, почему нет, из веб-службы.
2.1. MBean
Если вы новичок в Java Management Extensions (JMX), который представляет собой технологию Java, которая предоставляет инструменты для управления и мониторинга приложений, системных объектов, устройств (например, принтеров) и сервис-ориентированных сетей. Эти ресурсы представлены объектами, называемыми MBeans (для Managed Bean) , я настоятельно рекомендую вам начать с этого учебного курса: Java Management Extensions (JMX)
2.1.1. Интерфейс
Представленный метод позволит только сброс кеша через JMX:
CacheRest MBean
1
2
3
4
|
@MXBean public interface CacheResetMXBean { void resetReferenceDataCache(); } |
«MXBean — это тип MBean, который ссылается только на предопределенный набор типов данных. Таким образом, вы можете быть уверены, что ваш MBean будет использоваться любым клиентом, включая удаленные клиенты, без каких-либо требований, чтобы клиент имел доступ к классам, зависящим от модели, представляющим типы ваших MBean. MXBeans предоставляют удобный способ связать связанные значения вместе, не требуя, чтобы клиенты были специально настроены для обработки пакетов ». [4]
2.1.2. Реализация
MBean реализация CacheReset
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 @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
функциональность@PostConstruct
. Здесь этот bean-компонент зарегистрирован в JMX, прежде чем проверять, используется лиObjectName
для его удаления, если это так…
2.2. Вызов службы отдыха
Я также встроил возможность очищать кеш, вызывая ресурс REST. Это происходит, когда вы выполняете HTTP POST в (rest-context) / reference-data / flush-cache:
Остальные вызовы в кеш справочных данных
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, в которой развернуто приложение, при изменении справочных данных.
Ну вот и все. В этом посте мы узнали, как создать простой кэш с аннотациями Java EE. Конечно, вы можете легко расширить функциональность кэша, чтобы предложить более детальный доступ / очистку кешированных объектов. Не забудьте использовать LockType.WRITE
для методов очистки в этом случае …
Ссылка: | Как создать и очистить кэш справочных данных с помощью одноэлементных EJB-компонентов и MBeans от нашего партнера по JCG Адриана Матеи в блоге Codingpedia.org . |