1、简单分析

  • 以j2cache为切入点,从其使用方式来看,操作j2cache都是通过CacheChannel抽象类。
  • 开源的spring boot2版本的stater中J2CacheSpringCacheAutoConfiguration开启Spring Cache 支持的配置入口,org.springframework.cache.CacheManager接口的实现为:net.oschina.j2cache.cache.support.J2CacheCacheManger。
  • 操作Spring的CacheManager,底层操作的j2cache的CacheChannel。

2、Spring中CacheManager和Cache

从源码看一下这两个接口:CacheManager和Cache。(spring-context 5.3.18与j2cache-core 2.8.0-release)

SPI是JDK提供的一种服务扩展接口,全称为 (Service Provider Interface) 。

以j2cache为例,看一下J2CacheCacheManger与CacheManager、J2CacheCache与Cache的信息。

具体参看:红薯 / J2Cache

2.1、J2CacheCacheManger与CacheManager

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* Spring通用缓存SPI.
*/
public interface CacheManager {
/**
* 返回与给定名称关联的缓存。
* 在J2CacheCacheManger类的实现中,返回的Cache接口的实现类J2CacheCache
*/
Cache getCache(String name);

/**
* 返回此管理器已知的缓存名称的集合。
*/
Collection<String> getCacheNames();
}

2.2、J2CacheCache与Cache

J2CacheCache继承AbstractValueAdaptingCache实现Cache。

在J2CacheCache类中,Cache接口方法的实现都是操作CacheChannel的相关方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
/**
* 定义通用缓存操作的接口
*/
public interface Cache {
/**
* 返回缓存名称
*/
String getName();
/**
*返回底层的缓存提供,在J2CacheCache中,返回的CacheChannel对象
*/
Object getNativeCache();
/**
* 返回此缓存将指定键映射到的值。
*/
ValueWrapper get(Object key);
/**
* 返回此缓存映射指定键的值,一般指定返回值将转换到的类型。
*/
<T> T get(Object key, Class<T> type);
<T> T get(Object key, Callable<T> valueLoader);
/**
* 将指定值与此缓存中的指定键相关联。如果缓存以前包含此键的映射,则旧值将替换为指定值。
*/
void put(Object key, Object value);

ValueWrapper putIfAbsent(Object key, Object value);

/**
* 如果存在此键,从此缓存中逐出该键的映射。
*/
void evict(Object key);

/**
* 从缓存中删除所有映射。
*/
void clear();
/**
* A (wrapper) object representing a cache value.
* AbstractValueAdaptingCache中ValueWrapper接口的实现类为SimpleValueWrapper
*/
interface ValueWrapper {
/**
* Return the actual value in the cache.
*/
Object get();
}
/**
* Wrapper exception to be thrown from {@link #get(Object, Callable)}
* in case of the value loader callback failing with an exception.
* @since 4.3
*/
@SuppressWarnings("serial")
class ValueRetrievalException extends RuntimeException {
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;
}
}

}

3、@CachePut是怎么操作的CacheManager呢?

从下到上:从J2Cache的CacheChannel 到 CacheManager,再从CacheManager 到 @CachePut注解。

  • 搜索org.springframework.cache.annotation.CachePut哪些地方用到了这个注解,可以猜测是:org.springframework.cache.annotation.SpringCacheAnnotationParser(注解解析器)
  • 大概看看SpringCacheAnnotationParser#parsePutAnnotation(AnnotatedElement ae, DefaultCacheConfig defaultConfig, CachePut cachePut)方法,看起来是解析CachePut注解的。
  • 使用构建者模式创建了对象org.springframework.cache.interceptor.CachePutOperation。
  • CachePutOperation继承CacheOperation抽象类,实现BasicOperation接口。
  • 在CacheOperation抽象类中,终于看到了cacheManager,是个String类型的属性,但是怎么和CacheManager接口关联的呢?原来在CachePut注解中有个属性cacheManager:可以自己配置,如果没有就使用默认的SimpleCacheResolver。
1
2
3
4
5
6
7
8
9
10
11
12
public @interface CachePut {

/**
* The bean name of the custom {@link org.springframework.cache.CacheManager} to use to
* create a default {@link org.springframework.cache.interceptor.CacheResolver} if none
* is set already.
* <p>Mutually exclusive with the {@link #cacheResolver} attribute.
* @see org.springframework.cache.interceptor.SimpleCacheResolver
* @see CacheConfig#cacheManager
*/
String cacheManager() default "";
}
  • 找了半天cacheManager是在org.springframework.cache.interceptor.CacheAspectSupport#afterSingletonsInstantiated中配置的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Override
