CacheManager
先理解抽象层的几个观点:
1.1 Cache 缓存的抽象Cache 缓存的抽象,Spring定义为一个接口。
缓存抽象
一个缓存工具中可以存储多个Entry<key,value>, 几个主要方法:
getName() 每个缓存都有名称,通过这个方法可以获取到get(key) 方法, 获取这个缓存中某个 key映射的值put(key,value) 方法,保存或者更新这个缓存中某个key 映射的值evict(key) 方法,从这个缓存中删除某个key ,即删除缓存中的某个条款clear() 方法,清空缓存中的所有条款1.2 CacheManager 缓存管理器的抽象CacheManager 缓存管理器的抽象,Spring定义为一个接口:
CacheManager 方法
一个CacheManager可以管理多个Cache,以是一个CacheManager供应了两个方法:
getCache(String):Cache 根据缓存的名称得到缓存工具getCacheNames():Collection 获取管理器管理范围内的所有cache名称。在 org.springframework.cache.concurrent 包中,供应了 ConcurrentMapCache和 ConcurrentMapCacheManager 的实现,他们将cache中的Entry 保存在了本地内存中,其数据构造为ConcurrentHashMap
1.3 缓存操作的Annotation实际开拓的时候,我们每每会在方法上添加一些annotation来指明对缓存的操作,即指明 对哪个或哪些缓存(cacheName)中的哪个 key 在什么条件下,做什么操作(添加,更新,删除):
Annotation
浸染
@Cacheable
将方法的结果缓存起来,下一次方法实行参数相同时,将不实行方法,返回缓存中的结果。@Cacheable 会进行缓存检讨
@CacheEvict
移除指定缓存
@CachePut
标记该表明的方法总会实行,根据表明的配置将结果缓存。一样平常用于更新数据的时候,方法更新了数据,就将更新后的数据返回,如果有这个Annotation,那么这个数据就立即被缓存了。
@Caching
可以组合利用@Cacheable,@CacheEvict,@CachePut
@CacheConfig
类级别表明,可以设置一些共通的配置,@CacheConfig(cacheNames=“user”), 代表该类下的方法均利用这个cacheNames
在支持 Spring Cache 的环境下,对付利用 @Cacheable 标注的方法,Spring 在每次实行前都会检讨 Cache 中是否存在相同 key 的缓存元素,如果存在就不再实行该方法,而是直接从缓存中获取结果进行返回,否则才会实行并将返回结果存入指定的缓存中。@CachePut 也可以声明一个方法支持缓存功能。与 @Cacheable 不同的是利用 @CachePut 标注的方法在实行前不会去检讨缓存中是否存在之前实行过的结果,而是每次都会实行该方法,并将实行结果以键值对的形式存入指定的缓存中。
这些缓存操作Annotation 中常用属性的阐明:
cacheNames/Value: 缓存名字,可以指定多个key: 缓存数据时利用的key,默认空字符串。key可以利用spEL表达式keyGenerator: key的天生器。自己编写一个key天生器,并注册到Spring容器中,keyGenerator指定bean的名称即可,这样就会自动调用天生器来天生 keycacheManager: 指定缓存管理器。 即缓存管理器在Spring容器中的bean的名称cacheResolver:Cache 解析器缓存操作的Annotation会标注在业务方法上,当这些业务方法被调用的时候,如果指明了 Cache解析器,此时Cache解析器就会根据 方法调用的高下文(哪个业务方法被调用,方法的参数是什么,要做什么样的缓存操作,这个高下文叫做CacheOperationInvocationContext )情形,将须要用到的Cache解析出来缓存解析器
condition: 符合条件的才会被缓存,支持 spEL表达式unless: 否定缓存。当unless指定的条件为true时,方法的返回值不会被缓存。支持spELsync: 是否利用异步模式实际利用时:
1. key和keyGenerator 不能同时涌现,由于它们决定了 cache中某个 entry的 key。 key支持springEL 表达式,解析后便是key,而 keyGenerator 是通过调用一个方法返回值作为key。
2. cacheManager和cacheResolve 不能同时涌现。如果没有指定cacheResolve,cacheManager会作为默认的cacheResolve
SpringCacheAnnotationParser 类中的 validateCacheOperation 方法有解释:
validateCacheOperation 方法有解释
1.3 拦截器包拦截器 org.springframework.cache.interceptor 包
SpringCache 操作的Annotation (@Cacheable,@CacheEvict,@CachePut) 之以是能够起浸染,实在质还是用到了Spring AOP。
即: 容器启动后,会为业务 bean天生 代理工具 Proxy, 那要为哪些bean天生 Proxy?这便是 PointCut,PointCut 卖力筛选出要切入的点,这里大略理解为运用中业务类中的某个或某些方法,增强的操作是什么?这是 advice, 在Spring Cache中便是对缓存的操作。
Spring AOP中,一样平常 advisor 理解为 advisor = PointCut + Advice .
利用Spring Cache的第一步是须要在配置类上用 @EnableCaching 开启的,它实际上便是在注册 advisor.
这里要重点关注几个bean:实际开拓的时候并不须要我们的干预。
代理工具天生器。 跟踪源码,找到了@EnableCaching 在容器中注册了org.springframework.aop.framework.autoproxy.InfrastructureAdvisorAutoProxyCreator 它便是用来天生代理工具的。advisor: SpringCache 注册的 advisor 为 BeanFactoryCacheOperationSourceAdvisorpointcut : BeanFactoryCacheOperationSourceAdvisor内部的切入点实现类是 CacheOperationSourcePointcut在切入点中就用到了 AnnotationCacheOperationSource 工具来获取 CacheOperatios 的凑集CacheOperation的观点 : 缓存操作的抽象。
缓存操作的抽象
BasicOperation: 所有缓存操作(Operation, 即 @Cacheable 对应的先缓存检讨,后进行数据缓存操作, @CacheEvict 对应的缓存失落效操作, @CachePut 对应的不检讨进行数据缓存操作 )
CacheAnnotationParser的观点:将 put、caching、evict、cacheable四大表明 解析为 Cacheoperation 后存起来。
CacheAnnotationParser
advice: 名称为: cacheInterceptor 类型为 CacheInterceptor 的bean工具。 方法拦截器,用于操作Cache的 AOP Advice2. SpringBoot中利用默认Cache在没有做分外配置的情形下, SpringBoot的自动配置会为我们配置ConcurrentMapCacheManager 它利用的cache便是 ConcurrentMapCache
2.1 依赖引入新建SpringBoot项目,引入依赖:
pom.xml
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId></dependency>
2.2 打开Cache配置在入口处加入 @EnableCaching 打开缓存配置:
@SpringBootApplication@EnableCachingpublic class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); }}
2.3 业务方法上标注缓存操作
// 这里利用了 lombok// 产品实体类@Data@AllArgsConstructor@NoArgsConstructorpublic class Product implements Serializable { private String productId; private String category; private String name; private String descn;}// 业务接口public interface ProductService { public List<Product> findAll(); public Product findProductById(String id); public int updateProduct(Product product);}// 业务接口实现@Service@Slf4jpublic class ProductServiceImpl implements ProductService { private List<Product> data;//仿照数据库中的数据 public ProductServiceImpl(){ data=new ArrayList<>(); data.add(new Product("1001","水果","苹果","红富士苹果")); data.add(new Product("1002","水果","喷鼻香蕉","喷鼻香蕉喷鼻香蕉")); data.add(new Product("1003","洗护","洗发水","海飞丝")); data.add(new Product("1004","休闲食品","辣条","辣条辣条")); }// ...}
2.4 利用@Cacheable这里所有的方法都是 ProductServiceImpl 类中的方法
@Cacheable(cacheNames = {"product"},key="#root.methodName")public List<Product> findAll() { log.info("数据库访问:findAll方法"); return data;}@Cacheable(cacheNames = {"product"},key = "#root.methodName+'['+#id+']'")public Product findProductById(String id) { log.info("findProductById,数据库访问查询id:{}",id); for(Product p:data){ if(p.getProductId().equals(id)){ return p; } } return null;}
cacheNames : 可以指定多个cache名称,是一个数组。key : cache中的key,可以利用SpringEL表达式获取当前方法高下文信息,比如方法名称,参数的值等。Caching SpringEL Evaluation Context解释:属性名称
描述
示例
methodName
当前方法名
#root.methodName
method
当前方法
#root.method.name
target
当前被调用的工具
#root.target
targetClass
当前被调用的工具的class
#root.targetClass
args
当前方法参数组成的数组
#root.args[0]
caches
当前被调用的方法利用的Cache
#root.caches[0].name
要利用 root 工具的属性作为 key 时,也可以将“#root”省略,由于 Spring 默认利用的便是 root 工具的属性。
如果要直策应用方法参数通报的值,可以用 #参数名称 来取出方法调用的时候通报的实参值,比如上面的 #id
当findAll() 被调用一次后,产品凑集就会被放入缓存中。
ConcurrentMapCacheManager
当findProductById 被调用后,指定id的产品会放入到缓存中:
ConcurrentMapCacheManager
cacheManager不指定,则利用默认注册的cacheManager.
2.5 利用 keyGenerator如果key的生成规则比较繁芜,无法用 SpringEL来天生,可以自定义一个 KeyGenerator, 分为三个步骤来实现:
定义一个类,实现 org.springframework.cache.interceptor.KeyGenerator 接口。
@FunctionalInterfacepublic interface KeyGenerator {Object generate(Object target, Method method, Object... params);}
可以看到这个接口是一个函数式接口,只须要实现一个方法:
target : 被拦截的目标工具
method: 被拦截的方法
params: 被拦截的方法参数
为了方便,可以在第2步中用 lambda 表达式。
将自定义的KeyGenerator注册到容器中
@Configurationpublic class ApplicationCacheConfig {/ 天生缓存主键策略 (方法名+参数) 这里直接用 lambda表达式实现了 @return KeyGenerator /@Bean("cacheKeyGenerator") // 把稳这里的cacheKeyGenerator 是要在 @Cacheable的 keyGenerator 中指定的public KeyGenerator keyGenerator() {return (target, method, params) -> (method.getName() + " [ " + Arrays.asList(params) + " ]");}}
在@Cacheable 中利用keyGenerator 属性
@Cacheable(cacheNames = {"product"},keyGenerator = "cacheKeyGenerator")public Product findProductById(String id){ ...}
一旦利用了 keyGenerator ,就不要再利用 key属性了。
2.6 利用@CacheEvict2.6.1 利用办法1:指定要失落效的缓存的名称和 key
@CacheEvict(cacheNames = "product",key="'findProductById['+#product.productId+']'")public int updateProduct1(Product product) { log.info("updateProduct1,更新product,让缓存失落效"); return 0;}
2.6.2 利用办法2:如果同时要让其它的key也失落效,就须要利用 @Cacheing 来组合多个@CacheEvict
@Caching( evict={ @CacheEvict(cacheNames = "product",key="'findProductById['+#product.productId+']'"), @CacheEvict(cacheNames = "product",key="'findAll'") })public int updateProduct2(Product product) { log.info("updateProduct2,更新product,让多个缓存失落效"); return 0;}
2.6.3 利用办法3:如果向让cache 中所有的 key-value 都失落效,即清空cache中所有的数据,可以利用 allEntries=true
@CacheEvict(cacheNames = "product",allEntries = true)public int updateProduct3(Product product){ log.info("updateProduct3,让全体 emp 下所有的 k-v 全部失落效"); return 0;}
2.7 利用 @CachePut@CachePut 也是将数据加入到缓存中,与 @Cacheable 相似,但对付利用 @Cacheable 标注的方法,Spring 在每次实行前都会检讨 Cache 中是否存在相同 key 的缓存元素,如果存在就不再实行该方法,而是直接从缓存中获取结果进行返回,否则才会实行并将返回结果存入指定的缓存中。@CachePut 也可以声明一个方法支持缓存功能。与 @Cacheable 不同的是利用 @CachePut 标注的方法在实行前不会去检讨缓存中是否存在之前实行过的结果,而是每次都会实行该方法,并将实行结果以键值对的形式存入指定的缓存中。
一样平常在做数据更新的时候,可以考虑利用@CachePut, 它可以做到同时更新数据库中的数据和缓存中的数据。
2.8 利用 @CacheConfig类级别表明,可以设置一些共通的配置,@CacheConfig(cacheNames=“user”), 代表该类下的方法均利用这个cacheNames。
如果业务方法很多属性都相同,可以在业务类上利用 @CacheConfig 来做一些通用配置。
2.9 SpringBoot Cache 自动注册首先SpringFactoriesLoader加载器加载META-INF/spring.factories文件中指定了要加载的配置:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\...org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\...
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration :
@Configuration(proxyBeanMethods = false)@ConditionalOnClass(CacheManager.class)@ConditionalOnBean(CacheAspectSupport.class)@ConditionalOnMissingBean(value = CacheManager.class, name = "cacheResolver")@EnableConfigurationProperties(CacheProperties.class)@AutoConfigureAfter({ CouchbaseDataAutoConfiguration.class, HazelcastAutoConfiguration.class,HibernateJpaAutoConfiguration.class, RedisAutoConfiguration.class })@Import({ CacheConfigurationImportSelector.class, CacheManagerEntityManagerFactoryDependsOnPostProcessor.class })public class CacheAutoConfiguration { ...}
它导入了CacheConfigurationImportSelector, 缓存配置导入选择器, SpringCache 有很多类型:
public enum CacheType {GENERIC,JCACHE,EHCACHE,HAZELCAST,INFINISPAN,COUCHBASE,REDIS,CAFFEINE,SIMPLE,NONE}
它会按照类型的顺序来加载各种类型对应的配置,一旦加载了,那么 cacheManager 这个bean就存在了,其它类型配置加载的时候,由于添加了@ConditionalOnMissingBean,创造 cacheManager 这个bean已经存在,就不在加载配置了。
在没有引入其它缓存实现的情形下,默认利用了 SIMPLE这个类型,它对应的配置类是 SimpleCacheConfiguration:
final class CacheConfigurations {...static {Map<CacheType, String> mappings = new EnumMap<>(CacheType.class);mappings.put(CacheType.GENERIC, GenericCacheConfiguration.class.getName());mappings.put(CacheType.EHCACHE, EhCacheCacheConfiguration.class.getName());mappings.put(CacheType.HAZELCAST, HazelcastCacheConfiguration.class.getName());mappings.put(CacheType.INFINISPAN, InfinispanCacheConfiguration.class.getName());mappings.put(CacheType.JCACHE, JCacheCacheConfiguration.class.getName());mappings.put(CacheType.COUCHBASE, CouchbaseCacheConfiguration.class.getName());mappings.put(CacheType.REDIS, RedisCacheConfiguration.class.getName());mappings.put(CacheType.CAFFEINE, CaffeineCacheConfiguration.class.getName());mappings.put(CacheType.SIMPLE, SimpleCacheConfiguration.class.getName());mappings.put(CacheType.NONE, NoOpCacheConfiguration.class.getName());MAPPINGS = Collections.unmodifiableMap(mappings);} ...}
可以看到 SimpleCacheConfiguration 配置的cacheManager bean的名称为 cacheManager, 类型为ConcurrentMapCacheManager
@Configuration(proxyBeanMethods = false)@ConditionalOnMissingBean(CacheManager.class)@Conditional(CacheCondition.class)class SimpleCacheConfiguration {@BeanConcurrentMapCacheManager cacheManager(CacheProperties cacheProperties,CacheManagerCustomizers cacheManagerCustomizers) { ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();...}}
3. 利用Redis Cache3.1 引入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId></dependency><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId></dependency><!-- 将数据序列化成 JSON字符串后存储到redis中,非必须,根据情形引入 --><dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version> 1.2.70</version></dependency>
3.2 配置redis做事器application.yml
spring: # redis配置 redis: host: localhost port: 6379 database: 0 jedis: pool: max-active: -1 max-wait: 3000ms timeout: 3000ms
3.3 自动配置CacheType.REDIS 对应的 配置类为:RedisCacheConfiguration,在 RedisCacheConfiguration 之前,须要先加载 RedisAutoConfiguration 配置类,RedisAutoConfiguration 配置了注册了以下几个bean:
redisConnectionFactory => JedisConnectionFactory 默认利用的是Jedis连接redisTemplate =>RedisTemplate这个bean实现了InitializingBean 接口,以是spring容器在初始化它的时候,调用了afterPropertiesSet 方法,在这个方法中,为 redisTemplate 创建了默认的序列化器:JdkSerializationRedisSerializer ,默认的 key, value 序列化器都是这个默认的序列化器。stringRedisTemplate=> StringRedisTemplateRedisCacheCongiguration 注册的bean名称为cacheManager,类型为: RedisCacheManager
它供应了一个 createRedisCache 方法,内部在管理 cache的时候,用来创建cache,每次创建cache的时候,都可以为 cache 指定一个 新的 RedisCacheConfiguration 工具,如果没有指定,则利用默认的RedisCacheConfiguration:
public static RedisCacheConfiguration defaultCacheConfig(@Nullable ClassLoader classLoader) {...return new RedisCacheConfiguration( Duration.ZERO, // 失落效韶光 true, // 许可空值 true, // 利用前缀 CacheKeyPrefix.simple(),// 可以分隔符,默认利用 "::" SerializationPair.fromSerializer(RedisSerializer.string()),//key 序列化器 SerializationPair.fromSerializer(RedisSerializer.java(classLoader)), //value 序列化器 conversionService); }
3.4 利用一样平常情形下,引入了spring-boot-starter-data-redis 依赖后,就可以直策应用了, 业务类上无需做任何改动。
4. 为 Redis Cache中的key设置有效期在 RedisCacheConfiguration 类中定义了一个 ttl属性:
public class RedisCacheConfiguration { ...private final Duration ttl;...}
RedisCache 类在调用 put方法向redis做事器写入数据的源码中,调用了 cacheWriter来写入数据,可以看到,key的存活韶光(TTL) 是从 RedisCacheConfiguration 工具中获取的。
也便是说,每个RedisCache都会有一个对应的 RedisCacheConfiguration工具,这个工具中就保存了存活韶光,在Redis中创建key的时候,也确实指定了这个存活韶光。
默认情形下,为每个 RedisCache设置的RedisCacheConfiguration 工具中 这个TTL被设置成了 Duration.ZERO
public class RedisCache extends AbstractValueAdaptingCache { public void put(Object key, @Nullable Object value) {...cacheWriter.put( name, createAndConvertCacheKey(key), serializeCacheValue(cacheValue), cacheConfig.getTtl() // 从配置中获取key存活韶光ttl(Time To Live) );}}
以是要想实现为 RedisCache 设置key的有效期,只须要为 每个RedisCache配置一个新的 RedisCacheConfiguration 工具即可。
4.1 全局默认配置先看看 RedisCacheConfiguration 类注册 cacheManager的源码:
class RedisCacheConfiguration { @Bean RedisCacheManager cacheManager( CacheProperties cacheProperties, // 参数1 CacheManagerCustomizers cacheManagerCustomizers,// 参数2 ObjectProvider<org.springframework.data.redis.cache.RedisCacheConfiguration> redisCacheConfiguration, // 参数3 ObjectProvider<RedisCacheManagerBuilderCustomizer> redisCacheManagerBuilderCustomizers, // 参数4 RedisConnectionFactory redisConnectionFactory, // 参数5 ResourceLoader resourceLoader // 参数6 ) { ... } ...}
可以看到参数3 注入了一个 ObjectProvider<org.springframework.data.redis.cache.RedisCacheConfiguration>, ObjectProvider 是在 Spring 4.3 中引入的,ObjectProvider接口是ObjectFactory接口的扩展,专门为注入点设计的,可以让注入变得更加宽松和更具有可选项。
如果待注入参数的Bean为空或有多个时,便是ObjectProvider发挥浸染的时候了。如果注入实例为空时,利用ObjectProvider则避免了依赖工具不存在而导致非常,如果有多个实例,ObjectProvider的方法会根据Bean实现的Ordered接口或@Order表明指定的先后顺序获取一个Bean。从而了供应了一个更加宽松的依赖注入办法。
以是只须要在自定义的配置中,注册一个 RedisCacheConfiguration bean即可:
// 自定义的用于全局 RedisCache 的配置工具@Configurationpublic class RedisCacheConfig { private static final String REDIS_CACHE_PREFIX="Qw3RedisCache::"; // 默认key TTL为 1小时,根据实际情形做调度,这里仅用于演示 private static final Duration defaultTTL=Duration.ofHours(1); @Bean public RedisCacheConfiguration redisCacheConfiguration(){ // 利用 fastJson来序列化数据 FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class); //相称于new了一个RedisCacheConfiguration RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig(); configuration = configuration.serializeValuesWith // 指定value序列化器 (RedisSerializationContext.SerializationPair.fromSerializer(fastJsonRedisSerializer)) // 指定 key的TTL .entryTtl(defaultTTL) // 指定前缀 .prefixCacheNameWith(REDIS_CACHE_PREFIX); return configuration; }}
4.2 为每个cache key设置TTL全局配置后,每个cache key的TTL都是一样的,实际开拓的时候,如果须要为cache key 指定 TTL怎么办?网上有一种办法是将须要设置TTL的key在 配置文件中定义好。
然后自己来注册 RedisCacheManager,在注册RedisCacheManager的时候为每一个须要指定TTL的cache设置一个新的RedisCacheConfiguration 工具,并指定TTL韶光。
这样一来,同一个cacheName中的所有key都会利用相同的TTL,依然不足灵巧。最好的办法是能够为每一个key指定TTL。
可以自定义一个 Annotation,和@Cacheable一起添加到业务方法上 如:
@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface CacheExpire { / 失落效韶光,默认是60 / public long ttl() default 60L; / 单位,默认是秒 / public TimeUnit unit() default TimeUnit.SECONDS;}// 和@Cacheable 一起标注在业务方法上,指定TTL@Cacheable(cacheNames = {"product"},key="#root.methodName")@CacheExpire(ttl = 20,unit=TimeUnit.SECONDS)@Overridepublic List<Product> findAll() { ...}
在向redis写入数据的时候,得到当前被拦截的方法,通过反射读取@CacheExpire的值,然后为redis key设置 ttl
那么什么时候能够获取到 当前被拦截的方法?很自然想到了 Spring Cache 的 方法拦截器,即 bean name为 cacheInterceptor 类型为 CacheInterceptor 的bean工具。
即须要重写 CacheInterceptor 类,然后注册称为bean, 但是:
自己注册的 cacheInterceptor 无法更换掉默认的 cacheInterceptor Bean , Spring 不许可运用自己更换。
这条路行不通,还有其余一条路,那便是 自定义 CacheResolver
4.3 自定义 CacheResolver先看看CacheResolver接口定义:
@FunctionalInterfacepublic interface CacheResolver {Collection<? extends Cache> resolveCaches(CacheOperationInvocationContext<?> context);}public interface CacheOperationInvocationContext<O extends BasicOperation> {O getOperation();Object getTarget();Method getMethod();Object[] getArgs();}
可以看到,扩展了CacheResolver后,就相称于拦截了 Cache的解析,即能获取到 Cache工具,又能获取到被拦截的Method,这样就可以通过method 的反射 获取到@CacheExpire工具了。这样就能更换掉 RedisCache中的RedisCacheConfiguration 工具了。
然而我创造:
public class RedisCache extends AbstractValueAdaptingCache { //...private final RedisCacheConfiguration cacheConfig; //...}
cacheConfig 居然是一个 private final 的
那通过反射能将其更换掉吗?考试测验了一下,居然可以,这样就办理了每个key定义一个 TTL,下面是详细的做法:
4.3.1 自定义CacheResolver
@Slf4jpublic class RedisExpireCacheResolver extends SimpleCacheResolver { public RedisExpireCacheResolver(CacheManager cacheManager){ super(cacheManager); } @Override public Collection<? extends Cache> resolveCaches(CacheOperationInvocationContext<?> context) { Collection<String> cacheNames = getCacheNames(context); if (cacheNames == null) { return Collections.emptyList(); } Collection<Cache> result = new ArrayList<>(cacheNames.size()); for (String cacheName : cacheNames) { Cache cache = getCacheManager().getCache(cacheName); if (cache == null) { throw new IllegalArgumentException("Cannot find cache named '" + cacheName + "' for " + context.getOperation()); } // 获取到Cache工具后,开始解析 @CacheExpire parseCacheExpire(cache,context); result.add(cache); } return result; } private void parseCacheExpire(Cache cache,CacheOperationInvocationContext<?> context){ Method method= context.getMethod(); // 方法上是否标注了CacheExpire if(AnnotatedElementUtils.isAnnotated(method,CacheExpire.class)){ // 获取工具 CacheExpire cacheExpire=AnnotationUtils.getAnnotation(method,CacheExpire.class); log.info("CacheExpire ttl:{}, CacheExpire unit:{}",cacheExpire.ttl(), cacheExpire.unit()); // 将 cache逼迫转换成 RedisCache,准备更换掉 配置 RedisCache redisCache=(RedisCache) cache; Duration duration=Duration.ofMillis(cacheExpire.unit().toMillis(cacheExpire.ttl())); // 更换RedisCacheConfiguration 工具 setRedisCacheConfiguration(redisCache,duration); } } // 更换RedisCacheConfiguration 工具 private void setRedisCacheConfiguration(RedisCache redisCache, Duration duration){ RedisCacheConfiguration defaultConfiguration=redisCache.getCacheConfiguration(); RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig(); configuration = configuration.serializeValuesWith (defaultConfiguration.getValueSerializationPair()) .entryTtl(duration) .prefixCacheNameWith("Qw3RedisCache::Expire::"); // 实践创造可以更换掉 private final 的field值 //反射设置新的值 Field configField = ReflectionUtils.findField(RedisCache.class,"cacheConfig", RedisCacheConfiguration.class); configField.setAccessible(true); ReflectionUtils.setField(configField,redisCache,configuration); }}
当private final修端正接初始化的基本类型或String时,使⽤反射⽆法修正变量值,其它情形下可以修 改.
4.3.2 注册CacheResolver Bean
@Configurationpublic class RedisCacheConfig { ... // 注册Bean,Bean的名称为方法名: redisExpireCacheResolver @Bean public CacheResolver redisExpireCacheResolver(CacheManager cacheManager) { return new RedisExpireCacheResolver(cacheManager); }}
4.3.3 利用 cacheResolver
@Cacheable(cacheNames = {"product"},key="#root.methodName" ,cacheResolver = "redisExpireCacheResolver")@CacheExpire(ttl = 20,unit=TimeUnit.SECONDS)public List<Product> findAll() {log.info("数据库访问:这是第{}次调用findAll方法",++count);return data;}
方法实行之后,到Redis中查看,TTL已经生效了