@EnableCaching如何一键开启缓存

news2024/10/7 20:29:52

@EnableCaching如何一键开启缓存

  • 手动挡
    • CacheManager
    • Cache
    • 使用演示
    • 小结
  • 自动挡
    • CachingConfigurationSelector
      • AutoProxyRegistrar
      • ProxyCachingConfiguration
        • CacheOperationSource
        • CacheOperation
        • BeanFactoryCacheOperationSourceAdvisor
        • CacheInterceptor
  • 小结


手动挡

我们首先来看看Spring对缓存模块的抽象体系:

在这里插入图片描述

  • CacheManager:缓存管理器。管理各种缓存(Cache)组件
  • Cache:为缓存的组件规范定义,包含缓存的各种操作集合。比如它有很多实现:ConcurrentMapCache、RedisCache、EhCacheCache(额外导包)

Spring并没有提供缓存过期的处理,当然,后续我会给出具体的解决方案。


CacheManager

public interface CacheManager {
    //一个cacheName对应一个Cache
	Cache getCache(String name);
	//返回所有cacheNames
	Collection<String> getCacheNames();
}

CacheManager接口的子类如下:
在这里插入图片描述

  • Caffeine,EnCache,JCacheManager底层采用了不同第三方实现,这里不多叙述。
  • NoOpCacheManager用于禁用缓存。
  • SimpleCacheManager通过ConcurrentHashMap集合来存放cacheName到Cache的映射关系。
  • CompositeCacheManager内部保管了多个CacheManager,当要通过cacheName获取某个Cache时,会依次遍历CacheManager集合中每个CacheManager,只要其中一个返回的Cache不为null,那么就结束遍历,返回结果。
  • 含有Transaction的CacheManager用于控制是否需要在当前事务提交成功时,再进行缓存的添加和清除工作,本质是通过TransactionSynchronizationManager.registerSynchronization注册事务提交成功的监听器来完成的。

ConcurrentMapCacheManager是Spring Cache默认提供的缓基于内存的实现方案,我们来看看它具体是实现的:

public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderAware {
    //map存放映射关系
	private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<>(16);
	//该参数用于控制如果getCache时,对应的Cache不存在,是否自动创建Cache
	private boolean dynamic = true;
    //是否允许往缓存中添加null值,如果允许,那么null值会被进行特殊包装处理,方便在从缓存中获取数据时进行鉴别
	private boolean allowNullValues = true;
    //是存储副本还是引用
	private boolean storeByValue = false;
	
	...
    //如果我们手动设置了cacheNames,那么会关闭自动创建Cache的功能
	public void setCacheNames(@Nullable Collection<String> cacheNames) {
		if (cacheNames != null) {
			for (String name : cacheNames) {
			    //自动创建好对应的Cache
				this.cacheMap.put(name, createConcurrentMapCache(name));
			}
			this.dynamic = false;
		}
		else {
			this.dynamic = true;
		}
	}
	
	public Cache getCache(String name) {
		Cache cache = this.cacheMap.get(name);
		//如果cacheName对应的cache不存在,并且开启了自动创建cache功能
		//那么就自动创建cache
		if (cache == null && this.dynamic) {
			synchronized (this.cacheMap) {
				cache = this.cacheMap.get(name);
				if (cache == null) {
					cache = createConcurrentMapCache(name);
					this.cacheMap.put(name, cache);
				}
			}
		}
		return cache;
	}

	protected Cache createConcurrentMapCache(String name) {
	     //如果决定缓存中存储的是副本的话,那么需要 SerializationDelegate来完成深拷贝工作。
		SerializationDelegate actualSerialization = (isStoreByValue() ? this.serialization : null);
		return new ConcurrentMapCache(name, new ConcurrentHashMap<>(256), isAllowNullValues(), actualSerialization);
	}
}

Cache

public interface Cache {
	String getName();
	// 返回未被包装的缓存值
	Object getNativeCache();
	// 就是用下面的ValueWrapper把值包装了一下而已~
	@Nullable
	ValueWrapper get(Object key);
	@Nullable
	<T> T get(Object key, @Nullable Class<T> type);
	@Nullable
	<T> T get(Object key, Callable<T> valueLoader);
	
	void put(Object key, @Nullable Object value);

	// 不存在旧值直接put就先去了返回null,否则返回旧值(并且不会把新值put进去)
	@Nullable
	ValueWrapper putIfAbsent(Object key, @Nullable Object value);
	// 删除
	void evict(Object key);
	// 清空
	void clear();


