Java Cache 入门

什么是缓存

术语缓存在计算机中无处不在。在应用程序设计的上下文中,它经常被用来描述应用程序开发人员利用单独的内存或低延迟的数据结构。缓存,用于临时存储或缓存信息的副本或引用,应用程序可能会在稍后的某个时间点重复使用,从而减轻重新访问或重新创建它的成本。

在 JCache 的上下文中,术语缓存描述了 Java 的技术开发人员使用缓存提供程序临时缓存 Java 对象。

JSR107(JCache)

JCache 是 Java 的缓存 API。它由 JSR107 定义。它定义了供开发人员使用的标准 Java 缓存 API 和供实现者使用的标准 SPI(“服务提供者接口”)。

它为 Java 提供了通用的方法来创建,访问,更新和删除缓存中的条目(entries)。

标准定义文档

JCache 核心概念

Java 的缓存 API 定义了五个核心接口:CachingProviderCacheManagerCacheEntryExpiryPolicy

CachingProvider 定义了建立,配置,获取,管理和控制零个或多个 CacheManager 的机制。应用程序可以在运行期间访问和使用零个或多个 CachingProvider

CacheManager 定义在上下文中了建立,配置,获取,管理和控制零个或多个唯一命名的缓存的机制。 CacheManager 被单个 CachingProvider 拥有。

Cache 是一个像 Map 一样的数据结构,它允许基于 Key 的临时储存。缓存被单个 CacheManager 拥有。

Entry 是被 Cache 存储的单个 key-value 对,JCache 允许我们定义按值或按引用来存储条目。

每一个被 Cache 存储的 entry 都定义了持续时间,被称作过期持续时间,在这个时间内他们可以访问,更新和删除。一旦这个过期时间过去,该条目就被认为是过期了。一旦过期,就不能再访问,更新和删除条目,就像他们从未在缓存中存在。过期是使用 ExpiryPolicy 来设置的。

JCache 还为我们定义了缓存条目监听器的接口,我们可以注册条目不同事件的监听器在运行期或配置阶段,通过 CacheEntryListenerConfiguration 注册 CacheEntryListener

我们也可以配置启用和禁用管理和统计信息通过使用 MXBean:

  • setManagementEnabled(boolean enabled) 启用管理
  • setStatisticsEnabled(boolean enabled) 启用统计

按值存储和按引用存储

条目由每个缓存使用两种机制之一存储。

  • 默认的机制,称为值存储。指示实现使副本应用程序在将它们储存在 Cache 中之前提供键和值,之后在访问缓存时再返回新的条目副本。复制条目的目的是存储在缓存和从缓存中再次返回时应用程序继续变异键和值的状态,不会对缓存持有的条目造成副作用。

    一个简单的方法实现可用于制作键和值的副本是 Java 序列化。

  • 替代和可选机制,称为引用存储。指示 Cache 实现简单地存储和返回对应用程序提供的键和值的引用,而不是按照按值存储方法的要求制作副本。如果应用程序稍后使用引用存储语义来改变提供给缓存的键或值,则突变的副作用对于从缓存访问条目的人来说是可见的,而应用程序不必更新缓存。

    对于在 Java 堆上实现的缓存,引用存储是最快的存储技术。

从 JCache 到 EhCache

使用 JCache 可以完成大部分的缓存操作,但是如果需要使用到 EhCache 的特性,则必须使用提供给程序的特定 API。比如需要用到持久化目录等。下图是 EhCache 的架构图:

Image result for ehcache architecture diagram

使用 EhCache 作为 JCache 供应商

JCache 作为标准有很多的实现,我们这里主要以 EhCache 来展开学习,要想使用 EhCache 作为 JCache 的供应商很简单,我们只要将 EhCache 的 jar 包和 JCache 放在一起便可以使用。

通过 JCache 的接口,我们便可以操控缓存:

