@EnableCaching如何一键开启缓存

Posted 热爱编程的大忽悠

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了@EnableCaching如何一键开启缓存相关的知识,希望对你有一定的参考价值。

@EnableCaching如何一键开启缓存


手动挡

我们首先来看看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 (以上是关于@EnableCaching如何一键开启缓存的主要内容,如果未能解决你的问题,请参考以下文章

默认缓存体验

缓存注解介绍

本地缓存Caffeine详解+整合SpringBoot的@EnableCaching

本地缓存Caffeine详解+整合SpringBoot的@EnableCaching

Android缓存处理和清除数据清除缓存一键清理的区别

SpringBoot-缓存管理