Статьи

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

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