一.pom文件引入依赖
这里我项目用的是caffeine的cache
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
二.源码分析
入口
@Configuration
@EnableCaching
@Configuration
@EnableCaching
public class CacheConfiguration {
}
ConfigurationClassParser.class
解析@Configuration,processImports()方法处理@Import注解
/**
* Parses a {@link Configuration} class definition, populating a collection of
* {@link ConfigurationClass} objects (parsing a single Configuration class may result in
* any number of ConfigurationClass objects because one Configuration class may import
* another using the {@link Import} annotation).
*
* <p>This class helps separate the concern of parsing the structure of a Configuration
* class from the concern of registering BeanDefinition objects based on the content of
* that model (with the exception of {@code @ComponentScan} annotations which need to be
* registered immediately).
*
* <p>This ASM-based implementation avoids reflection and eager class loading in order to
* interoperate effectively with lazy class loading in a Spring ApplicationContext.
*
*/
class ConfigurationClassParser {
// Process any @Import annotations
processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
/**
* Returns {@code @Import} class, considering all meta-annotations.
*/
private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {
Set<SourceClass> imports = new LinkedHashSet<>();
Set<SourceClass> visited = new LinkedHashSet<>();
collectImports(sourceClass, imports, visited);
return imports;
}
/**
* Recursively collect all declared {@code @Import} values. Unlike most
* meta-annotations it is valid to have several {@code @Import}s declared with
* different values; the usual process of returning values from the first
* meta-annotation on a class is not sufficient.
* <p>For example, it is common for a {@code @Configuration} class to declare direct
* {@code @Import}s in addition to meta-imports originating from an {@code @Enable}
* annotation.
* @param sourceClass the class to search
* @param imports the imports collected so far
* @param visited used to track visited classes to prevent infinite recursion
* @throws IOException if there is any problem reading metadata from the named class
*/
private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited)
throws IOException {
if (visited.add(sourceClass)) {
for (SourceClass annotation : sourceClass.getAnnotations()) {
String annName = annotation.getMetadata().getClassName();
if (!annName.equals(Import.class.getName())) {
collectImports(annotation, imports, visited);
}
}
imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
}
}
}
2.@EnableCaching
主要看这个@Import(CachingConfigurationSelector.class),将CachingConfigurationSelector.class选择器引进来
package org.springframework.cache.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.AdviceMode;
import org.springframework.context.annotation.Import;
import org.springframework.core.Ordered;
@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;
}
3.CachingConfigurationSelector.class
selectImports里面的adviceMode默认走的是PROXY,可以看
AdviceModeImportSelector.class
AdviceMode adviceMode = attributes.getEnum(getAdviceModeAttributeName());
getProxyImports() 注册了两个Bean
AutoProxyRegistrar.class
ProxyCachingConfiguration.class
因为spring缓存抽象是基于注解(比如@Cacheable、@CacheEvict等)实现的,所以会在实际运行的时候生成代理类
public class CachingConfigurationSelector extends AdviceModeImportSelector<EnableCaching> {
/**
* Returns {@link ProxyCachingConfiguration} or {@code AspectJCachingConfiguration}
* for {@code PROXY} and {@code ASPECTJ} values of {@link EnableCaching#mode()},
* respectively. Potentially includes corresponding JCache configuration as well.
*/
@Override
public String[] selectImports(AdviceMode adviceMode) {
//adviceMode 默认走的是PROXY
switch (adviceMode) {
case PROXY:
return getProxyImports();
case ASPECTJ:
return getAspectJImports();
default:
return null;
}
}
/**
* 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);
}
}
4.ProxyCachingConfiguration.class
供了BeanFactoryCacheOperationSourceAdvisor 、CacheOperationSource 、CacheInterceptor 三个Bean
package org.springframework.cache.annotation;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.cache.config.CacheManagementConfigUtils;
import org.springframework.cache.interceptor.BeanFactoryCacheOperationSourceAdvisor;
import org.springframework.cache.interceptor.CacheInterceptor;
import org.springframework.cache.interceptor.CacheOperationSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Role;
/**
* {@code @Configuration} class that registers the Spring infrastructure beans necessary
* to enable proxy-based annotation-driven cache management.
*
* @see EnableCaching
* @see CachingConfigurationSelector
*/
@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;
}
}
5.CacheInterceptor.class
重点关注这个CacheInterceptor.class,它对于@Cacheable,@CachePut,@CacheEvict等注解做一个拦截aop切面操作
Object target = invocation.getThis();通过代理创建并获取了这个对象
Method method = invocation.getMethod();获取了需要进行切面的方法
执行execute(aopAllianceInvoker, target, method, invocation.getArguments());
注:至于这里为什么能够拿到pointcut,挖一个坑,后续可以研究aop的源码
package org.springframework.cache.interceptor;
import java.io.Serializable;
import java.lang.reflect.Method;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* AOP Alliance MethodInterceptor for declarative cache
* management using the common Spring caching infrastructure
* ({@link org.springframework.cache.Cache}).
*
* <p>Derives from the {@link CacheAspectSupport} class which
* contains the integration with Spring's underlying caching API.
* CacheInterceptor simply calls the relevant superclass methods
* in the correct order.
*
* <p>CacheInterceptors are thread-safe.
*
*/
@SuppressWarnings("serial")
public class CacheInterceptor extends CacheAspectSupport implements MethodInterceptor, Serializable {
@Override
@Nullable
public Object invoke(final MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod();
CacheOperationInvoker aopAllianceInvoker = () -> {
try {
return invocation.proceed();
}
catch (Throwable ex) {
throw new CacheOperationInvoker.ThrowableWrapper(ex);
}
};
Object target = invocation.getThis();
Assert.state(target != null, "Target must not be null");
try {
return execute(aopAllianceInvoker, target, method, invocation.getArguments());
}
catch (CacheOperationInvoker.ThrowableWrapper th) {
throw th.getOriginal();
}
}
}
6.CacheAspectSupport.class
public abstract class CacheAspectSupport extends AbstractCacheInvoker
implements BeanFactoryAware, InitializingBean, SmartInitializingSingleton {
@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<CacheOperation> operations = cacheOperationSource.getCacheOperations(method, targetClass);
if (!CollectionUtils.isEmpty(operations)) {
return execute(invoker, method,
new CacheOperationContexts(operations, method, args, target, targetClass));
}
}
}
return invoker.invoke();
}
public CacheOperationContexts(Collection<? extends CacheOperation> operations, Method method,
Object[] args, Object target, Class<?> targetClass) {
this.contexts = new LinkedMultiValueMap<>(operations.size());
for (CacheOperation op : operations) {
this.contexts.add(op.getClass(), getOperationContext(op, method, args, target, targetClass));
}
this.sync = determineSyncFlag(method);
}
protected CacheOperationContext getOperationContext(
CacheOperation operation, Method method, Object[] args, Object target, Class<?> targetClass) {
CacheOperationMetadata metadata = getCacheOperationMetadata(operation, method, targetClass);
return new CacheOperationContext(metadata, args, target);
}
/**
* Return the {@link CacheOperationMetadata} for the specified operation.
* <p>Resolve the {@link CacheResolver} and the {@link KeyGenerator} to be
* used for the operation.
* @param operation the operation
* @param method the method on which the operation is invoked
* @param targetClass the target type
* @return the resolved metadata for the operation
*/
protected CacheOperationMetadata getCacheOperationMetadata(
CacheOperation operation, Method method, Class<?> targetClass) {
CacheOperationCacheKey cacheKey = new CacheOperationCacheKey(operation, method, targetClass);
CacheOperationMetadata metadata = this.metadataCache.get(cacheKey);
if (metadata == null) {
KeyGenerator operationKeyGenerator;
if (StringUtils.hasText(operation.getKeyGenerator())) {
operationKeyGenerator = getBean(operation.getKeyGenerator(), KeyGenerator.class);
}
else {
operationKeyGenerator = getKeyGenerator();
}
CacheResolver operationCacheResolver;
if (StringUtils.hasText(operation.getCacheResolver())) {
operationCacheResolver = getBean(operation.getCacheResolver(), CacheResolver.class);
}
else if (StringUtils.hasText(operation.getCacheManager())) {
CacheManager cacheManager = getBean(operation.getCacheManager(), CacheManager.class);
operationCacheResolver = new SimpleCacheResolver(cacheManager);
}
else {
operationCacheResolver = getCacheResolver();
Assert.state(operationCacheResolver != null, "No CacheResolver/CacheManager set");
}
metadata = new CacheOperationMetadata(operation, method, targetClass,
operationKeyGenerator, operationCacheResolver);
this.metadataCache.put(cacheKey, metadata);
}
return metadata;
}
@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;
}
}
构建了一个CacheOperationMetadata对象
new CacheOperationContexts(operations, method, args, target, targetClass));->
getOperationContext(op, method, args, target, targetClass));->
getCacheOperationMetadata(operation, method, targetClass);->
return metadata;
protected static class CacheOperationMetadata {
private final CacheOperation operation;
private final Method method;
private final Class<?> targetClass;
private final Method targetMethod;
private final AnnotatedElementKey methodKey;
private final KeyGenerator keyGenerator;
private final CacheResolver cacheResolver;
public CacheOperationMetadata(CacheOperation operation, Method method, Class<?> targetClass,
KeyGenerator keyGenerator, CacheResolver cacheResolver) {
this.operation = operation;
this.method = BridgeMethodResolver.findBridgedMethod(method);
this.targetClass = targetClass;
this.targetMethod = (!Proxy.isProxyClass(targetClass) ?
AopUtils.getMostSpecificMethod(method, targetClass) : this.method);
this.methodKey = new AnnotatedElementKey(this.targetMethod, targetClass);
this.keyGenerator = keyGenerator;
this.cacheResolver = cacheResolver;
}
}
优先处理@CacheEvict的beforeInvocation属性为true
processCacheEvicts(contexts.get(CacheEvictOperation.class), true,
CacheOperationExpressionEvaluator.NO_RESULT);
再检查@Cacheable注解,通过key去cache里面找是否有符合条件的缓存
Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));
然后重点关注一下findCachedItem里面的generateKey
有key属性:用SpEL解析key
没有key属性:否则走自定义的keyGenerator,默认是使用SimpleKeyGenerator创建key
/**
* Find a cached item only for {@link CacheableOperation} that passes the condition.
* @param contexts the cacheable operations
* @return a {@link Cache.ValueWrapper} holding the cached item,
* or {@code null} if none is found
*/
@Nullable
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;
}
/**
* Compute the key for the given caching operation.
*/
@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);
}
public class SimpleKeyGenerator implements KeyGenerator {
@Override
public Object generate(Object target, Method method, Object... params) {
return generateKey(params);
}
/**
* Generate a key based on the specified parameters.
*/
public static Object generateKey(Object... params) {
if (params.length == 0) {
return SimpleKey.EMPTY;
}
if (params.length == 1) {
Object param = params[0];
if (param != null && !param.getClass().isArray()) {
return param;
}
}
return new SimpleKey(params);
}
}
收集@Cacheable注解,假如cacheHit没找到符合条件的cache,@Cacheable相当于第一次进入,等同于@CachePut用collectPutRequests进行收集(new CachePutRequest,add 进List<CachePutRequest> cachePutRequests;)
collectPutRequests(contexts.get(CacheableOperation.class),CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);
如果能找到cache(相当于第二次访问),且注解不为@CachePut就直接通过cache.get()拿到value值
if (cacheHit != null && !hasCachePut(contexts)) {
// If there are no put requests, just use the cache hit
cacheValue = cacheHit.get();
returnValue = wrapCacheValue(method, cacheValue);
}
再收集@CachePut注解(new CachePutRequest,add 进List<CachePutRequest> cachePutRequests;)
collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);
然后将cacheValue存进CacheOperationContext.cache里面
注:cache里面的对象用哪一个就看引入的第三方组件,我这里是CaffeineCache,默认使用ConcurrentHashMap
cachePutRequest.apply(cacheValue);
最后再处理@CacheEvict注解,CacheOperationContext.cache里面清除缓存
processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);
三.拓展
1.SimpleCacheConfiguration.class
springboot提供了默认的Manager:ConcurrentMapCacheManager
配置类为SimpleCacheConfiguration
但是这个Bean的创建需要依赖CacheProperties,CacheManagerCustomizers
然后我们可以去看springboot提供的自动配置类为CacheAutoConfiguration
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class SimpleCacheConfiguration {
@Bean
ConcurrentMapCacheManager cacheManager(CacheProperties cacheProperties,
CacheManagerCustomizers cacheManagerCustomizers) {
ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
List<String> cacheNames = cacheProperties.getCacheNames();
if (!cacheNames.isEmpty()) {
cacheManager.setCacheNames(cacheNames);
}
return cacheManagerCustomizers.customize(cacheManager);
}
}
2.CacheAutoConfiguration.class
@EnableConfigurationProperties(CacheProperties.class)
@ConditionalOnMissingBean
自动配置类,会被自动加载
注意:@ConditionalOnBean(CacheAspectSupport.class)
CacheAutoConfiguration想生效必须依赖CacheAspectSupport,
可以看回我们之前的CacheInterceptor.class是继承CacheAspectSupport.class的
/**
* {@link EnableAutoConfiguration Auto-configuration} for the cache abstraction. Creates a
* {@link CacheManager} if necessary when caching is enabled via
* {@link EnableCaching @EnableCaching}.
* <p>
* Cache store can be auto-detected or specified explicitly through configuration.
*
* @author Stephane Nicoll
* @since 1.3.0
* @see EnableCaching
*/
@AutoConfiguration(after = { CouchbaseDataAutoConfiguration.class, HazelcastAutoConfiguration.class,
HibernateJpaAutoConfiguration.class, RedisAutoConfiguration.class })
@ConditionalOnClass(CacheManager.class)
@ConditionalOnBean(CacheAspectSupport.class)
@ConditionalOnMissingBean(value = CacheManager.class, name = "cacheResolver")
@EnableConfigurationProperties(CacheProperties.class)
@Import({ CacheConfigurationImportSelector.class, CacheManagerEntityManagerFactoryDependsOnPostProcessor.class })
public class CacheAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public CacheManagerCustomizers cacheManagerCustomizers(ObjectProvider<CacheManagerCustomizer<?>> customizers) {
return new CacheManagerCustomizers(customizers.orderedStream().collect(Collectors.toList()));
}
}
四.总结
有了EnableCaching,才有了CacheInterceptor
有了CacheInterceptor,CacheAutoConfiguration就会生效
CacheAutoConfiguration会提供CacheProperties和CacheManagerCustomizers
有了CacheProperties和CacheManagerCustomizers,ConcurrentMapCacheManager就会生效
@Cacheable,@CacheEvict,@CachePut可以作用在同一个方法或者不同方法上面
文章有些内容引用了https://blog.csdn.net/xl_1803/article/details/110802398,如有侵犯可联系本人进行删除,感谢阅读🙇