CachingProvider provider = Caching.getCachingProvider();  
CacheManager cacheManager = provider.getCacheManager();   
MutableConfiguration<Long, String> configuration =
    new MutableConfiguration<Long, String>()  
        .setTypes(Long.class, String.class)   
        .setStoreByValue(false)   
        .setExpiryPolicyFactory(CreatedExpiryPolicy.factoryOf(Duration.ONE_MINUTE));  
Cache<Long, String> cache = cacheManager.createCache("jCache", configuration); 
cache.put(1L, "one"); 
String value = cache.get(1L); 

JCache 会从应用程序的类路径中检索默认的 CachingProvider 实现。但这必须保证类路径上只有一个 JCache 实现,否则必须指定具体的供应商名称,可以使用设置系统变量 javax.cache.spi.CachingProvider 或通过调用 Caching.getCachingProvider(String) 静态方法指定完整的类名称。

上述代码中,我们获取到缓存的供应器,并从中得到一个默认的 CacheManager 实例,使用不可用的配置类设置了键和值的类型,条目的存储类型,以及过期方案来生成一个名称为 JCache 的缓存对象,并向缓存中添加了一组数据,并使用键获取值。

JCache 提供了一组最小配置,非常适合内存缓存。但是 EhCache 在拓扑和功能方面提供了更强大的实现。

从 JCache 配置访问基础 Ehcache 配置

当以使用 JCache 创建了配置时,你任然可以访问底层的 EhCache CacheRuntimeConfiguration。

MutableConfiguration<Long, String> configuration = new MutableConfiguration<>();
configuration.setTypes(Long.class, String.class);
Cache<Long, String> cache = cacheManager.createCache("someCache", configuration); 

CompleteConfiguration<Long, String> completeConfiguration = cache.getConfiguration(CompleteConfiguration.class); 

Eh107Configuration<Long, String> eh107Configuration = cache.getConfiguration(Eh107Configuration.class); 

CacheRuntimeConfiguration<Long, String> runtimeConfiguration = eh107Configuration.unwrap(CacheRuntimeConfiguration.class);

我们还可以使用 Ehcache CacheConfiguration 创建 JCache 缓存。

EhCache 不总是同意 JCache 的默认行为。两者在默认值上存在一定的差异。

使用编程的方式配置 EhCache

我们可以使用编程配置或 XML 来配置 EhCache,个人比较喜欢以编程的形式配置大多数不是很容易变动的配置,通过使用提供流畅 API 的构建器我们可以很容易完成配置。

CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder() 
    .withCache("preConfigured",
        CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class, ResourcePoolsBuilder.heap(10))) 
    .build(); 
cacheManager.init(); 

Cache<Long, String> preConfigured =
    cacheManager.getCache("preConfigured", Long.class, String.class); 

Cache<Long, String> myCache = cacheManager.createCache("myCache", 
    CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class, ResourcePoolsBuilder.heap(10)));

myCache.put(1L, "da one!"); 
String value = myCache.get(1L); 

cacheManager.removeCache("preConfigured"); 

cacheManager.close(); 

这里的配置很清晰明了,具体配置我们可以查看配置参数查看。

EhCache 存储层

我们可以配置 Ehcache 以使用各种数据存储区域。当缓存配置为使用多个存储区域时,这些区域将按层排列和管理。它们按层次结构组织,最低层(更远)称为权限层,其他层则是缓存层的一部分(更近,也称为近缓存)。缓存层本身可以由多个存储区域组成。最热门的数据保存在缓存层中,这通常不如权限级别那么丰富但速度更快。所有数据都保存在权限层中,这更慢但更丰富。