	@FunctionalInterface
	interface ValueWrapper {
		@Nullable
		Object get();
	}
}

Cache的继承树也非常的简单:
在这里插入图片描述
我们先来看看AbstractValueAdaptingCache的实现:

// @since 4.2.2 出现得还是挺晚的~~~
public abstract class AbstractValueAdaptingCache implements Cache {
	//是否需要缓存NULL值
	private final boolean allowNullValues;
	...
	// lookup为抽象方法
	@Override
	@Nullable
	public ValueWrapper get(Object key) {
		Object value = lookup(key);
		return toValueWrapper(value);
	}
	@Nullable
	protected abstract Object lookup(Object key);

	// lookup出来的value继续交给fromStoreValue()处理~  其实就是对null值进行了处理
	// 若是null值就返回null,而不是具体的值了~~~
	@Override
	@SuppressWarnings("unchecked")
	@Nullable
	public <T> T get(Object key, @Nullable Class<T> type) {
		Object value = fromStoreValue(lookup(key));
		if (value != null && type != null && !type.isInstance(value)) {
			throw new IllegalStateException("Cached value is not of required type [" + type.getName() + "]: " + value);
		}
		return (T) value;
	}

	// 它是protected 方法  子类有复写
	@Nullable
	protected Object fromStoreValue(@Nullable Object storeValue) {
		if (this.allowNullValues && storeValue == NullValue.INSTANCE) {
			return null;
		}
		return storeValue;
	}

	// 提供给子类使用的方法,对null值进行转换~  子类有复写
	protected Object toStoreValue(@Nullable Object userValue) {
		if (userValue == null) {
			if (this.allowNullValues) {
				return NullValue.INSTANCE;
			}
			throw new IllegalArgumentException("Cache '" + getName() + "' is configured to not allow null values but null was provided");
		}
		return userValue;
	}

	// 把value进行了一层包装为SimpleValueWrapper
	@Nullable
	protected Cache.ValueWrapper toValueWrapper(@Nullable Object storeValue) {
		return (storeValue != null ? new SimpleValueWrapper(fromStoreValue(storeValue)) : null);
	}
}

显然该类是后来(Spring4.2.2)插入进来的专门对null值进行的处理。它提供了通用实现,来适配null值的问题。若你自定义Cache的实现,建议继承自此抽象类。


ConcurrentMapCache实现:

public class ConcurrentMapCache extends AbstractValueAdaptingCache {
	//当前cache对应的cacheName
	private final String name;
    //使用map集合作为缓存存储地方
	private final ConcurrentMap<Object, Object> store;
	//如果缓存中要求保存副本,则通过序列化器来完成深拷贝
	private final SerializationDelegate serialization;
	...
	// 这是父类的抽象方法
	@Override
	protected Object lookup(Object key) {
		return this.store.get(key);
	}
	
	@Override
	public void put(Object key, @Nullable Object value) {
		this.store.put(key, toStoreValue(value));
	}
	
	@Override
	public void evict(Object key) {
		this.store.remove(key);
	}
	
	@Override
	public void clear() {
		this.store.clear();
	}
	...
}

ConcurrentMapCache它是spring-context提供的内建唯一缓存实现,它是完全基于本地内存的。

Springboot默认使用的是SimpleCacheConfiguration,它配置的是ConcurrentMapCacheManager来实现缓存,因此对应Cache实现为ConcurrentMapCache


使用演示

    @Test
    public void test(){
        CacheManager cacheManager = new ConcurrentMapCacheManager();
        // 即使我们上面没有放进去名字为car的Cache,此处也会帮我们自动生成
        Cache carCache = cacheManager.getCache("car");
        // 向缓存里加数据
        carCache.put("benz", "奔驰");
        carCache.put("bmw", "宝马");
        carCache.put("audi", "奥迪");

        System.out.println(carCache.getClass()); //class org.springframework.cache.concurrent.ConcurrentMapCache
        // 从缓存里获取数据
        System.out.println(carCache.get("benz").get()); //奔驰
        System.out.println(carCache.get("benz", String.class)); //奔驰
    }

小结

到此为止,我们了解了如何使用原生缓存API来实现缓存功能,这一点和之前使用原生API完成Spring事务控制一样,但是问题在于使用编码来实现缓存,会导致缓存相关代码散落在项目代码中各个地方,不方便管理;

因此,和声明式事务一样,缓存模块同样可以借助于Spring提供的AOP体系来完成声明式缓存。

原生API方式实现缓存详细学习可以阅读此篇文章


