1肖粮、Spring緩存
1.1孤页、緩存依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
1.2、緩存注解
@EnableCaching:在主入口類上添加該注解涩馆,用于開啟緩存行施;
@CacheConfig:標(biāo)注在類上,表示當(dāng)前類使用的緩存組件中的key為該注解指定的cacheNames/value魂那,當(dāng)該注解指定了cacheNames/value之后蛾号,@Cacheable上就無需再使用cacheNames/value了;
-
@Cacheable:將方法的結(jié)果進(jìn)行緩存涯雅;
cacheNames/value:緩存組件的名字须教;
key:緩存數(shù)據(jù)使用的key,默認(rèn)是使用方法參數(shù)的值,可以使用SpEL表達(dá)式轻腺,比如#id == #a0 == #p0 == #root.args[0]都表示使用第一個(gè)參數(shù)的值作為緩存的key乐疆;
keyGenerator:key的生成器,可以自己指定key的生成器的組件id贬养;
cacheManager:指定緩存管理器挤土;
cacheResolver:指定獲取解析器;
-
condition:指定符合條件的情況下才緩存误算,支持SpEL表達(dá)式仰美;
- condition=“#id>1”:表示id的值大于1時(shí)才緩存。
-
unless:否定緩存儿礼,當(dāng)unless指定的條件為true咖杂,方法的返回值就不會(huì)被緩存,該屬性可以獲取到結(jié)果進(jìn)行判斷蚊夫,unless與condition剛好相反诉字;
- unless=“#a0==2”:表示第一個(gè)參數(shù)的值為2時(shí),查詢結(jié)果不緩存知纷。
-
sync:是否使用異步模式壤圃,使用sync后unless就不支持了;
其中keyGenerator與key二者只能選一琅轧,cacheResolver與cacheManager也二者選一伍绳。
-
@CachePut:用于更新緩存,它先調(diào)用方法乍桂,然后將目標(biāo)方法的結(jié)果進(jìn)行緩存冲杀。
? 能夠更新緩存的前提是,@CachePut更新使用的key與@Cacheable使用的key要保持一致睹酌。
@CachePut注解屬性同@Cacheable的屬性類似权谁,少了sync屬性。
-
@CacheEvict:緩存清除
? 同樣如果要清除緩存忍疾,則使用的key值要與@Cacheable使用的key一致闯传,否則達(dá)不到清除緩存的功能谨朝。
@CacheEvict注解屬性比@Cacheable注解少了sync和unless卤妒,但是多了兩個(gè)屬性:allEntries和beforeInvocation。
? allEntries:表示是否刪除所有緩存字币。
? beforeInvocation:表示刪除緩存的操作是否在目標(biāo)方法執(zhí)行之前则披。
1.3、原理
CacheAutoConfiguration是緩存的自動(dòng)配置類洗出。
@Configuration
@ConditionalOnClass(CacheManager.class)
@ConditionalOnBean(CacheAspectSupport.class)
@ConditionalOnMissingBean(value = CacheManager.class, name = "cacheResolver")
@EnableConfigurationProperties(CacheProperties.class)
@AutoConfigureBefore(HibernateJpaAutoConfiguration.class)
@AutoConfigureAfter({ CouchbaseAutoConfiguration.class, HazelcastAutoConfiguration.class,
RedisAutoConfiguration.class })
@Import(CacheConfigurationImportSelector.class)
public class CacheAutoConfiguration {
//other code...
static class CacheConfigurationImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
CacheType[] types = CacheType.values();
String[] imports = new String[types.length];
for (int i = 0; i < types.length; i++) {
imports[i] = CacheConfigurations.getConfigurationClass(types[i]);
}
return imports;
}
}
}
緩存配置類(有順序):
org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration
org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration
org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration
org.springframework.boot.autoconfigure.cache.HazelcastCacheConfiguration
org.springframework.boot.autoconfigure.cache.InfinispanCacheConfiguration
org.springframework.boot.autoconfigure.cache.CouchbaseCacheConfiguration
org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration
org.springframework.boot.autoconfigure.cache.CaffeineCacheConfiguration
org.springframework.boot.autoconfigure.cache.GuavaCacheConfiguration
org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration
org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration
開啟debug=true士复,查看控制臺(tái),可以發(fā)現(xiàn):默認(rèn)cache配置類生效的只有SimpleCacheConfiguration
@Configuration
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class SimpleCacheConfiguration {
private final CacheProperties cacheProperties;
private final CacheManagerCustomizers customizerInvoker;
SimpleCacheConfiguration(CacheProperties cacheProperties,
CacheManagerCustomizers customizerInvoker) {
this.cacheProperties = cacheProperties;
this.customizerInvoker = customizerInvoker;
}
@Bean
public ConcurrentMapCacheManager cacheManager() {
ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
List<String> cacheNames = this.cacheProperties.getCacheNames();
if (!cacheNames.isEmpty()) {
cacheManager.setCacheNames(cacheNames);
}
return this.customizerInvoker.customize(cacheManager);
}
}
該類給容器中注冊(cè)了一個(gè)ConcurrentMapCacheManager類型的cacheManager組件。
public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderAware {
private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<String, Cache>(16);
//other code...
//從緩存ConcurrentMap對(duì)象中獲取cacheNames/value指定的Cache對(duì)象阱洪。
@Override
public Cache getCache(String name) {
Cache cache = this.cacheMap.get(name);
if (cache == null && this.dynamic) {
synchronized (this.cacheMap) {
cache = this.cacheMap.get(name);
if (cache == null) {
cache = createConcurrentMapCache(name);
this.cacheMap.put(name, cache);
}
}
}
return cache;
}
}
該組件可以獲取和創(chuàng)建ConcurrentMapCache類型的緩存組件便贵,作用是將數(shù)據(jù)保存在ConcurrentMap對(duì)象中。
運(yùn)行流程:
? 1)冗荸、方法運(yùn)行之前承璃,先去查詢Cache(緩存組件),按照cacheNames指定的名字去緩存中獲劝霰尽(CacheManager先獲取相應(yīng)的緩存)盔粹,第一次獲取緩存,如果沒有Cache組件程癌,則會(huì)自動(dòng)創(chuàng)建舷嗡。
? 2)、去Cache中查找緩存的內(nèi)容嵌莉,使用一個(gè)key(默認(rèn)是方法的參數(shù))进萄,其中key是按照某種策略生成的,默認(rèn)是使用SimpleKeyGenerator生成的烦秩。
? SimpleKeyGenerator生成key的策略:
? 如果沒有參數(shù)垮斯,key=new SimpleKey();
? 如果有一個(gè)參數(shù),key=參數(shù)值
? 如果有多個(gè)參數(shù)只祠,key=new SimpleKey(params);
? 3)兜蠕、沒有查到緩存,就調(diào)用目標(biāo)方法抛寝;如果查到緩存熊杨,則直接返回結(jié)果,不調(diào)用目標(biāo)方法盗舰。
? 4)晶府、沒有查到緩存后去調(diào)用目標(biāo)方法,然后將目標(biāo)方法返回的結(jié)果放進(jìn)緩存钻趋。
1.4川陆、注解解釋
@Cacheable:
自定義key:
? methodName:當(dāng)前被調(diào)用的方法名,例如#root.methodName
? method:當(dāng)前被調(diào)用的方法蛮位,例如#root.method.name
? target:當(dāng)前被調(diào)用的目標(biāo)對(duì)象较沪,例如#root.target
? targetClass:當(dāng)前被調(diào)用的目標(biāo)對(duì)象類,例如#root.targetClass
? args:當(dāng)前被調(diào)用的方法的參數(shù)列表失仁,例如#root.args[0]
? caches:當(dāng)前方法調(diào)用使用的緩存列表尸曼,
? argument name:方法參數(shù)的名字,可以直接#參數(shù)名萄焦,也可以使用#a0或者#p0的形式控轿,0表示參數(shù)索引,例如#id,#a0茬射,#p0等鹦蠕。
例如:
@Cacheable(cacheNames = "emp", key = "#root.methodName+'[' + #id + ']'")
當(dāng)然也可以自定義KeyGenerator:
@Bean("myKeyGenerator")
public KeyGenerator keyGenerator(){
return (target, method, params) -> method.getName() + "[" + Arrays.asList(params).toString() + "]";
}
則在@Cacheable上使用該自定義的KeyGenerator:
@Cacheable(cacheNames = "emp", keyGenerator = "myKeyGenerator")
@CachePut:同步更新緩存
它主要用于更新緩存,它先調(diào)用方法在抛,然后將目標(biāo)方法的結(jié)果進(jìn)行緩存片部。但是能夠更新緩存的前提是,@CachePut更新使用的key與@Cacheable使用的key要保持一致霜定。
@Cacheable(cacheNames = "emp", key = "#a0")
public Employee getById(Integer id){}
@CachePut(cacheNames = "emp", key = "#result.id")
public Employee update(Employee employee){}
其中#result表示目標(biāo)方法的返回值档悠。因?yàn)锧CachePut更新緩存比目標(biāo)方法晚,所有@CachePut能獲取到返回值望浩。而@Cacheable比目標(biāo)方法早辖所,所以無法使用#result。
@CacheEvict:緩存清除
同樣如果要清除緩存磨德,則使用的key值要與@Cacheable使用的key一致缘回,否則達(dá)不到清除緩存的功能。
? 屬性allEntries典挑,默認(rèn)為false酥宴,表示不把cacheNames里所有的緩存都刪除掉。當(dāng)該值為true的時(shí)候您觉,會(huì)把緩存中對(duì)應(yīng)的cacheNames里的所有緩存都刪除拙寡。
? 屬性beforeInvocation,默認(rèn)為false琳水,表示刪除緩存的操作在目標(biāo)方法執(zhí)行之后肆糕。當(dāng)該值為true的時(shí)候,則刪除緩存的操作在目標(biāo)方法執(zhí)行之前在孝。區(qū)別:如果設(shè)為true诚啃,當(dāng)目標(biāo)方法執(zhí)行出現(xiàn)異常后,對(duì)應(yīng)的緩存已經(jīng)被刪除了私沮。如果設(shè)為false(默認(rèn))始赎,當(dāng)目標(biāo)方法執(zhí)行出現(xiàn)異常后,就不會(huì)把緩存刪除掉仔燕。
2造垛、Redis緩存
2.1、redis依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2.2涨享、redis自動(dòng)配置類
@Configuration
@ConditionalOnClass({ JedisConnection.class, RedisOperations.class, Jedis.class })
@EnableConfigurationProperties(RedisProperties.class)
public class RedisAutoConfiguration {
@Configuration
@ConditionalOnClass(GenericObjectPool.class)
protected static class RedisConnectionConfiguration {
//other code...
@Bean
@ConditionalOnMissingBean(RedisConnectionFactory.class)
public JedisConnectionFactory redisConnectionFactory()
throws UnknownHostException {
return applyProperties(createJedisConnectionFactory());
}
//other code...
}
@Configuration
protected static class RedisConfiguration {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(
RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate<Object, Object>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean(StringRedisTemplate.class)
public StringRedisTemplate stringRedisTemplate(
RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
}
在RedisAutoConfiguration自動(dòng)配置類中筋搏,注冊(cè)了幾個(gè)重要的Bean仆百,分別是JedisConnectionFactory厕隧,RedisTemplate和StringRedisTemplate。可以在類中直接注入RedisTemplate和StringRedisTemplate吁讨。
2.3髓迎、redis常用數(shù)據(jù)類型
String——字符串
List——列表
Set——集合
Hash——散列
ZSet——有序集合
具體命令可參考Redis命令
2.4、使用redis序列化對(duì)象
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<Object, Employee> employeeRedisTemplate(@Autowired RedisConnectionFactory redisConnectionFactory){
RedisTemplate<Object, Employee> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer<Employee> serializer = new Jackson2JsonRedisSerializer<>(Employee.class);
template.setDefaultSerializer(serializer);
return template;
}
}
使用該redisTemplate:
@Autowired
private RedisTemplate<Object, Employee> employeeRedisTemplate;
@Test
public void test02(){
Employee e = employeeService.getById(1);
//默認(rèn)保存對(duì)象建丧,使用jdk序列化機(jī)制排龄,序列化后的數(shù)據(jù)保存到redis中
employeeRedisTemplate.opsForValue().set("emp-01", e);
Employee employee = employeeRedisTemplate.opsForValue().get("emp-01");
System.out.println(employee);
}
2.5、redis緩存
一旦引入了redis的starter之后翎朱,cache的配置類就變成了RedisCacheConfiguration橄维。
@Configuration
@AutoConfigureAfter(RedisAutoConfiguration.class)
@ConditionalOnBean(RedisTemplate.class)
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class RedisCacheConfiguration {
private final CacheProperties cacheProperties;
private final CacheManagerCustomizers customizerInvoker;
RedisCacheConfiguration(CacheProperties cacheProperties,
CacheManagerCustomizers customizerInvoker) {
this.cacheProperties = cacheProperties;
this.customizerInvoker = customizerInvoker;
}
@Bean
public RedisCacheManager cacheManager(RedisTemplate<Object, Object> redisTemplate) {
RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
cacheManager.setUsePrefix(true);
List<String> cacheNames = this.cacheProperties.getCacheNames();
if (!cacheNames.isEmpty()) {
cacheManager.setCacheNames(cacheNames);
}
return this.customizerInvoker.customize(cacheManager);
}
}
而CacheManager就變成了RedisCacheManager:
//加載緩存
@Override
protected Collection<? extends Cache> loadCaches() {
Assert.notNull(this.redisOperations, "A redis template is required in order to interact with data store");
Set<Cache> caches = new LinkedHashSet<Cache>(
loadRemoteCachesOnStartup ? loadAndInitRemoteCaches() : new ArrayList<Cache>());
Set<String> cachesToLoad = new LinkedHashSet<String>(this.configuredCacheNames);
cachesToLoad.addAll(this.getCacheNames());
if (!CollectionUtils.isEmpty(cachesToLoad)) {
for (String cacheName : cachesToLoad) {
caches.add(createCache(cacheName));
}
}
return caches;
}
protected RedisCache createCache(String cacheName) {
long expiration = computeExpiration(cacheName);
return new RedisCache(cacheName, (usePrefix ? cachePrefix.prefix(cacheName) : null), redisOperations, expiration,
cacheNullValues);
}
RedisCacheManager通過創(chuàng)建RedisCache來作為緩存組件,然后通過RedisCache來進(jìn)行緩存的操作拴曲。
其中在創(chuàng)建RedisCacheManager組件的時(shí)候傳入的是RedisTemplate<Object, Object>争舞,而它默認(rèn)使用的是jdk的序列化機(jī)制。
public RedisTemplate() {}
public void afterPropertiesSet() {
super.afterPropertiesSet();
boolean defaultUsed = false;
if (defaultSerializer == null) {
defaultSerializer = new JdkSerializationRedisSerializer(
classLoader != null ? classLoader : this.getClass().getClassLoader());
}
if (enableDefaultSerializer) {
if (keySerializer == null) {
keySerializer = defaultSerializer;
defaultUsed = true;
}
if (valueSerializer == null) {
valueSerializer = defaultSerializer;
defaultUsed = true;
}
if (hashKeySerializer == null) {
hashKeySerializer = defaultSerializer;
defaultUsed = true;
}
if (hashValueSerializer == null) {
hashValueSerializer = defaultSerializer;
defaultUsed = true;
}
}
if (enableDefaultSerializer && defaultUsed) {
Assert.notNull(defaultSerializer, "default serializer null and not all serializers initialized");
}
if (scriptExecutor == null) {
this.scriptExecutor = new DefaultScriptExecutor<K>(this);
}
initialized = true;
}
問題:如何將RedisCacheManager使用json序列化機(jī)制呢澈灼?
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<Object, Employee> employeeRedisTemplate(@Autowired RedisConnectionFactory redisConnectionFactory){
RedisTemplate<Object, Employee> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer<Employee> serializer = new Jackson2JsonRedisSerializer<>(Employee.class);
template.setDefaultSerializer(serializer);
return template;
}
//自定義緩存管理器CacheManager
@Bean
@Primary
public RedisCacheManager employeeRedisCacheManager(@Autowired RedisTemplate<Object, Employee> employeeRedisTemplate){
RedisCacheManager manager = new RedisCacheManager(employeeRedisTemplate);
//使用前綴竞川,會(huì)使key多一個(gè)前綴,默認(rèn)會(huì)使用cacheNames作為key的前綴
manager.setUsePrefix(true);
return manager;
}
}
但是該RedisCacheManager只能操作Employee叁熔,如果使用該RedisCacheManager來操作其他Object委乌,就會(huì)報(bào)錯(cuò),所以通常情況下荣回,不應(yīng)該修改默認(rèn)的RedisCacheManager遭贸。
如果有多個(gè)CacheManager緩存管理器,則可以在@CacheConfig或者@Cacheable中的屬性cacheManager上指定所使用的CacheManager心软。