001/* 002 * Copyright 2002-2020 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.cache.interceptor; 018 019import java.lang.reflect.Method; 020import java.lang.reflect.Proxy; 021import java.util.ArrayList; 022import java.util.Collection; 023import java.util.Collections; 024import java.util.LinkedList; 025import java.util.List; 026import java.util.Map; 027import java.util.Optional; 028import java.util.concurrent.ConcurrentHashMap; 029import java.util.function.Supplier; 030 031import org.apache.commons.logging.Log; 032import org.apache.commons.logging.LogFactory; 033 034import org.springframework.aop.framework.AopProxyUtils; 035import org.springframework.aop.support.AopUtils; 036import org.springframework.beans.factory.BeanFactory; 037import org.springframework.beans.factory.BeanFactoryAware; 038import org.springframework.beans.factory.InitializingBean; 039import org.springframework.beans.factory.NoSuchBeanDefinitionException; 040import org.springframework.beans.factory.NoUniqueBeanDefinitionException; 041import org.springframework.beans.factory.SmartInitializingSingleton; 042import org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils; 043import org.springframework.cache.Cache; 044import org.springframework.cache.CacheManager; 045import org.springframework.context.expression.AnnotatedElementKey; 046import org.springframework.core.BridgeMethodResolver; 047import org.springframework.expression.EvaluationContext; 048import org.springframework.lang.Nullable; 049import org.springframework.util.Assert; 050import org.springframework.util.ClassUtils; 051import org.springframework.util.CollectionUtils; 052import org.springframework.util.LinkedMultiValueMap; 053import org.springframework.util.MultiValueMap; 054import org.springframework.util.ObjectUtils; 055import org.springframework.util.ReflectionUtils; 056import org.springframework.util.StringUtils; 057import org.springframework.util.function.SingletonSupplier; 058import org.springframework.util.function.SupplierUtils; 059 060/** 061 * Base class for caching aspects, such as the {@link CacheInterceptor} or an 062 * AspectJ aspect. 063 * 064 * <p>This enables the underlying Spring caching infrastructure to be used easily 065 * to implement an aspect for any aspect system. 066 * 067 * <p>Subclasses are responsible for calling relevant methods in the correct order. 068 * 069 * <p>Uses the <b>Strategy</b> design pattern. A {@link CacheOperationSource} is 070 * used for determining caching operations, a {@link KeyGenerator} will build the 071 * cache keys, and a {@link CacheResolver} will resolve the actual cache(s) to use. 072 * 073 * <p>Note: A cache aspect is serializable but does not perform any actual caching 074 * after deserialization. 075 * 076 * @author Costin Leau 077 * @author Juergen Hoeller 078 * @author Chris Beams 079 * @author Phillip Webb 080 * @author Sam Brannen 081 * @author Stephane Nicoll 082 * @since 3.1 083 */ 084public abstract class CacheAspectSupport extends AbstractCacheInvoker 085 implements BeanFactoryAware, InitializingBean, SmartInitializingSingleton { 086 087 protected final Log logger = LogFactory.getLog(getClass()); 088 089 private final Map<CacheOperationCacheKey, CacheOperationMetadata> metadataCache = new ConcurrentHashMap<>(1024); 090 091 private final CacheOperationExpressionEvaluator evaluator = new CacheOperationExpressionEvaluator(); 092 093 @Nullable 094 private CacheOperationSource cacheOperationSource; 095 096 private SingletonSupplier<KeyGenerator> keyGenerator = SingletonSupplier.of(SimpleKeyGenerator::new); 097 098 @Nullable 099 private SingletonSupplier<CacheResolver> cacheResolver; 100 101 @Nullable 102 private BeanFactory beanFactory; 103 104 private boolean initialized = false; 105 106 107 /** 108 * Configure this aspect with the given error handler, key generator and cache resolver/manager 109 * suppliers, applying the corresponding default if a supplier is not resolvable. 110 * @since 5.1 111 */ 112 public void configure( 113 @Nullable Supplier<CacheErrorHandler> errorHandler, @Nullable Supplier<KeyGenerator> keyGenerator, 114 @Nullable Supplier<CacheResolver> cacheResolver, @Nullable Supplier<CacheManager> cacheManager) { 115 116 this.errorHandler = new SingletonSupplier<>(errorHandler, SimpleCacheErrorHandler::new); 117 this.keyGenerator = new SingletonSupplier<>(keyGenerator, SimpleKeyGenerator::new); 118 this.cacheResolver = new SingletonSupplier<>(cacheResolver, 119 () -> SimpleCacheResolver.of(SupplierUtils.resolve(cacheManager))); 120 } 121 122 123 /** 124 * Set one or more cache operation sources which are used to find the cache 125 * attributes. If more than one source is provided, they will be aggregated 126 * using a {@link CompositeCacheOperationSource}. 127 * @see #setCacheOperationSource 128 */ 129 public void setCacheOperationSources(CacheOperationSource... cacheOperationSources) { 130 Assert.notEmpty(cacheOperationSources, "At least 1 CacheOperationSource needs to be specified"); 131 this.cacheOperationSource = (cacheOperationSources.length > 1 ? 132 new CompositeCacheOperationSource(cacheOperationSources) : cacheOperationSources[0]); 133 } 134 135 /** 136 * Set the CacheOperationSource for this cache aspect. 137 * @since 5.1 138 * @see #setCacheOperationSources 139 */ 140 public void setCacheOperationSource(@Nullable CacheOperationSource cacheOperationSource) { 141 this.cacheOperationSource = cacheOperationSource; 142 } 143 144 /** 145 * Return the CacheOperationSource for this cache aspect. 146 */ 147 @Nullable 148 public CacheOperationSource getCacheOperationSource() { 149 return this.cacheOperationSource; 150 } 151 152 /** 153 * Set the default {@link KeyGenerator} that this cache aspect should delegate to 154 * if no specific key generator has been set for the operation. 155 * <p>The default is a {@link SimpleKeyGenerator}. 156 */ 157 public void setKeyGenerator(KeyGenerator keyGenerator) { 158 this.keyGenerator = SingletonSupplier.of(keyGenerator); 159 } 160 161 /** 162 * Return the default {@link KeyGenerator} that this cache aspect delegates to. 163 */ 164 public KeyGenerator getKeyGenerator() { 165 return this.keyGenerator.obtain(); 166 } 167 168 /** 169 * Set the default {@link CacheResolver} that this cache aspect should delegate 170 * to if no specific cache resolver has been set for the operation. 171 * <p>The default resolver resolves the caches against their names and the 172 * default cache manager. 173 * @see #setCacheManager 174 * @see SimpleCacheResolver 175 */ 176 public void setCacheResolver(@Nullable CacheResolver cacheResolver) { 177 this.cacheResolver = SingletonSupplier.ofNullable(cacheResolver); 178 } 179 180 /** 181 * Return the default {@link CacheResolver} that this cache aspect delegates to. 182 */ 183 @Nullable 184 public CacheResolver getCacheResolver() { 185 return SupplierUtils.resolve(this.cacheResolver); 186 } 187 188 /** 189 * Set the {@link CacheManager} to use to create a default {@link CacheResolver}. 190 * Replace the current {@link CacheResolver}, if any. 191 * @see #setCacheResolver 192 * @see SimpleCacheResolver 193 */ 194 public void setCacheManager(CacheManager cacheManager) { 195 this.cacheResolver = SingletonSupplier.of(new SimpleCacheResolver(cacheManager)); 196 } 197 198 /** 199 * Set the containing {@link BeanFactory} for {@link CacheManager} and other 200 * service lookups. 201 * @since 4.3 202 */ 203 @Override 204 public void setBeanFactory(BeanFactory beanFactory) { 205 this.beanFactory = beanFactory; 206 } 207 208 209 @Override 210 public void afterPropertiesSet() { 211 Assert.state(getCacheOperationSource() != null, "The 'cacheOperationSources' property is required: " + 212 "If there are no cacheable methods, then don't use a cache aspect."); 213 } 214 215 @Override 216 public void afterSingletonsInstantiated() { 217 if (getCacheResolver() == null) { 218 // Lazily initialize cache resolver via default cache manager... 219 Assert.state(this.beanFactory != null, "CacheResolver or BeanFactory must be set on cache aspect"); 220 try { 221 setCacheManager(this.beanFactory.getBean(CacheManager.class)); 222 } 223 catch (NoUniqueBeanDefinitionException ex) { 224 throw new IllegalStateException("No CacheResolver specified, and no unique bean of type " + 225 "CacheManager found. Mark one as primary or declare a specific CacheManager to use."); 226 } 227 catch (NoSuchBeanDefinitionException ex) { 228 throw new IllegalStateException("No CacheResolver specified, and no bean of type CacheManager found. " + 229 "Register a CacheManager bean or remove the @EnableCaching annotation from your configuration."); 230 } 231 } 232 this.initialized = true; 233 } 234 235 236 /** 237 * Convenience method to return a String representation of this Method 238 * for use in logging. Can be overridden in subclasses to provide a 239 * different identifier for the given method. 240 * @param method the method we're interested in 241 * @param targetClass class the method is on 242 * @return log message identifying this method 243 * @see org.springframework.util.ClassUtils#getQualifiedMethodName 244 */ 245 protected String methodIdentification(Method method, Class<?> targetClass) { 246 Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass); 247 return ClassUtils.getQualifiedMethodName(specificMethod); 248 } 249 250 protected Collection<? extends Cache> getCaches( 251 CacheOperationInvocationContext<CacheOperation> context, CacheResolver cacheResolver) { 252 253 Collection<? extends Cache> caches = cacheResolver.resolveCaches(context); 254 if (caches.isEmpty()) { 255 throw new IllegalStateException("No cache could be resolved for '" + 256 context.getOperation() + "' using resolver '" + cacheResolver + 257 "'. At least one cache should be provided per cache operation."); 258 } 259 return caches; 260 } 261 262 protected CacheOperationContext getOperationContext( 263 CacheOperation operation, Method method, Object[] args, Object target, Class<?> targetClass) { 264 265 CacheOperationMetadata metadata = getCacheOperationMetadata(operation, method, targetClass); 266 return new CacheOperationContext(metadata, args, target); 267 } 268 269 /** 270 * Return the {@link CacheOperationMetadata} for the specified operation. 271 * <p>Resolve the {@link CacheResolver} and the {@link KeyGenerator} to be 272 * used for the operation. 273 * @param operation the operation 274 * @param method the method on which the operation is invoked 275 * @param targetClass the target type 276 * @return the resolved metadata for the operation 277 */ 278 protected CacheOperationMetadata getCacheOperationMetadata( 279 CacheOperation operation, Method method, Class<?> targetClass) { 280 281 CacheOperationCacheKey cacheKey = new CacheOperationCacheKey(operation, method, targetClass); 282 CacheOperationMetadata metadata = this.metadataCache.get(cacheKey); 283 if (metadata == null) { 284 KeyGenerator operationKeyGenerator; 285 if (StringUtils.hasText(operation.getKeyGenerator())) { 286 operationKeyGenerator = getBean(operation.getKeyGenerator(), KeyGenerator.class); 287 } 288 else { 289 operationKeyGenerator = getKeyGenerator(); 290 } 291 CacheResolver operationCacheResolver; 292 if (StringUtils.hasText(operation.getCacheResolver())) { 293 operationCacheResolver = getBean(operation.getCacheResolver(), CacheResolver.class); 294 } 295 else if (StringUtils.hasText(operation.getCacheManager())) { 296 CacheManager cacheManager = getBean(operation.getCacheManager(), CacheManager.class); 297 operationCacheResolver = new SimpleCacheResolver(cacheManager); 298 } 299 else { 300 operationCacheResolver = getCacheResolver(); 301 Assert.state(operationCacheResolver != null, "No CacheResolver/CacheManager set"); 302 } 303 metadata = new CacheOperationMetadata(operation, method, targetClass, 304 operationKeyGenerator, operationCacheResolver); 305 this.metadataCache.put(cacheKey, metadata); 306 } 307 return metadata; 308 } 309 310 /** 311 * Return a bean with the specified name and type. Used to resolve services that 312 * are referenced by name in a {@link CacheOperation}. 313 * @param beanName the name of the bean, as defined by the operation 314 * @param expectedType type for the bean 315 * @return the bean matching that name 316 * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException if such bean does not exist 317 * @see CacheOperation#getKeyGenerator() 318 * @see CacheOperation#getCacheManager() 319 * @see CacheOperation#getCacheResolver() 320 */ 321 protected <T> T getBean(String beanName, Class<T> expectedType) { 322 if (this.beanFactory == null) { 323 throw new IllegalStateException( 324 "BeanFactory must be set on cache aspect for " + expectedType.getSimpleName() + " retrieval"); 325 } 326 return BeanFactoryAnnotationUtils.qualifiedBeanOfType(this.beanFactory, expectedType, beanName); 327 } 328 329 /** 330 * Clear the cached metadata. 331 */ 332 protected void clearMetadataCache() { 333 this.metadataCache.clear(); 334 this.evaluator.clear(); 335 } 336 337 @Nullable 338 protected Object execute(CacheOperationInvoker invoker, Object target, Method method, Object[] args) { 339 // Check whether aspect is enabled (to cope with cases where the AJ is pulled in automatically) 340 if (this.initialized) { 341 Class<?> targetClass = getTargetClass(target); 342 CacheOperationSource cacheOperationSource = getCacheOperationSource(); 343 if (cacheOperationSource != null) { 344 Collection<CacheOperation> operations = cacheOperationSource.getCacheOperations(method, targetClass); 345 if (!CollectionUtils.isEmpty(operations)) { 346 return execute(invoker, method, 347 new CacheOperationContexts(operations, method, args, target, targetClass)); 348 } 349 } 350 } 351 352 return invoker.invoke(); 353 } 354 355 /** 356 * Execute the underlying operation (typically in case of cache miss) and return 357 * the result of the invocation. If an exception occurs it will be wrapped in a 358 * {@link CacheOperationInvoker.ThrowableWrapper}: the exception can be handled 359 * or modified but it <em>must</em> be wrapped in a 360 * {@link CacheOperationInvoker.ThrowableWrapper} as well. 361 * @param invoker the invoker handling the operation being cached 362 * @return the result of the invocation 363 * @see CacheOperationInvoker#invoke() 364 */ 365 protected Object invokeOperation(CacheOperationInvoker invoker) { 366 return invoker.invoke(); 367 } 368 369 private Class<?> getTargetClass(Object target) { 370 return AopProxyUtils.ultimateTargetClass(target); 371 } 372 373 @Nullable 374 private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) { 375 // Special handling of synchronized invocation 376 if (contexts.isSynchronized()) { 377 CacheOperationContext context = contexts.get(CacheableOperation.class).iterator().next(); 378 if (isConditionPassing(context, CacheOperationExpressionEvaluator.NO_RESULT)) { 379 Object key = generateKey(context, CacheOperationExpressionEvaluator.NO_RESULT); 380 Cache cache = context.getCaches().iterator().next(); 381 try { 382 return wrapCacheValue(method, handleSynchronizedGet(invoker, key, cache)); 383 } 384 catch (Cache.ValueRetrievalException ex) { 385 // Directly propagate ThrowableWrapper from the invoker, 386 // or potentially also an IllegalArgumentException etc. 387 ReflectionUtils.rethrowRuntimeException(ex.getCause()); 388 } 389 } 390 else { 391 // No caching required, only call the underlying method 392 return invokeOperation(invoker); 393 } 394 } 395 396 397 // Process any early evictions 398 processCacheEvicts(contexts.get(CacheEvictOperation.class), true, 399 CacheOperationExpressionEvaluator.NO_RESULT); 400 401 // Check if we have a cached item matching the conditions 402 Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class)); 403 404 // Collect puts from any @Cacheable miss, if no cached item is found 405 List<CachePutRequest> cachePutRequests = new LinkedList<>(); 406 if (cacheHit == null) { 407 collectPutRequests(contexts.get(CacheableOperation.class), 408 CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests); 409 } 410 411 Object cacheValue; 412 Object returnValue; 413 414 if (cacheHit != null && !hasCachePut(contexts)) { 415 // If there are no put requests, just use the cache hit 416 cacheValue = cacheHit.get(); 417 returnValue = wrapCacheValue(method, cacheValue); 418 } 419 else { 420 // Invoke the method if we don't have a cache hit 421 returnValue = invokeOperation(invoker); 422 cacheValue = unwrapReturnValue(returnValue); 423 } 424 425 // Collect any explicit @CachePuts 426 collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests); 427 428 // Process any collected put requests, either from @CachePut or a @Cacheable miss 429 for (CachePutRequest cachePutRequest : cachePutRequests) { 430 cachePutRequest.apply(cacheValue); 431 } 432 433 // Process any late evictions 434 processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue); 435 436 return returnValue; 437 } 438 439 @Nullable 440 private Object handleSynchronizedGet(CacheOperationInvoker invoker, Object key, Cache cache) { 441 InvocationAwareResult invocationResult = new InvocationAwareResult(); 442 Object result = cache.get(key, () -> { 443 invocationResult.invoked = true; 444 if (logger.isTraceEnabled()) { 445 logger.trace("No cache entry for key '" + key + "' in cache " + cache.getName()); 446 } 447 return unwrapReturnValue(invokeOperation(invoker)); 448 }); 449 if (!invocationResult.invoked && logger.isTraceEnabled()) { 450 logger.trace("Cache entry for key '" + key + "' found in cache '" + cache.getName() + "'"); 451 } 452 return result; 453 } 454 455 @Nullable 456 private Object wrapCacheValue(Method method, @Nullable Object cacheValue) { 457 if (method.getReturnType() == Optional.class && 458 (cacheValue == null || cacheValue.getClass() != Optional.class)) { 459 return Optional.ofNullable(cacheValue); 460 } 461 return cacheValue; 462 } 463 464 @Nullable 465 private Object unwrapReturnValue(Object returnValue) { 466 return ObjectUtils.unwrapOptional(returnValue); 467 } 468 469 private boolean hasCachePut(CacheOperationContexts contexts) { 470 // Evaluate the conditions *without* the result object because we don't have it yet... 471 Collection<CacheOperationContext> cachePutContexts = contexts.get(CachePutOperation.class); 472 Collection<CacheOperationContext> excluded = new ArrayList<>(); 473 for (CacheOperationContext context : cachePutContexts) { 474 try { 475 if (!context.isConditionPassing(CacheOperationExpressionEvaluator.RESULT_UNAVAILABLE)) { 476 excluded.add(context); 477 } 478 } 479 catch (VariableNotAvailableException ex) { 480 // Ignoring failure due to missing result, consider the cache put has to proceed 481 } 482 } 483 // Check if all puts have been excluded by condition 484 return (cachePutContexts.size() != excluded.size()); 485 } 486 487 private void processCacheEvicts( 488 Collection<CacheOperationContext> contexts, boolean beforeInvocation, @Nullable Object result) { 489 490 for (CacheOperationContext context : contexts) { 491 CacheEvictOperation operation = (CacheEvictOperation) context.metadata.operation; 492 if (beforeInvocation == operation.isBeforeInvocation() && isConditionPassing(context, result)) { 493 performCacheEvict(context, operation, result); 494 } 495 } 496 } 497 498 private void performCacheEvict( 499 CacheOperationContext context, CacheEvictOperation operation, @Nullable Object result) { 500 501 Object key = null; 502 for (Cache cache : context.getCaches()) { 503 if (operation.isCacheWide()) { 504 logInvalidating(context, operation, null); 505 doClear(cache, operation.isBeforeInvocation()); 506 } 507 else { 508 if (key == null) { 509 key = generateKey(context, result); 510 } 511 logInvalidating(context, operation, key); 512 doEvict(cache, key, operation.isBeforeInvocation()); 513 } 514 } 515 } 516 517 private void logInvalidating(CacheOperationContext context, CacheEvictOperation operation, @Nullable Object key) { 518 if (logger.isTraceEnabled()) { 519 logger.trace("Invalidating " + (key != null ? "cache key [" + key + "]" : "entire cache") + 520 " for operation " + operation + " on method " + context.metadata.method); 521 } 522 } 523 524 /** 525 * Find a cached item only for {@link CacheableOperation} that passes the condition. 526 * @param contexts the cacheable operations 527 * @return a {@link Cache.ValueWrapper} holding the cached item, 528 * or {@code null} if none is found 529 */ 530 @Nullable 531 private Cache.ValueWrapper findCachedItem(Collection<CacheOperationContext> contexts) { 532 Object result = CacheOperationExpressionEvaluator.NO_RESULT; 533 for (CacheOperationContext context : contexts) { 534 if (isConditionPassing(context, result)) { 535 Object key = generateKey(context, result); 536 Cache.ValueWrapper cached = findInCaches(context, key); 537 if (cached != null) { 538 return cached; 539 } 540 else { 541 if (logger.isTraceEnabled()) { 542 logger.trace("No cache entry for key '" + key + "' in cache(s) " + context.getCacheNames()); 543 } 544 } 545 } 546 } 547 return null; 548 } 549 550 /** 551 * Collect the {@link CachePutRequest} for all {@link CacheOperation} using 552 * the specified result item. 553 * @param contexts the contexts to handle 554 * @param result the result item (never {@code null}) 555 * @param putRequests the collection to update 556 */ 557 private void collectPutRequests(Collection<CacheOperationContext> contexts, 558 @Nullable Object result, Collection<CachePutRequest> putRequests) { 559 560 for (CacheOperationContext context : contexts) { 561 if (isConditionPassing(context, result)) { 562 Object key = generateKey(context, result); 563 putRequests.add(new CachePutRequest(context, key)); 564 } 565 } 566 } 567 568 @Nullable 569 private Cache.ValueWrapper findInCaches(CacheOperationContext context, Object key) { 570 for (Cache cache : context.getCaches()) { 571 Cache.ValueWrapper wrapper = doGet(cache, key); 572 if (wrapper != null) { 573 if (logger.isTraceEnabled()) { 574 logger.trace("Cache entry for key '" + key + "' found in cache '" + cache.getName() + "'"); 575 } 576 return wrapper; 577 } 578 } 579 return null; 580 } 581 582 private boolean isConditionPassing(CacheOperationContext context, @Nullable Object result) { 583 boolean passing = context.isConditionPassing(result); 584 if (!passing && logger.isTraceEnabled()) { 585 logger.trace("Cache condition failed on method " + context.metadata.method + 586 " for operation " + context.metadata.operation); 587 } 588 return passing; 589 } 590 591 private Object generateKey(CacheOperationContext context, @Nullable Object result) { 592 Object key = context.generateKey(result); 593 if (key == null) { 594 throw new IllegalArgumentException("Null key returned for cache operation (maybe you are " + 595 "using named params on classes without debug info?) " + context.metadata.operation); 596 } 597 if (logger.isTraceEnabled()) { 598 logger.trace("Computed cache key '" + key + "' for operation " + context.metadata.operation); 599 } 600 return key; 601 } 602 603 604 private class CacheOperationContexts { 605 606 private final MultiValueMap<Class<? extends CacheOperation>, CacheOperationContext> contexts; 607 608 private final boolean sync; 609 610 public CacheOperationContexts(Collection<? extends CacheOperation> operations, Method method, 611 Object[] args, Object target, Class<?> targetClass) { 612 613 this.contexts = new LinkedMultiValueMap<>(operations.size()); 614 for (CacheOperation op : operations) { 615 this.contexts.add(op.getClass(), getOperationContext(op, method, args, target, targetClass)); 616 } 617 this.sync = determineSyncFlag(method); 618 } 619 620 public Collection<CacheOperationContext> get(Class<? extends CacheOperation> operationClass) { 621 Collection<CacheOperationContext> result = this.contexts.get(operationClass); 622 return (result != null ? result : Collections.emptyList()); 623 } 624 625 public boolean isSynchronized() { 626 return this.sync; 627 } 628 629 private boolean determineSyncFlag(Method method) { 630 List<CacheOperationContext> cacheOperationContexts = this.contexts.get(CacheableOperation.class); 631 if (cacheOperationContexts == null) { // no @Cacheable operation at all 632 return false; 633 } 634 boolean syncEnabled = false; 635 for (CacheOperationContext cacheOperationContext : cacheOperationContexts) { 636 if (((CacheableOperation) cacheOperationContext.getOperation()).isSync()) { 637 syncEnabled = true; 638 break; 639 } 640 } 641 if (syncEnabled) { 642 if (this.contexts.size() > 1) { 643 throw new IllegalStateException( 644 "@Cacheable(sync=true) cannot be combined with other cache operations on '" + method + "'"); 645 } 646 if (cacheOperationContexts.size() > 1) { 647 throw new IllegalStateException( 648 "Only one @Cacheable(sync=true) entry is allowed on '" + method + "'"); 649 } 650 CacheOperationContext cacheOperationContext = cacheOperationContexts.iterator().next(); 651 CacheableOperation operation = (CacheableOperation) cacheOperationContext.getOperation(); 652 if (cacheOperationContext.getCaches().size() > 1) { 653 throw new IllegalStateException( 654 "@Cacheable(sync=true) only allows a single cache on '" + operation + "'"); 655 } 656 if (StringUtils.hasText(operation.getUnless())) { 657 throw new IllegalStateException( 658 "@Cacheable(sync=true) does not support unless attribute on '" + operation + "'"); 659 } 660 return true; 661 } 662 return false; 663 } 664 } 665 666 667 /** 668 * Metadata of a cache operation that does not depend on a particular invocation 669 * which makes it a good candidate for caching. 670 */ 671 protected static class CacheOperationMetadata { 672 673 private final CacheOperation operation; 674 675 private final Method method; 676 677 private final Class<?> targetClass; 678 679 private final Method targetMethod; 680 681 private final AnnotatedElementKey methodKey; 682 683 private final KeyGenerator keyGenerator; 684 685 private final CacheResolver cacheResolver; 686 687 public CacheOperationMetadata(CacheOperation operation, Method method, Class<?> targetClass, 688 KeyGenerator keyGenerator, CacheResolver cacheResolver) { 689 690 this.operation = operation; 691 this.method = BridgeMethodResolver.findBridgedMethod(method); 692 this.targetClass = targetClass; 693 this.targetMethod = (!Proxy.isProxyClass(targetClass) ? 694 AopUtils.getMostSpecificMethod(method, targetClass) : this.method); 695 this.methodKey = new AnnotatedElementKey(this.targetMethod, targetClass); 696 this.keyGenerator = keyGenerator; 697 this.cacheResolver = cacheResolver; 698 } 699 } 700 701 702 /** 703 * A {@link CacheOperationInvocationContext} context for a {@link CacheOperation}. 704 */ 705 protected class CacheOperationContext implements CacheOperationInvocationContext<CacheOperation> { 706 707 private final CacheOperationMetadata metadata; 708 709 private final Object[] args; 710 711 private final Object target; 712 713 private final Collection<? extends Cache> caches; 714 715 private final Collection<String> cacheNames; 716 717 @Nullable 718 private Boolean conditionPassing; 719 720 public CacheOperationContext(CacheOperationMetadata metadata, Object[] args, Object target) { 721 this.metadata = metadata; 722 this.args = extractArgs(metadata.method, args); 723 this.target = target; 724 this.caches = CacheAspectSupport.this.getCaches(this, metadata.cacheResolver); 725 this.cacheNames = createCacheNames(this.caches); 726 } 727 728 @Override 729 public CacheOperation getOperation() { 730 return this.metadata.operation; 731 } 732 733 @Override 734 public Object getTarget() { 735 return this.target; 736 } 737 738 @Override 739 public Method getMethod() { 740 return this.metadata.method; 741 } 742 743 @Override 744 public Object[] getArgs() { 745 return this.args; 746 } 747 748 private Object[] extractArgs(Method method, Object[] args) { 749 if (!method.isVarArgs()) { 750 return args; 751 } 752 Object[] varArgs = ObjectUtils.toObjectArray(args[args.length - 1]); 753 Object[] combinedArgs = new Object[args.length - 1 + varArgs.length]; 754 System.arraycopy(args, 0, combinedArgs, 0, args.length - 1); 755 System.arraycopy(varArgs, 0, combinedArgs, args.length - 1, varArgs.length); 756 return combinedArgs; 757 } 758 759 protected boolean isConditionPassing(@Nullable Object result) { 760 if (this.conditionPassing == null) { 761 if (StringUtils.hasText(this.metadata.operation.getCondition())) { 762 EvaluationContext evaluationContext = createEvaluationContext(result); 763 this.conditionPassing = evaluator.condition(this.metadata.operation.getCondition(), 764 this.metadata.methodKey, evaluationContext); 765 } 766 else { 767 this.conditionPassing = true; 768 } 769 } 770 return this.conditionPassing; 771 } 772 773 protected boolean canPutToCache(@Nullable Object value) { 774 String unless = ""; 775 if (this.metadata.operation instanceof CacheableOperation) { 776 unless = ((CacheableOperation) this.metadata.operation).getUnless(); 777 } 778 else if (this.metadata.operation instanceof CachePutOperation) { 779 unless = ((CachePutOperation) this.metadata.operation).getUnless(); 780 } 781 if (StringUtils.hasText(unless)) { 782 EvaluationContext evaluationContext = createEvaluationContext(value); 783 return !evaluator.unless(unless, this.metadata.methodKey, evaluationContext); 784 } 785 return true; 786 } 787 788 /** 789 * Compute the key for the given caching operation. 790 */ 791 @Nullable 792 protected Object generateKey(@Nullable Object result) { 793 if (StringUtils.hasText(this.metadata.operation.getKey())) { 794 EvaluationContext evaluationContext = createEvaluationContext(result); 795 return evaluator.key(this.metadata.operation.getKey(), this.metadata.methodKey, evaluationContext); 796 } 797 return this.metadata.keyGenerator.generate(this.target, this.metadata.method, this.args); 798 } 799 800 private EvaluationContext createEvaluationContext(@Nullable Object result) { 801 return evaluator.createEvaluationContext(this.caches, this.metadata.method, this.args, 802 this.target, this.metadata.targetClass, this.metadata.targetMethod, result, beanFactory); 803 } 804 805 protected Collection<? extends Cache> getCaches() { 806 return this.caches; 807 } 808 809 protected Collection<String> getCacheNames() { 810 return this.cacheNames; 811 } 812 813 private Collection<String> createCacheNames(Collection<? extends Cache> caches) { 814 Collection<String> names = new ArrayList<>(); 815 for (Cache cache : caches) { 816 names.add(cache.getName()); 817 } 818 return names; 819 } 820 } 821 822 823 private class CachePutRequest { 824 825 private final CacheOperationContext context; 826 827 private final Object key; 828 829 public CachePutRequest(CacheOperationContext context, Object key) { 830 this.context = context; 831 this.key = key; 832 } 833 834 public void apply(@Nullable Object result) { 835 if (this.context.canPutToCache(result)) { 836 for (Cache cache : this.context.getCaches()) { 837 doPut(cache, this.key, result); 838 } 839 } 840 } 841 } 842 843 844 private static final class CacheOperationCacheKey implements Comparable<CacheOperationCacheKey> { 845 846 private final CacheOperation cacheOperation; 847 848 private final AnnotatedElementKey methodCacheKey; 849 850 private CacheOperationCacheKey(CacheOperation cacheOperation, Method method, Class<?> targetClass) { 851 this.cacheOperation = cacheOperation; 852 this.methodCacheKey = new AnnotatedElementKey(method, targetClass); 853 } 854 855 @Override 856 public boolean equals(@Nullable Object other) { 857 if (this == other) { 858 return true; 859 } 860 if (!(other instanceof CacheOperationCacheKey)) { 861 return false; 862 } 863 CacheOperationCacheKey otherKey = (CacheOperationCacheKey) other; 864 return (this.cacheOperation.equals(otherKey.cacheOperation) && 865 this.methodCacheKey.equals(otherKey.methodCacheKey)); 866 } 867 868 @Override 869 public int hashCode() { 870 return (this.cacheOperation.hashCode() * 31 + this.methodCacheKey.hashCode()); 871 } 872 873 @Override 874 public String toString() { 875 return this.cacheOperation + " on " + this.methodCacheKey; 876 } 877 878 @Override 879 public int compareTo(CacheOperationCacheKey other) { 880 int result = this.cacheOperation.getName().compareTo(other.cacheOperation.getName()); 881 if (result == 0) { 882 result = this.methodCacheKey.compareTo(other.methodCacheKey); 883 } 884 return result; 885 } 886 } 887 888 /** 889 * Internal holder class for recording that a cache method was invoked. 890 */ 891 private static class InvocationAwareResult { 892 893 boolean invoked; 894 895 } 896 897}