SpringCache源码分析,你学会了吗?

2023年 8月 26日 37.6k 0

1、入口说明

@EnableCaching是开启SpringCache的一个总开关,开启时候我们的缓存相关注解才会生效,所以我们@EnableCaching开始作为入口进行分析,

2、分析@EnableCaching注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(CachingConfigurationSelector.class) // 这里有一个Import,导入了一个Selector类
public @interface EnableCaching {

    // 是否创建cglib代理,默认为false, 也就是使用jdk动态代理
    boolean proxyTargetClass() default false;

    // 增强模式 默认使用JDK动态代理,引入cglib可以使用ASPECTJ
    AdviceMode mode() default AdviceMode.PROXY;

    // 排序字段
    int order() default Ordered.LOWEST_PRECEDENCE;

}

2.1、分析导入的CachingConfigurationSelector类

public class CachingConfigurationSelector extends AdviceModeImportSelector {

    // ...此处省略一万行代码

    // CachingConfigurationSelector继承了AdviceModeImportSelector, 而AdviceModeImportSelector又实现了ImportSelector
    // 所以我们实现类selectImports,用于返回要导入的配置类列表
    @Override
    public String[] selectImports(AdviceMode adviceMode) {
        // 如果是jdk动态代理,走getProxyImports逻辑。如果是cglib动态代理,走getAspectJImports逻辑
        switch (adviceMode) {
            case PROXY:
                return getProxyImports();
            case ASPECTJ:
                return getAspectJImports();
            default:
                return null;
        }
    }

    // 获取要进行自动配置的配置类
    private String[] getProxyImports() {
        List result = new ArrayList(3);
        // 这里添加了两个类,AutoProxyRegistrar(自动代理注册器),ProxyCachingConfiguration(代理缓存配置类)
        // AutoProxyRegistrar点进去可以发现,里面其实就是提供了registerBeanDefinitions方法用于注册BeanDefinition
        result.add(AutoProxyRegistrar.class.getName());
        // ProxyCachingConfiguration点进去发现,配置类缓存相关的一些Bean(就是SpringCache的一些核心Bean)
        result.add(ProxyCachingConfiguration.class.getName());
        if (jsr107Present && jcacheImplPresent) {
            result.add(PROXY_JCACHE_CONFIGURATION_CLASS);
        }
        return StringUtils.toStringArray(result);
    }

    // ...此处省略一万行代码
}

CachingConfigurationSelector继承了AdviceModeImportSelector, 而AdviceModeImportSelector又实现了ImportSelector,所以我们实现了selectImports方法,用于返回要导入的配置类列表.

selectImports会去判断,如果是jdk动态代理,走getProxyImports逻辑。如果是cglib动态代理,走getAspectJImports逻辑。

我们直接关注JDK动态代理的方法getProxyImports。这里面添加了两个类AutoProxyRegistrar和ProxyCachingConfiguration。

AutoProxyRegistrar点进去可以发现,里面其实就是提供了registerBeanDefinitions方法用于注册BeanDefinition。

ProxyCachingConfiguration点进去发现,配置类缓存相关的一些Bean(就是SpringCache的一些核心Bean),所以我们会重点关注ProxyCachingConfiguration并着重分析。

2.1.1、分析ProxyCachingConfiguration配置类

@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyCachingConfiguration extends AbstractCachingConfiguration {

    // BeanFactoryCacheOperationSourceAdvisor是对CacheOperationSource进行增强,其实就是添加一个拦截器,用于获取相关缓存的注解信息
    // 所以有些逻辑会在CacheInterceptor里
    @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.getNumber("order"));
        }
        return advisor;
    }

    // 定义一个CacheOperationSource,主要用于获取类或者方法上的注解。
    @Bean
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public CacheOperationSource cacheOperationSource() {
        return new AnnotationCacheOperationSource();
    }

    // 定义了一个拦截器,该拦截器用于用于拦截缓存相关注解,做AOP操作。比如先查询缓存,查询到直接返回,查询不到就执行方法体,将结果写入缓存。
    @Bean
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public CacheInterceptor cacheInterceptor(CacheOperationSource cacheOperationSource) {
        CacheInterceptor interceptor = new CacheInterceptor();
        // 缓存拦截器在这里注入了cacheManager(缓存管理器)
        interceptor.configure(this.errorHandler, this.keyGenerator, this.cacheResolver, this.cacheManager);
        interceptor.setCacheOperationSource(cacheOperationSource);
        return interceptor;
    }

}

来分析一下BeanFactoryCacheOperationSourceAdvisor

public class BeanFactoryCacheOperationSourceAdvisor extends AbstractBeanFactoryPointcutAdvisor {
    @Nullable
    private CacheOperationSource cacheOperationSource;