Ehcache 支持的数据存储包括:

  • 堆内存储 - 利用 Java 的堆内 RAM 内存来存储缓存条目。此层使用与 Java 应用程序相同的堆内存,所有这些都必须由 JVM 垃圾收集器扫描。JVM 使用的堆空间越多,应用程序性能就越会受到垃圾收集暂停的影响。这个存储非常快,但通常是您最有限的存储资源。

  • 堆外存储 - 大小受限于可用的 RAM 内存。不受 Java 垃圾收集(GC)的限制。速度非常快,但比堆内存储慢,因为数据必须在存储和重新访问时移入和移出 JVM 堆。

  • 磁盘存储 - 利用磁盘(文件系统)来存储缓存条目。这种类型的存储资源通常非常丰富,但比基于 RAM 的存储要慢得多。对于使用磁盘存储的所有应用程序,建议使用快速专用磁盘来优化吞吐量。

  • 集群存储 - 此数据存储是远程服务器上的缓存。远程服务器可以可选地提供具有高可用性的故障转移服务器。由于集群存储会因网络延迟以及建立客户端/服务器一致性等因素而导致性能下降,这个层本质上比本地堆外存储慢。

    EhcacheTerminology

用户管理的缓存

用户管理的缓存为我们提供了一种直接配置缓存的简单方法,而无需设置或使用 CacheManager 的复杂性。是否使用 UserManagedCache 而不是 CacheManager 的选择通常取决于我们是否需要 CacheManager 的所有内置功能。如果缓存要求相对简单,并且不需要CacheManager的全部功能,则可以考虑使用UserManagedCache。

使用 UserManagedCache 的典型方案有:方法本地缓存,线程本地缓存或缓存生命周期短于应用程序生命周期的任何其他位置。

使用介绍

Hibernate 缓存

数据库抽象层(如ORM(对象 - 关系映射)框架)的一个优点是它们能够透明地缓存从底层存储中检索的数据。这有助于消除频繁访问数据的数据库访问成本。

在运行时,Hibernate处理将数据移入和移出二级缓存以响应 Session 执行的操作,该操作充当持久数据的事务级缓存。一旦实体被管理,该对象就会被添加到当前持久化上下文(EntityManager 或 Session)的内部缓存中。持久性上下文也称为第一级缓存,默认情况下已启用。

可以在 class-by-class 逐个类和 collection-by-collection 逐个集合的基础上配置 JVM 级别(SessionFactory 级别)甚至是集群缓存,二级缓存是 SessionFactory 域的,意味着它由使用相同会话工厂创建的所有会话共享。

Hibernate 缓存不了解其他应用程序对持久性存储所做的更改。比如 EhCache 管理的缓存。要解决此限制,可以在二级缓存区域级别配置 TTL(生存时间)保留策略,以便底层缓存条目定期到期。

Hibernate 可以与各种缓存提供程序集成,以便在特定会话的上下文之外缓存数据。

Hibernate 二级缓存条目默认是使用哪种键存储方式呢?

StrategySelectorBuilder 策略选择器构建器,添加了两个默认的缓存键工厂类的策略实现。

private void addCacheKeysFactories(StrategySelectorImpl strategySelector) {
   strategySelector.registerStrategyImplementor(
      CacheKeysFactory.class,
      DefaultCacheKeysFactory.SHORT_NAME,
      DefaultCacheKeysFactory.class
   );
   strategySelector.registerStrategyImplementor(
      CacheKeysFactory.class,
      SimpleCacheKeysFactory.SHORT_NAME,
      SimpleCacheKeysFactory.class
   );
}

在第一个 “default” 工厂类我们可以看到,它为实体,集合都是使用 CacheKeyImplementation 来生成键,也就是使用值存储,在放入前生成键和值。

第二个 “simple” 工厂类在为对象和集合生成键时直接引用传递过来的对象,也就是使用引用存储的形式。

RegionFactory

