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。