    // 定义我们自己的切点,缓存操作切点
    private final CacheOperationSourcePointcut pointcut = new CacheOperationSourcePointcut() {
        // 该切点存在一个方法,获取CacheOperationSource(获取切点的那些注解操作)。
        @Override
        @Nullable
        protected CacheOperationSource getCacheOperationSource() {
            return cacheOperationSource;
        }
    };
    
    // 使用该方法设置CacheOperationSource,在上一层有设置advisor.setCacheOperationSource(cacheOperationSource);
    // 把这个数据塞入BeanFactoryCacheOperationSourceAdvisor, 以便于在自定义的切点类CacheOperationSourcePointcut中可以获取
    public void setCacheOperationSource(CacheOperationSource cacheOperationSource) {
        this.cacheOperationSource = cacheOperationSource;
    }
    
    // 设置ClassFilter到CacheOperationSourcePointcut
    public void setClassFilter(ClassFilter classFilter) {
        this.pointcut.setClassFilter(classFilter);
    }

    // 重写getPointcut。也就是获取切点的方法,因为需要对切点进行增强
    @Override
    public Pointcut getPointcut() {
        return this.pointcut;
    }
}

BeanFactoryCacheOperationSourceAdvisor继承了AbstractBeanFactoryPointcutAdvisor,重写了Pointcut getPointcut()方法。

使用自定义的切点类CacheOperationSourcePointcut来作为切面的切点。而里面需要用到CacheOperationSource和ClassFilter。在BeanFactoryCacheOperationSourceAdvisor实例化时就已经设置。

而上面又执行了advisor.setAdvice(cacheInterceptor); 其实就是对这个切点添加了一个缓存拦截器,所以核心逻辑就在拦截器里面。

先再来看一下AnnotationCacheOperationSource

public class AnnotationCacheOperationSource extends AbstractFallbackCacheOperationSource implements Serializable {

    private final boolean publicMethodsOnly;

    // 缓存注解解析集合
    private final Set annotationParsers;
    
    public AnnotationCacheOperationSource() {
        this(true);
    }
    
    public AnnotationCacheOperationSource(boolean publicMethodsOnly) {
        this.publicMethodsOnly = publicMethodsOnly;
        // 重点:解析集合从SpringCacheAnnotationParser中获取,这个解析类就是解析注解的核心
        this.annotationParsers = Collections.singleton(new SpringCacheAnnotationParser());
    }
    
    // ...此处省略一万行代码

    // 判断是否时候选类
    @Override
    public boolean isCandidateClass(Class targetClass) {
        for (CacheAnnotationParser parser : this.annotationParsers) {
            if (parser.isCandidateClass(targetClass)) {
                return true;
            }
        }
        return false;
    }

    // 重点:查找类级别的CacheOperation列表,就是看标注在类上的@Cacheable,@CacheEvict的集合
    @Override
    @Nullable
    protected Collection findCacheOperations(Class clazz) {
        return determineCacheOperations(parser -> parser.parseCacheAnnotations(clazz));
    }

    // 重点:查找方法级别的CacheOperation列表,就是看标注在方法上的@Cacheable,@CacheEvict的集合
    @Override
    @Nullable
    protected Collection findCacheOperations(Method method) {
        return determineCacheOperations(parser -> parser.parseCacheAnnotations(method));
    }
    
    // ...此处省略一万行代码
}

接着看一下SpringCacheAnnotationParser

public class SpringCacheAnnotationParser implements CacheAnnotationParser, Serializable {

