Spring Cache使用场景

Spring Cache核心API

Spring提供的核心Cache接口:

public interface Cache {
   String getName();  //缓存的名字  
   Object getNativeCache(); //得到底层使用的缓存,如Ehcache
   ValueWrapper get(Object key); //根据key得到一个ValueWrapper,然后调用其get方法获取值
   <T> T get(Object key, Class<T> type); //根据key,和value的类型直接获取value
   <T> T get(Object key, Callable<T> valueLoader); //相当于“if cached, return; otherwise create, cache and return”, 底层实现需要保证同步
   void put(Object key, Object value);  //往缓存放数据
   ValueWrapper putIfAbsent(Object key, Object value); //往缓存放数据相当于ConcurrentHashMap的putIfAbsent
   void evict(Object key); //从缓存中移除key对应的缓存
   void clear(); //清空缓存
   interface ValueWrapper { //缓存值的Wrapper
      Object get();  //得到真实的value
   }

   @SuppressWarnings("serial")
   class ValueRetrievalException extends RuntimeException { //缓存异常的Wrapper
      private final Object key;
      public ValueRetrievalException(Object key, Callable<?> loader, Throwable ex) {
         super(String.format("Value for key '%s' could not be loaded using '%s'", key, loader), ex);
         this.key = key;
      }
      public Object getKey() {
         return this.key;
      }
   }
}

Spring提供的核心CacheManager接口:

public interface CacheManager {
   Cache getCache(String name); //通过缓存名来获取缓存
   Collection<String> getCacheNames(); //获取此Manger下的所有缓存名
}

Spring Cache注解

1.@Cacheable主要针对方法配置,能够根据方法的请求参数对其结果进行缓存

public @interface Cacheable {
   @AliasFor("cacheNames")
   String[] value() default {}; //缓存的名字

   @AliasFor("value")
   String[] cacheNames() default {}; //缓存的名字

   String key() default ""; //缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省将使用默认的KeyGenerator生成,按照方法的所有参数进行组合  

   String keyGenerator() default "";  //Key的生成器,一般不使用

   String cacheManager() default "";  //缓存管理器的bean名,指定时即从此取缓存

   String cacheResolver() default ""; //缓存解析器,一般不使用

   String condition() default "";  //满足缓存条件的数据才会放入缓存,condition在调用方法之前和之后都会判断  

   String unless() default ""; //用于否决缓存更新的,不像condition,该表达只在方法执行之后判断,此时可以拿到返回值result进行判断了 

   boolean sync() default false; //指示底层缓存实现是否锁住缓存入口直到缓存值被设置,意味着只有一个线程能操作缓存值而别的线程将阻塞直至其更新
}

2.@CacheEvict主要针对方法配置,能够根据一定的条件对缓存进行清空

public @interface CacheEvict {

   @AliasFor("cacheNames")
   String[] value() default {}; //缓存的名字

   @AliasFor("value")
   String[] cacheNames() default {}; //缓存的名字

   String key() default ""; //缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省将使用默认的KeyGenerator生成,按照方法的所有参数进行组合

   String keyGenerator() default "";  //Key的生成器,一般不使用

   String cacheManager() default "";  //缓存管理器的bean名,指定时即从此取缓存

   String cacheResolver() default ""; //缓存解析器,一般不使用

   String condition() default "";  //缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才清空缓存  

   boolean allEntries() default false; //是否清空所有缓存内容,缺省为 false,如果指定为 true,则方法调用后将立即清空所有缓存

   boolean beforeInvocation() default false; //是否在方法执行前就清空,缺省为 false,如果指定为 true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存
}

3.@CachePut主要针对方法配置,能够根据方法的请求参数对其结果进行缓存,和 @Cacheable 不同的是,它每次都会触发真实方法的调用(不建议使用)

public @interface CachePut {

   @AliasFor("cacheNames")
   String[] value() default {}; //缓存的名字

   @AliasFor("value")
   String[] cacheNames() default {}; //缓存的名字

   String key() default ""; //缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省将使用默认的KeyGenerator生成,按照方法的所有参数进行组合

   String keyGenerator() default "";  //Key的生成器,一般不使用

   String cacheManager() default "";  //缓存管理器的bean名,指定时即从此取缓存

   String cacheResolver() default ""; //缓存解析器,一般不使用

   String condition() default "";  //缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存  

   String unless() default ""; //用于否决缓存更新的,不像condition,该表达只在方法执行之后判断,此时可以拿到返回值result进行判断了
}

4.@Caching组合多个Cache注解使用

public @interface Caching {

   Cacheable[] cacheable() default {}; //声明多个@Cacheable

   CachePut[] put() default {}; //声明多个@CachePut  

   CacheEvict[] evict() default {}; //声明多个@CacheEvict  
}

