@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体系来完成声明式缓存。
自动挡
@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最终注册到容器中的自动代理创建器类型为InfrastructureAdvisorAutoProxyCreator
,InfrastructureAdvisorAutoProxyCreator
自动代理创建器的特点在于它只会搜集出容器中所有基础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 默认缓存体验
本地缓存Caffeine详解+整合SpringBoot的@EnableCaching