    private static final Set targetClass = getTargetClass(target);
            CacheOperationSource cacheOperationSource = getCacheOperationSource();
            if (cacheOperationSource != null) {
                // 通过CacheOperationSource,获取所有的CacheOperation列表(就是那一堆标有缓存注解的类和方法的集合)
                Collection operations = cacheOperationSource.getCacheOperations(method, targetClass);
                if (!CollectionUtils.isEmpty(operations)) {
                    // 调用重载的execute方法
                    return execute(invoker, method,
                            new CacheOperationContexts(operations, method, args, target, targetClass));
                }
            }
        }

        // 否则,执行原方法返回即可
        return invoker.invoke();
    }


    // 执行方法(核心)
    @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);
            }
        }

        // 如果存在@CacheEvict注解、并且标记为在调用前执行,调用processCacheEvicts进行缓存清除操作
        processCacheEvicts(contexts.get(CacheEvictOperation.class), true,
                CacheOperationExpressionEvaluator.NO_RESULT);

        // 如果存在Cacheable注解、调用findCachedItem查询缓存
        Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));

        // 如果没有命中缓存,则调用cachePutRequests,存储在List中,后续执行原始方法后会写入缓存
        List cachePutRequests = new ArrayList();
        if (cacheHit == null) {
            collectPutRequests(contexts.get(CacheableOperation.class),
                    CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);
        }

        Object cacheValue;
        Object returnValue;

        // 如果缓存命中且没有@CachePut注解,使用缓存的值作为返回值
        if (cacheHit != null && !hasCachePut(contexts)) {
            // If there are no put requests, just use the cache hit
            cacheValue = cacheHit.get();
            returnValue = wrapCacheValue(method, cacheValue);
        }
        // 缓存没有命中或者有@CachePut注解
        else {
            // 调用原始方法作为返回值
            returnValue = invokeOperation(invoker);
            // 将原始方法的返回值作为缓存值
            cacheValue = unwrapReturnValue(returnValue);
        }

        // 如果有@CachePut注解,则新增到cachePutRequests
        collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);

        // 缓存未命中或者存在@CachePut注解,调用CachePutRequest的apply方法将数据写入缓存
        for (CachePutRequest cachePutRequest : cachePutRequests) {
            cachePutRequest.apply(cacheValue);
        }

        // 如果有@CacheEvict注解,并且标记为在调用后执行,则还需要执行清除缓存操作
        processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);

        return returnValue;
    }
    // 此处省略一万行代码
}

总结来说,

  • 如果存在@CacheEvict注解,并且标记在方法执行前执行,就执行清除缓存相关操作。
  • 使用findCachedItem获取缓存,缓存没有命中,加入collectPutRequests,后续进行写入缓存操作。
  • 如果命中缓存并且没有@CachePut注解,获取命中的值作为方法的返回值
  • 如果没有命中,或者包含了@CachePut注解,加入collectPutRequests,后续进行写入缓存操作。
  • 遍历cachePutRequests,将需要写入缓存的数据写入缓存
  • 如果存在@CacheEvict注解,并且标记在方法执行后执行,就执行清除缓存相关操作。

还没完呢,因为我们定义的CacheManager怎么没有用到呢?我们继续跟踪下去,以get缓存方法为例子分析。

关注findCachedItem获取缓存方法

@Nullable
private Cache.ValueWrapper findCachedItem(Collection contexts) {
    // 遍历上下文列表
    Object result = CacheOperationExpressionEvaluator.NO_RESULT;
    for (CacheOperationContext context : contexts) {
        if (isConditionPassing(context, result)) {
            Object key = generateKey(context, result);
            // 根据生成的key获取缓存值
            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;
}

关注findInCaches获取缓存方法

@Nullable
private Cache.ValueWrapper findInCaches(CacheOperationContext context, Object key) {
    // 遍历缓存集合(getCaches),使用缓存的key去和获取缓存
    for (Cache cache : context.getCaches()) {
        // 最终是使用Cache接口的get方法去获取缓存的
        Cache.ValueWrapper wrapper = doGet(cache, key);
        if (wrapper != null) {
            if (logger.isTraceEnabled()) {
                logger.trace("Cache entry for key '" + key + "' found in cache '" + cache.getName() + "'");
            }
            return wrapper;
        }
    }
    return null;
}

关注doGet获取缓存方法

@Nullable
protected Cache.ValueWrapper doGet(Cache cache, Object key) {
    try {
        return cache.get(key);
    }
    catch (RuntimeException ex) {
        getErrorHandler().handleCacheGetError(ex, cache, key);
        return null;  // If the exception is handled, return a cache miss
    }
}

我们发现,最终是通过Cache接口的get方法去获取缓存的,那么我们只要知道Cache集合对象是在哪里传入进来的就清楚了整个逻辑。

重新回到execute方法

@Nullable
protected Object execute(CacheOperationInvoker invoker, Object target, Method method, Object[] args) {
    // Check whether aspect is enabled (to cope with cases where the AJ is pulled in automatically)
    if (this.initialized) {
        Class targetClass = getTargetClass(target);
        CacheOperationSource cacheOperationSource = getCacheOperationSource();
        if (cacheOperationSource != null) {
            Collection operations = cacheOperationSource.getCacheOperations(method, targetClass);
            if (!CollectionUtils.isEmpty(operations)) {
                // 这里创建了一个CacheOperationContexts,我们有理由猜测CacheOperationContext.getCaches方法就是在这里面
                return execute(invoker, method,
                        new CacheOperationContexts(operations, method, args, target, targetClass));
            }
        }
    }

    return invoker.invoke();
}

跟踪CacheOperationContexts

private class CacheOperationContexts {
// 就是一个CacheOperationContext的集合,key是CacheOperation或者其子类
private final MultiValueMap

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论