自动挡

@EnableCaching注解可以一键开启缓存功能,然后我们使用**@Cacheable、@CachePut、@CacheEvict、@Caching等注解就可以完成声明式缓存了,这一点和@EnableTransactionManagement,@Transactional**组合一样。

下面,我们就来探究一下@EnableCaching到底是如何实现一键开启缓存的:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
//CachingConfigurationSelector是重点
@Import(CachingConfigurationSelector.class)
public @interface EnableCaching {
    //声明式缓存和声明式事务,还包括@Async注解实现,背后全部依靠Spring完善的AOP体系
    //是否强制采用cglib代理
	boolean proxyTargetClass() default false;
	//代理模式,默认是普通代理--及采用jdk或者cglib代理完成
	//另一种模式就是采用aspectj完成代理
	AdviceMode mode() default AdviceMode.PROXY;
	//目标对象可能同时被多个advisor切中,这里的order用于指定当前advisor的优先级
	//用于对多个advisor进行排序
	int order() default Ordered.LOWEST_PRECEDENCE;
}

CachingConfigurationSelector

public class CachingConfigurationSelector extends AdviceModeImportSelector<EnableCaching> {

	private static final String PROXY_JCACHE_CONFIGURATION_CLASS =
			"org.springframework.cache.jcache.config.ProxyJCacheConfiguration";
    ...
     
	private static final boolean jsr107Present;

	private static final boolean jcacheImplPresent;

	static {
		ClassLoader classLoader = CachingConfigurationSelector.class.getClassLoader();
		jsr107Present = ClassUtils.isPresent("javax.cache.Cache", classLoader);
		jcacheImplPresent = ClassUtils.isPresent(PROXY_JCACHE_CONFIGURATION_CLASS, classLoader);
	}

    //返回的className列表,表示需要向IOC容器中加入的bean集合
	@Override
	public String[] selectImports(AdviceMode adviceMode) {
		switch (adviceMode) {
			case PROXY:
				//上面说过代理模式一般都是普通模式,下面的ASPECTJ可以忽略掉
				return getProxyImports();
			case ASPECTJ:
				return getAspectJImports();
			default:
				return null;
		}
	}
   
  	// 向容器导入了AutoProxyRegistrar和ProxyCachingConfiguration
	// 若JSR107的包存在(导入了javax.cache:cache-api这个包),并且并且存在ProxyJCacheConfiguration这个类
	// 显然ProxyJCacheConfiguration这个类我们一般都不会导进来~~~~  所以JSR107是不生效的。   但是但是Spring是支持的
	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);
	}
}

AdviceModeImportSelector会先获取到对应@Enablexxx注解中的adviceMode属性,然后将adviceMode作为参数传入selectImports,该方法由子类实现,子类再根据adviceMode决定导入哪些类到IOC容器中去。


AutoProxyRegistrar

为了在bean初始化的生命周期中通过相关回调接口(BeanPostProcessor)完成对bean的代理,我们需要往容器中添加一个自动代理创建器。

AutoProxyRegistrar负责完成往容器中注入自动代理创建器功能。

public class AutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
    ...
	@Override
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
		boolean candidateFound = false;
		//@EnableCaching注解需要标注在某个配置类上,importingClassMetadata就是该配置类的原数据信息
		//这里是先获取到配置类上所有的注解全类名
		Set<String> annTypes = importingClassMetadata.getAnnotationTypes();
		for (String annType : annTypes) {
		    //配置类上通常存在很多注解,如何过滤出我们需要的@EnableCaching注解呢?  
			AnnotationAttributes candidate = AnnotationConfigUtils.attributesFor(importingClassMetadata, annType);
			if (candidate == null) {
				continue;
			}
			//如果注解中存在属性mode和proxyTargetClass,那么你就是我们要找的注解
			Object mode = candidate.get("mode");
			Object proxyTargetClass = candidate.get("proxyTargetClass");
			if (mode != null && proxyTargetClass != null && AdviceMode.class == mode.getClass() &&
					Boolean.class == proxyTargetClass.getClass()) {	
				candidateFound = true;
				//自动代理创建器注入容器的前提是代理模式为proxy,即采用cglib或者jdk完成动态代理
				if (mode == AdviceMode.PROXY) {
				    //往容器中放入一个自动代理创建器  
					AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
					//如果强制采用cglib代理的话,会将自动代理创建器的proxyTargetClass属性设置为true
					//自动代理创建器都继承了proxyConfig
					if ((Boolean) proxyTargetClass) {
						AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
						return;
					}
				}
			}
		}
		...
	}
}

