此篇文章利用一个简单的示例代码,说明为什么类内调用自身切面方法或其他切面方法失效,并且简单的阐述了切面原理。着急要结论的可以看示例代码说明以及fix bug部分。
本示例代码为一个简单的切面代码(借鉴于spring in action第四章aop的示例代码)。
大致意思及效果如下:一个音乐家演奏一定次数的钢琴,每次演奏前观众进行鼓掌。
演奏钢琴为接口类实现方法。
演奏前观众进行鼓掌为切面方法。
接口类:
public interface Performance {
void perform(int numberOfExecution);
}
实现类:
@Component
public class Music implements Performance {
private int executionCount = 1;
@Override
public void perform(int numberOfExecution) {
System.out.print("This is " + executionCount + "st Piano Solo\n");
if (executionCount < numberOfExecution) {
executionCount++;
perform(numberOfExecution); //此处为迭代调用切面方法,其切面是否生效?
}
}
}
@Aspect
public class Audience {
@Pointcut("execution(* concert.Performance.perform(..))")
public void performance() {
}
@Around("performance()")
public void watchPerformance(ProceedingJoinPoint jp) {
try {
System.out.print("CLAP CLAP CLAP!!!\n");
jp.proceed();
} catch (Throwable e) {
System.out.print("Demanding a refund\n");
}
}
}
@RunWith(SpringJUnit4ClassRunner.class)
public class ConcertTest {
@Autowired
private Performance performance;
@Test
public void testPerformance() {
performance.perform(2); //演奏两次
}
}
我们将调试指针指向第二次切面执行的内部。
我们可以看到第二次执行代码其实是直接调用this.perform,故第二次切面没有起作用,故第二次音乐家演奏之前没有鼓掌。
在程序执行的入口,我们在调试窗口中可以看见,Performance自动装配后不是Music接口实现类,而是一个Proxy代理,并且是由JdkDynamicAopProxy类实现的。
JDK生成动态代理的代码如下:
Proxy.newProxyInstance(ProxyTest.class.getClassLoader(), new Class[]{ProxyInterface.class}, (InvocationHandler)handler);
参数说明如下:
我们可以看出第二个参数与第三个参数是最重要的。
Spring中生成动态代理对象实现切面的代码位于JdkDynamicAopProxy中。
@Override
public Object getProxy(ClassLoader classLoader) {
if (logger.isDebugEnabled()) {
logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());
}
Class>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised);
findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}
我们对其进行代码调试,其第二个参数的反射类数组与第三个参数handler:
从图中可得第二个参数反射类数组除了通常的被代理接口,还有Spring的SpringProxy类与Advised类来实现切面的功能。
fix bug的主要思路是从IOC中将代理proxy再次取出,再调用音乐家方法。
@Component
public class Music implements Performance, ApplicationContextAware {//实现ApplicationContextAware接口可对IOC容器进行操作。
private int executionCount = 1;
private Performance proxyPerformance;
@Override
public void perform(int numberOfExecution) {
System.out.print("This is " + executionCount + "st Piano Solo\n");
if (executionCount < numberOfExecution) {
executionCount++;
// perform(numberOfExecution);
proxyPerformance.perform(numberOfExecution);//从proxy代理执行音乐家演奏方法。
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
proxyPerformance = applicationContext.getBean(Performance.class); //从IOC容器中取出proxy代理的音乐家。
}
}
代码执行结果:
结果中我们可以看出第二次音乐家方法之前,又有人为他鼓掌了(笑)。
对代码进行调试:
在代码中我们可以看出,proxyPerform是第一次调用音乐家方法的proxy对象,故切面会再次起作用。