赞
踩
坚持系统化的学习方式,由量变到质变。仅仅解决工作中的问题,并不能叫系统化的学习
上篇文章介绍了@EnableCaching
,用它来开启Spring对缓存注解的支持。本篇文章将继续分析Spring Cache
,并且讲解的是我们最为关心的:缓存注解实操方面的原理支持和使用。
开发过程中因注解的优雅、使用简单使得这种方式广泛被大家所接受和使用,本文将按照先原理,再实操的步骤,一步步解惑Spring缓存注解的原理
关于Spring的缓存注解,一共有如下5个:
@Cacheable
:缓存// @since 3.1 可以标注在方法上、类上 下同 @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface Cacheable { // 缓存名称 可以写多个~ @AliasFor("cacheNames") String[] value() default {}; @AliasFor("value") String[] cacheNames() default {}; // 支持写SpEL,切可以使用#root String key() default ""; // Mutually exclusive:它和key属性互相排斥。请只使用一个 String keyGenerator() default ""; String cacheManager() default ""; String cacheResolver() default ""; // SpEL,可以使用#root。 只有true时,才会作用在这个方法上 String condition() default ""; // 可以写SpEL #root,并且可以使用#result拿到方法返回值~~~ String unless() default ""; // true:表示强制同步执行。(若多个线程试图为**同一个键**加载值,以同步的方式来进行目标方法的调用) // 同步的好处是:后一个线程会读取到前一个缓存的缓存数据,不用再查库了~~~ // 默认是false,不开启同步one by one的 // @since 4.3 注意是sync而不是Async // 它的解析依赖于Spring4.3提供的Cache.get(Object key, Callable<T> valueLoader);方法 boolean sync() default false; }
@CachePut
:缓存更新@Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface Cacheable { @AliasFor("cacheNames") String[] value() default {}; @AliasFor("value") String[] cacheNames() default {}; // 注意:它和上面区别是。此处key它还能使用#result String key() default ""; String keyGenerator() default ""; String cacheManager() default ""; String cacheResolver() default ""; String condition() default ""; String unless() default ""; }
@CacheEvict
:缓存删除@Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface Cacheable { @AliasFor("cacheNames") String[] value() default {}; @AliasFor("value") String[] cacheNames() default {}; // 它也能使用#result String key() default ""; String keyGenerator() default ""; String cacheManager() default ""; String cacheResolver() default ""; String condition() default ""; // 是否把上面cacheNames指定的所有的缓存都清除掉,默认false boolean allEntries() default false; // 是否让清理缓存动作在目标方法之前执行,默认是false(在目标方法之后执行) // 注意:若在之后执行的话,目标方法一旦抛出异常了,那缓存就清理不掉了~~~~ boolean beforeInvocation() default false; }
@Caching
:用于处理复杂的缓存情况。比如用户既要根据id缓存一份,也要根据电话缓存一份,还要根据电子邮箱缓存一份,就可以使用它@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Caching {
Cacheable[] cacheable() default {};
CachePut[] put() default {};
CacheEvict[] evict() default {};
}
@CacheConfig
:可以在类级别上标注一些共用的缓存属性。(所有方法共享,@since 4.1)// @since 4.1 出现得还是比较晚的
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CacheConfig {
String[] cacheNames() default {};
String keyGenerator() default "";
String cacheManager() default "";
String cacheResolver() default "";
}
属性说明表格:
属性名 | 解释 |
---|---|
value | 缓存的名称。可定义多个(至少需要定义一个) |
cacheNames | 同value属性 |
keyGenerator | key生成器。字符串为:beanName |
key | 缓存的 key。可使用SpEL。优先级大于keyGenerator |
cacheManager | 缓存管理器。填写beanName |
cacheResolver | 缓存处理器。填写beanName |
condition | 缓存条件。若填写了,返回true才会执行此缓存。可使用SpEL |
unless | 否定缓存。false就生效。可以写SpEL |
sync | true:所有相同key的同线程顺序执行。默认值是false |
allEntries | 是否清空所有缓存内容,缺省为 false,如果指定为 true,则方法调用后将立即清空所有缓存 |
beforeInvocation | 是否在方法执行前就清空,缺省为 false,如果指定为 true |
先阅读:【小家Spring】玩转Spring Cache — @Cacheable/@CachePut/@CacheEvict缓存注解相关基础类打点 再读本文,效果会像德芙一般丝滑~
从上篇文章中已经知道了@EnableCaching
主要向容器注入了三个Bean:CacheOperationSource
、BeanFactoryCacheOperationSourceAdvisor
、CacheInterceptor
。他们是让注解生效
的核心类。
它代表缓存操作源,已经分析过。
从名字就能看出它是一个增强器Advisor
,并且还和BeanFactory
有关。
@Bean(name = CacheManagementConfigUtils.CACHE_ADVISOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public BeanFactoryCacheOperationSourceAdvisor cacheAdvisor() {
BeanFactoryCacheOperationSourceAdvisor advisor = new BeanFactoryCacheOperationSourceAdvisor();
advisor.setCacheOperationSource(cacheOperationSource());
advisor.setAdvice(cacheInterceptor());
if (this.enableCaching != null) {
advisor.setOrder(this.enableCaching.<Integer>getNumber("order"));
}
return advisor;
}
从上配置知道,这个增强器的切面Advice是CacheInterceptor
,并且持有CacheOperationSource
的引用。
public class BeanFactoryCacheOperationSourceAdvisor extends AbstractBeanFactoryPointcutAdvisor { @Nullable private CacheOperationSource cacheOperationSource; // 切面Pointcut private final CacheOperationSourcePointcut pointcut = new CacheOperationSourcePointcut() { @Override @Nullable protected CacheOperationSource getCacheOperationSource() { return cacheOperationSource; } }; public void setCacheOperationSource(CacheOperationSource cacheOperationSource) { this.cacheOperationSource = cacheOperationSource; } // 注意:此处你可以自定义一个ClassFilter,过滤掉你想忽略的类 public void setClassFilter(ClassFilter classFilter) { this.pointcut.setClassFilter(classFilter); } @Override public Pointcut getPointcut() { return this.pointcut; } }
此Advisor
的实现非常的简单,切点是CacheOperationSourcePointcut
,核心逻辑都依托于缓存属性源。所以还没有看这块的,此处再一次推荐:【小家Spring】玩转Spring Cache — @Cacheable/@CachePut/@CacheEvict缓存注解相关基础类打点
缓存拦截器。先说明一点,它的实现模式几乎和TransactionInterceptor
一毛一样。所以我又想建议一句了,有空先看看它吧:【小家Spring】源码分析Spring的事务拦截器:TransactionInterceptor和事务管理器:PlatformTransactionManager
同样,CacheInterceptor
是缓存真正执行的核心,处理逻辑还是稍显复杂的。
// @since 3.1 它是个MethodInterceptor环绕增强器~~~ public class CacheInterceptor extends CacheAspectSupport implements MethodInterceptor, Serializable { @Override @Nullable public Object invoke(final MethodInvocation invocation) throws Throwable { Method method = invocation.getMethod(); // 采用函数的形式,最终把此函数传交给父类的execute()去执行 // 但是很显然,最终**执行目标方法**的是invocation.proceed();它 //这里就是对执行方法调用的一次封装,主要是为了处理对异常的包装。 CacheOperationInvoker aopAllianceInvoker = () -> { try { return invocation.proceed(); } catch (Throwable ex) { throw new CacheOperationInvoker.ThrowableWrapper(ex); } }; try { // //真正地去处理缓存操作的执行,很显然这是父类的方法,所以我们要到父类CacheAspectSupport中去看看。 return execute(aopAllianceInvoker, invocation.getThis(), method, invocation.getArguments()); } catch (CacheOperationInvoker.ThrowableWrapper th) { throw th.getOriginal(); } } }
这个类本身的实现很少,主要逻辑都在他的抽象父类:CacheAspectSupport
它类似于TransactionAspectSupport
,父类实现了所有的核心逻辑
// @since 3.1 它相较于TransactionAspectSupport额外实现了SmartInitializingSingleton接口 // SmartInitializingSingleton应该也不会陌生。它在初始化完所有的单例Bean后会执行这个接口的`afterSingletonsInstantiated()`方法 // 比如我们熟悉的ScheduledAnnotationBeanPostProcessor、EventListenerMethodProcessor都是这么来处理的 // 另外还需要注意,它还继承自AbstractCacheInvoker:主要对异常情况用CacheErrorHandler处理 public abstract class CacheAspectSupport extends AbstractCacheInvoker implements BeanFactoryAware, InitializingBean, SmartInitializingSingleton { // CacheOperationCacheKey:缓存的key CacheOperationMetadata就是持有一些基础属性的性息 // 这个缓存挺大,相当于每一个类、方法都有气对应的**缓存属性元数据** private final Map<CacheOperationCacheKey, CacheOperationMetadata> metadataCache = new ConcurrentHashMap<>(1024); // 解析一些condition、key、unless等可以写el表达式的处理器~~~ // 之前讲过的熟悉的有:EventExpressionEvaluator private final CacheOperationExpressionEvaluator evaluator = new CacheOperationExpressionEvaluator(); // 属性源,默认情况下是基于注解的`AnnotationCacheOperationSource` @Nullable private CacheOperationSource cacheOperationSource; // 看到了吧 key生成器默认使用的SimpleKeyGenerator // 注意SingletonSupplier是Spring5.1的新类,实现了接口java.util.function.Supplier 主要是对null值进行了容错 private SingletonSupplier<KeyGenerator> keyGenerator = SingletonSupplier.of(SimpleKeyGenerator::new); @Nullable private SingletonSupplier<CacheResolver> cacheResolver; @Nullable private BeanFactory beanFactory; private boolean initialized = false; // @since 5.1 public void configure(@Nullable Supplier<CacheErrorHandler> errorHandler, @Nullable Supplier<KeyGenerator> keyGenerator,@Nullable Supplier<CacheResolver> cacheResolver, @Nullable Supplier<CacheManager> cacheManager) { // 第二个参数都是默认值,若调用者没传的话 this.errorHandler = new SingletonSupplier<>(errorHandler, SimpleCacheErrorHandler::new); this.keyGenerator = new SingletonSupplier<>(keyGenerator, SimpleKeyGenerator::new); this.cacheResolver = new SingletonSupplier<>(cacheResolver, () -> SimpleCacheResolver.of(SupplierUtils.resolve(cacheManager))); } // 此处:若传入了多个cacheOperationSources,那最终使用的就是CompositeCacheOperationSource包装起来 // 所以发现,Spring是支持我们多种 缓存属性源的 public void setCacheOperationSources(CacheOperationSource... cacheOperationSources) { Assert.notEmpty(cacheOperationSources, "At least 1 CacheOperationSource needs to be specified"); this.cacheOperationSource = (cacheOperationSources.length > 1 ? new CompositeCacheOperationSource(cacheOperationSources) : cacheOperationSources[0]); } // @since 5.1 单数形式的设置 public void setCacheOperationSource(@Nullable CacheOperationSource cacheOperationSource) { this.cacheOperationSource = cacheOperationSource; } ... // 省略各种get/set方法~~~ // CacheOperationSource必须不为null,因为一切依托于它 @Override public void afterPropertiesSet() { Assert.state(getCacheOperationSource() != null, "The 'cacheOperationSources' property is required: " + "If there are no cacheable methods, then don't use a cache aspect."); } // 这个来自于接口:SmartInitializingSingleton 在实例化完所有单例Bean后调用 @Override public void afterSingletonsInstantiated() { // 若没有给这个切面手动设置cacheResolver 那就去拿CacheManager吧 // 这就是为何我们只需要把CacheManager配进容器里即可 就自动会设置在切面里了 if (getCacheResolver() == null) { // Lazily initialize cache resolver via default cache manager... Assert.state(this.beanFactory != null, "CacheResolver or BeanFactory must be set on cache aspect"); try { // 请注意:这个方法实际上是把CacheManager包装成了一个SimpleCacheResolver // 所以最终还是给SimpleCacheResolver赋值 setCacheManager(this.beanFactory.getBean(CacheManager.class)); } ... } this.initialized = true; } // 主要为了输出日志,子类可复写 protected String methodIdentification(Method method, Class<?> targetClass) { Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass); return ClassUtils.getQualifiedMethodName(specificMethod); } // 从这里也能看出,至少要指定一个Cache才行(也就是cacheNames) protected Collection<? extends Cache> getCaches(CacheOperationInvocationContext<CacheOperation> context, CacheResolver cacheResolver) { Collection<? extends Cache> caches = cacheResolver.resolveCaches(context); if (caches.isEmpty()) { throw new IllegalStateException("No cache could be resolved for '" + context.getOperation() + "' using resolver '" + cacheResolver + "'. At least one cache should be provided per cache operation."); } return caches; } // 这个根据CacheOperation 这部分还是比较重要的 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) { // 1、指定了KeyGenerator就去拿这个Bean(没有就报错,所以key不要写错了) // 没有指定就用默认的 KeyGenerator operationKeyGenerator; if (StringUtils.hasText(operation.getKeyGenerator())) { operationKeyGenerator = getBean(operation.getKeyGenerator(), KeyGenerator.class); } else { operationKeyGenerator = getKeyGenerator(); } // 1、自己指定的CacheResolver // 2、再看指定的的CacheManager,包装成一个SimpleCacheResolver // 3、 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 metadata = new CacheOperationMetadata(operation, method, targetClass, operationKeyGenerator, operationCacheResolver); this.metadataCache.put(cacheKey, metadata); } return metadata; } // qualifiedBeanOfType的意思是,@Bean类上面标注@Qualifier注解也生效 protected <T> T getBean(String beanName, Class<T> expectedType) { if (this.beanFactory == null) { throw new IllegalStateException( "BeanFactory must be set on cache aspect for " + expectedType.getSimpleName() + " retrieval"); } return BeanFactoryAnnotationUtils.qualifiedBeanOfType(this.beanFactory, expectedType, beanName); } // 请Meta数据的缓存 protected void clearMetadataCache() { this.metadataCache.clear(); this.evaluator.clear(); } // 父类最为核心的方法,真正执行目标方法 + 缓存操作 @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) // 如果已经表示初始化过了(有CacheManager,CacheResolver了),执行这里 if (this.initialized) { // getTargetClass拿到原始Class 解剖代理(N层都能解开) Class<?> targetClass = getTargetClass(target); CacheOperationSource cacheOperationSource = getCacheOperationSource(); if (cacheOperationSource != null) { // 简单的说就是拿到该方法上所有的CacheOperation缓存操作,最终一个一个的执行~~~~ Collection<CacheOperation> operations = cacheOperationSource.getCacheOperations(method, targetClass); if (!CollectionUtils.isEmpty(operations)) { // CacheOperationContexts是非常重要的一个私有内部类 // 注意它是复数哦~不是CacheOperationContext单数 所以它就像持有多个注解上下文一样 一个个执行吧 // 所以我建议先看看此类的描述,再继续往下看~~~ 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, cache.get(key, () -> unwrapReturnValue(invokeOperation(invoker)))); } catch (Cache.ValueRetrievalException ex) { // The invoker wraps any Throwable in a ThrowableWrapper instance so we // can just make sure that one bubbles up the stack. throw (CacheOperationInvoker.ThrowableWrapper) ex.getCause(); } } else { // No caching required, only call the underlying method return invokeOperation(invoker); } } // sync=false的情况,走这里~~~ // Process any early evictions beforeInvocation=true的会在此处最先执行~~~ // 最先处理@CacheEvict注解~~~真正执行的方法请参见:performCacheEvict // context.getCaches()拿出所有的caches,看看是执行cache.evict(key);方法还是cache.clear();而已 // 需要注意的的是context.isConditionPassing(result); condition条件此处生效,并且可以使用#result // context.generateKey(result)也能使用#result // @CacheEvict没有unless属性 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 LinkedList<>(); // 如果缓存没有命中,那就准备一个cachePutRequest // 因为@Cacheable首次进来肯定命中不了,最终肯定是需要执行一次put操作的~~~这样下次进来就能命中了呀 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(); // wrapCacheValue主要是支持到了Optional returnValue = wrapCacheValue(method, cacheValue); } else { //到此处,目标方法就肯定是需要执行了的~~~~~ // Invoke the method if we don't have a cache hit // 啥都不说,先invokeOperation执行目标方法,拿到方法的的返回值 后续在处理put啥的 returnValue = invokeOperation(invoker); cacheValue = unwrapReturnValue(returnValue); } // Collect any explicit @CachePuts explicit:明确的 collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests); // Process any collected put requests, either from @CachePut or a @Cacheable miss for (CachePutRequest cachePutRequest : cachePutRequests) { // 注意:此处unless啥的生效~~~~ // 最终执行cache.put(key, result);方法 cachePutRequest.apply(cacheValue); } // Process any late evictions beforeInvocation=true的会在此处最先执行~~~ beforeInvocation=false的会在此处最后执行~~~ // 所以中途若抛出异常,此部分就不会执行了~~~~ processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue); return returnValue; } // 缓存属性的上下文们。每个方法可以对应多个上下文~~~ private class CacheOperationContexts { // 因为方法上可以标注多个注解 // 需要注意的是它的key是Class,而CacheOperation的子类也就那三个哥们而已~ private final MultiValueMap<Class<? extends CacheOperation>, CacheOperationContext> contexts; // 是否要求同步执行,默认值是false private final boolean sync; 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)); } // sync这个属性虽然不怎么使用,但determineSyncFlag这个方法可以看一下 this.sync = determineSyncFlag(method); } public Collection<CacheOperationContext> get(Class<? extends CacheOperation> operationClass) { Collection<CacheOperationContext> result = this.contexts.get(operationClass); return (result != null ? result : Collections.emptyList()); } public boolean isSynchronized() { return this.sync; } // 因为只有@Cacheable有sync属性,所以只需要看CacheableOperation即可 private boolean determineSyncFlag(Method method) { List<CacheOperationContext> cacheOperationContexts = this.contexts.get(CacheableOperation.class); if (cacheOperationContexts == null) { // no @Cacheable operation at all return false; } boolean syncEnabled = false; // 单反只要有一个@Cacheable的sync=true了,那就为true 并且下面还有检查逻辑 for (CacheOperationContext cacheOperationContext : cacheOperationContexts) { if (((CacheableOperation) cacheOperationContext.getOperation()).isSync()) { syncEnabled = true; break; } } // 执行sync=true的检查逻辑 if (syncEnabled) { // 人话解释:sync=true时候,不能还有其它的缓存操作 也就是说@Cacheable(sync=true)的时候只能单独使用 if (this.contexts.size() > 1) { throw new IllegalStateException("@Cacheable(sync=true) cannot be combined with other cache operations on '" + method + "'"); } // 人话解释:@Cacheable(sync=true)时,多个@Cacheable也是不允许的 if (cacheOperationContexts.size() > 1) { throw new IllegalStateException("Only one @Cacheable(sync=true) entry is allowed on '" + method + "'"); } // 拿到唯一的一个@Cacheable CacheOperationContext cacheOperationContext = cacheOperationContexts.iterator().next(); CacheableOperation operation = (CacheableOperation) cacheOperationContext.getOperation(); // 人话解释:@Cacheable(sync=true)时,cacheName只能使用一个 if (cacheOperationContext.getCaches().size() > 1) { throw new IllegalStateException("@Cacheable(sync=true) only allows a single cache on '" + operation + "'"); } // 人话解释:sync=true时,unless属性是不支持的~~~并且是不能写的 if (StringUtils.hasText(operation.getUnless())) { throw new IllegalStateException("@Cacheable(sync=true) does not support unless attribute on '" + operation + "'"); } return true; // 只有校验都通过后,才返回true } return false; } } ... }
以上,拦截器实现了Spring Cache
处理注解缓存的执行的核心步骤,个人建议上述代码可多读几遍,其义自见。
Spring Cache是Spring框架的核心模块之一,不可谓不重要。用了好几篇文章专门来讲解使用、分析原理。下面按照正常的思路,我把Spring处理的步骤总结如下:
CacheOperation
封装了@CachePut
、@Cacheable
、@CacheEvict
(下称三大缓存注解)的属性信息,以便于拦截的时候能直接操作此对象来执行逻辑。CacheOperation
的过程是由CacheAnnotationParser
完成的CacheAnnotationSource
代表缓存属性源,非常非常重要的一个概念。它提供接口方法来获取目标方法的CacheOperation
集合。由上可知,这个具体工作是委托给CacheAnnotationParser
去完成的BeanFactoryCacheOperationSourceAdvisor
它代表增强器,至于需要增强哪些类呢???就是看有没有存在CacheOperation
属性的方法CacheInterceptor
实现了MethodInterceptor
接口,在Spring AOP
中实现对执行方法的拦截。在调用invoke
执行目标方法前后,通过CacheAnnotationSource
获取到方法所有的缓存操作属性,从而一个个的执行CacheOperation
最后被封装成了CacheOperationContext
,而CacheOperationContext
最终通过CacheResolver
解析出缓存对象Cache
(可能是多个)CacheInterceptor
调用其父类AbstractCacheInvoker
执行对应的doPut
/ doGet
/ doEvict
/ doClear
等等。(可以处理执行异常)其实ProxyFactoryBean
的设计模式在Spring AOP中已经非常不陌生了:【小家Spring】面向切面编程Spring AOP创建代理的方式:ProxyFactoryBean、ProxyFactory、AspectJProxyFactory(JDK Proxy和CGLIB)
如下截图,Spring内有非常多的xxxProxyFactoryBean
的实现:
如果说把@EnableCaching
称为自动模式的话,那使用CacheProxyFactoryBean
就完全是手动档。话不多说,此处给个使用Demo就收场了:
@Configuration public class RootConfig { @Bean public CacheProxyFactoryBean cacheProxyFactoryBean() { CacheProxyFactoryBean proxyFactoryBean = new CacheProxyFactoryBean(); // 使用AnnotationCacheOperationSource来识别三大注解缓存 proxyFactoryBean.setCacheOperationSources(new AnnotationCacheOperationSource()); // 设置需要代理的目标类 CacheDemoService cacheDemoService = new CacheDemoServiceImpl(); proxyFactoryBean.setTarget(cacheDemoService); //proxyFactoryBean.setProxyInterfaces(); // 设置个性化的一些东西 CacheManager cacheManager = new ConcurrentMapCacheManager(); proxyFactoryBean.setCacheManager(cacheManager); //proxyFactoryBean.setKeyGenerator(); //proxyFactoryBean.setCacheResolver(); return proxyFactoryBean; } } //@Service // 因为使用了CacheProxyFactoryBean手动额皮质,此处请不要再被扫描进去,否则容器内就出现两个这样的Bean了 public class CacheDemoServiceImpl implements CacheDemoService { @Cacheable(cacheNames = "demoCache", key = "#id") @Override public Object getFromDB(Integer id) { System.out.println("模拟去db查询~~~" + id); return "hello cache..."; } }
测试:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {RootConfig.class, CacheConfig.class})
public class TestSpringBean {
@Autowired
private CacheDemoService cacheDemoService;
@Test
public void test1() {
cacheDemoService.getFromDB(1);
cacheDemoService.getFromDB(1);
}
}
打印结果:
模拟去db查询~~~1
只输出一套日志:缓存生效
此示例中
@EnableCaching
可不是打开状态哦,但我们依然能够使用手动档让缓存生效。
使用手动档,我们可以很方便的使用NameMatchCacheOperationSource
来根据方法名匹配~~~
关于缓存注解的常规使用案例,我觉得本文没有必要介绍。
接下来主要讲解一些特殊的使用:
比如上例返回值改为null:
@Service
public class CacheDemoServiceImpl implements CacheDemoService {
@Cacheable(cacheNames = "demoCache", key = "#id")
@Override
public Object getFromDB(Integer id) {
System.out.println("模拟去db查询~~~" + id);
//return "hello cache...";
return null;
}
}
执行单测:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = {RootConfig.class, CacheConfig.class}) public class TestSpringBean { @Autowired private CacheDemoService cacheDemoService; @Autowired private CacheManager cacheManager; @Test public void test1() { cacheDemoService.getFromDB(1); cacheDemoService.getFromDB(1); System.out.println("----------验证缓存是否生效----------"); Cache cache = cacheManager.getCache("demoCache"); System.out.println(cache); System.out.println(cache.get(1, String.class)); } }
结果打印:
模拟去db查询~~~1
----------验证缓存是否生效----------
org.springframework.cache.concurrent.ConcurrentMapCache@15f2eda3
null
结论是:默认情况下,返回值是null也是会缓存的(第二次过来就不会再查询了)。通过一个断点会更清晰:
但假如修改缓存注解如下:
// 注意:unless 是非的意思
@Cacheable(cacheNames = "demoCache", key = "#id",unless = "#result == null")
运行打印如下:
模拟去db查询~~~1
模拟去db查询~~~1
----------验证缓存是否生效----------
org.springframework.cache.concurrent.ConcurrentMapCache@6a282fdd
null
查了两次DB,证明此时返回null就不会再缓存了,unless生效。
倘若修改配置如下:
@Bean
public CacheManager cacheManager() {
ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
cacheManager.setAllowNullValues(false);
return cacheManager;
}
运行则报错:
java.lang.IllegalArgumentException: Cache 'demoCache' is configured to not allow null values but null was provided
一般情况下,不建议这么设置,因为一般都是允许缓存null值的。
@Cacheable
注解sync=true
的效果在多线程环境下,某些操作可能使用相同参数同步调用(相同的key)。默认情况下,缓存不锁定任何资源,可能导致多次计算,而违反了缓存的目的。对于这些特定的情况,属性 sync 可以指示底层将缓存锁住,使只有一个线程可以进入计算,而其他线程堵塞,直到返回结果更新到缓存中(Spring4.3提供的)。
下面给个例子直接看看效果就成:
@Cacheable(cacheNames = "demoCache", key = "#id")
测试Demo(多线程):
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = {RootConfig.class, CacheConfig.class}) public class TestSpringBean { @Autowired private CacheDemoService cacheDemoService; @Test public void test1() throws InterruptedException { for (int i = 0; i < 10; i++) { new Thread(() -> { cacheDemoService.getFromDB(1); }).start(); } // 保证main线程不要这么快结束(否则没有日志结果的~~~) TimeUnit.SECONDS.sleep(10); } }
打印结果:
模拟去db查询~~~1
模拟去db查询~~~1
模拟去db查询~~~1
模拟去db查询~~~1
模拟去db查询~~~1
打印可能5次、可能6次不确定。但是若我们sync=true了呢?
@Cacheable(cacheNames = "demoCache", key = "#id", sync = true)
再次运行测试打印结果如下:
模拟去db查询~~~1
永远只会打印一次。 所以在高并发环境下,我个人是十分建议开启此同步开关的,至于非高并发,无所谓啦~
因为源码都分析了,所以此处看结论即可。
@CachePut(cacheNames = "demoCache", key = "#id") // 不同的注解可以标注多个
//@Cacheable(cacheNames = "demoCache", key = "#id") // 相同注解标注两个是不行的 因为它并不是@Repeatable的
@Cacheable(cacheNames = "demoCache", key = "#id")
@Override
public Object getFromDB(Integer id) {
System.out.println("模拟去db查询~~~" + id);
return "hello cache...";
}
不同的注解可以标注多个,且都能生效。若需要相同注解标注多个等更复杂的场景,推荐使用@Caching
注解
如何
给缓存注解设置专用的key生成器、缓存管理器等等标准写法是这样的:
@EnableCaching @Configuration public class CacheConfig implements CachingConfigurer { @Override public CacheResolver cacheResolver() { return null; } @Override public KeyGenerator keyGenerator() { return null; } @Override public CacheErrorHandler errorHandler() { return null; } }
实现对应的方法,可以new一个对象返回,也可以用容器内的。
大多数情况下我们都不需要特别的指定缓存注解使用的管理器,因为它自己会去容器里找。 但是,但是,但是当你使用了多套缓存时,我还是建议显示的指定的。
本篇文章相对较长,因为从原理处深度分析了Spring Cache
的执行过程,希望能帮助到大家做到心里有数,从而更加健康的书写代码和扩展功能。
关于缓存注解这块,我相信很多人都有一个痛点:注解并不支持设置过期时间。其实我想说,如果你看了上篇文章就知道,这个Spring它做不了,因为它并没有对Expire
进行抽象。
但是Spring做不了不代表我们自己做不了,因此有兴趣的同学可以在此基础上,扩展出可以自定义超时时间的能力~~~~
Author | A哥(YourBatman) |
---|---|
个人站点 | www.yourbatman.cn |
yourbatman@qq.com | |
微 信 | fsx641385712 |
活跃平台 | |
公众号 | BAT的乌托邦(ID:BAT-utopia) |
知识星球 | BAT的乌托邦 |
每日文章推荐 | 每日文章推荐 |
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。