+ add XML support for cache abstraction (cache-advice) - DRAFT
This commit is contained in:
@@ -48,11 +48,6 @@ import static org.springframework.context.annotation.AnnotationConfigUtils.*;
|
|||||||
*/
|
*/
|
||||||
class AnnotationDrivenCacheBeanDefinitionParser implements BeanDefinitionParser {
|
class AnnotationDrivenCacheBeanDefinitionParser implements BeanDefinitionParser {
|
||||||
|
|
||||||
private static final String CACHE_MANAGER_ATTRIBUTE = "cache-manager";
|
|
||||||
|
|
||||||
private static final String DEFAULT_CACHE_MANAGER_BEAN_NAME = "cacheManager";
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses the '<code><cache:annotation-driven/></code>' tag. Will
|
* Parses the '<code><cache:annotation-driven/></code>' tag. Will
|
||||||
* {@link AopNamespaceUtils#registerAutoProxyCreatorIfNecessary register an AutoProxyCreator}
|
* {@link AopNamespaceUtils#registerAutoProxyCreatorIfNecessary register an AutoProxyCreator}
|
||||||
@@ -71,13 +66,9 @@ class AnnotationDrivenCacheBeanDefinitionParser implements BeanDefinitionParser
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String extractCacheManager(Element element) {
|
|
||||||
return (element.hasAttribute(CACHE_MANAGER_ATTRIBUTE) ? element.getAttribute(CACHE_MANAGER_ATTRIBUTE)
|
|
||||||
: DEFAULT_CACHE_MANAGER_BEAN_NAME);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void registerCacheManagerProperty(Element element, BeanDefinition def) {
|
private static void registerCacheManagerProperty(Element element, BeanDefinition def) {
|
||||||
def.getPropertyValues().add("cacheManager", new RuntimeBeanReference(extractCacheManager(element)));
|
def.getPropertyValues().add("cacheManager",
|
||||||
|
new RuntimeBeanReference(CacheNamespaceHandler.extractCacheManager(element)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -124,7 +115,7 @@ class AnnotationDrivenCacheBeanDefinitionParser implements BeanDefinitionParser
|
|||||||
interceptorDef.setSource(eleSource);
|
interceptorDef.setSource(eleSource);
|
||||||
interceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
interceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
||||||
registerCacheManagerProperty(element, interceptorDef);
|
registerCacheManagerProperty(element, interceptorDef);
|
||||||
interceptorDef.getPropertyValues().add("cacheOperationSource", new RuntimeBeanReference(sourceName));
|
interceptorDef.getPropertyValues().add("cacheOperationSources", new RuntimeBeanReference(sourceName));
|
||||||
String interceptorName = parserContext.getReaderContext().registerWithGeneratedName(interceptorDef);
|
String interceptorName = parserContext.getReaderContext().registerWithGeneratedName(interceptorDef);
|
||||||
|
|
||||||
// Create the CacheAdvisor definition.
|
// Create the CacheAdvisor definition.
|
||||||
|
|||||||
171
org.springframework.context/src/main/java/org/springframework/cache/config/CacheAdviceParser.java
vendored
Normal file
171
org.springframework.context/src/main/java/org/springframework/cache/config/CacheAdviceParser.java
vendored
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2011 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.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.cache.config;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.config.TypedStringValue;
|
||||||
|
import org.springframework.beans.factory.parsing.ReaderContext;
|
||||||
|
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||||
|
import org.springframework.beans.factory.support.ManagedList;
|
||||||
|
import org.springframework.beans.factory.support.ManagedMap;
|
||||||
|
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||||
|
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
|
||||||
|
import org.springframework.beans.factory.xml.ParserContext;
|
||||||
|
import org.springframework.cache.annotation.AnnotationCacheOperationSource;
|
||||||
|
import org.springframework.cache.interceptor.CacheEvictOperation;
|
||||||
|
import org.springframework.cache.interceptor.CacheInterceptor;
|
||||||
|
import org.springframework.cache.interceptor.CacheOperation;
|
||||||
|
import org.springframework.cache.interceptor.CacheUpdateOperation;
|
||||||
|
import org.springframework.cache.interceptor.NameMatchCacheOperationSource;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
import org.springframework.util.xml.DomUtils;
|
||||||
|
import org.w3c.dom.Element;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link org.springframework.beans.factory.xml.BeanDefinitionParser
|
||||||
|
* BeanDefinitionParser} for the {@code <tx:advice/>} tag.
|
||||||
|
*
|
||||||
|
* @author Costin Leau
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class CacheAdviceParser extends AbstractSingleBeanDefinitionParser {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple, reusable class used for overriding defaults.
|
||||||
|
*
|
||||||
|
* @author Costin Leau
|
||||||
|
*/
|
||||||
|
private static class Props {
|
||||||
|
|
||||||
|
private String key, condition;
|
||||||
|
private String[] caches = null;
|
||||||
|
|
||||||
|
Props(Element root) {
|
||||||
|
String defaultCache = root.getAttribute("cache");
|
||||||
|
key = root.getAttribute("key");
|
||||||
|
condition = root.getAttribute("condition");
|
||||||
|
|
||||||
|
if (StringUtils.hasText(defaultCache)) {
|
||||||
|
caches = StringUtils.commaDelimitedListToStringArray(defaultCache.trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CacheOperation merge(Element element, ReaderContext readerCtx, CacheOperation op) {
|
||||||
|
String cache = element.getAttribute("cache");
|
||||||
|
String k = element.getAttribute("key");
|
||||||
|
String c = element.getAttribute("condition");
|
||||||
|
|
||||||
|
String[] localCaches = caches;
|
||||||
|
String localKey = key, localCondition = condition;
|
||||||
|
|
||||||
|
// sanity check
|
||||||
|
if (StringUtils.hasText(cache)) {
|
||||||
|
localCaches = StringUtils.commaDelimitedListToStringArray(cache.trim());
|
||||||
|
} else {
|
||||||
|
if (caches == null) {
|
||||||
|
readerCtx.error("No cache specified specified for " + element.getNodeName(), element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtils.hasText(k)) {
|
||||||
|
localKey = k.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtils.hasText(c)) {
|
||||||
|
localCondition = c.trim();
|
||||||
|
}
|
||||||
|
op.setCacheNames(localCaches);
|
||||||
|
op.setKey(localKey);
|
||||||
|
op.setCondition(localCondition);
|
||||||
|
|
||||||
|
return op;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final String CACHEABLE_ELEMENT = "cacheable";
|
||||||
|
private static final String CACHE_EVICT_ELEMENT = "cache-evict";
|
||||||
|
private static final String METHOD_ATTRIBUTE = "method";
|
||||||
|
private static final String DEFS_ELEMENT = "definitions";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Class<?> getBeanClass(Element element) {
|
||||||
|
return CacheInterceptor.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
|
||||||
|
builder.addPropertyReference("cacheManager", CacheNamespaceHandler.extractCacheManager(element));
|
||||||
|
|
||||||
|
List<Element> cacheDefs = DomUtils.getChildElementsByTagName(element, DEFS_ELEMENT);
|
||||||
|
if (cacheDefs.size() >= 1) {
|
||||||
|
// Using attributes source.
|
||||||
|
List<RootBeanDefinition> attributeSourceDefinitions = parseDefinitionsSources(cacheDefs, parserContext);
|
||||||
|
builder.addPropertyValue("cacheOperationSources", attributeSourceDefinitions);
|
||||||
|
} else {
|
||||||
|
// Assume annotations source.
|
||||||
|
builder.addPropertyValue("cacheOperationSources", new RootBeanDefinition(
|
||||||
|
AnnotationCacheOperationSource.class));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<RootBeanDefinition> parseDefinitionsSources(List<Element> definitions, ParserContext parserContext) {
|
||||||
|
ManagedList<RootBeanDefinition> defs = new ManagedList<RootBeanDefinition>(definitions.size());
|
||||||
|
|
||||||
|
// extract default param for the definition
|
||||||
|
for (Element element : definitions) {
|
||||||
|
defs.add(parseDefinitionSource(element, parserContext));
|
||||||
|
}
|
||||||
|
|
||||||
|
return defs;
|
||||||
|
}
|
||||||
|
|
||||||
|
private RootBeanDefinition parseDefinitionSource(Element definition, ParserContext parserContext) {
|
||||||
|
Props prop = new Props(definition);
|
||||||
|
// add cacheable first
|
||||||
|
|
||||||
|
ManagedMap<TypedStringValue, CacheOperation> cacheOpeMap = new ManagedMap<TypedStringValue, CacheOperation>();
|
||||||
|
cacheOpeMap.setSource(parserContext.extractSource(definition));
|
||||||
|
|
||||||
|
List<Element> updateCacheMethods = DomUtils.getChildElementsByTagName(definition, CACHEABLE_ELEMENT);
|
||||||
|
|
||||||
|
for (Element opElement : updateCacheMethods) {
|
||||||
|
String name = opElement.getAttribute(METHOD_ATTRIBUTE);
|
||||||
|
TypedStringValue nameHolder = new TypedStringValue(name);
|
||||||
|
nameHolder.setSource(parserContext.extractSource(opElement));
|
||||||
|
CacheOperation op = prop.merge(opElement, parserContext.getReaderContext(), new CacheUpdateOperation());
|
||||||
|
|
||||||
|
cacheOpeMap.put(nameHolder, op);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Element> evictCacheMethods = DomUtils.getChildElementsByTagName(definition, CACHE_EVICT_ELEMENT);
|
||||||
|
|
||||||
|
for (Element opElement : evictCacheMethods) {
|
||||||
|
String name = opElement.getAttribute(METHOD_ATTRIBUTE);
|
||||||
|
TypedStringValue nameHolder = new TypedStringValue(name);
|
||||||
|
nameHolder.setSource(parserContext.extractSource(opElement));
|
||||||
|
CacheOperation op = prop.merge(opElement, parserContext.getReaderContext(), new CacheEvictOperation());
|
||||||
|
|
||||||
|
cacheOpeMap.put(nameHolder, op);
|
||||||
|
}
|
||||||
|
|
||||||
|
RootBeanDefinition attributeSourceDefinition = new RootBeanDefinition(NameMatchCacheOperationSource.class);
|
||||||
|
attributeSourceDefinition.setSource(parserContext.extractSource(definition));
|
||||||
|
attributeSourceDefinition.getPropertyValues().add("nameMap", cacheOpeMap);
|
||||||
|
return attributeSourceDefinition;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
package org.springframework.cache.config;
|
package org.springframework.cache.config;
|
||||||
|
|
||||||
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
|
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
|
||||||
|
import org.w3c.dom.Element;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <code>NamespaceHandler</code> allowing for the configuration of
|
* <code>NamespaceHandler</code> allowing for the configuration of
|
||||||
@@ -30,8 +31,18 @@ import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
|
|||||||
*/
|
*/
|
||||||
public class CacheNamespaceHandler extends NamespaceHandlerSupport {
|
public class CacheNamespaceHandler extends NamespaceHandlerSupport {
|
||||||
|
|
||||||
|
static final String CACHE_MANAGER_ATTRIBUTE = "cache-manager";
|
||||||
|
static final String DEFAULT_CACHE_MANAGER_BEAN_NAME = "cacheManager";
|
||||||
|
|
||||||
|
static String extractCacheManager(Element element) {
|
||||||
|
return (element.hasAttribute(CacheNamespaceHandler.CACHE_MANAGER_ATTRIBUTE) ? element
|
||||||
|
.getAttribute(CacheNamespaceHandler.CACHE_MANAGER_ATTRIBUTE)
|
||||||
|
: CacheNamespaceHandler.DEFAULT_CACHE_MANAGER_BEAN_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
public void init() {
|
public void init() {
|
||||||
registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenCacheBeanDefinitionParser());
|
registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenCacheBeanDefinitionParser());
|
||||||
|
registerBeanDefinitionParser("advice", new CacheAdviceParser());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -117,11 +117,6 @@ public abstract class AbstractFallbackCacheOperationSource implements CacheOpera
|
|||||||
return new DefaultCacheKey(method, targetClass);
|
return new DefaultCacheKey(method, targetClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Same signature as {@link #getTransactionAttribute}, but doesn't cache the result.
|
|
||||||
* {@link #getTransactionAttribute} is effectively a caching decorator for this method.
|
|
||||||
* @see #getTransactionAttribute
|
|
||||||
*/
|
|
||||||
private CacheOperation computeCacheOperationDefinition(Method method, Class<?> targetClass) {
|
private CacheOperation computeCacheOperationDefinition(Method method, Class<?> targetClass) {
|
||||||
// Don't allow no-public methods as required.
|
// Don't allow no-public methods as required.
|
||||||
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
|
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
|
||||||
|
|||||||
@@ -44,12 +44,8 @@ import org.springframework.util.StringUtils;
|
|||||||
*
|
*
|
||||||
* <p>Subclasses are responsible for calling methods in this class in the correct order.
|
* <p>Subclasses are responsible for calling methods in this class in the correct order.
|
||||||
*
|
*
|
||||||
* <p>If no caching name has been specified in the <code>CacheOperationDefinition</code>,
|
|
||||||
* the exposed name will be the <code>fully-qualified class name + "." + method name</code>
|
|
||||||
* (by default).
|
|
||||||
*
|
|
||||||
* <p>Uses the <b>Strategy</b> design pattern. A <code>CacheManager</code>
|
* <p>Uses the <b>Strategy</b> design pattern. A <code>CacheManager</code>
|
||||||
* implementation will perform the actual transaction management, and a
|
* implementation will perform the actual cache management, and a
|
||||||
* <code>CacheDefinitionSource</code> is used for determining caching operation definitions.
|
* <code>CacheDefinitionSource</code> is used for determining caching operation definitions.
|
||||||
*
|
*
|
||||||
* <p>A cache aspect is serializable if its <code>CacheManager</code>
|
* <p>A cache aspect is serializable if its <code>CacheManager</code>
|
||||||
@@ -155,6 +151,7 @@ public abstract class CacheAspectSupport implements InitializingBean {
|
|||||||
return ClassUtils.getQualifiedMethodName(specificMethod);
|
return ClassUtils.getQualifiedMethodName(specificMethod);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected Collection<Cache> getCaches(CacheOperation operation) {
|
protected Collection<Cache> getCaches(CacheOperation operation) {
|
||||||
Set<String> cacheNames = operation.getCacheNames();
|
Set<String> cacheNames = operation.getCacheNames();
|
||||||
Collection<Cache> caches = new ArrayList<Cache>(cacheNames.size());
|
Collection<Cache> caches = new ArrayList<Cache>(cacheNames.size());
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2011 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.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.cache.interceptor;
|
||||||
|
|
||||||
|
import java.beans.PropertyEditorSupport;
|
||||||
|
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PropertyEditor for {@link CacheOperation} objects. Accepts a String of form
|
||||||
|
* <p><tt>action,cache,key,condition</tt>
|
||||||
|
* <p>where only action and cache are required. Available definitions for action are
|
||||||
|
* <tt>cacheable</tt> and <tt>evict</tt>.
|
||||||
|
* When specifying multiple caches, use ; as a separator
|
||||||
|
*
|
||||||
|
* A typical example would be:
|
||||||
|
* <p><code>cacheable, orders;books, #p0</code>
|
||||||
|
*
|
||||||
|
* <p>The tokens need to be specified in the order above.
|
||||||
|
*
|
||||||
|
* @author Costin Leau
|
||||||
|
*
|
||||||
|
* @see org.springframework.transaction.TransactionAttributeEditor
|
||||||
|
* @see org.springframework.core.Constants
|
||||||
|
*/
|
||||||
|
public class CacheOperationEditor extends PropertyEditorSupport {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format is action, cache, key, condition.
|
||||||
|
* Null or the empty string means that the method is non cacheable.
|
||||||
|
* @see java.beans.PropertyEditor#setAsText(java.lang.String)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setAsText(String text) throws IllegalArgumentException {
|
||||||
|
if (StringUtils.hasLength(text)) {
|
||||||
|
// tokenize it with ","
|
||||||
|
String[] tokens = StringUtils.commaDelimitedListToStringArray(text);
|
||||||
|
if (tokens.length < 2) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"too little arguments found, at least the cache action and cache name are required");
|
||||||
|
}
|
||||||
|
|
||||||
|
CacheOperation op;
|
||||||
|
|
||||||
|
if ("cacheable".contains(tokens[0])) {
|
||||||
|
op = new CacheUpdateOperation();
|
||||||
|
}
|
||||||
|
|
||||||
|
else if ("evict".contains(tokens[0])) {
|
||||||
|
op = new CacheEvictOperation();
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Invalid cache action specified " + tokens[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
op.setCacheNames(StringUtils.delimitedListToStringArray(tokens[1], ";"));
|
||||||
|
|
||||||
|
if (tokens.length > 2) {
|
||||||
|
op.setKey(tokens[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tokens.length > 3) {
|
||||||
|
op.setCondition(tokens[3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
setValue(op);
|
||||||
|
} else {
|
||||||
|
setValue(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2011 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.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.cache.interceptor;
|
||||||
|
|
||||||
|
import org.springframework.aop.Pointcut;
|
||||||
|
import org.springframework.aop.framework.AbstractSingletonProxyFactoryBean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Proxy factory bean for simplified declarative caching handling.
|
||||||
|
* This is a convenient alternative to a standard AOP
|
||||||
|
* {@link org.springframework.aop.framework.ProxyFactoryBean}
|
||||||
|
* with a separate {@link CachingInterceptor} definition.
|
||||||
|
*
|
||||||
|
* <p>This class is intended to cover the <i>typical</i> case of declarative
|
||||||
|
* cache demarcation: namely, wrapping a singleton target object with a
|
||||||
|
* caching proxy, proxying all the interfaces that the target implements.
|
||||||
|
*
|
||||||
|
* @author Costin Leau
|
||||||
|
* @see org.springframework.aop.framework.ProxyFactoryBean
|
||||||
|
* @see CachingInterceptor
|
||||||
|
*/
|
||||||
|
public class CacheProxyFactoryBean extends AbstractSingletonProxyFactoryBean {
|
||||||
|
|
||||||
|
private final CacheInterceptor cachingInterceptor = new CacheInterceptor();
|
||||||
|
private Pointcut pointcut;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Object createMainInterceptor() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the caching attribute source which is used to find the cache operation
|
||||||
|
* definition.
|
||||||
|
*
|
||||||
|
* @param cacheDefinitionSources cache definition sources
|
||||||
|
*/
|
||||||
|
public void setCacheDefinitionSources(CacheOperationSource... cacheDefinitionSources) {
|
||||||
|
this.cachingInterceptor.setCacheOperationSources(cacheDefinitionSources);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2011 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.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.cache.interceptor;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.springframework.util.ObjectUtils;
|
||||||
|
import org.springframework.util.PatternMatchUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple {@link CacheOperationSource} implementation that
|
||||||
|
* allows attributes to be matched by registered name.
|
||||||
|
*
|
||||||
|
* @author Costin Leau
|
||||||
|
*/
|
||||||
|
public class NameMatchCacheOperationSource implements CacheOperationSource, Serializable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logger available to subclasses.
|
||||||
|
* <p>Static for optimal serialization.
|
||||||
|
*/
|
||||||
|
protected static final Log logger = LogFactory.getLog(NameMatchCacheOperationSource.class);
|
||||||
|
|
||||||
|
/** Keys are method names; values are TransactionAttributes */
|
||||||
|
private Map<String, CacheOperation> nameMap = new LinkedHashMap<String, CacheOperation>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a name/attribute map, consisting of method names
|
||||||
|
* (e.g. "myMethod") and CacheOperation instances
|
||||||
|
* (or Strings to be converted to CacheOperation instances).
|
||||||
|
* @see CacheOperation
|
||||||
|
* @see CacheOperationEditor
|
||||||
|
*/
|
||||||
|
public void setNameMap(Map<String, CacheOperation> nameMap) {
|
||||||
|
for (Map.Entry<String, CacheOperation> entry : nameMap.entrySet()) {
|
||||||
|
addCacheMethod(entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the given properties into a name/attribute map.
|
||||||
|
* Expects method names as keys and String attributes definitions as values,
|
||||||
|
* parsable into CacheOperation instances via CacheOperationEditor.
|
||||||
|
* @see #setNameMap
|
||||||
|
* @see CacheOperationEditor
|
||||||
|
*/
|
||||||
|
public void setProperties(Properties cacheOperations) {
|
||||||
|
CacheOperationEditor tae = new CacheOperationEditor();
|
||||||
|
Enumeration propNames = cacheOperations.propertyNames();
|
||||||
|
while (propNames.hasMoreElements()) {
|
||||||
|
String methodName = (String) propNames.nextElement();
|
||||||
|
String value = cacheOperations.getProperty(methodName);
|
||||||
|
tae.setAsText(value);
|
||||||
|
CacheOperation op = (CacheOperation) tae.getValue();
|
||||||
|
addCacheMethod(methodName, op);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an attribute for a cacheable method.
|
||||||
|
* <p>Method names can be exact matches, or of the pattern "xxx*",
|
||||||
|
* "*xxx" or "*xxx*" for matching multiple methods.
|
||||||
|
* @param methodName the name of the method
|
||||||
|
* @param operation operation associated with the method
|
||||||
|
*/
|
||||||
|
public void addCacheMethod(String methodName, CacheOperation operation) {
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debug("Adding method [" + methodName + "] with cache operation [" + operation + "]");
|
||||||
|
}
|
||||||
|
this.nameMap.put(methodName, operation);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CacheOperation getCacheOperation(Method method, Class<?> targetClass) {
|
||||||
|
// look for direct name match
|
||||||
|
String methodName = method.getName();
|
||||||
|
CacheOperation attr = this.nameMap.get(methodName);
|
||||||
|
|
||||||
|
if (attr == null) {
|
||||||
|
// Look for most specific name match.
|
||||||
|
String bestNameMatch = null;
|
||||||
|
for (String mappedName : this.nameMap.keySet()) {
|
||||||
|
if (isMatch(methodName, mappedName)
|
||||||
|
&& (bestNameMatch == null || bestNameMatch.length() <= mappedName.length())) {
|
||||||
|
attr = this.nameMap.get(mappedName);
|
||||||
|
bestNameMatch = mappedName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return attr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return if the given method name matches the mapped name.
|
||||||
|
* <p>The default implementation checks for "xxx*", "*xxx" and "*xxx*" matches,
|
||||||
|
* as well as direct equality. Can be overridden in subclasses.
|
||||||
|
* @param methodName the method name of the class
|
||||||
|
* @param mappedName the name in the descriptor
|
||||||
|
* @return if the names match
|
||||||
|
* @see org.springframework.util.PatternMatchUtils#simpleMatch(String, String)
|
||||||
|
*/
|
||||||
|
protected boolean isMatch(String methodName, String mappedName) {
|
||||||
|
return PatternMatchUtils.simpleMatch(mappedName, methodName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object other) {
|
||||||
|
if (this == other) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(other instanceof NameMatchCacheOperationSource)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
NameMatchCacheOperationSource otherTas = (NameMatchCacheOperationSource) other;
|
||||||
|
return ObjectUtils.nullSafeEquals(this.nameMap, otherTas.nameMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return NameMatchCacheOperationSource.class.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return getClass().getName() + ": " + this.nameMap;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -89,4 +89,112 @@
|
|||||||
</xsd:complexType>
|
</xsd:complexType>
|
||||||
</xsd:element>
|
</xsd:element>
|
||||||
|
|
||||||
|
<xsd:element name="advice">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:annotation>
|
||||||
|
<xsd:documentation source="java:org.springframework.cache.interceptor.CacheInterceptor"><![CDATA[
|
||||||
|
Defines the cache semantics of the AOP advice that is to be
|
||||||
|
executed.
|
||||||
|
|
||||||
|
That is, this advice element is where the cacheable semantics of
|
||||||
|
any number of methods are defined (where cacheable semantics
|
||||||
|
includes the backing cache(s), the key, cache condition rules, and suchlike).
|
||||||
|
]]></xsd:documentation>
|
||||||
|
<xsd:appinfo>
|
||||||
|
<tool:annotation>
|
||||||
|
<tool:exports type="java:org.springframework.cache.interceptor.CacheInterceptor"/>
|
||||||
|
</tool:annotation>
|
||||||
|
</xsd:appinfo>
|
||||||
|
</xsd:annotation>
|
||||||
|
<xsd:complexContent>
|
||||||
|
<xsd:extension base="beans:identifiedType">
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="definitions" type="definitionsType" minOccurs="0" maxOccurs="unbounded"/>
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="cache-manager" type="xsd:string" default="cacheManager">
|
||||||
|
<xsd:annotation>
|
||||||
|
<xsd:documentation source="java:org.springframework.cache.CacheManager"><![CDATA[
|
||||||
|
The bean name of the CacheManager that is to be used
|
||||||
|
for storing and retrieving data.
|
||||||
|
|
||||||
|
This attribute is not required, and only needs to be specified
|
||||||
|
explicitly if the bean name of the desired CacheManager
|
||||||
|
is not 'cacheManager'.
|
||||||
|
]]></xsd:documentation>
|
||||||
|
<xsd:appinfo>
|
||||||
|
<tool:annotation kind="ref">
|
||||||
|
<tool:expected-type type="org.springframework.cache.CacheManager"/>
|
||||||
|
</tool:annotation>
|
||||||
|
</xsd:appinfo>
|
||||||
|
</xsd:annotation>
|
||||||
|
</xsd:attribute>
|
||||||
|
</xsd:extension>
|
||||||
|
</xsd:complexContent>
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
|
||||||
|
<xsd:complexType name="basedefinitionType">
|
||||||
|
<xsd:attribute name="cache" type="xsd:string" use="optional">
|
||||||
|
<xsd:annotation>
|
||||||
|
<xsd:documentation><![CDATA[
|
||||||
|
The name of the backing cache(s). Multiple caches can be specified by separating them using comma: 'orders, books']]></xsd:documentation>
|
||||||
|
</xsd:annotation>
|
||||||
|
</xsd:attribute>
|
||||||
|
<xsd:attribute name="key" type="xsd:string" use="optional">
|
||||||
|
<xsd:annotation>
|
||||||
|
<xsd:documentation><![CDATA[
|
||||||
|
The SpEL expression used for computing the cache key.]]></xsd:documentation>
|
||||||
|
</xsd:annotation>
|
||||||
|
</xsd:attribute>
|
||||||
|
<xsd:attribute name="condition" type="xsd:string" use="optional">
|
||||||
|
<xsd:annotation>
|
||||||
|
<xsd:documentation><![CDATA[
|
||||||
|
The SpEL expression used for conditioning the method caching.]]></xsd:documentation>
|
||||||
|
</xsd:annotation>
|
||||||
|
</xsd:attribute>
|
||||||
|
</xsd:complexType>
|
||||||
|
|
||||||
|
<xsd:complexType name="definitionType">
|
||||||
|
<xsd:complexContent>
|
||||||
|
<xsd:extension base="basedefinitionType">
|
||||||
|
<xsd:attribute name="method" type="xsd:string" use="required">
|
||||||
|
<xsd:annotation>
|
||||||
|
<xsd:documentation><![CDATA[
|
||||||
|
The method name(s) with which the cache attributes are to be
|
||||||
|
associated. The wildcard (*) character can be used to associate the
|
||||||
|
same cache attribute settings with a number of methods; for
|
||||||
|
example, 'get*', 'handle*', '*Order', 'on*Event', etc.
|
||||||
|
]]></xsd:documentation>
|
||||||
|
</xsd:annotation>
|
||||||
|
</xsd:attribute>
|
||||||
|
</xsd:extension>
|
||||||
|
</xsd:complexContent>
|
||||||
|
</xsd:complexType>
|
||||||
|
|
||||||
|
<xsd:complexType name="definitionsType">
|
||||||
|
<xsd:complexContent>
|
||||||
|
<xsd:extension base="basedefinitionType">
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:choice>
|
||||||
|
<xsd:element name="cacheable" minOccurs="0" maxOccurs="unbounded" type="definitionType"/>
|
||||||
|
<xsd:element name="cache-evict" minOccurs="0" maxOccurs="unbounded">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:complexContent>
|
||||||
|
<xsd:extension base="definitionType">
|
||||||
|
<xsd:attribute name="all-entries" type="xsd:boolean" use="optional">
|
||||||
|
<xsd:annotation>
|
||||||
|
<xsd:documentation><![CDATA[
|
||||||
|
The name of the backing cache.]]></xsd:documentation>
|
||||||
|
</xsd:annotation>
|
||||||
|
</xsd:attribute>
|
||||||
|
</xsd:extension>
|
||||||
|
</xsd:complexContent>
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:choice>
|
||||||
|
</xsd:sequence>
|
||||||
|
</xsd:extension>
|
||||||
|
</xsd:complexContent>
|
||||||
|
</xsd:complexType>
|
||||||
|
|
||||||
</xsd:schema>
|
</xsd:schema>
|
||||||
@@ -86,7 +86,7 @@ public abstract class AbstractAnnotationTests {
|
|||||||
Object r2 = service.cache(o1);
|
Object r2 = service.cache(o1);
|
||||||
|
|
||||||
assertSame(r1, r2);
|
assertSame(r1, r2);
|
||||||
service.invalidate(o1, null);
|
service.evict(o1, null);
|
||||||
Object r3 = service.cache(o1);
|
Object r3 = service.cache(o1);
|
||||||
Object r4 = service.cache(o1);
|
Object r4 = service.cache(o1);
|
||||||
assertNotSame(r1, r3);
|
assertNotSame(r1, r3);
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ public class AnnotatedClassCacheableService implements CacheableService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@CacheEvict(value = "default", key = "#p0")
|
@CacheEvict(value = "default", key = "#p0")
|
||||||
public void invalidate(Object arg1, Object arg2) {
|
public void evict(Object arg1, Object arg2) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Cacheable(value = "default", key = "#p0")
|
@Cacheable(value = "default", key = "#p0")
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2011 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.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.cache.config;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Costin Leau
|
||||||
|
*/
|
||||||
|
public class CacheAdviceNamespaceTests extends AbstractAnnotationTests {
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getConfig() {
|
||||||
|
return "/org/springframework/cache/config/cache-advice.xml";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,7 +28,7 @@ public interface CacheableService<T> {
|
|||||||
|
|
||||||
void invalidate(Object arg1);
|
void invalidate(Object arg1);
|
||||||
|
|
||||||
void invalidate(Object arg1, Object arg2);
|
void evict(Object arg1, Object arg2);
|
||||||
|
|
||||||
T conditional(int field);
|
T conditional(int field);
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ public class DefaultCacheableService implements CacheableService<Long> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@CacheEvict(value = "default", key = "#p0")
|
@CacheEvict(value = "default", key = "#p0")
|
||||||
public void invalidate(Object arg1, Object arg2) {
|
public void evict(Object arg1, Object arg2) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Cacheable(value = "default", condition = "#classField == 3")
|
@Cacheable(value = "default", condition = "#classField == 3")
|
||||||
|
|||||||
59
org.springframework.context/src/test/resources/org/springframework/cache/config/cache-advice.xml
vendored
Normal file
59
org.springframework.context/src/test/resources/org/springframework/cache/config/cache-advice.xml
vendored
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xmlns:aop="http://www.springframework.org/schema/aop"
|
||||||
|
xmlns:cache="http://www.springframework.org/schema/cache"
|
||||||
|
xmlns:p="http://www.springframework.org/schema/p"
|
||||||
|
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||||
|
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
|
||||||
|
http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd">
|
||||||
|
|
||||||
|
<cache:advice id="cacheAdviceInterface" cache-manager="cacheManager">
|
||||||
|
<cache:definitions cache="default">
|
||||||
|
<cache:cacheable method="cache"/>
|
||||||
|
<cache:cacheable method="conditional" condition="#classField == 3"/>
|
||||||
|
<cache:cacheable method="key" key="#p0"/>
|
||||||
|
<cache:cacheable method="nam*" key="#root.methodName"/>
|
||||||
|
<cache:cacheable method="rootVars" key="#root.methodName + #root.method.name + #root.targetClass + #root.target"/>
|
||||||
|
<cache:cacheable method="nullValue" cache="default"/>
|
||||||
|
</cache:definitions>
|
||||||
|
<cache:definitions>
|
||||||
|
<cache:cache-evict method="invalidate" cache="default"/>
|
||||||
|
<cache:cache-evict method="evict" key="#p0" cache="default"/>
|
||||||
|
</cache:definitions>
|
||||||
|
</cache:advice>
|
||||||
|
|
||||||
|
<cache:advice id="cacheAdviceClass" cache-manager="cacheManager">
|
||||||
|
<cache:definitions cache="default">
|
||||||
|
<cache:cacheable method="key" key="#p0"/>
|
||||||
|
<cache:cacheable method="nam*" key="#root.methodName + #root.caches[0].name"/>
|
||||||
|
<cache:cacheable method="rootVars" key="#root.methodName + #root.method.name + #root.targetClass + #root.target"/>
|
||||||
|
<cache:cacheable method="cache"/>
|
||||||
|
<cache:cacheable method="conditional"/>
|
||||||
|
<cache:cacheable method="null*"/>
|
||||||
|
</cache:definitions>
|
||||||
|
<cache:definitions>
|
||||||
|
<cache:cache-evict method="invalidate" cache="default"/>
|
||||||
|
<cache:cache-evict method="evict" key="#p0" cache="default"/>
|
||||||
|
</cache:definitions>
|
||||||
|
</cache:advice>
|
||||||
|
|
||||||
|
<aop:config>
|
||||||
|
<aop:advisor advice-ref="cacheAdviceInterface" pointcut="execution(* *..DefaultCacheableService.*(..))" order="1"/>
|
||||||
|
<aop:advisor advice-ref="cacheAdviceClass" pointcut="execution(* *..AnnotatedClassCacheableService.*(..))" order="1"/>
|
||||||
|
<aop:advisor advice-ref="debugInterceptor" pointcut="execution(* *..CacheableService.*(..))" order="2"/>
|
||||||
|
</aop:config>
|
||||||
|
|
||||||
|
<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
|
||||||
|
<property name="caches">
|
||||||
|
<set>
|
||||||
|
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="default"/>
|
||||||
|
</set>
|
||||||
|
</property>
|
||||||
|
</bean>
|
||||||
|
|
||||||
|
<bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor"/>
|
||||||
|
|
||||||
|
<bean id="service" class="org.springframework.cache.config.DefaultCacheableService"/>
|
||||||
|
|
||||||
|
<bean id="classService" class="org.springframework.cache.config.AnnotatedClassCacheableService"/>
|
||||||
|
</beans>
|
||||||
Reference in New Issue
Block a user