5.@CacheConfig用于需要缓存操作的类上,精简以上缓存注解的配置,即给以上注解提供相应的缺省值。

public @interface CacheConfig {

   String[] cacheNames() default {}; //缓存的名字

   String keyGenerator() default "";  //Key的生成器,一般不使用

   String cacheManager() default "";  //缓存管理器的bean名,指定时即从此取缓存

   String cacheResolver() default ""; //缓存解析器,一般不使用
}

注意:我们不推荐使用@CachePut,这易引起更大的不一致性和注解操作的复杂性,建议注解式缓存只使用Cacheable和CacheEvict两种操作。

注解式缓存中SpEL上下文数据

Spring Cache提供了一些供我们使用的SpEL上下文数据,如下:

名字 位置 描述 示例
methodName root对象 当前被调用的方法名 #root.methodName
method root对象 当前被调用的方法 #root.method.name
target root对象 当前被调用的目标对象 #root.target
targetClass root对象 当前被调用的目标对象类 #root.targetClass
args root对象 当前被调用的方法的参数列表 #root.args[0]
caches root对象 当前方法调用使用的缓存列表(如@Cacheable(value={"cache1", "cache2"})),则有两个cache #root.caches[0].name
argument name 执行上下文 当前被调用的方法的参数,如findById(Long id),我们可以通过#id拿到参数 #user.id
result 执行上下文 方法执行后的返回值(仅当方法执行之后的判断有效,如‘unless’,'cache evict'的beforeInvocation=false) #result

注解式缓存操作

注解式缓存操作,一般适用于Spring托管的bean的接口方法上,通常是@Service注解的bean上。示例:

@Service("acctItemTypeService")
@CacheConfig(cacheManager = "cacheManager") //多CacheManager环境下,必须指定所用的cacheManager名,防止冲突
public class AcctItemTypeServiceImpl implements AcctItemTypeService {

    @Cacheable(cacheNames = "Billing:AcctItemTypeDto:id", key = "#acctItemTypeDto.acctItemTypeId",
            condition = "#acctItemTypeDto.acctItemTypeId ne null")
    public AcctItemTypeDto qryAcctItemType(AcctItemTypeDto acctItemTypeDto) throws BaseAppException {
        logger.info("addAcctItemType invoked!");
        AcctItemTypeDo acctItemTypeDo = acctItemTypeDoMapper.selectByPrimaryKey(acctItemTypeDto.getAcctItemTypeId());

        AcctItemTypeDto dto = new AcctItemTypeDto();
        BeanUtils.copyProperties(acctItemTypeDo, dto);
        return dto;
    }
……
}

更多示例,请参考Demo代码。

编程式缓存操作

编程式缓存操作,通常用于需要自己控制缓存的场景,通过注入CacheManager调用getCache(cacheName)方法来获取对应的缓存,进行相应操作。

@Autowired
private AcctItemTypeService acctItemTypeService;

@Autowired
private CacheManager cacheManager;

@Test
public void tesCacheable() throws BaseAppException {
    Cache cache = cacheManager.getCache("Billing:AcctItemTypeDto:id");

    AcctItemTypeDto dto = cache.get(9000L, AcctItemTypeDto.class);

    if(dto == null) {
        AcctItemTypeDto example = new AcctItemTypeDto();
        example.setAcctItemTypeId(9000L);
        example.setAcctItemTypeCode("Test");
        dto = acctItemTypeService.qryAcctItemTypeByCode(example);

        cache.put("Billing:AcctItemTypeDto:id:9000", dto);
    }
    //返回对象
    // return dto
    Assert.assertTrue(dto.getAcctItemTypeId().equals(9000L));
}

@Test
public void tesCacheEvict() throws BaseAppException {
    Cache cache = cacheManager.getCache("Billing:AcctItemTypeDto:id");
    cache.evict(9000L);
    AcctItemTypeDto dto = cache.get(9000L, AcctItemTypeDto.class);
    Assert.assertTrue(dto == null);
}

注意:我们推荐优先使用注解的方式操作缓存。

配置使用说明

1.配置CacheManager

a.如果底层使用ZCache,需要进行如下配置

private static final String PREFIX = "crm.cache";

@Bean
@ConfigurationProperties(prefix = PREFIX)
public ZCacheClientConfig crmZCacheClientConfig() {
    return new ZCacheClientConfig();
}

@Bean
@ConditionalOnProperty(prefix = PREFIX, value = "enabled", matchIfMissing = true)
public DefaultZCacheClient crmZCacheClient() {
    return new DefaultZCacheClient(crmZCacheClientConfig());
}

@Bean
@ConditionalOnProperty(prefix = PREFIX, value = "redis-enabled", matchIfMissing = true)
public RedisConnectionFactory crmRedisConnectionFactory() {
    return new ZCacheJedisConnectionFactory(crmZCacheClient());
}