AutoProxyRegistrar最终注册到容器中的自动代理创建器类型为InfrastructureAdvisorAutoProxyCreatorInfrastructureAdvisorAutoProxyCreator自动代理创建器的特点在于它只会搜集出容器中所有基础advisor,然后再依次判断每个advisor是否能应用于当前bean。

这里基础的意思是bean对应的BeanDefinition中的Role为ROLE_INFRASTRUCTURE。

如果bean的role为ROLE_INFRASTRUCTURE,表示该bean是spring内部的基础设施bean。

Spring内部通常采用如下的方式来指定某个bean为基础设施bean。

	@Bean(name = CacheManagementConfigUtils.CACHE_ADVISOR_BEAN_NAME)
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)

ProxyCachingConfiguration

自动代理创建器已经就绪,下一步我们还需要将Caching相关的advisor放入容器中,这样InfrastructureAdvisorAutoProxyCreator就可以收集到该advisor,然后在对bean进行动态代理时,将该advisor放入代理对象的拦截器链中:

@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;
	}
}

在这里插入图片描述
ProxyCachingConfiguration往容器中注入了以上三个Bean,这三个Bean就是声明式缓存实现的全部了。


ProxyCachingConfiguration的父类AbstractCachingConfiguration用于搜集用户注入容器中的CachingConfigurer,通过CachingConfigurer可以完成对Cache模块相关组件的自定义修改:

@Configuration(proxyBeanMethods = false)
public abstract class AbstractCachingConfiguration implements ImportAware {
    ...  
	@Nullable
	protected Supplier<CacheManager> cacheManager;

	@Nullable
	protected Supplier<CacheResolver> cacheResolver;

	@Nullable
	protected Supplier<KeyGenerator> keyGenerator;

	@Nullable
	protected Supplier<CacheErrorHandler> errorHandler;
    
    ...
	
	@Autowired(required = false)
	void setConfigurers(Collection<CachingConfigurer> configurers) {
		if (CollectionUtils.isEmpty(configurers)) {
			return;
		}
		
		if (configurers.size() > 1) {
			throw new IllegalStateException(configurers.size() + " implementations of " +
					"CachingConfigurer were found when only 1 was expected. " +
					"Refactor the configuration such that CachingConfigurer is " +
					"implemented only once or not at all.");
		}
		//用户只能往容器中注入一个configure,多了会报错的
		CachingConfigurer configurer = configurers.iterator().next();
		//使用用户的自定义配置来修改缓存模块相关组件
		useCachingConfigurer(configurer);
	}

	protected void useCachingConfigurer(CachingConfigurer config) {
		this.cacheManager = config::cacheManager;
		this.cacheResolver = config::cacheResolver;
		this.keyGenerator = config::keyGenerator;
		this.errorHandler = config::errorHandler;
	}
}

CacheOperationSource

大家思考一个问题BeanFactoryCacheOperationSourceAdvisor增强器是如何判断当前bean是否需要缓存支持呢?

如果需要缓存支持,那么当拦截到目标对象方法执行时,又该如何解析获取到方法上缓存注解相关信息呢?

上面两个难题的解决最终靠的是CacheOperationSource:

public interface CacheOperationSource {
    //判断当前bean是否需要缓存支持
	default boolean isCandidateClass(Class<?> targetClass) {
		return true;
	}
	
   //解析获取到方法上缓存注解相关信息
	Collection<CacheOperation> getCacheOperations(Method method, @Nullable Class<?> targetClass);
}

CacheOperationSource的实现类如下:
在这里插入图片描述
我们这里只看通过解析注解来获取缓存信息的实现类:

AbstractFallbackCacheOperationSource中Fallback的意思是如果在方法上无法查询到相关缓存注解,那么还会尝试从以下几个地方去查询缓存注解:

  • 目标对象所在类上
  • 接口方法上
  • 接口上
public abstract class AbstractFallbackCacheOperationSource implements CacheOperationSource {
    //如果某个方法不需要缓存支持,为了避免每次都要解析很久来判断当前方法是否需要缓存支持,通过 NULL_CACHING_ATTRIBUTE 进行标记
	private static final Collection<CacheOperation> NULL_CACHING_ATTRIBUTE = Collections.emptyList();
    //缓存方法和该方法对应的缓存信息
	private final Map<Object, Collection<CacheOperation>> attributeCache = new ConcurrentHashMap<>(1024);

