spring调用切面失效分析(类内调用自身切面方法或其他切面方法失效)

spring调用切面失效分析(类内调用自身切面方法或其他切面方法失效)

  • 简述
    • 示例代码说明
      • 演奏钢琴
      • 观众鼓掌
      • 执行程序及结果
      • 原因分析
    • Spring切面原理
      • 代码调试
    • fix bug
      • 代码
      • 结果

简述

此篇文章利用一个简单的示例代码,说明为什么类内调用自身切面方法或其他切面方法失效,并且简单的阐述了切面原理。着急要结论的可以看示例代码说明以及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);  //演奏两次
    }
}

执行结果:
执行结果,结果显示只有第一次切面起作用,第二次切面失效。
结果显示只有第一次切面起作用,第二次切面失效。

原因分析

我们将调试指针指向第二次切面执行的内部。
spring调用切面失效分析(类内调用自身切面方法或其他切面方法失效)_第1张图片
我们可以看到第二次执行代码其实是直接调用this.perform,故第二次切面没有起作用,故第二次音乐家演奏之前没有鼓掌。

Spring切面原理

代码调试

在程序执行的入口,我们在调试窗口中可以看见,Performance自动装配后不是Music接口实现类,而是一个Proxy代理,并且是由JdkDynamicAopProxy类实现的。
spring调用切面失效分析(类内调用自身切面方法或其他切面方法失效)_第2张图片
JDK生成动态代理的代码如下:

 Proxy.newProxyInstance(ProxyTest.class.getClassLoader(), new Class[]{ProxyInterface.class}, (InvocationHandler)handler);

参数说明如下:

  • 第一个参数为类加载器,一般为当前线程的类加载器。
  • 第二个参数是反射类数组,表示新生成的Proxy可以调用的method。
  • 第三个参数是实现InvocationHandler接口的对象,如前文所述,所有对代理对象调用方法都会至invoke方法中处理。

我们可以看出第二个参数与第三个参数是最重要的。

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调用切面失效分析(类内调用自身切面方法或其他切面方法失效)_第3张图片
从图中可得第二个参数反射类数组除了通常的被代理接口,还有Spring的SpringProxy类与Advised类来实现切面的功能。

fix bug

代码

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代理的音乐家。
    }
}

结果

代码执行结果:
在这里插入图片描述
结果中我们可以看出第二次音乐家方法之前,又有人为他鼓掌了(笑)。

对代码进行调试:
spring调用切面失效分析(类内调用自身切面方法或其他切面方法失效)_第4张图片
在代码中我们可以看出,proxyPerform是第一次调用音乐家方法的proxy对象,故切面会再次起作用。

你可能感兴趣的:(spring调用切面失效分析(类内调用自身切面方法或其他切面方法失效))