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