	@Override
	public Collection<CacheOperation> getCacheOperations(Method method, @Nullable Class<?> targetClass) {
		if (method.getDeclaringClass() == Object.class) {
			return null;
		}
		//构建缓存key 
		Object cacheKey = getCacheKey(method, targetClass);
		//缓存中已经存在了,那么直接返回当前方法对应的缓存信息
		Collection<CacheOperation> cached = this.attributeCache.get(cacheKey);
        //缓存中存在
		if (cached != null) {
			return (cached != NULL_CACHING_ATTRIBUTE ? cached : null);
		}
		else {
		    //缓存中不存在,那么就进行解析
			Collection<CacheOperation> cacheOps = computeCacheOperations(method, targetClass);
			if (cacheOps != null) {
				.... 
				this.attributeCache.put(cacheKey, cacheOps);
			}
			else {
			    //当前方法不需要缓存支持,进行特殊标记
				this.attributeCache.put(cacheKey, NULL_CACHING_ATTRIBUTE);
			}
			return cacheOps;
		}
	}
    ...
    
	@Nullable
	private Collection<CacheOperation> computeCacheOperations(Method method, @Nullable Class<?> targetClass) {
		//是否只对public方法提供缓存支持,默认为true(唯一的子类覆写了为true)
		if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
			return null;
		}

		// The method may be on an interface, but we need attributes from the target class.
		// If the target class is null, the method will be unchanged.
		Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);

		// 首先查询方法上是否存在相关注解,如果存在就进行解析,然后将每个注解中的信息都保存到一个CacheOperation后返回
		Collection<CacheOperation> opDef = findCacheOperations(specificMethod);
		if (opDef != null) {
			return opDef;
		}

		// 查询类上是否存在缓存注解信息
		opDef = findCacheOperations(specificMethod.getDeclaringClass());
		if (opDef != null && ClassUtils.isUserLevelMethod(method)) {
			return opDef;
		}
        
        //缓存注解可能标注在了接口方法上      
		if (specificMethod != method) {
			// method可以看做是接口中的方法,那么此时就尝试解析接口方法上是否存在相关缓存注解信息
			opDef = findCacheOperations(method);
			if (opDef != null) {
				return opDef;
			}
			// 再查询接口上是否存在相关缓存注解信息
			opDef = findCacheOperations(method.getDeclaringClass());
			if (opDef != null && ClassUtils.isUserLevelMethod(method)) {
				return opDef;
			}
		}

		return null;
	}
	protected abstract Collection<CacheOperation> findCacheOperations(Class<?> clazz);
	protected abstract Collection<CacheOperation> findCacheOperations(Method method);
	...
}

AbstractFallbackCacheOperationSource提供了一套获取CacheOperation的模板支持,但是具体如何从目标对象上解析得到缓存信息,即CacheOperation,则靠子类实现。

public class AnnotationCacheOperationSource extends AbstractFallbackCacheOperationSource implements Serializable {
    //是否只对public方法提供缓存支持,无参构造函数中默认设置为了true 
	private final boolean publicMethodsOnly;
    //缓存注解解析器
	private final Set<CacheAnnotationParser> annotationParsers;

	public AnnotationCacheOperationSource() {
		this(true);
	}

	public AnnotationCacheOperationSource(boolean publicMethodsOnly) {
		this.publicMethodsOnly = publicMethodsOnly;
		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;
	}

	@Override
	protected Collection<CacheOperation> findCacheOperations(Class<?> clazz) {
		return determineCacheOperations(parser -> parser.parseCacheAnnotations(clazz));
	}

	@Overridee
	protected Collection<CacheOperation> findCacheOperations(Method method) {
		return determineCacheOperations(parser -> parser.parseCacheAnnotations(method));
	}

	@Nullable
	protected Collection<CacheOperation> determineCacheOperations(CacheOperationProvider provider) {
		Collection<CacheOperation> ops = null;
		for (CacheAnnotationParser parser : this.annotationParsers) {
		    //依次尝试让每一个注解解析器都进行解析,我们可以自定义注解解析器,来解析我们自定义的缓存注解
			Collection<CacheOperation> annOps = provider.getCacheOperations(parser);
			if (annOps != null) {
				if (ops == null) {
					ops = annOps;
				}
				else {
					Collection<CacheOperation> combined = new ArrayList<>(ops.size() + annOps.size());
					combined.addAll(ops);
					combined.addAll(annOps);
					ops = combined;
				}
			}
		}
		return ops;
	}

	@FunctionalInterface
	protected interface CacheOperationProvider {
		@Nullable
		Collection<CacheOperation> getCacheOperations(CacheAnnotationParser parser);
	}

}