public void afterSingletonsInstantiated() {
if (getCacheResolver() == null) {
// Lazily initialize cache resolver via default cache manager...
Assert.state(this.beanFactory != null, "CacheResolver or BeanFactory must be set on cache aspect");
try {
setCacheManager(this.beanFactory.getBean(CacheManager.class));
}
catch (NoUniqueBeanDefinitionException ex) {
throw new IllegalStateException("No CacheResolver specified, and no unique bean of type " +
"CacheManager found. Mark one as primary or declare a specific CacheManager to use.", ex);
}
catch (NoSuchBeanDefinitionException ex) {
throw new IllegalStateException("No CacheResolver specified, and no bean of type CacheManager found. " +
"Register a CacheManager bean or remove the @EnableCaching annotation from your configuration.", ex);
}
}
this.initialized = true;
}

大概猜一下@CachePut生效原理:SpringAOP通过方法切面,在执行完目标方法后,将结果通过CacheManager操作j2Cache的CacheChannel对象。下面通过两个方面简单了解一下这个猜测。

3.1、工程启动后发生了什么

在使用j2cache-spring-boot2-starter时,忽略了一个细节:@EnableCaching在net.oschina.j2cache.autoconfigure.J2CacheSpringCacheAutoConfiguration配置类上使用,表示:开启spring cache。

1
2
3
4
5
6
7
8
9
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(CachingConfigurationSelector.class)
public @interface EnableCaching {
boolean proxyTargetClass() default false;
AdviceMode mode() default AdviceMode.PROXY;
int order() default Ordered.LOWEST_PRECEDENCE;
}

从源码中可以看出,配置时可以指定代理方式,着重看引入的CachingConfigurationSelector。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class CachingConfigurationSelector extends AdviceModeImportSelector<EnableCaching> {
//...
/**
* Return the imports to use if the {@link AdviceMode} is set to {@link AdviceMode#PROXY}.
* <p>Take care of adding the necessary JSR-107 import if it is available.
*/
private String[] getProxyImports() {
List<String> result = new ArrayList<>(3);
result.add(AutoProxyRegistrar.class.getName());
result.add(ProxyCachingConfiguration.class.getName());
if (jsr107Present && jcacheImplPresent) {
result.add(PROXY_JCACHE_CONFIGURATION_CLASS);
}
return StringUtils.toStringArray(result);
}
//...
}

主要是将AutoProxyRegistrar和ProxyCachingConfiguration加到Spring容器里。

AutoProxyRegistrar就是让EnableCaching注解中的属性mode和proxyTargetClass生效,在Spring AOP中注册代理创建器。

从ProxyCachingConfiguration这个配置类中可以看到CacheInterceptor,而这个CacheInterceptor继承CacheAspectSupport。这样就把上面说的CacheManager串起来了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyCachingConfiguration extends AbstractCachingConfiguration {

@Bean(name = CacheManagementConfigUtils.CACHE_ADVISOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public BeanFactoryCacheOperationSourceAdvisor cacheAdvisor(
CacheOperationSource cacheOperationSource, CacheInterceptor cacheInterceptor) {

BeanFactoryCacheOperationSourceAdvisor advisor = new BeanFactoryCacheOperationSourceAdvisor();
advisor.setCacheOperationSource(cacheOperationSource);
advisor.setAdvice(cacheInterceptor);
if (this.enableCaching != null) {
advisor.setOrder(this.enableCaching.<Integer>getNumber("order"));
}
return advisor;
}

@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public CacheOperationSource cacheOperationSource() {
return new AnnotationCacheOperationSource();
}

@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public CacheInterceptor cacheInterceptor(CacheOperationSource cacheOperationSource) {
CacheInterceptor interceptor = new CacheInterceptor();
interceptor.configure(this.errorHandler, this.keyGenerator, this.cacheResolver, this.cacheManager);
interceptor.setCacheOperationSource(cacheOperationSource);
return interceptor;
}

}

3.2、方法执行时发生了什么

这里就是SpringAOP对方法执行的切面增强了,肯定会走到CacheInterceptor的invoke方法中。SpringAOP的原理太复杂,以后再学,本次只看缓存逻辑。

在org.springframework.cache.interceptor.CacheInterceptor#invoke方法debug分析:

最终进入其父类CacheAspectSupport#execute方法中,这个里就是核心了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
public abstract class CacheAspectSupport extends AbstractCacheInvoker
implements BeanFactoryAware, InitializingBean, SmartInitializingSingleton {
//略
@Nullable
private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {
// Special handling of synchronized invocation
if (contexts.isSynchronized()) {
CacheOperationContext context = contexts.get(CacheableOperation.class).iterator().next();
if (isConditionPassing(context, CacheOperationExpressionEvaluator.NO_RESULT)) {
Object key = generateKey(context, CacheOperationExpressionEvaluator.NO_RESULT);
Cache cache = context.getCaches().iterator().next();
try {
return wrapCacheValue(method, handleSynchronizedGet(invoker, key, cache));
} catch (Cache.ValueRetrievalException ex) {
// Directly propagate ThrowableWrapper from the invoker,
// or potentially also an IllegalArgumentException etc.
ReflectionUtils.rethrowRuntimeException(ex.getCause());
}
} else {
// No caching required, only call the underlying method
return invokeOperation(invoker);
}
}


// Process any early evictions
processCacheEvicts(contexts.get(CacheEvictOperation.class), true,
CacheOperationExpressionEvaluator.NO_RESULT);

// Check if we have a cached item matching the conditions
Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));

// Collect puts from any @Cacheable miss, if no cached item is found
List < CachePutRequest > cachePutRequests = new ArrayList < > ();
if (cacheHit == null) {
collectPutRequests(contexts.get(CacheableOperation.class),
CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);
}

Object cacheValue;
Object returnValue;

if (cacheHit != null && !hasCachePut(contexts)) {
// If there are no put requests, just use the cache hit
cacheValue = cacheHit.get();
returnValue = wrapCacheValue(method, cacheValue);
} else {
// Invoke the method if we don't have a cache hit
returnValue = invokeOperation(invoker);
cacheValue = unwrapReturnValue(returnValue);
}

// Collect any explicit @CachePuts
collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);