org.hibernate.cache.spi.RegionFactory 定义了 Hibernate 与可插拔缓存提供程序之间的集成。Hibernate 二级缓存设计为让我们感觉不到所使用的实际缓存,SPI(服务提供者接口)为缓存实现者定义了接口,Hibernate 只需要提供 org.hibernate.cache.spi.RegionFactory 接口的实现,该接口封装了特定于实际缓存提供者的所有细节。基本上,它充当 Hibernate 和缓存提供者之间的桥梁。Hibernate 内置支持 Java 缓存标准 JCache 以及两个流行的缓存库:EhcacheInfinispan

  • 我们可以通过定义 hibernate.cache.region.factory_class 来指定具体提供商。

  • hibernate 还提供了其他的配置,比如是否启用二级缓存,hibernate.cache.use_second_level_cache,默认二级缓存是关闭的,如果配置了 RegionFacoty 且不是 NoCachingRegionFactory时,二级缓存默认启用。

  • hibernate.cache.use_query_cache:是否启用查询缓存,默认关闭。

  • hibernate.cache.query_cache_factory:查询结果缓存由特殊的规则处理,该规则定义了数据的实效策略。默认实现是不失效,适合用于对数据要求松散的应用。或者我们可以指定自己的规则。

  • hibernate.cache.auto_evict_collection_cache:当关联仅从拥有方改变时,启用或禁用双向关联的集合缓存条目的自动驱逐。默认情况下禁用该选项,因为它会跟踪此状态对性能产生影响。但是,如果您的应用程序不管理高速缓存集合端的双向关联的双方,则替代方法是在该集合缓存中使用陈旧数据。

    使用陈旧数据时很可能会出现数据不一致的情况,比如实体存在于缓存中但不存在于数据库中,在做级联删除时,会报 EntityNotFoundException 异常,可以在级联删除的集合上配置 @NotFound(action = NotFoundAction.IGNORE) 忽略该异常。

  • hibernate.cache.use_reference_entries:允许将实体引用直接存储到二级缓存中,以用于只读或不可变实体。使用引用存储缓存的好处是我们不用显式的维护缓存,缓存更新时直接反映给调用者。缺点是该缓存只可用于只读状态,否则对象逸出,会很难控制。

配置二级缓存映射

可以通过 JPA 注解或 XML 描述符或使用特定于 Hibernate 的映射文件来配置缓存映射。默认情况下,实体不是二级缓存的一部分,我们可以通过配置 javax.persistence.sharedCache.mode 来映射缓存。该配置有四个选项:

  • ENABLE_SELECTIVE:默认也是推荐的选项。除非明确标记为可缓存(使用 @Cacheable 注解),否则不会缓存实体。
  • DISABLE_SELECTIVE:除非明确标记为不可缓存 @Cachable 注解 value 设置为 false,否则实体将被缓存。
  • ALL:即使标记为不可缓存,实体也始终被缓存。
  • NONE:即使标记为可缓存,也不会缓存任何实体。此选项可以完全禁用二级缓存。

默认情况下使用的缓存并发策略可以通过以下方式全局设置 hibernate.cache.default_cache_concurrency_strategy 来配置属性,配置选项如下:

  • read-only:如果您的应用程序需要读取但不修改持久化类的实例,则只读缓存是最佳选择。应用程序仍然可以删除实体,这些更改应该反映在二级缓存中,以便缓存不提供过时的实体。实现可以基于实体的不变性来使用性能优化。

  • read-write:如果应用程序需要更新数据,则读写缓存可能是合适的。此策略提供对单个实体的一致访问,但不提供可序列化的事务隔离级别;实现可以基于实体的不变性来使用性能优化。

  • nonstrict-read-write:与读写策略类似,但在并发访问实体时可能偶尔会出现过时读取。如果应用程序很少同时更新相同的数据并且不需要严格的事务隔离,则可以选择此策略。实现可以使用利用宽松一致性保证的性能优化。

  • transactional:提供可序列化的事务隔离级别。

建议不要使用全局设置,而是基于每个实体定义缓存并发策略。为此,请使用 @ org.hibernate.annotations.Cache 注解。