关于缓存注解解析器是如何解析缓存注解,然后提取注解中信息,包装为CacheOperation的,这部分内容比较简单,大家可以自行阅读源码学习。


CacheOperation

CacheAnnotationParser会解析出缓存注解中相关属性,然后填充到CacheOperation中:

public abstract class CacheOperation implements BasicOperation {

	private final String name;

	private final Set<String> cacheNames;

	private final String key;

	private final String keyGenerator;

	private final String cacheManager;

	private final String cacheResolver;

	private final String condition;

	private final String toString;
    ...

CacheOperation父类中存放所有缓存注解的通用属性,而相关子实现类会提供对应缓存注解的特有属性:
在这里插入图片描述


BeanFactoryCacheOperationSourceAdvisor

增强器的核心是通过内部的pointCut判断是否要切入某个bean,BeanFactoryCacheOperationSourceAdvisor的pointCut过滤主要依靠CacheOperationSource完成:

public class BeanFactoryCacheOperationSourceAdvisor extends AbstractBeanFactoryPointcutAdvisor {

	@Nullable
	private CacheOperationSource cacheOperationSource;

	private final CacheOperationSourcePointcut pointcut = new CacheOperationSourcePointcut() {
		@Override
		@Nullable
		protected CacheOperationSource getCacheOperationSource() {
			return cacheOperationSource;
		}
	};
    ...
}

pointcut具体实现如下:

abstract class CacheOperationSourcePointcut extends StaticMethodMatcherPointcut implements Serializable {

	protected CacheOperationSourcePointcut() {
		setClassFilter(new CacheOperationSourceClassFilter());
	}


	@Override
	public boolean matches(Method method, Class<?> targetClass) {
	    //通过CacheOperationSource的getCacheOperations方法,完成方法级别是否需要切入的判断
		CacheOperationSource cas = getCacheOperationSource();
		return (cas != null && !CollectionUtils.isEmpty(cas.getCacheOperations(method, targetClass)));
	}

	protected abstract CacheOperationSource getCacheOperationSource();

	private class CacheOperationSourceClassFilter implements ClassFilter {
		@Override
		public boolean matches(Class<?> clazz) {
			if (CacheManager.class.isAssignableFrom(clazz)) {
				return false;
			}
			//通过CacheOperationSource的isCandidateClass方法完成类级别的过滤
			CacheOperationSource cas = getCacheOperationSource();
			return (cas == null || cas.isCandidateClass(clazz));
		}
	}

}

CacheInterceptor

如果BeanFactoryCacheOperationSourceAdvisor成功切入某个方法,那么下一步就是将CacheInterceptor加入代理对象的拦截器链中,然后执行CacheInterceptor的invoke方法了:

public class CacheInterceptor extends CacheAspectSupport implements MethodInterceptor, Serializable {

	@Override
	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();
		}
	}
}

execute方法在父类CacheAspectSupport中实现,我们来看一下:

	protected Object execute(CacheOperationInvoker invoker, Object target, Method method, Object[] args) {
		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();
	}

真正进行缓存逻辑处理的核心方法:

	private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {
		// 多线程情况下,是否采用同步访问缓存方式--后面会介绍
		if (contexts.isSynchronized()) {
                ....
		}
		
		// 在目标方法执行前,执行@CacheEvicts缓存清除逻辑
		processCacheEvicts(contexts.get(CacheEvictOperation.class), true,
				CacheOperationExpressionEvaluator.NO_RESULT);

		//处理@Cacheable注解逻辑,判断缓存是否命中
		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<>();
		//缓存未命中,cachePutRequests集合中保存着需要缓存此次方法执行结果的请求
		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();
			//对Optional返回值进行特殊处理
			returnValue = wrapCacheValue(method, cacheValue);
		}
		else {
			// Invoke the method if we don't have a cache hit
			//缓存没有命中,触发目标方法执行,获取返回结果
			returnValue = invokeOperation(invoker);
			//还是对Optional返回值进行特殊处理
			cacheValue = unwrapReturnValue(returnValue);
		}

		// Collect any explicit @CachePuts
		//收集@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
		//在目标方法执行完后,再执行@CacheEvit逻辑
		processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);

		return returnValue;
	}

