+ initial commit of caching abstraction
+ main API
+ Spring AOP and AspectJ support
+ annotation driven, declarative support
+ initial namespace draft
This commit is contained in:
Costin Leau
2010-10-29 17:00:08 +00:00
parent 416777022d
commit 85c02981b5
62 changed files with 4225 additions and 0 deletions

View File

@@ -0,0 +1,81 @@
/*
* Copyright 2010 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.aspectj;
import java.lang.reflect.Method;
import java.util.concurrent.Callable;
import org.aspectj.lang.annotation.SuppressAjWarnings;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.cache.interceptor.CacheAspectSupport;
import org.springframework.cache.interceptor.CacheDefinitionSource;
/**
* Abstract superaspect for AspectJ cache aspects. Concrete
* subaspects will implement the <code>cacheMethodExecution()</code>
* pointcut using a strategy such as Java 5 annotations.
*
* <p>Suitable for use inside or outside the Spring IoC container.
* Set the "cacheManager" property appropriately, allowing
* use of any cache implementation supported by Spring.
*
* <p><b>NB:</b> If a method implements an interface that is itself
* cache annotated, the relevant Spring cache definition
* will <i>not</i> be resolved.
* @author Costin Leau
*/
public abstract aspect AbstractCacheAspect extends CacheAspectSupport {
protected AbstractCacheAspect() {
}
/**
* Construct object using the given caching metadata retrieval strategy.
* @param cds {@link CacheDefinitionSource} implementation, retrieving Spring
* cache metadata for each joinpoint.
*/
protected AbstractCacheAspect(CacheDefinitionSource... cds) {
setCacheDefinitionSources(cds);
}
@SuppressAjWarnings("adviceDidNotMatch")
Object around(final Object cachedObject) : cacheMethodExecution(cachedObject){
MethodSignature methodSignature = (MethodSignature) thisJoinPoint.getSignature();
Method method = methodSignature.getMethod();
Callable<Object> ajInvocation = new Callable<Object>() {
public Object call() {
return proceed(cachedObject);
}
};
try {
return execute(ajInvocation, thisJoinPoint.getTarget(), method, thisJoinPoint.getArgs());
} catch (Exception ex) {
throw new UnsupportedOperationException("Should not throw exception", ex);
}
}
/**
* Concrete subaspects must implement this pointcut, to identify
* cached methods. For each selected joinpoint, {@link CacheOperationDefinition}
* will be retrieved using Spring's {@link CacheDefinitionSource} interface.
*/
protected abstract pointcut cacheMethodExecution(Object cachedObject);
}

View File

@@ -0,0 +1,86 @@
/*
* Copyright 2010 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.aspectj;
import org.springframework.cache.annotation.AnnotationCacheDefinitionSource;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
/**
* Concrete AspectJ cache aspect using Spring {@link Cacheable} annotation
* for JDK 1.5+.
*
* <p>When using this aspect, you <i>must</i> annotate the implementation class
* (and/or methods within that class), <i>not</i> the interface (if any) that
* the class implements. AspectJ follows Java's rule that annotations on
* interfaces are <i>not</i> inherited.
*
* <p>A {@link Cacheable} annotation on a class specifies the default caching
* semantics for the execution of any <b>public</b> operation in the class.
*
* <p>A {@link Cacheable} annotation on a method within the class overrides the
* default caching semantics given by the class annotation (if present).
* Any method may be annotated (regardless of visibility).
* Annotating non-public methods directly is the only way
* to get caching demarcation for the execution of such operations.
*
* @author Costin Leau
*/
public aspect AnnotationCacheAspect extends AbstractCacheAspect {
public AnnotationCacheAspect() {
super(new AnnotationCacheDefinitionSource(false));
}
/**
* Matches the execution of any public method in a type with the
* {@link Cacheable} annotation, or any subtype of a type with the
* {@link Cacheable} annotation.
*/
private pointcut executionOfAnyPublicMethodInAtCacheableType() :
execution(public * ((@Cacheable *)+).*(..)) && @this(Cacheable);
/**
* Matches the execution of any public method in a type with the
* {@link CacheEvict} annotation, or any subtype of a type with the
* {@link CacheEvict} annotation.
*/
private pointcut executionOfAnyPublicMethodInAtCacheEvictType() :
execution(public * ((@CacheEvict *)+).*(..)) && @this(CacheEvict);
/**
* Matches the execution of any method with the
* Cacheable annotation.
*/
private pointcut executionOfCacheableMethod() :
execution(* *(..)) && @annotation(Cacheable);
/**
* Matches the execution of any method with the {@link CacheEvict} annotation.
*/
private pointcut executionOfCacheEvictMethod() :
execution(* *(..)) && @annotation(CacheEvict);
/**
* Definition of pointcut from super aspect - matched join points
* will have Spring cache management applied.
*/
protected pointcut cacheMethodExecution(Object cachedObject) :
(executionOfAnyPublicMethodInAtCacheableType() || executionOfAnyPublicMethodInAtCacheEvictType()
|| executionOfCacheableMethod() || executionOfCacheEvictMethod())
&& this(cachedObject);
}

View File

