/*
* Copyright 2002-2013 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.aop.aspectj.autoproxy;
import java.lang.reflect.Method;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.junit.Test;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator;
import org.springframework.aop.aspectj.annotation.AspectMetadata;
import org.springframework.aop.config.AopConfigUtils;
import org.springframework.aop.framework.ProxyConfig;
import org.springframework.aop.support.AopUtils;
import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.config.MethodInvokingFactoryBean;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.NestedRuntimeException;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.ClassPathResource;
import org.springframework.tests.Assume;
import org.springframework.tests.TestGroup;
import org.springframework.tests.sample.beans.INestedTestBean;
import org.springframework.tests.sample.beans.ITestBean;
import org.springframework.tests.sample.beans.NestedTestBean;
import org.springframework.tests.sample.beans.TestBean;
import org.springframework.util.StopWatch;
import static java.lang.String.format;
import static org.junit.Assert.*;
/**
* Integration tests for AspectJ auto-proxying. Includes mixing with Spring AOP Advisors
* to demonstrate that existing autoproxying contract is honoured.
*
* @author Rod Johnson
* @author Juergen Hoeller
* @author Chris Beams
* @author Sam Brannen
*/
public class AspectJAutoProxyCreatorTests {
private static final Log factoryLog = LogFactory.getLog(DefaultListableBeanFactory.class);
private static void assertStopWatchTimeLimit(final StopWatch sw, final long maxTimeMillis) {
final long totalTimeMillis = sw.getTotalTimeMillis();
assertTrue("'" + sw.getLastTaskName() + "' took too long: expected less than<" + maxTimeMillis
+ "> ms, actual<" + totalTimeMillis + "> ms.", totalTimeMillis < maxTimeMillis);
}
@Test
public void testAspectsAreApplied() {
ClassPathXmlApplicationContext bf = newContext("aspects.xml");
ITestBean tb = (ITestBean) bf.getBean("adrian");
assertEquals(68, tb.getAge());
MethodInvokingFactoryBean factoryBean = (MethodInvokingFactoryBean) bf.getBean("&factoryBean");
assertTrue(AopUtils.isAopProxy(factoryBean.getTargetObject()));
assertEquals(68, ((ITestBean) factoryBean.getTargetObject()).getAge());
}
@Test
public void testMultipleAspectsWithParameterApplied() {
ClassPathXmlApplicationContext bf = newContext("aspects.xml");
ITestBean tb = (ITestBean) bf.getBean("adrian");
tb.setAge(10);
assertEquals(20, tb.getAge());
}
@Test
public void testAspectsAreAppliedInDefinedOrder() {
ClassPathXmlApplicationContext bf = newContext("aspectsWithOrdering.xml");
ITestBean tb = (ITestBean) bf.getBean("adrian");
assertEquals(71, tb.getAge());
}
@Test
public void testAspectsAndAdvisorAreApplied() {
ClassPathXmlApplicationContext ac = newContext("aspectsPlusAdvisor.xml");
ITestBean shouldBeWeaved = (ITestBean) ac.getBean("adrian");
doTestAspectsAndAdvisorAreApplied(ac, shouldBeWeaved);
}
@Test
public void testAspectsAndAdvisorAppliedToPrototypeIsFastEnough() {
Assume.group(TestGroup.PERFORMANCE);
Assume.notLogging(factoryLog);
ClassPathXmlApplicationContext ac = newContext("aspectsPlusAdvisor.xml");
StopWatch sw = new StopWatch();
sw.start("Prototype Creation");
for (int i = 0; i < 10000; i++) {
ITestBean shouldBeWeaved = (ITestBean) ac.getBean("adrian2");
if (i < 10) {
doTestAspectsAndAdvisorAreApplied(ac, shouldBeWeaved);
}
}
sw.stop();
// What's a reasonable expectation for _any_ server or developer machine load?
// 9 seconds?
assertStopWatchTimeLimit(sw, 9000);
}
@Test
public void testAspectsAndAdvisorNotAppliedToPrototypeIsFastEnough() {
Assume.group(TestGroup.PERFORMANCE);
Assume.notLogging(factoryLog);
ClassPathXmlApplicationContext ac = newContext("aspectsPlusAdvisor.xml");
StopWatch sw = new StopWatch();
sw.start("Prototype Creation");
for (int i = 0; i < 100000; i++) {
INestedTestBean shouldNotBeWeaved = (INestedTestBean) ac.getBean("i21");
if (i < 10) {
assertFalse(AopUtils.isAopProxy(shouldNotBeWeaved));
}
}
sw.stop();
// What's a reasonable expectation for _any_ server or developer machine load?
// 3 seconds?
assertStopWatchTimeLimit(sw, 6000);
}
@Test
public void testAspectsAndAdvisorNotAppliedToManySingletonsIsFastEnough() {
Assume.group(TestGroup.PERFORMANCE);
Assume.notLogging(factoryLog);
GenericApplicationContext ac = new GenericApplicationContext();
new XmlBeanDefinitionReader(ac).loadBeanDefinitions(new ClassPathResource(qName("aspectsPlusAdvisor.xml"),
getClass()));
for (int i = 0; i < 10000; i++) {
ac.registerBeanDefinition("singleton" + i, new RootBeanDefinition(NestedTestBean.class));
}
StopWatch sw = new StopWatch();
sw.start("Singleton Creation");
ac.refresh();
sw.stop();
// What's a reasonable expectation for _any_ server or developer machine load?
// 8 seconds?
assertStopWatchTimeLimit(sw, 8000);
}
@Test
public void testAspectsAndAdvisorAreAppliedEvenIfComingFromParentFactory() {
ClassPathXmlApplicationContext ac = newContext("aspectsPlusAdvisor.xml");
GenericApplicationContext childAc = new GenericApplicationContext(ac);
// Create a child factory with a bean that should be woven
RootBeanDefinition bd = new RootBeanDefinition(TestBean.class);
bd.getPropertyValues().addPropertyValue(new PropertyValue("name", "Adrian"))
.addPropertyValue(new PropertyValue("age", new Integer(34)));
childAc.registerBeanDefinition("adrian2", bd);
// Register the advisor auto proxy creator with subclass
childAc.registerBeanDefinition(AnnotationAwareAspectJAutoProxyCreator.class.getName(), new RootBeanDefinition(
AnnotationAwareAspectJAutoProxyCreator.class));
childAc.refresh();
ITestBean beanFromChildContextThatShouldBeWeaved = (ITestBean) childAc.getBean("adrian2");
//testAspectsAndAdvisorAreApplied(childAc, (ITestBean) ac.getBean("adrian"));
doTestAspectsAndAdvisorAreApplied(childAc, beanFromChildContextThatShouldBeWeaved);
}
protected void doTestAspectsAndAdvisorAreApplied(ApplicationContext ac, ITestBean shouldBeWeaved) {
TestBeanAdvisor tba = (TestBeanAdvisor) ac.getBean("advisor");
MultiplyReturnValue mrv = (MultiplyReturnValue) ac.getBean("aspect");
assertEquals(3, mrv.getMultiple());
tba.count = 0;
mrv.invocations = 0;
assertTrue("Autoproxying must apply from @AspectJ aspect", AopUtils.isAopProxy(shouldBeWeaved));
assertEquals("Adrian", shouldBeWeaved.getName());
assertEquals(0, mrv.invocations);
assertEquals(34 * mrv.getMultiple(), shouldBeWeaved.getAge());
assertEquals("Spring advisor must be invoked", 2, tba.count);
assertEquals("Must be able to hold state in aspect", 1, mrv.invocations);
}
@Test
public void testPerThisAspect() {
ClassPathXmlApplicationContext bf = newContext("perthis.xml");
ITestBean adrian1 = (ITestBean) bf.getBean("adrian");
assertTrue(AopUtils.isAopProxy(adrian1));
assertEquals(0, adrian1.getAge());
assertEquals(1, adrian1.getAge());
ITestBean adrian2 = (ITestBean) bf.getBean("adrian");
assertNotSame(adrian1, adrian2);
assertTrue(AopUtils.isAopProxy(adrian1));
assertEquals(0, adrian2.getAge());
assertEquals(1, adrian2.getAge());
assertEquals(2, adrian2.getAge());
assertEquals(3, adrian2.getAge());
assertEquals(2, adrian1.getAge());
}
@Test
public void testPerTargetAspect() throws SecurityException, NoSuchMethodException {
ClassPathXmlApplicationContext bf = newContext("pertarget.xml");
ITestBean adrian1 = (ITestBean) bf.getBean("adrian");
assertTrue(AopUtils.isAopProxy(adrian1));
// Does not trigger advice or count
int explicitlySetAge = 25;
adrian1.setAge(explicitlySetAge);
assertEquals("Setter does not initiate advice", explicitlySetAge, adrian1.getAge());
// Fire aspect
AspectMetadata am = new AspectMetadata(PerTargetAspect.class, "someBean");
assertTrue(am.getPerClausePointcut().getMethodMatcher().matches(TestBean.class.getMethod("getSpouse"), null));
adrian1.getSpouse();
assertEquals("Advice has now been instantiated", 0, adrian1.getAge());
adrian1.setAge(11);
assertEquals("Any int setter increments", 2, adrian1.getAge());
adrian1.setName("Adrian");
//assertEquals("Any other setter does not increment", 2, adrian1.getAge());
ITestBean adrian2 = (ITestBean) bf.getBean("adrian");
assertNotSame(adrian1, adrian2);
assertTrue(AopUtils.isAopProxy(adrian1));
assertEquals(34, adrian2.getAge());
adrian2.getSpouse();
assertEquals("Aspect now fired", 0, adrian2.getAge());
assertEquals(1, adrian2.getAge());
assertEquals(2, adrian2.getAge());
assertEquals(3, adrian1.getAge());
}
@Test
public void testTwoAdviceAspectSingleton() {
doTestTwoAdviceAspectWith("twoAdviceAspect.xml");
}
@Test
public void testTwoAdviceAspectPrototype() {
doTestTwoAdviceAspectWith("twoAdviceAspectPrototype.xml");
}
private void doTestTwoAdviceAspectWith(String location) {
ClassPathXmlApplicationContext bf = newContext(location);
boolean aspectSingleton = bf.isSingleton("aspect");
ITestBean adrian1 = (ITestBean) bf.getBean("adrian");
testPrototype(adrian1, 0);
ITestBean adrian2 = (ITestBean) bf.getBean("adrian");
assertNotSame(adrian1, adrian2);
testPrototype(adrian2, aspectSingleton ? 2 : 0);
}
@Test
public void testAdviceUsingJoinPoint() {
ClassPathXmlApplicationContext bf = newContext("usesJoinPointAspect.xml");
ITestBean adrian1 = (ITestBean) bf.getBean("adrian");
adrian1.getAge();
AdviceUsingThisJoinPoint aspectInstance = (AdviceUsingThisJoinPoint) bf.getBean("aspect");
//(AdviceUsingThisJoinPoint) Aspects.aspectOf(AdviceUsingThisJoinPoint.class);
//assertEquals("method-execution(int TestBean.getAge())",aspectInstance.getLastMethodEntered());
assertTrue(aspectInstance.getLastMethodEntered().indexOf("TestBean.getAge())") != 0);
}
@Test
public void testIncludeMechanism() {
ClassPathXmlApplicationContext bf = newContext("usesInclude.xml");
ITestBean adrian = (ITestBean) bf.getBean("adrian");
assertTrue(AopUtils.isAopProxy(adrian));
assertEquals(68, adrian.getAge());
}
private void testPrototype(ITestBean adrian1, int start) {
assertTrue(AopUtils.isAopProxy(adrian1));
//TwoAdviceAspect twoAdviceAspect = (TwoAdviceAspect) bf.getBean(TwoAdviceAspect.class.getName());
adrian1.setName("");
assertEquals(start++, adrian1.getAge());
int newAge = 32;
adrian1.setAge(newAge);
assertEquals(start++, adrian1.getAge());
adrian1.setAge(0);
assertEquals(start++, adrian1.getAge());
}
@Test
public void testForceProxyTargetClass() {
ClassPathXmlApplicationContext bf = newContext("aspectsWithCGLIB.xml");
ProxyConfig pc = (ProxyConfig) bf.getBean(AopConfigUtils.AUTO_PROXY_CREATOR_BEAN_NAME);
assertTrue("should be proxying classes", pc.isProxyTargetClass());
assertTrue("should expose proxy", pc.isExposeProxy());
}
@Test
public void testWithAbstractFactoryBeanAreApplied() {
ClassPathXmlApplicationContext bf = newContext("aspectsWithAbstractBean.xml");
ITestBean adrian = (ITestBean) bf.getBean("adrian");
assertTrue(AopUtils.isAopProxy(adrian));
assertEquals(68, adrian.getAge());
}
@Test
public void testRetryAspect() throws Exception {
ClassPathXmlApplicationContext bf = newContext("retryAspect.xml");
UnreliableBean bean = (UnreliableBean) bf.getBean("unreliableBean");
RetryAspect aspect = (RetryAspect) bf.getBean("retryAspect");
int attempts = bean.unreliable();
assertEquals(2, attempts);
assertEquals(2, aspect.getBeginCalls());
assertEquals(1, aspect.getRollbackCalls());
assertEquals(1, aspect.getCommitCalls());
}
/**
* Returns a new {@link ClassPathXmlApplicationContext} for the file ending in <var>fileSuffix</var>.
*/
private ClassPathXmlApplicationContext newContext(String fileSuffix) {
return new ClassPathXmlApplicationContext(qName(fileSuffix), getClass());
}
/**
* Returns the relatively qualified name for <var>fileSuffix</var>.
* e.g. for a fileSuffix='foo.xml', this method will return
* 'AspectJAutoProxyCreatorTests-foo.xml'
*/
private String qName(String fileSuffix) {
return format("%s-%s", getClass().getSimpleName(), fileSuffix);
}
}
@Aspect("pertarget(execution(* *.getSpouse()))")
class PerTargetAspect implements Ordered {
public int count;
private int order = Ordered.LOWEST_PRECEDENCE;
@Around("execution(int *.getAge())")
public int returnCountAsAge() {
return count++;
}
@Before("execution(void *.set*(int))")
public void countSetter() {
++count;
}
@Override
public int getOrder() {
return this.order;
}
public void setOrder(int order) {
this.order = order;
}
}
@Aspect
class AdviceUsingThisJoinPoint {
private String lastEntry = "";
public String getLastMethodEntered() {
return this.lastEntry;
}
@Pointcut("execution(* *(..))")
public void methodExecution() {
}
@Before("methodExecution()")
public void entryTrace(JoinPoint jp) {
this.lastEntry = jp.toString();
}
}
@Aspect
class DummyAspect {
@Around("execution(* setAge(int))")
public Object test(ProceedingJoinPoint pjp) throws Throwable {
return pjp.proceed();
}
}
@Aspect
class DummyAspectWithParameter {
@Around("execution(* setAge(int)) && args(age)")
public Object test(ProceedingJoinPoint pjp, int age) throws Throwable {
return pjp.proceed();
}
}
class DummyFactoryBean implements FactoryBean<Object> {
@Override
public Object getObject() throws Exception {
throw new UnsupportedOperationException();
}
@Override
public Class<?> getObjectType() {
throw new UnsupportedOperationException();
}
@Override
public boolean isSingleton() {
throw new UnsupportedOperationException();
}
}
@Aspect
@Order(10)
class IncreaseReturnValue {
@Around("execution(int *.getAge())")
public Object doubleReturnValue(ProceedingJoinPoint pjp) throws Throwable {
int result = (Integer) pjp.proceed();
return result + 3;
}
}
@Aspect
class MultiplyReturnValue {
private int multiple = 2;
public int invocations;
public void setMultiple(int multiple) {
this.multiple = multiple;
}
public int getMultiple() {
return this.multiple;
}
@Around("execution(int *.getAge())")
public Object doubleReturnValue(ProceedingJoinPoint pjp) throws Throwable {
++this.invocations;
int result = (Integer) pjp.proceed();
return result * this.multiple;
}
}
@Aspect
class RetryAspect {
private int beginCalls;
private int commitCalls;
private int rollbackCalls;
@Pointcut("execution(public * UnreliableBean.*(..))")
public void execOfPublicMethod() {
}
/**
* Retry Advice
*/
@Around("execOfPublicMethod()")
public Object retry(ProceedingJoinPoint jp) throws Throwable {
boolean retry = true;
Object o = null;
while (retry) {
try {
retry = false;
this.beginCalls++;
try {
o = jp.proceed();
this.commitCalls++;
}
catch (RetryableException re) {
this.rollbackCalls++;
throw re;
}
}
catch (RetryableException re) {
retry = true;
}
}
return o;
}
public int getBeginCalls() {
return this.beginCalls;
}
public int getCommitCalls() {
return this.commitCalls;
}
public int getRollbackCalls() {
return this.rollbackCalls;
}
}
@SuppressWarnings("serial")
class RetryableException extends NestedRuntimeException {
public RetryableException(String msg) {
super(msg);
}
public RetryableException(String msg, Throwable cause) {
super(msg, cause);
}
}
class UnreliableBean {
private int calls;
public int unreliable() {
this.calls++;
if (this.calls % 2 != 0) {
throw new RetryableException("foo");
}
return this.calls;
}
}
@SuppressWarnings("serial")
class TestBeanAdvisor extends StaticMethodMatcherPointcutAdvisor {
public int count;
public TestBeanAdvisor() {
setAdvice(new MethodBeforeAdvice() {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
++count;
}
});
}
@Override
public boolean matches(Method method, Class<?> targetClass) {
return ITestBean.class.isAssignableFrom(targetClass);
}
}