execute方法中调用的其他方法实现都比较简单,这里我就不展开讲述了,大家自行翻阅源码学习即可,下面我简单对处理缓存注解的步骤进行一个小的总结:

  1. CacheOperation封装了@CachePut、@Cacheable、@CacheEvict(下称三大缓存注解)的属性信息,以便于拦截的时候能直接操作此对象来执行逻辑。
    1. 解析三大注解到CacheOperation的过程是由CacheAnnotationParser完成的
  2. CacheAnnotationSource代表缓存属性源,非常非常重要的一个概念。它提供接口方法来获取目标方法的CacheOperation集合。由上可知,这个具体工作是委托给CacheAnnotationParser去完成的。
  3. BeanFactoryCacheOperationSourceAdvisor它代表增强器,至于需要增强哪些类呢???就是看有没有存在CacheOperation属性的方法
  4. CacheInterceptor实现了MethodInterceptor接口,在Spring AOP中实现对执行方法的拦截。在调用invoke执行目标方法前后,通过CacheAnnotationSource获取到方法所有的缓存操作属性,从而一个个的执行
  5. 执行的时候,每一个CacheOperation最后被封装成了CacheOperationContext,而CacheOperationContext最终通过CacheResolver解析出缓存对象Cache(可能是多个)
  6. 最后最后最后,CacheInterceptor调用其父类AbstractCacheInvoker执行对应的doPut / doGet / doEvict / doClear 等等。(可以处理执行异常)

小结

本文从头到尾将Spring缓存模块的实现进行了一遍透彻的分析,下一篇文章将会对缓存模块的使用注意事项进行介绍,包括如何设置缓存过期时间。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/101727.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

成本、利润分析法在企业管理中的应用

1 、成本、利润分析法的主要内容 成本、利润分析法主要是指&#xff0c;利用数学模型&#xff0c;对关于企业成本、利润的要素分析&#xff0c;然后计算出要素的改变对企业成本、利润的影响&#xff0c;进而对企业决策提出建议的一种方法。在成本、利润分析法中&#xff0c;最主…

基础IO——文件描述符

文章目录1. 文件描述符fd1.1 open返回值2. 理解Linux下一切皆文件3. 文件描述符的分配规则4. 重定向的本质4.1 使用 dup2 系统调用4.2 追加重定向4.3 输入重定向1. 文件描述符fd 1.1 open返回值 我们先来看下面的例子&#xff1a; 运行结果如下&#xff1a; 我们知道open的…

磺基-CY5 马来酰亚胺 Cyanine5 Maleimide

磺基-CY5 马来酰亚胺 Cyanine5 Maleimide Cyanine5 maleimide是单一活性染料&#xff0c;有选择性的与硫醇基团&#xff08;比如蛋白和多肽的半胱氨酸&#xff09;结合以进行标记。我们使用水溶的Sulfo-Cyanine5 maleimide标记抗体和其他敏感蛋白。Cyanine5是Cy5的类似物&am…

Pb协议的接口测试

Protocol Buffers 是谷歌开源的序列化与反序列化框架。它与语言无关、平台无关、具有可扩展的机制。用于序列化结构化数据&#xff0c;此工具对标 XML &#xff0c;支持自动编码&#xff0c;解码。比 XML 性能好&#xff0c;且数据易于解析。更多有关工具的介绍可参考官网。 P…

Java8新特性学习

文章目录Lambda表达式为什么使用Lambda表达式Lambda表达式语法语法格式一&#xff1a;无参数&#xff0c;无返回值语法格式二&#xff1a;有一个参数&#xff0c;并且无返回值语法格式三&#xff1a;若只有一个参数&#xff0c;小括号可以省略不写语法格式四&#xff1a;有两个…

Docker容器数据卷

是什么 卷就是目录或文件&#xff0c;存在于一个或多个容器中&#xff0c;由docker挂载到容器&#xff0c;但不属于联合文件系统&#xff0c;因此能够绕过Union File System提供一些用于持续存储或共享数据的特性&#xff1a;卷的设计目的就是数据的持久化&#xff0c;完全独立…

LSTM(Long Short-Term Memory)

长短期记忆&#xff08;long short-term memory&#xff0c;LSTM&#xff09;&#xff0c;LSTM 中引入了3个门&#xff0c;即输入门&#xff08;input gate&#xff09;、遗忘门&#xff08;forget gate&#xff09;和输出门&#xff08;output gate&#xff09;&#xff0c;以…

华为时习知,让企业培训更简单!

在数字经济的发展过程中&#xff0c;人才始终是不容忽视的关键因素&#xff0c;企业对数字化人才培养的需求也愈加迫切。然而企业培训说起来简单&#xff0c;要做好却绝非易事。企业可能会面临员工分散各地、流动性大、关键岗位人才培训等复杂培训场景问题&#xff0c;无法高效…

