Статьи

Как создать и очистить кэш справочных данных с помощью одноэлементных EJB, Ehcache и MBeans

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

Простая реализация кэша справочных данных с 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)
@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) кэша.

Инициализация Ehcache

01
02
03
04
05
06
07
08
09
10
11
@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 начинаются с создания 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
@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:

Запустить обновление кэша через ресурс 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 для методов очистки в этом случае …