今天在开发过程中,遇到一个问题卡了很久,测试代码如下:
package spring.pointcut; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; /** * @Description: Pointcut测试 * @Author: qionghui.fang * @Date: 2018/10/23 上午10:10 */ @Aspect public class TargetMonitor { // 配置方法1 // @Pointcut("execution(* spring.pointcut.Target.onEvent(String))") // private void anyMethod() {} // @Around("anyMethod()") //配置方法2 @Around("execution(* spring.pointcut.Target.onEvent(..))") public Object monitor(ProceedingJoinPoint point) throws Throwable { System.out.println("before"); try { return point.proceed(); } finally { System.out.println("after"); } } }
目标类:
public class Target { public void otherEvent(){ System.out.println("Call otherEvent()"); } public boolean onEvent(Integer type, Long Value){ System.out.println("Call onEvent(Integer type, Long Value)"); for (int i=0; i<=3; i++){ onEvent(""); } System.out.println("End Call onEvent(Integer type, Long Value)"); return true; } public boolean onEvent(String type){ System.out.println("Call onEvent(String type)"); return true; } }
Main方法:
public class Main { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("spring/spring.xml"); Target target = (Target) ctx.getBean("target"); System.out.println("\n*****************************"); target.onEvent(1,1L); System.out.println("\n*****************************"); target.onEvent(""); System.out.println("\n*****************************"); target.otherEvent(); System.out.println("\n*****************************"); } }
spring.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" 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/context http://www.springframework.org/schema/context/spring-context.xsd"> <bean id="target" class="spring.pointcut.Target"/> <bean id="monitor" class="spring.pointcut.TargetMonitor"/> <!-- 基于@AspectJ切面的驱动器 --> <aop:aspectj-autoproxy proxy-target-class="true"/> </beans>
结果输出:
22:14:25.092 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'target' ***************************** 22:14:25.096 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'monitor' before Call onEvent(Integer type, Long Value) Call onEvent(String type) Call onEvent(String type) Call onEvent(String type) Call onEvent(String type) End Call onEvent(Integer type, Long Value) after ***************************** before Call onEvent(String type) after ***************************** Call otherEvent() *****************************
问题描述:
在目标类里有两个同名的onEvent方法,下面这个是目标切点方法,但是系统调用时,方法入口是上面的onEvent方法,所以怎么都执行不到想要的逻辑。
其实想一想动态代理,是代理的类这一层级,关于类中方法的相互调用,是不能侵入这么深的。
注意:切点方法当前仅当在类执行入口时才能被调用,而非类内部的其他接口调用。
原理跟踪,跟踪生成的动态代理类:
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; import spring.dynamicproxy.IFace; public final class Target1$Proxy extends Proxy implements Target { private static Method m1; private static Method m2; private static Method m3; private static Method m4; private static Method m5; private static Method m0; static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m2 = Class.forName("java.lang.Object").getMethod("toString"); m3 = Class.forName("spring.dynamicproxy.Target").getMethod("onEvent"); m4 = Class.forName("spring.dynamicproxy.Target").getMethod("onEvent1"); m5 = Class.forName("spring.dynamicproxy.Target").getMethod("otherEvent"); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } public Target1$Proxy(InvocationHandler var1) throws { super(var1); } public final boolean onEvent() throws { try { return (Boolean)super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final boolean onEvent1() throws { try { return (Boolean)super.h.invoke(this, m4, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final void otherEvent() throws { try { super.h.invoke(this, m5, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } }
上述省略了部分其他方法,大家关注这个动态代理类内容,
JDK的动态代理核心是生成了一个新的代理类,这个代理类基础Proxy类,实现了目标对象的接口,而在具体方法执行时,
调用super.h.invoke,这里的super.h 是指我们初始化动态代理的InvocationHandler,这里有点绕,大家要好好理解。
也就是说动态代理生效的方法是,当调用代理类Target.m的目标方法时,其实执行的是ProxyTarget.m,
这样我们在切点中round前后的逻辑就可以执行到。
但是当执行round中的super.h.invoke时,这个方法里执行的是原始类的原生逻辑,比如执行上述例子的onEvent1,
但onEnvent1中调用onEvent时,是执行的this.onEvent,而非ProxyTarget.onEvent,
这便是为什么上述例子无法执行的底层核心原因。
这篇文章也描述了类似的问题:https://blog.csdn.net/bobozai86/article/details/78896487
解决办法:
1、在调用点使用代理类,而非this调用:
main中获取容器对象的测试方法:
2、思考业务层面,是否可以切在更内层的方法。
本质还是要理解动态代理的实现原理。
以上。