// Process any collected put requests, either from @CachePut or a @Cacheable miss
for (CachePutRequest cachePutRequest: cachePutRequests) {
cachePutRequest.apply(cacheValue);
}

// Process any late evictions
processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);

return returnValue;
}
}
private class CachePutRequest {
//略
public void apply(@Nullable Object result) {
if (this.context.canPutToCache(result)) {
for (Cache cache: this.context.getCaches()) {
doPut(cache, this.key, result);
}
}
}
}
public abstract class AbstractCacheInvoker {
//略
protected void doPut(Cache cache, Object key, @Nullable Object result) {
try {
cache.put(key, result);
}
catch (RuntimeException ex) {
getErrorHandler().handleCachePutError(ex, cache, key, result);
}
}
}

看代码的49行,invokeOperation直接执行目标方法了,debug时,能看到控制台输出了方法内的日志打印,而redis中没有缓存数据。

再看58行cachePutRequest.apply(cacheValue),@CachePut中终于要生效了。执行后,再看redis中已经存在缓存数据了,底层就是操作的cache.put(key, result),而这里的cache为net.oschina.j2cache.cache.support.J2CacheCache。

至此,整个流程就通了。

4、@Cacheable操作步骤

从3.2源码中可以很容易看出对CacheableOperation的操作步骤:

  • 首先findCachedItem从缓存中获取值。
  • 存在即返回。
  • 不存在就执行目标方法,并且创建一个CachePutRequest插入返回值到缓存中。

这里分析一下findCachedItem方法,比较感兴趣的是:使用Cacheable时key的拼接:

1
@Cacheable(value = "myJ2CacheRegion", key = "'getUserByAnnotation_'+#id", condition = "#id!=null", unless = "#result==null")

阅读源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
private Cache.ValueWrapper findCachedItem(Collection<CacheOperationContext> contexts) {
Object result = CacheOperationExpressionEvaluator.NO_RESULT;
for (CacheOperationContext context : contexts) {
if (isConditionPassing(context, result)) {
Object key = generateKey(context, result);
Cache.ValueWrapper cached = findInCaches(context, key);
if (cached != null) {
return cached;
}
else {
if (logger.isTraceEnabled()) {
logger.trace("No cache entry for key '" + key + "' in cache(s) " + context.getCacheNames());
}
}
}
}
return null;
}

@Nullable
protected Object generateKey(@Nullable Object result) {
if (StringUtils.hasText(this.metadata.operation.getKey())) {
EvaluationContext evaluationContext = createEvaluationContext(result);
return evaluator.key(this.metadata.operation.getKey(), this.metadata.methodKey, evaluationContext);
}
return this.metadata.keyGenerator.generate(this.target, this.metadata.method, this.args);
}

走到CacheOperationExpressionEvaluator#key方法中,最底层使用org.springframework.expression.spel.ast.SpelNodeImpl#getValue(org.springframework.expression.spel.ExpressionState)方法,即SpEL表达式的解析,这个对于使用自定义注解有很大的用处。

参考

https://my.oschina.net/xiaoqiyiye?tab=newest&catalogId=5742361

https://blog.csdn.net/weixin_42189048/category_11605926.html

https://blog.csdn.net/f641385712/article/details/94562018

https://blog.csdn.net/CrossLimit/article/details/123839598