@Bean
public <K, V> RedisTemplate<K, V> crmRedisTemplate() {
    RedisTemplate<K, V> redisTemplate = new RedisTemplate<>();
    redisTemplate.setConnectionFactory(crmRedisConnectionFactory());
    redisTemplate.setKeySerializer(new ZCacheKeyRedisSerializer());
    redisTemplate.setHashKeySerializer(new ZCacheKeyRedisSerializer());
    redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
    redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
    return redisTemplate;
}

@Bean
public CacheManager cacheManager() {
    RedisCacheManager cacheManager = new RedisCacheManager(crmRedisTemplate());
    cacheManager.setUsePrefix(true);
    return cacheManager;
}

并配置zcache的属性值,主要配置crm.cache.server-list即可,详细配置如下

crm.cache.enabled=true
crm.cache.redis.enabled=true
crm.cache.server-list=           #一般情况下只需配置此值
crm.cache.cluster-name=zcache  #多个ZCache环境下,必须保证此值不重复,默认值是zcache
crm.cache.client-name=JavaClient  #多个ZCache环境下,必须保证此值不重复,默认值是JavaClient
crm.cache.max-total=32
crm.cache.max-idle=16
crm.cache.min-idle=4
crm.cache.conn-timeout=2000
crm.cache.so-timeout=2000
crm.cache.max-redirections=5
crm.cache.max-scanCount=2000
crm.cache.max-wait-millis=5000
crm.cache.test-on-borrow=true
crm.cache.test-on-return=true
crm.cache.local-cache-pattern=       #使用ZCache的本地缓存时需要配置
crm.cache.local-cache-subs-notify=false
crm.cache.local-cache-maxSize=100000
crm.cache.local-cache-expire=43200000
crm.cache.local-cache-concurrency=32
crm.cache.serialize-type=fastjson

b.如果不需要分布式缓存而使用本地缓存,以Ehcache示例,需要进行如下配置:

@Autowired
private EhCacheProperties ehCacheProperties;

@Bean
public EhCacheCacheManager springEhCacheCacheManager(net.sf.ehcache.CacheManager ehCacheCacheManager) {
    return new EhCacheCacheManager(ehCacheCacheManager);
}

@Bean
public EhCacheManagerFactoryBean ehCacheCacheManager() {
    EhCacheManagerFactoryBean factoryBean = new EhCacheManagerFactoryBean();
    factoryBean.setConfigLocation(ehCacheProperties.getConfig());
    return factoryBean;
}

配置EhCacheProperties中的配置文件属性值,示例:crm.ehcache.config=classpath:ehcache.xml,详见Demo代码中的示例。

注:此处仅做Ehcache接入Spring Cache示例,框架暂不提供Ehcache支持

2.使用注解式缓存需要开启配置@EnableCaching,并且配置缓存的服务,示例如下

@Configuration
@EnableCaching  //用于开启Spring Cache支持
public class DevZCacheConfiguration {
}


//配置需要缓存的方法
    @Cacheable(cacheNames = "Billing:GstTypeDto:List", key = "'all'")
    public List<GstTypeDto> qryGSTType() throws BaseAppException {                
    }

3.编程式缓存服务方法,需要注入CacheManager,示例如下:

@Autowired
private CacheManager cacheManager;

@Test
public void tesCacheManager() throws UnsupportedEncodingException {
    Cache cache = cacheManager.getCache("Billing:AcctItemTypeDto:id");
    AcctItemTypeDto dto = cache.get(9000L, AcctItemTypeDto.class);
    Assert.assertTrue(dto.getAcctItemTypeId().equals(9000L));
}

注意:多个CacheManager的环境下,对于注解式缓存必须指定CacheManager的bean名称:

示例一:使用@CacheConfig进行类级设置

@CacheConfig(cacheManager = "crmCacheManager") //多CacheManager环境下,必须指定所用的cacheManager名,防止冲突
public class AcctItemTypeServiceImpl implements AcctItemTypeService { }

示例二:使用方法级配置

@Cacheable(cacheNames = "Billing:AcctItemTypeDto:id", cacheManager = "crmCacheManager", key = "#acctItemTypeDto.acctItemTypeId",
        condition = "#acctItemTypeDto.acctItemTypeId ne null")
public AcctItemTypeDto qryAcctItemType(AcctItemTypeDto acctItemTypeDto) throws BaseAppException {

反序列化问题

Spring Cache只提供Cache抽象,具体的序列化由底层Cache去支持,而且注解式Spring Cache在获取缓存值时,是没有指定Class类型,而一些反序列化需要指定其类型,如ProtoBuf、fastjson等,因此配置RedisTemplate的值序列化方式时,需要注意,对于一般场景,我们推荐使用的是GenericJackson2JsonRedisSerializer。

results matching ""

    No results matching ""