该注解有三个属性:

  • usage:定义 CacheConcurrencyStrategy。
  • region:定义存储条目的缓存区域。对于每个实体类,Hibernate 将使用单独的缓存区域来存储该类的实例状态。区域名称是完全限定的类名。
  • include:如果(lazy)延迟属性应包含在二级缓存中。默认值是所有延迟属性都可缓存。另一个可能的值是非延迟的,因此延迟属性不可缓存。

实体继承和二级缓存映射

传统上,当使用实体继承时,Hibernate 要求实体层次结构完全缓存或根本不缓存。因此,如果要缓存属于给定实体层次结构的子类,则 JPA @Cacheable 和特定于 Hibernate 的 @Cache 注解必须仅在根实体级别声明。

Cacheable 注解的值由子类继承;可以通过在子类上指定 Cacheable 来覆盖它。从 Hibernate ORM 5.3 开始,您现在可以在子类级别覆盖基类 @Cacheable 或 @Cache 定义。但是,Hibernate 缓存并发策略(例如,只读,非严格读写,读写,事务)仍然在根实体级别定义,并且不能被覆盖。

管理缓存数据

传统上,Hibernate 定义了 CacheMode 枚举来描述与缓存数据交互的方式。 JPA 通过存储(CacheStoreMode)和检索(CacheRetrieveMode)分割缓存模式。两者的关系如下表:

Hibernate JPA 描述
CacheMode.NORMAL CacheStoreMode.USECacheRetrieveMode.USE 默认。from/into 缓存读取/写入数据
CacheMode.REFRESH CacheStoreMode.REFRESH` 和 `CacheRetrieveMode.BYPASS 不从缓存中读取,而是在从数据库加载时写入缓存
CacheMode.PUT CacheStoreMode.USECacheRetrieveMode.BYPASS 不从缓存中读取,而是在从数据库读取时写入缓存
CacheMode.GET CacheStoreMode.BYPASSCacheRetrieveMode.USE 从缓存中读取,但不写入缓存
CacheMode.IGNORE CacheStoreMode.BYPASSCacheRetrieveMode.BYPASS 不从缓存读取/写入数据

设置缓存模式可以在直接加载实体或执行查询时完成。

  • 使用 JPA 自定义缓存模式:
Map<String, Object> hints = new HashMap<>(  );
hints.put( "javax.persistence.cache.retrieveMode" , CacheRetrieveMode.USE );
hints.put( "javax.persistence.cache.storeMode" , CacheStoreMode.REFRESH );
Person person = entityManager.find( Person.class, 1L , hints);
  • 使用 Hibernate 自定义缓存模式:
session.setCacheMode( CacheMode.REFRESH );
Person person = session.get( Person.class, 1L );
  • 使用 Spring Data JPA 自定义缓存模式(1.10 版本之后):
@QueryHints(value = {
    @QueryHint(name = "javax.persistence.cache.retrieveMode", value = "USE"),
    @QueryHint(name = "javax.persistence.cache.storeMode", value = "REFRESH")
})
Optional<Person> findById(Long id);

查询缓存,集合缓存,和缓存统计以后再来补充。

Spring Boot 使用 EhCache

个人使用 Gradle 管理项目依赖,以下所述依赖皆是以基于Groovy 的特定于域的语言(DSL)定义。

仅使用缓存

Spring Boot 使用缓存非常简单,我们只需要导入所需要的包即可开箱即用,如果我们仅仅想使用缓存,则直接引入:org.springframework.boot:spring-boot-starter-cache starter 便可使用。

在配置类或启动类上加入 @EnableCaching 注解,该注解会触发一个后处理器(post processor)去检测每个 Spring Bean 上是否存在公共方法的缓存注解。如果找到这样的注解,则自动创建代理以拦截方法调用并相应地处理缓存行为。