为什么我们说NFT拥有无限潜力?

欢迎来到Hubbleverse &#x1f30d; 关注我们 关注宇宙新鲜事 &#x1f4cc; 预计阅读时长&#xff1a;8分钟 本文仅代表作者个人观点&#xff0c;不代表平台意见&#xff0c;不构成投资建议。 2021年底&#xff0c;NFT就已经发展得炙手可热了&#xff0c;热门到410亿美元投…

YOLO-V5 算法和代码解析系列(一)—— 快速开始

文章目录运行环境配置Demo重新训练 YOLO-V5s运行环境配置 环境配置的官方教程如下&#xff0c;如果一些库安装失败&#xff0c;导致安装中断&#xff0c;可以单独安装一些库&#xff0c;比如 Pytorch&#xff0c;然后再执行下列安装步骤&#xff0c;具体如下&#xff1a; 个人建…

国内食糖行业数据浅析

大家好&#xff0c;这里是小安说网控。 食糖行业是国民消费不可或缺的产业之一。2022年9月份国内成品糖产量当期值为40.4万吨&#xff0c;同比增长30.7%&#xff1b;10月份当期值为63.7万吨&#xff0c;同比下滑2%。今年1-10月份&#xff0c;国内成品糖产量累计值为1089.4万吨&…

艾美捷细胞糖酵解分析试剂盒基本参数和相关研究

艾美捷基于糖酵解细胞的测定试剂盒提供了一种比色法&#xff0c;用于检测培养细胞产生和分泌的糖酵解最终产物L-乳酸。在该测定中&#xff0c;乳酸脱氢酶催化NAD和乳酸之间的反应&#xff0c;产生丙酮酸和NADH。NADH直接将四氮唑盐&#xff08;INT&#xff09;还原为吸收490至5…

【High 翻天】Higer-order Networks with Battiston Federico (3)

目录模型&#xff08;1&#xff09;Equilibrium modelsBipartite modelsMotifs modelsStochastic set modelsHypergraphs modelsSimplicial complexes models模型的目的是再现、解释和预测系统的结构&#xff0c;最好用涉及系统两个或多个元素的交互来描述。为了考虑其输出的可…

【1971. 寻找图中是否存在路径】

来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 描述&#xff1a; 有一个具有 n 个顶点的 双向 图&#xff0c;其中每个顶点标记从 0 到 n - 1&#xff08;包含 0 和 n - 1&#xff09;。图中的边用一个二维整数数组 edges 表示&#xff0c;其中 edges[i] [ui, vi] …

计算机毕设Python+Vue学衡国学堂围棋社管理系统(程序+LW+部署)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

nvidia 使用

watch -n 0.5 nvidia-smi ./build/examples/openpose/openpose.bin --video examples/media/video.avi Linux CPU&GPU烤机&#xff08;压力测试&#xff09; 盛夏捷关注IP属地: 青海 0.1342021.04.14 09:50:16字数 152阅读 6,307 GPU-burn工具进行GPU烤机 下载Multi-G…

基于MATLAB的车牌识别系统设计(Matlab代码实现)

&#x1f468;‍&#x1f393;个人主页&#xff1a;研学社的博客 &#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜…

vue之非父子组件通信实现方式

在开发中&#xff0c;我们构建了组件树之后&#xff0c;除了父子组件之间的通信之外&#xff0c;还会有非父子组件之间的通信。这里主要讲两种方式&#xff1a; Provide/InjectMitt全局事件总线 1、Provide和Inject 应用场景 比如有一些深度嵌套的组件&#xff0c;子组件想要…

SVG 在前端的7种使用方法,你还知道哪几种?

本文简介 点赞 关注 收藏 学会了 技术一直在演变&#xff0c;在网页中使用 SVG 的方法也层出不穷。每个时期都有对应的最优解。 所以我打算把我知道的 7种 SVG 的使用方法列举出来&#xff0c;有备无患~ 如果你还知道其他方法&#xff0c;可以在评论区补充~ 1. 在浏览器直…

PMO(项目管理办公室)的未来趋势

PMO&#xff08;项目管理办公室&#xff09;是在组织内部将实践、过程、运作形式化和标准化的部门&#xff0c;也是提高组织管理成熟度的核心部门。现在&#xff0c;让我们把目光投向当前PMO的典型职责之外&#xff0c;思考一下&#xff1a;PMO的未来是什么&#xff1f; 如今&a…