@@ -0,0 +1,130 @@
/*
* Copyright 2010 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.aspectj;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertSame;
import org.junit.Before;
import org.junit.Test;
import org.springframework.cache.config.CacheableService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* Abstract annotation test (containing several reusable methods).
* @author Costin Leau
*/
public abstract class AbstractAnnotationTest {
protected ApplicationContext ctx;
protected CacheableService cs;
protected CacheableService ccs;
protected abstract String getConfig();
@Before
public void setup() {
ctx = new ClassPathXmlApplicationContext(getConfig());
cs = ctx.getBean("service", CacheableService.class);
ccs = ctx.getBean("classService", CacheableService.class);
}
public void testCacheable(CacheableService service) throws Exception {
Object o1 = new Object();
Object o2 = new Object();
Object r1 = service.cache(o1);
Object r2 = service.cache(o1);
Object r3 = service.cache(o1);
assertSame(r1, r2);
assertSame(r1, r3);
}
public void testInvalidate(CacheableService service) throws Exception {
Object o1 = new Object();
Object o2 = new Object();
Object r1 = service.cache(o1);
Object r2 = service.cache(o1);
assertSame(r1, r2);
service.invalidate(o1);
Object r3 = service.cache(o1);
Object r4 = service.cache(o1);
assertNotSame(r1, r3);
assertSame(r3, r4);
}
public void testConditionalExpression(CacheableService service) throws Exception {
Object r1 = service.conditional(4);
Object r2 = service.conditional(4);
assertNotSame(r1, r2);
Object r3 = service.conditional(3);
Object r4 = service.conditional(3);
assertSame(r3, r4);
}
public void testKeyExpression(CacheableService service) throws Exception {
Object r1 = service.key(5, 1);
Object r2 = service.key(5, 2);
assertSame(r1, r2);
Object r3 = service.key(1, 5);
Object r4 = service.key(2, 5);
assertNotSame(r3, r4);
}
@Test
public void testCacheable() throws Exception {
testCacheable(cs);
}
@Test
public void testInvalidate() throws Exception {
testInvalidate(cs);
}
@Test
public void testConditionalExpression() throws Exception {
testConditionalExpression(cs);
}
@Test
public void testKeyExpression() throws Exception {
testKeyExpression(cs);
}
@Test
public void testClassCacheCacheable() throws Exception {
testCacheable(ccs);
}
@Test
public void testClassCacheInvalidate() throws Exception {
testInvalidate(ccs);
}
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2010 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.aspectj;
/**
* @author Costin Leau
*/
public class AspectJAnnotationTest extends AbstractAnnotationTest {
@Override
protected String getConfig() {
return "/org/springframework/cache/config/annotation-cache-aspectj.xml";
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2010 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.concurrent.atomic.AtomicLong;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
/**
* @author Costin Leau
*/
@Cacheable
public class AnnotatedClassCacheableService implements CacheableService {
private AtomicLong counter = new AtomicLong();
public Object cache(Object arg1) {
return counter.getAndIncrement();
}
public Object conditional(int field) {
return null;
}
@CacheEvict
public void invalidate(Object arg1) {
}
@Cacheable(key = "#p0")
public Object key(Object arg1, Object arg2) {
return counter.getAndIncrement();
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2010 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;
/**
* Basic service interface.
*
* @author Costin Leau
*/
public interface CacheableService<T> {
T cache(Object arg1);
void invalidate(Object arg1);
T conditional(int field);
T key(Object arg1, Object arg2);
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2010 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.concurrent.atomic.AtomicLong;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
/**
* Simple cacheable service
*
* @author Costin Leau
*/
public class DefaultCacheableService implements CacheableService<Long> {
private AtomicLong counter = new AtomicLong();
@Cacheable
public Long cache(Object arg1) {
return counter.getAndIncrement();
}
@CacheEvict
public void invalidate(Object arg1) {
}
@Cacheable(condition = "#classField == 3")
public Long conditional(int classField) {
return counter.getAndIncrement();
}
@Cacheable(key = "#p0")
public Long key(Object arg1, Object arg2) {
return counter.getAndIncrement();
}
}

View File

@@ -0,0 +1,39 @@
<?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: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">
<bean id="annotationSource" class="org.springframework.cache.annotation.AnnotationCacheDefinitionSource"/>
<aop:config>
<aop:advisor advice-ref="debugInterceptor" pointcut="execution(* *..CacheableService.*(..))" order="1"/>
</aop:config>
<bean id="cacheAspect" class="org.springframework.cache.aspectj.AnnotationCacheAspect" factory-method="aspectOf">
<property name="cacheManager" ref="cacheManager"/>
<property name="cacheDefinitionSources" ref="annotationSource"/>
</bean>
<!--
<bean id="advisor" class="org.springframework.cache.interceptor.BeanFactoryCacheDefinitionSourceAdvisor">
<property name="cacheDefinitionSource" ref="annotationSource"/>
<property name="adviceBeanName" value="cacheAspect"/>
</bean>
-->
<bean id="cacheManager" class="org.springframework.cache.support.MapCacheManager">
<property name="caches">
<set>
<bean class="org.springframework.cache.concurrent.ConcurrentCacheFactoryBean" 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>