此后处理器管理的注释是 CacheableCachePutCacheEvict。Spring Boot 会自动配置合适的CacheManager 作为相关缓存的提供程序。如果只引入了该包,则默认只会使用 Spring 上下文 ConcurrentHashMap 结构来存储缓存,这完全符合 JCache 的标准。

如果当前上下文中存在 JSR-107 API,即 javax.cache:cache-api 该 jar 包,将额外的为 JSR-107 API 注解的 bean 创建代理,这些注解是 @CacheResult@CachePut@CacheRemove@CacheRemoveAll

使用 Hibernate JCache 标准来构建二级缓存

要使用 JCache 的内置集成,您需要将 org.hibernate:hibernate-jcache jar 集成进去。此外,还需要添加 JCache 实现。

hibernate-jcache 模块定义了以下区域工厂:JCacheRegionFactory

要使用该 JCacheRegionFactory,只需配置该属性为:hibernate.cache.region.factory_class: org.hibernate.cache.jcache.JCacheRegionFactory

JCache 要求共享相同 URI 和类加载器的 CacheManagers 在 JVM 中是唯一的。如果未指定其他属性,JCacheRegionFactory 将加载默认的 JCache 提供程序并创建默认的 CacheManager。同样的,将使用默认的 javax.cache.configuration.MutableConfiguration 创建缓存。

为了控制使用哪个提供程序为 CacheManager 和 Caches 指定配置,您可以使用以下两个属性:

<property
    name="hibernate.javax.cache.provider"
    value="org.ehcache.jsr107.EhcacheCachingProvider"/>
<property
    name="hibernate.javax.cache.uri"
    value="file:/path/to/ehcache.xml"/>

使用 EhCache 2.0 构建二级缓存

使用 Ehcache 的内置集成要求我们引入 org.hibernate:hibernate-ehcahe jar 作为依赖。

同样的我们需要配置 RegionFactory 为:hibernate.cache.region.factory_class: org.hibernate.cache.ehcache.EhCacheRegionFactory

强制 Hibernate 使用单个 Ehcache CacheManager:hibernate.cache.region.factory_class: org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory

使用 EhCache 3.0 构建二级缓存

使用 EhCache 3.0 时,hibernate 并没有默认的模块支持。但是我们可以通过 JCache 来指定具体的实现完成。

我们同时引入 org.ehcache:ehcacheorg.hibernate:hibernate-jcache,这里我们并没有指明具体版本,因为我们使用 spring boot starter 管理的版本,具体可以查看自己 spring boot starter 的版本和依赖版本。

我们可以指定服务提供者 hibernate.javax.cache.provider 来完成配置。但是 JCacheRegionFactory 会使用默认的 MutableConfiguration 来创建 Cache,如果我们需要使用 EhCache 的配置来定义缓存,我们需要自己定义 RegionFactory 并以此作为 RegionFactory 配置。

为了防止 JCacheRegionFactory 自动创建缓存,我们可以按如下配置:

public class NoDefaultJCacheRegionFactory extends JCacheRegionFactory {

    public static final String EXCEPTION_MESSAGE = "All Hibernate caches should be created upfront. " +
        "Please update CacheConfiguration.java to add";

    @Override
    protected Cache<Object, Object> createCache(String regionName, Properties properties, CacheDataDescription
        metadata) {
        throw new IllegalStateException(EXCEPTION_MESSAGE + " " + regionName);
    }
}

然后指定二级缓存 CacheManager 的创建方法,并使用当前的 CalssLoader,修复了 Spring Boot 2.0.3 中引入的 Spring 类加载器问题。 这允许对 ehcache 使用相同的类加载器,包括 Spring Cache 抽象和 Hibernate 2nd 级缓存。

public class BeanClassLoaderAwareJCacheRegionFactory extends NoDefaultJCacheRegionFactory {

    private static volatile ClassLoader classLoader;

