diff options
Diffstat (limited to 'spring-context/src/main/java/org/springframework/cache/interceptor')
11 files changed, 487 insertions, 189 deletions
diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/AbstractFallbackCacheOperationSource.java b/spring-context/src/main/java/org/springframework/cache/interceptor/AbstractFallbackCacheOperationSource.java index d383b281..88d0aec5 100644 --- a/spring-context/src/main/java/org/springframework/cache/interceptor/AbstractFallbackCacheOperationSource.java +++ b/spring-context/src/main/java/org/springframework/cache/interceptor/AbstractFallbackCacheOperationSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,27 +26,24 @@ import java.util.concurrent.ConcurrentHashMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.context.expression.AnnotatedElementKey; import org.springframework.core.BridgeMethodResolver; +import org.springframework.core.MethodClassKey; import org.springframework.util.ClassUtils; /** - * Abstract implementation of {@link CacheOperation} that caches - * attributes for methods and implements a fallback policy: 1. specific - * target method; 2. target class; 3. declaring method; 4. declaring - * class/interface. + * Abstract implementation of {@link CacheOperation} that caches attributes + * for methods and implements a fallback policy: 1. specific target method; + * 2. target class; 3. declaring method; 4. declaring class/interface. * * <p>Defaults to using the target class's caching attribute if none is - * associated with the target method. Any caching attribute associated - * with the target method completely overrides a class caching attribute. - * If none found on the target class, the interface that the invoked - * method has been called through (in case of a JDK proxy) will be - * checked. + * associated with the target method. Any caching attribute associated with + * the target method completely overrides a class caching attribute. + * If none found on the target class, the interface that the invoked method + * has been called through (in case of a JDK proxy) will be checked. * - * <p>This implementation caches attributes by method after they are - * first used. If it is ever desirable to allow dynamic changing of - * cacheable attributes (which is very unlikely), caching could be made - * configurable. + * <p>This implementation caches attributes by method after they are first + * used. If it is ever desirable to allow dynamic changing of cacheable + * attributes (which is very unlikely), caching could be made configurable. * * @author Costin Leau * @author Juergen Hoeller @@ -69,7 +66,7 @@ public abstract class AbstractFallbackCacheOperationSource implements CacheOpera protected final Log logger = LogFactory.getLog(getClass()); /** - * Cache of CacheOperations, keyed by {@link AnnotatedElementKey}. + * Cache of CacheOperations, keyed by method on a specific target class. * <p>As this base class is not marked Serializable, the cache will be recreated * after serialization - provided that the concrete subclass is Serializable. */ @@ -117,7 +114,7 @@ public abstract class AbstractFallbackCacheOperationSource implements CacheOpera * @return the cache key (never {@code null}) */ protected Object getCacheKey(Method method, Class<?> targetClass) { - return new AnnotatedElementKey(method, targetClass); + return new MethodClassKey(method, targetClass); } private Collection<CacheOperation> computeCacheOperations(Method method, Class<?> targetClass) { @@ -140,19 +137,23 @@ public abstract class AbstractFallbackCacheOperationSource implements CacheOpera // Second try is the caching operation on the target class. opDef = findCacheOperations(specificMethod.getDeclaringClass()); - if (opDef != null) { + if (opDef != null && ClassUtils.isUserLevelMethod(method)) { return opDef; } if (specificMethod != method) { - // Fall back is to look at the original method. + // Fallback is to look at the original method. opDef = findCacheOperations(method); if (opDef != null) { return opDef; } - // Last fall back is the class of the original method. - return findCacheOperations(method.getDeclaringClass()); + // Last fallback is the class of the original method. + opDef = findCacheOperations(method.getDeclaringClass()); + if (opDef != null && ClassUtils.isUserLevelMethod(method)) { + return opDef; + } } + return null; } diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java index 6dd21c1d..2de8bd8f 100644 --- a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java +++ b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,12 +23,16 @@ import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Optional; +import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.aop.framework.AopProxyUtils; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.NoUniqueBeanDefinitionException; @@ -36,11 +40,10 @@ import org.springframework.beans.factory.SmartInitializingSingleton; import org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils; import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; -import org.springframework.cache.support.SimpleValueWrapper; import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; import org.springframework.context.expression.AnnotatedElementKey; import org.springframework.expression.EvaluationContext; +import org.springframework.lang.UsesJava8; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.CollectionUtils; @@ -76,17 +79,26 @@ import org.springframework.util.StringUtils; * @since 3.1 */ public abstract class CacheAspectSupport extends AbstractCacheInvoker - implements InitializingBean, SmartInitializingSingleton, ApplicationContextAware { + implements BeanFactoryAware, InitializingBean, SmartInitializingSingleton { + + private static Class<?> javaUtilOptionalClass = null; + + static { + try { + javaUtilOptionalClass = + ClassUtils.forName("java.util.Optional", CacheAspectSupport.class.getClassLoader()); + } + catch (ClassNotFoundException ex) { + // Java 8 not available - Optional references simply not supported then. + } + } protected final Log logger = LogFactory.getLog(getClass()); - /** - * Cache of CacheOperationMetadata, keyed by {@link CacheOperationCacheKey}. - */ private final Map<CacheOperationCacheKey, CacheOperationMetadata> metadataCache = new ConcurrentHashMap<CacheOperationCacheKey, CacheOperationMetadata>(1024); - private final ExpressionEvaluator evaluator = new ExpressionEvaluator(); + private final CacheOperationExpressionEvaluator evaluator = new CacheOperationExpressionEvaluator(); private CacheOperationSource cacheOperationSource; @@ -94,7 +106,7 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker private CacheResolver cacheResolver; - private ApplicationContext applicationContext; + private BeanFactory beanFactory; private boolean initialized = false; @@ -163,12 +175,26 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker return this.cacheResolver; } + /** + * Set the containing {@link BeanFactory} for {@link CacheManager} and other + * service lookups. + * @since 4.3 + */ @Override + public void setBeanFactory(BeanFactory beanFactory) { + this.beanFactory = beanFactory; + } + + /** + * @deprecated as of 4.3, in favor of {@link #setBeanFactory} + */ + @Deprecated public void setApplicationContext(ApplicationContext applicationContext) { - this.applicationContext = applicationContext; + this.beanFactory = applicationContext; } + @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."); @@ -180,7 +206,7 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker if (getCacheResolver() == null) { // Lazily initialize cache resolver via default cache manager... try { - setCacheManager(this.applicationContext.getBean(CacheManager.class)); + setCacheManager(this.beanFactory.getBean(CacheManager.class)); } catch (NoUniqueBeanDefinitionException ex) { throw new IllegalStateException("No CacheResolver specified, and no unique bean of type " + @@ -273,7 +299,7 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker * Return a bean with the specified name and type. Used to resolve services that * are referenced by name in a {@link CacheOperation}. * @param beanName the name of the bean, as defined by the operation - * @param expectedType type type for the bean + * @param expectedType type for the bean * @return the bean matching that name * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException if such bean does not exist * @see CacheOperation#keyGenerator @@ -281,7 +307,7 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker * @see CacheOperation#cacheResolver */ protected <T> T getBean(String beanName, Class<T> expectedType) { - return BeanFactoryAnnotationUtils.qualifiedBeanOfType(this.applicationContext, expectedType, beanName); + return BeanFactoryAnnotationUtils.qualifiedBeanOfType(this.beanFactory, expectedType, beanName); } /** @@ -293,13 +319,12 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker } 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 + // Check whether aspect is enabled (to cope with cases where the AJ is pulled in automatically) if (this.initialized) { Class<?> targetClass = getTargetClass(target); Collection<CacheOperation> operations = getCacheOperationSource().getCacheOperations(method, targetClass); if (!CollectionUtils.isEmpty(operations)) { - return execute(invoker, new CacheOperationContexts(operations, method, args, target, targetClass)); + return execute(invoker, method, new CacheOperationContexts(operations, method, args, target, targetClass)); } } @@ -328,9 +353,37 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker return targetClass; } - private Object execute(CacheOperationInvoker invoker, CacheOperationContexts contexts) { + 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 cache.get(key, new Callable<Object>() { + @Override + public Object call() throws Exception { + return 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); + } + } + + // Process any early evictions - processCacheEvicts(contexts.get(CacheEvictOperation.class), true, ExpressionEvaluator.NO_RESULT); + processCacheEvicts(contexts.get(CacheEvictOperation.class), true, + CacheOperationExpressionEvaluator.NO_RESULT); // Check if we have a cached item matching the conditions Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class)); @@ -338,42 +391,56 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker // Collect puts from any @Cacheable miss, if no cached item is found List<CachePutRequest> cachePutRequests = new LinkedList<CachePutRequest>(); if (cacheHit == null) { - collectPutRequests(contexts.get(CacheableOperation.class), ExpressionEvaluator.NO_RESULT, cachePutRequests); + collectPutRequests(contexts.get(CacheableOperation.class), + CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests); } - Cache.ValueWrapper result = null; + Object cacheValue; + Object returnValue; - // If there are no put requests, just use the cache hit - if (cachePutRequests.isEmpty() && !hasCachePut(contexts)) { - result = cacheHit; + if (cacheHit != null && cachePutRequests.isEmpty() && !hasCachePut(contexts)) { + // If there are no put requests, just use the cache hit + cacheValue = cacheHit.get(); + if (method.getReturnType() == javaUtilOptionalClass && + (cacheValue == null || cacheValue.getClass() != javaUtilOptionalClass)) { + returnValue = OptionalUnwrapper.wrap(cacheValue); + } + else { + returnValue = cacheValue; + } } - - // Invoke the method if don't have a cache hit - if (result == null) { - result = new SimpleValueWrapper(invokeOperation(invoker)); + else { + // Invoke the method if we don't have a cache hit + returnValue = invokeOperation(invoker); + if (returnValue != null && returnValue.getClass() == javaUtilOptionalClass) { + cacheValue = OptionalUnwrapper.unwrap(returnValue); + } + else { + cacheValue = returnValue; + } } // Collect any explicit @CachePuts - collectPutRequests(contexts.get(CachePutOperation.class), result.get(), cachePutRequests); + collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests); // Process any collected put requests, either from @CachePut or a @Cacheable miss for (CachePutRequest cachePutRequest : cachePutRequests) { - cachePutRequest.apply(result.get()); + cachePutRequest.apply(cacheValue); } // Process any late evictions - processCacheEvicts(contexts.get(CacheEvictOperation.class), false, result.get()); + processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue); - return result.get(); + return returnValue; } private boolean hasCachePut(CacheOperationContexts contexts) { - // Evaluate the conditions *without* the result object because we don't have it yet. + // Evaluate the conditions *without* the result object because we don't have it yet... Collection<CacheOperationContext> cachePutContexts = contexts.get(CachePutOperation.class); Collection<CacheOperationContext> excluded = new ArrayList<CacheOperationContext>(); for (CacheOperationContext context : cachePutContexts) { try { - if (!context.isConditionPassing(ExpressionEvaluator.RESULT_UNAVAILABLE)) { + if (!context.isConditionPassing(CacheOperationExpressionEvaluator.RESULT_UNAVAILABLE)) { excluded.add(context); } } @@ -425,7 +492,7 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker * or {@code null} if none is found */ private Cache.ValueWrapper findCachedItem(Collection<CacheOperationContext> contexts) { - Object result = ExpressionEvaluator.NO_RESULT; + Object result = CacheOperationExpressionEvaluator.NO_RESULT; for (CacheOperationContext context : contexts) { if (isConditionPassing(context, result)) { Object key = generateKey(context, result); @@ -501,18 +568,57 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker private final MultiValueMap<Class<? extends CacheOperation>, CacheOperationContext> contexts = new LinkedMultiValueMap<Class<? extends CacheOperation>, CacheOperationContext>(); + private final boolean sync; + public CacheOperationContexts(Collection<? extends CacheOperation> operations, Method method, Object[] args, Object target, Class<?> targetClass) { for (CacheOperation operation : operations) { this.contexts.add(operation.getClass(), getOperationContext(operation, method, args, target, targetClass)); } + this.sync = determineSyncFlag(method); } public Collection<CacheOperationContext> get(Class<? extends CacheOperation> operationClass) { Collection<CacheOperationContext> result = this.contexts.get(operationClass); return (result != null ? result : Collections.<CacheOperationContext>emptyList()); } + + public boolean isSynchronized() { + return this.sync; + } + + 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; + for (CacheOperationContext cacheOperationContext : cacheOperationContexts) { + if (((CacheableOperation) cacheOperationContext.getOperation()).isSync()) { + syncEnabled = true; + break; + } + } + if (syncEnabled) { + if (this.contexts.size() > 1) { + throw new IllegalStateException("@Cacheable(sync=true) cannot be combined with other cache operations on '" + method + "'"); + } + if (cacheOperationContexts.size() > 1) { + throw new IllegalStateException("Only one @Cacheable(sync=true) entry is allowed on '" + method + "'"); + } + CacheOperationContext cacheOperationContext = cacheOperationContexts.iterator().next(); + CacheableOperation operation = (CacheableOperation) cacheOperationContext.getOperation(); + if (cacheOperationContext.getCaches().size() > 1) { + throw new IllegalStateException("@Cacheable(sync=true) only allows a single cache on '" + operation + "'"); + } + if (StringUtils.hasText(operation.getUnless())) { + throw new IllegalStateException("@Cacheable(sync=true) does not support unless attribute on '" + operation + "'"); + } + return true; + } + return false; + } } @@ -635,8 +741,8 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker } private EvaluationContext createEvaluationContext(Object result) { - return evaluator.createEvaluationContext( - this.caches, this.metadata.method, this.args, this.target, this.metadata.targetClass, result); + return evaluator.createEvaluationContext(this.caches, this.metadata.method, this.args, + this.target, this.metadata.targetClass, result, beanFactory); } protected Collection<? extends Cache> getCaches() { @@ -678,7 +784,7 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker } - private static class CacheOperationCacheKey { + private static final class CacheOperationCacheKey implements Comparable<CacheOperationCacheKey> { private final CacheOperation cacheOperation; @@ -706,6 +812,42 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker public int hashCode() { return (this.cacheOperation.hashCode() * 31 + this.methodCacheKey.hashCode()); } + + @Override + public String toString() { + return this.cacheOperation + " on " + this.methodCacheKey; + } + + @Override + public int compareTo(CacheOperationCacheKey other) { + int result = this.cacheOperation.getName().compareTo(other.cacheOperation.getName()); + if (result == 0) { + result = this.methodCacheKey.compareTo(other.methodCacheKey); + } + return result; + } + } + + + /** + * Inner class to avoid a hard dependency on Java 8. + */ + @UsesJava8 + private static class OptionalUnwrapper { + + public static Object unwrap(Object optionalObject) { + Optional<?> optional = (Optional<?>) optionalObject; + if (!optional.isPresent()) { + return null; + } + Object result = optional.get(); + Assert.isTrue(!(result instanceof Optional), "Multi-level Optional usage not supported"); + return result; + } + + public static Object wrap(Object value) { + return Optional.ofNullable(value); + } } } diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheEvictOperation.java b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheEvictOperation.java index 6bb937a5..5f5e0dc0 100644 --- a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheEvictOperation.java +++ b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheEvictOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,38 +20,65 @@ package org.springframework.cache.interceptor; * Class describing a cache 'evict' operation. * * @author Costin Leau + * @author Marcin Kamionowski * @since 3.1 */ public class CacheEvictOperation extends CacheOperation { - private boolean cacheWide = false; + private final boolean cacheWide; - private boolean beforeInvocation = false; + private final boolean beforeInvocation; - public void setCacheWide(boolean cacheWide) { - this.cacheWide = cacheWide; + /** + * @since 4.3 + */ + public CacheEvictOperation(CacheEvictOperation.Builder b) { + super(b); + this.cacheWide = b.cacheWide; + this.beforeInvocation = b.beforeInvocation; } + public boolean isCacheWide() { return this.cacheWide; } - public void setBeforeInvocation(boolean beforeInvocation) { - this.beforeInvocation = beforeInvocation; - } - public boolean isBeforeInvocation() { return this.beforeInvocation; } - @Override - protected StringBuilder getOperationDescription() { - StringBuilder sb = super.getOperationDescription(); - sb.append(","); - sb.append(this.cacheWide); - sb.append(","); - sb.append(this.beforeInvocation); - return sb; + + /** + * @since 4.3 + */ + public static class Builder extends CacheOperation.Builder { + + private boolean cacheWide = false; + + private boolean beforeInvocation = false; + + public void setCacheWide(boolean cacheWide) { + this.cacheWide = cacheWide; + } + + public void setBeforeInvocation(boolean beforeInvocation) { + this.beforeInvocation = beforeInvocation; + } + + @Override + protected StringBuilder getOperationDescription() { + StringBuilder sb = super.getOperationDescription(); + sb.append(","); + sb.append(this.cacheWide); + sb.append(","); + sb.append(this.beforeInvocation); + return sb; + } + + public CacheEvictOperation build() { + return new CacheEvictOperation(this); + } } + } diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperation.java b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperation.java index 6264a040..271b59f1 100644 --- a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperation.java +++ b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,45 +27,45 @@ import org.springframework.util.Assert; * * @author Costin Leau * @author Stephane Nicoll + * @author Marcin Kamionowski * @since 3.1 */ public abstract class CacheOperation implements BasicOperation { - private String name = ""; + private final String name; - private Set<String> cacheNames = Collections.emptySet(); + private final Set<String> cacheNames; - private String key = ""; + private final String key; - private String keyGenerator = ""; + private final String keyGenerator; - private String cacheManager = ""; + private final String cacheManager; - private String cacheResolver = ""; + private final String cacheResolver; - private String condition = ""; + private final String condition; + private final String toString; - public void setName(String name) { - Assert.hasText(name); - this.name = name; - } - public String getName() { - return this.name; + /** + * @since 4.3 + */ + protected CacheOperation(Builder b) { + this.name = b.name; + this.cacheNames = b.cacheNames; + this.key = b.key; + this.keyGenerator = b.keyGenerator; + this.cacheManager = b.cacheManager; + this.cacheResolver = b.cacheResolver; + this.condition = b.condition; + this.toString = b.getOperationDescription().toString(); } - public void setCacheName(String cacheName) { - Assert.hasText(cacheName); - this.cacheNames = Collections.singleton(cacheName); - } - public void setCacheNames(String... cacheNames) { - this.cacheNames = new LinkedHashSet<String>(cacheNames.length); - for (String cacheName : cacheNames) { - Assert.hasText(cacheName, "Cache name must be non-null if specified"); - this.cacheNames.add(cacheName); - } + public String getName() { + return this.name; } @Override @@ -73,47 +73,22 @@ public abstract class CacheOperation implements BasicOperation { return this.cacheNames; } - public void setKey(String key) { - Assert.notNull(key); - this.key = key; - } - public String getKey() { return this.key; } - public void setKeyGenerator(String keyGenerator) { - Assert.notNull(keyGenerator); - this.keyGenerator = keyGenerator; - } - public String getKeyGenerator() { return this.keyGenerator; } - public void setCacheManager(String cacheManager) { - Assert.notNull(cacheManager); - this.cacheManager = cacheManager; - } - public String getCacheManager() { return this.cacheManager; } - public void setCacheResolver(String cacheResolver) { - Assert.notNull(cacheManager); - this.cacheResolver = cacheResolver; - } - public String getCacheResolver() { return this.cacheResolver; } - public void setCondition(String condition) { - Assert.notNull(condition); - this.condition = condition; - } - public String getCondition() { return this.condition; } @@ -139,29 +114,116 @@ public abstract class CacheOperation implements BasicOperation { /** * Return an identifying description for this cache operation. - * <p>Has to be overridden in subclasses for correct {@code equals} - * and {@code hashCode} behavior. Alternatively, {@link #equals} - * and {@link #hashCode} can be overridden themselves. + * <p>Returned value is produced by calling {@link Builder#getOperationDescription()} + * during object construction. This method is used in {@link #hashCode} and + * {@link #equals}. + * @see Builder#getOperationDescription() */ @Override - public String toString() { - return getOperationDescription().toString(); + public final String toString() { + return this.toString; } + /** - * Return an identifying description for this caching operation. - * <p>Available to subclasses, for inclusion in their {@code toString()} result. + * @since 4.3 */ - protected StringBuilder getOperationDescription() { - StringBuilder result = new StringBuilder(getClass().getSimpleName()); - result.append("[").append(this.name); - result.append("] caches=").append(this.cacheNames); - result.append(" | key='").append(this.key); - result.append("' | keyGenerator='").append(this.keyGenerator); - result.append("' | cacheManager='").append(this.cacheManager); - result.append("' | cacheResolver='").append(this.cacheResolver); - result.append("' | condition='").append(this.condition).append("'"); - return result; + public abstract static class Builder { + + private String name = ""; + + private Set<String> cacheNames = Collections.emptySet(); + + private String key = ""; + + private String keyGenerator = ""; + + private String cacheManager = ""; + + private String cacheResolver = ""; + + private String condition = ""; + + public void setName(String name) { + Assert.hasText(name); + this.name = name; + } + + public void setCacheName(String cacheName) { + Assert.hasText(cacheName); + this.cacheNames = Collections.singleton(cacheName); + } + + public void setCacheNames(String... cacheNames) { + this.cacheNames = new LinkedHashSet<String>(cacheNames.length); + for (String cacheName : cacheNames) { + Assert.hasText(cacheName, "Cache name must be non-null if specified"); + this.cacheNames.add(cacheName); + } + } + + public Set<String> getCacheNames() { + return this.cacheNames; + } + + public void setKey(String key) { + Assert.notNull(key); + this.key = key; + } + + public String getKey() { + return this.key; + } + + public String getKeyGenerator() { + return this.keyGenerator; + } + + public String getCacheManager() { + return this.cacheManager; + } + + public String getCacheResolver() { + return this.cacheResolver; + } + + public void setKeyGenerator(String keyGenerator) { + Assert.notNull(keyGenerator); + this.keyGenerator = keyGenerator; + } + + public void setCacheManager(String cacheManager) { + Assert.notNull(cacheManager); + this.cacheManager = cacheManager; + } + + public void setCacheResolver(String cacheResolver) { + Assert.notNull(this.cacheManager); + this.cacheResolver = cacheResolver; + } + + public void setCondition(String condition) { + Assert.notNull(condition); + this.condition = condition; + } + + /** + * Return an identifying description for this caching operation. + * <p>Available to subclasses, for inclusion in their {@code toString()} result. + */ + protected StringBuilder getOperationDescription() { + StringBuilder result = new StringBuilder(getClass().getSimpleName()); + result.append("[").append(this.name); + result.append("] caches=").append(this.cacheNames); + result.append(" | key='").append(this.key); + result.append("' | keyGenerator='").append(this.keyGenerator); + result.append("' | cacheManager='").append(this.cacheManager); + result.append("' | cacheResolver='").append(this.cacheResolver); + result.append("' | condition='").append(this.condition).append("'"); + return result; + } + + public abstract CacheOperation build(); } } diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/ExpressionEvaluator.java b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperationExpressionEvaluator.java index 88ed1484..9111bab0 100644 --- a/spring-context/src/main/java/org/springframework/cache/interceptor/ExpressionEvaluator.java +++ b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperationExpressionEvaluator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,11 +22,11 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.springframework.aop.support.AopUtils; +import org.springframework.beans.factory.BeanFactory; import org.springframework.cache.Cache; import org.springframework.context.expression.AnnotatedElementKey; +import org.springframework.context.expression.BeanFactoryResolver; import org.springframework.context.expression.CachedExpressionEvaluator; -import org.springframework.core.DefaultParameterNameDiscoverer; -import org.springframework.core.ParameterNameDiscoverer; import org.springframework.expression.EvaluationContext; import org.springframework.expression.Expression; @@ -43,7 +43,7 @@ import org.springframework.expression.Expression; * @author Stephane Nicoll * @since 3.1 */ -class ExpressionEvaluator extends CachedExpressionEvaluator { +class CacheOperationExpressionEvaluator extends CachedExpressionEvaluator { /** * Indicate that there is no result variable. @@ -60,8 +60,6 @@ class ExpressionEvaluator extends CachedExpressionEvaluator { */ public static final String RESULT_VARIABLE = "result"; - // shared param discoverer since it caches data internally - private final ParameterNameDiscoverer paramNameDiscoverer = new DefaultParameterNameDiscoverer(); private final Map<ExpressionKey, Expression> keyCache = new ConcurrentHashMap<ExpressionKey, Expression>(64); @@ -75,12 +73,12 @@ class ExpressionEvaluator extends CachedExpressionEvaluator { /** * Create an {@link EvaluationContext} without a return value. - * @see #createEvaluationContext(Collection, Method, Object[], Object, Class, Object) + * @see #createEvaluationContext(Collection, Method, Object[], Object, Class, Object, BeanFactory) */ public EvaluationContext createEvaluationContext(Collection<? extends Cache> caches, - Method method, Object[] args, Object target, Class<?> targetClass) { + Method method, Object[] args, Object target, Class<?> targetClass, BeanFactory beanFactory) { - return createEvaluationContext(caches, method, args, target, targetClass, NO_RESULT); + return createEvaluationContext(caches, method, args, target, targetClass, NO_RESULT, beanFactory); } /** @@ -95,19 +93,23 @@ class ExpressionEvaluator extends CachedExpressionEvaluator { * @return the evaluation context */ public EvaluationContext createEvaluationContext(Collection<? extends Cache> caches, - Method method, Object[] args, Object target, Class<?> targetClass, Object result) { + Method method, Object[] args, Object target, Class<?> targetClass, Object result, + BeanFactory beanFactory) { - CacheExpressionRootObject rootObject = new CacheExpressionRootObject(caches, - method, args, target, targetClass); + CacheExpressionRootObject rootObject = new CacheExpressionRootObject( + caches, method, args, target, targetClass); Method targetMethod = getTargetMethod(targetClass, method); - CacheEvaluationContext evaluationContext = new CacheEvaluationContext(rootObject, - targetMethod, args, this.paramNameDiscoverer); + CacheEvaluationContext evaluationContext = new CacheEvaluationContext( + rootObject, targetMethod, args, getParameterNameDiscoverer()); if (result == RESULT_UNAVAILABLE) { evaluationContext.addUnavailableVariable(RESULT_VARIABLE); } else if (result != NO_RESULT) { evaluationContext.setVariable(RESULT_VARIABLE, result); } + if (beanFactory != null) { + evaluationContext.setBeanResolver(new BeanFactoryResolver(beanFactory)); + } return evaluationContext; } diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperationInvoker.java b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperationInvoker.java index 8901ba19..3529438d 100644 --- a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperationInvoker.java +++ b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperationInvoker.java @@ -30,9 +30,8 @@ package org.springframework.cache.interceptor; public interface CacheOperationInvoker { /** - * Invoke the cache operation defined by this instance. Wraps any - * exception that is thrown during the invocation in a - * {@link ThrowableWrapper}. + * Invoke the cache operation defined by this instance. Wraps any exception + * that is thrown during the invocation in a {@link ThrowableWrapper}. * @return the result of the operation * @throws ThrowableWrapper if an error occurred while invoking the operation */ diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/CachePutOperation.java b/spring-context/src/main/java/org/springframework/cache/interceptor/CachePutOperation.java index e6a61b0a..01cf15f1 100644 --- a/spring-context/src/main/java/org/springframework/cache/interceptor/CachePutOperation.java +++ b/spring-context/src/main/java/org/springframework/cache/interceptor/CachePutOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,27 +21,51 @@ package org.springframework.cache.interceptor; * * @author Costin Leau * @author Phillip Webb + * @author Marcin Kamionowski * @since 3.1 */ public class CachePutOperation extends CacheOperation { - private String unless; + private final String unless; - public String getUnless() { - return unless; + /** + * @since 4.3 + */ + public CachePutOperation(CachePutOperation.Builder b) { + super(b); + this.unless = b.unless; } - public void setUnless(String unless) { - this.unless = unless; + + public String getUnless() { + return this.unless; } - @Override - protected StringBuilder getOperationDescription() { - StringBuilder sb = super.getOperationDescription(); - sb.append(" | unless='"); - sb.append(this.unless); - sb.append("'"); - return sb; + + /** + * @since 4.3 + */ + public static class Builder extends CacheOperation.Builder { + + private String unless; + + public void setUnless(String unless) { + this.unless = unless; + } + + @Override + protected StringBuilder getOperationDescription() { + StringBuilder sb = super.getOperationDescription(); + sb.append(" | unless='"); + sb.append(this.unless); + sb.append("'"); + return sb; + } + + public CachePutOperation build() { + return new CachePutOperation(this); + } } + } diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheableOperation.java b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheableOperation.java index f9375a9a..c3414e4d 100644 --- a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheableOperation.java +++ b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheableOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,27 +21,68 @@ package org.springframework.cache.interceptor; * * @author Costin Leau * @author Phillip Webb + * @author Marcin Kamionowski * @since 3.1 */ public class CacheableOperation extends CacheOperation { - private String unless; + private final String unless; + + private final boolean sync; + + + /** + * @since 4.3 + */ + public CacheableOperation(CacheableOperation.Builder b) { + super(b); + this.unless = b.unless; + this.sync = b.sync; + } public String getUnless() { - return unless; + return this.unless; } - public void setUnless(String unless) { - this.unless = unless; + public boolean isSync() { + return this.sync; } - @Override - protected StringBuilder getOperationDescription() { - StringBuilder sb = super.getOperationDescription(); - sb.append(" | unless='"); - sb.append(this.unless); - sb.append("'"); - return sb; + + /** + * @since 4.3 + */ + public static class Builder extends CacheOperation.Builder { + + private String unless; + + private boolean sync; + + public void setUnless(String unless) { + this.unless = unless; + } + + public void setSync(boolean sync) { + this.sync = sync; + } + + @Override + protected StringBuilder getOperationDescription() { + StringBuilder sb = super.getOperationDescription(); + sb.append(" | unless='"); + sb.append(this.unless); + sb.append("'"); + sb.append(" | sync='"); + sb.append(this.sync); + sb.append("'"); + return sb; + } + + @Override + public CacheableOperation build() { + return new CacheableOperation(this); + } } + } diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/NamedCacheResolver.java b/spring-context/src/main/java/org/springframework/cache/interceptor/NamedCacheResolver.java index 51e95c15..f75fd2e8 100644 --- a/spring-context/src/main/java/org/springframework/cache/interceptor/NamedCacheResolver.java +++ b/spring-context/src/main/java/org/springframework/cache/interceptor/NamedCacheResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -51,7 +51,7 @@ public class NamedCacheResolver extends AbstractCacheResolver { @Override protected Collection<String> getCacheNames(CacheOperationInvocationContext<?> context) { - return cacheNames; + return this.cacheNames; } } diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/SimpleKey.java b/spring-context/src/main/java/org/springframework/cache/interceptor/SimpleKey.java index b9304539..e6573045 100644 --- a/spring-context/src/main/java/org/springframework/cache/interceptor/SimpleKey.java +++ b/spring-context/src/main/java/org/springframework/cache/interceptor/SimpleKey.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -57,7 +57,7 @@ public class SimpleKey implements Serializable { @Override public final int hashCode() { - return hashCode; + return this.hashCode; } @Override diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/VariableNotAvailableException.java b/spring-context/src/main/java/org/springframework/cache/interceptor/VariableNotAvailableException.java index b6f52d61..6f7fdde3 100644 --- a/spring-context/src/main/java/org/springframework/cache/interceptor/VariableNotAvailableException.java +++ b/spring-context/src/main/java/org/springframework/cache/interceptor/VariableNotAvailableException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,6 +37,6 @@ class VariableNotAvailableException extends EvaluationException { public String getName() { - return name; + return this.name; } } |