1.引入spring-cache并且和redis进行整合
1.引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
<version>2.7.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.7.5</version>
</dependency>
2.加注解
@EnableCaching
public class Application {}
3.添加配置
spring:
cache:
type: redis
4.使用注解操作缓存
@Cacheale @CachePut @CacheEvict @Caching
生成key的策略为cacheName::key
2.原理
通过 @EnableCaching 引入 CachingConfigurationSelector.class
CachingConfigurationSelector#selectImports()
//默认使用PROXY增强
public String[] selectImports(AdviceMode adviceMode) {
switch (adviceMode) {
case PROXY:
return this.getProxyImports();
case ASPECTJ:
return this.getAspectJImports();
default:
return null;
}
}
private String[] getProxyImports() {
List<String> result = new ArrayList(3);
result.add(AutoProxyRegistrar.class.getName());
result.add(ProxyCachingConfiguration.class.getName());
if (jsr107Present && jcacheImplPresent) {
result.add("org.springframework.cache.jcache.config.ProxyJCacheConfiguration");
}
return StringUtils.toStringArray(result);
}
ProxyCachingConfiguration 注入了三个bean
BeanFactoryCacheOperationSourceAdvisor 将CacheOperationSource 作为pointCut进行拦截
private CacheOperationSource cacheOperationSource;
private final CacheOperationSourcePointcut pointcut = new CacheOperationSourcePointcut() {
@Nullable
protected CacheOperationSource getCacheOperationSource() {
return BeanFactoryCacheOperationSourceAdvisor.this.cacheOperationSource;
}
};
CacheInterceptor 走到CacheAspectSupport#execute()
按照规定流程执行响应注解对应的动作
-
存在 @CacheEvit,满足 beforeInvocation == true 先执行删除
-
存在@Cacheable,先判断缓存中是否有值。有值直接从缓存中拿,如果没有值则放一个
CachePutRequest,用于之后缓存值,先执行逻辑,从数据库中查询值 -
存在@CachePut,放
CachePutRequest,缓存值 -
存在 @CacheEvit,满足 beforeInvocation ==false 执行删除
4.1 删除的时候判断allEntries是否为true,true则删除cacheName下的所有缓存,否则删除匹配的那个
private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {
if (contexts.isSynchronized()) {
CacheOperationContext context = (CacheOperationContext)contexts.get(CacheableOperation.class).iterator().next();
if (!this.isConditionPassing(context, CacheOperationExpressionEvaluator.NO_RESULT)) {
return this.invokeOperation(invoker);
}
Object key = this.generateKey(context, CacheOperationExpressionEvaluator.NO_RESULT);
Cache cache = (Cache)context.getCaches().iterator().next();
try {
return this.wrapCacheValue(method, this.handleSynchronizedGet(invoker, key, cache));
} catch (Cache.ValueRetrievalException var10) {
ReflectionUtils.rethrowRuntimeException(var10.getCause());
}
}
this.processCacheEvicts(contexts.get(CacheEvictOperation.class), true, CacheOperationExpressionEvaluator.NO_RESULT);
Cache.ValueWrapper cacheHit = this.findCachedItem(contexts.get(CacheableOperation.class));
List<CachePutRequest> cachePutRequests = new ArrayList();
if (cacheHit == null) {
this.collectPutRequests(contexts.get(CacheableOperation.class), CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);
}
Object returnValue;
Object cacheValue;
if (cacheHit != null && !this.hasCachePut(contexts)) {
cacheValue = cacheHit.get();
returnValue = this.wrapCacheValue(method, cacheValue);
} else {
returnValue = this.invokeOperation(invoker);
cacheValue = this.unwrapReturnValue(returnValue);
}
this.collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);
Iterator var8 = cachePutRequests.iterator();
while(var8.hasNext()) {
CachePutRequest cachePutRequest = (CachePutRequest)var8.next();
cachePutRequest.apply(cacheValue);
}
this.processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);
return returnValue;
}
上述操作缓存的行为,具体要看整合进来缓存类型。例如整合的是redis,则会在redis包中实现操作缓存的行为,整合进来caffeine则会再caffeine包中实现。类似于模板模式。
3. 注解解析成CacheOperation
SpringCacheAnnotationParser#parseCacheAnnotations
static {
CACHE_OPERATION_ANNOTATIONS.add(Cacheable.class);
CACHE_OPERATION_ANNOTATIONS.add(CacheEvict.class);
CACHE_OPERATION_ANNOTATIONS.add(CachePut.class);
CACHE_OPERATION_ANNOTATIONS.add(Caching.class);
}
private Collection<CacheOperation> parseCacheAnnotations(DefaultCacheConfig cachingConfig, AnnotatedElement ae, boolean localOnly) {
//拿到所有的注解
Collection<? extends Annotation> anns = localOnly ? AnnotatedElementUtils.getAllMergedAnnotations(ae, CACHE_OPERATION_ANNOTATIONS) : AnnotatedElementUtils.findAllMergedAnnotations(ae, CACHE_OPERATION_ANNOTATIONS);
if (anns.isEmpty()) {
return null;
} else {
Collection<CacheOperation> ops = new ArrayList(1);
anns.stream().filter((ann) -> {
return ann instanceof Cacheable;
}).forEach((ann) -> {
ops.add(this.parseCacheableAnnotation(ae, cachingConfig, (Cacheable)ann));
});
anns.stream().filter((ann) -> {
return ann instanceof CacheEvict;
}).forEach((ann) -> {
ops.add(this.parseEvictAnnotation(ae, cachingConfig, (CacheEvict)ann));
});
anns.stream().filter((ann) -> {
return ann instanceof CachePut;
}).forEach((ann) -> {
ops.add(this.parsePutAnnotation(ae, cachingConfig, (CachePut)ann));
});
anns.stream().filter((ann) -> {
return ann instanceof Caching;
}).forEach((ann) -> {
this.parseCachingAnnotation(ae, cachingConfig, (Caching)ann, ops);
});
return ops;
}
}
4.配置生效
spring:
cache:
type: redis
RedisCacheConfiguration 上@Conditional({CacheCondition.class})
满足CacheCondition
class CacheCondition extends SpringBootCondition {
CacheCondition() {
}
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
String sourceClass = "";
if (metadata instanceof ClassMetadata) {
sourceClass = ((ClassMetadata)metadata).getClassName();
}
ConditionMessage.Builder message = ConditionMessage.forCondition("Cache", new Object[]{sourceClass});
Environment environment = context.getEnvironment();
try {
//将配置里的值转成枚举中的值
BindResult<CacheType> specified = Binder.get(environment).bind("spring.cache.type", CacheType.class);
if (!specified.isBound()) {
return ConditionOutcome.match(message.because("automatic cache type"));
}
CacheType required = CacheConfigurations.getType(((AnnotationMetadata)metadata).getClassName());
//进行类型的匹配注入
if (specified.get() == required) {
return ConditionOutcome.match(message.because(specified.get() + " cache type"));
}
} catch (BindException var8) {
}
return ConditionOutcome.noMatch(message.because("unknown cache type"));
}
}
5.自定义配置
没有自定义配置会注入默认的配置
对cacheName下的key设置过期时间
public class MyRedisManager extends RedisCacheManager {
public MyRedisManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) {
super(cacheWriter, defaultCacheConfiguration);
}
@Override
protected RedisCache createRedisCache(String name, RedisCacheConfiguration cacheConfig) {
//对cacheName进行分隔,设置过期时间
String[] split = name.split("=");
if (split.length > 1) {
Long time = new Long(split[1]);
cacheConfig = cacheConfig.entryTtl(Duration.ofSeconds(time));
}
return super.createRedisCache(split[0], cacheConfig);
}
}
进行配置
@Configuration
public class RedisConfig {
@Bean
public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
//初始化RedisCacheWriter
RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory);
//序列化方式
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
RedisSerializationContext.SerializationPair<Object> pair = RedisSerializationContext.SerializationPair.fromSerializer(serializer);
RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(pair);
//对key设置过期时间
defaultCacheConfig = defaultCacheConfig.entryTtl(Duration.ofMinutes(30));
//对固定的key设置过期时间
Map<String, RedisCacheConfiguration> map = new HashMap<>();
map.put("user", defaultCacheConfig.entryTtl(Duration.ofMinutes(60)));
return MyRedisManager.builder(redisCacheWriter).cacheDefaults(defaultCacheConfig).build();
}
}
user下的key过期时间为10s
@Cacheable(cacheNames = "user=10",key = "#id")
默认使用的redis数据结构为string,想要使用其他的数据结构需要重写底层实现。还有数据一致性和redis一些其他常见的问题需要考虑。