    @Override
    protected CacheManager getCacheManager(Properties properties) {
        Objects.requireNonNull(classLoader, "Please set Spring's classloader in the setBeanClassLoader " +
            "method before using this class in Hibernate");

        CachingProvider cachingProvider = getCachingProvider(properties);
        String cacheManagerUri = getProp(properties, CONFIG_URI);

        URI uri = getUri(cachingProvider, cacheManagerUri);
        CacheManager cacheManager = cachingProvider.getCacheManager(uri, classLoader);

        // To prevent some class loader memory leak this might cause
        setBeanClassLoader(null);

        return cacheManager;
    }

    private URI getUri(CachingProvider cachingProvider, String cacheManagerUri) {
        URI uri;
        if (cacheManagerUri != null) {
            try {
                uri = new URI(cacheManagerUri);
            } catch (URISyntaxException e) {
                throw new CacheException("Couldn't create URI from " + cacheManagerUri, e);
            }
        } else {
            uri = cachingProvider.getDefaultURI();
        }
        return uri;
    }

    /**
     * This method must be called from a Spring Bean to get the classloader.
     * For example: BeanClassLoaderAwareJCacheRegionFactory.setBeanClassLoader(this.getClass().getClassLoader());
     *
     * @param classLoader The Spring classloader
     */
    public static void setBeanClassLoader(ClassLoader classLoader) {
        BeanClassLoaderAwareJCacheRegionFactory.classLoader = classLoader;
    }
}

然后,我们就可以使用 EhCache 的配置来创建缓存。

public class CacheConfiguration {

    private final javax.cache.configuration.Configuration<Object, Object> jcacheConfiguration;

    public CacheConfiguration(ApplicationProperties applicationProperties) {
        BeanClassLoaderAwareJCacheRegionFactory.setBeanClassLoader(this.getClass().getClassLoader());
        ApplicationProperties.Cache.Ehcache ehcache =
            applicationProperties.getCache().getEhcache();

        CacheEventListenerConfigurationBuilder cacheEventListenerConfiguration = CacheEventListenerConfigurationBuilder
            .newEventListenerConfiguration(new CacheListener(applicationProperties), EventType.CREATED, EventType.UPDATED, EventType.EVICTED, EventType.EXPIRED, EventType.REMOVED)
            .unordered().asynchronous();

        jcacheConfiguration = Eh107Configuration.fromEhcacheCacheConfiguration(
            CacheConfigurationBuilder.newCacheConfigurationBuilder(Object.class, Object.class,
                ResourcePoolsBuilder.heap(ehcache.getMaxEntries()))
                .add(cacheEventListenerConfiguration)
                .withExpiry(ExpiryPolicyBuilder.timeToLiveExpiration(Duration.ofSeconds(ehcache.getTimeToLiveSeconds())))
                .build());
    }

    @Bean
    public JCacheManagerCustomizer cacheManagerCustomizer() {
        return cm -> {
         cm.createCache(com.zeral.domain.User.class.getName(), jcacheConfiguration);
            // application-needle-ehcache-add-entry
        };
    }
}

上述代码使用了堆内存储,并指定了最大对象数,指定了缓存键和值的类型,以及过期策略,并配置了缓存监听器。

并使用了 JCacheManagerCustomizer 函数接口完成了 CacheManager 创建缓存,之所以这么写是因为如果我们没有显式配置 JCacheManager,Spring Boot 会为我们自动使用当前环境下的服务提供者创建该对象,我们可以通过函数接口作为对象创建完毕的回调方法使用。

@Bean
@ConditionalOnMissingBean
public CacheManager jCacheCacheManager() throws IOException {
   CacheManager jCacheCacheManager = createCacheManager();
   List<String> cacheNames = this.cacheProperties.getCacheNames();
   if (!CollectionUtils.isEmpty(cacheNames)) {
      for (String cacheName : cacheNames) {
         jCacheCacheManager.createCache(cacheName, getDefaultCacheConfiguration());
      }
   }
   customize(jCacheCacheManager);
   return jCacheCacheManager;
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容