Spring AOP 依靠 JDK 和 CGLib 进行动态代理实现。在此对两种实现方式的一些知识进行整理。
JDK
使用示例
/**
* 需要被代理的接口
*/
interface Iinterface {
String proxyMethod(String gift);
}
/**
* 实现 InvocationHandler 接口,对 invoke 方法进行重写
*/
class MyHandler implements {
/**
* @param proxy 生成的代理实例
* @param method 被代理类的方法
* @param args 传入的参数列表
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 对应被代理的方法
if (method.getName().equals("proxyMethod")) {
System.out.println("接受到参数:" + args[0]);
return "返回值";
}
return null;
}
}
public class Main {
public static void main(String[] args) {
Iinterface proxy = (Iinterface) Proxy.newProxyInstance(
Thread.currentThread().getContextClassLoader(),
new Class[]{Iinterface.class},
new MyHandler()
);
String res = proxy.proxyMethod("传入的参数");
System.out.println(res);
}
}
可见代理成功。
概括一下,动态代理的方式一般为:
-
继承
InvocationHandler
,重写方法invoke
-
执行
Proxy.newProxyInstance
生成动态代理类
invoke 方法
由上可以看出,proxy 成功对 Iinterface 接口进行代理,但是在使用时,我们并未见到 InvocationHandler 中 invoke
方法的调用,动态代理是如何执行 invoke
的呢?
采用其他资料生成的案例,以下是代理类的反编译代码。
首先来看看代理类的继承关系:
可以看到代理类继承了 Proxy ,再来看看代理类中的方法调用,其中 teach()
是被代理接口的方法声明,内部知识简单地调用了父类即 Proxy 的 h 属性的 invoke
方法
再回看 Proxy 类的属性
可以猜测,对被代理方法进行调用时,会转而由被代理类中继承自 Proxy 的 InvocationHandler 执行,从而 invoke
方法被调用。再来看看调用的 newProxyInstance
的逻辑,代码进行了省略,添加了简单注释
public static Object newProxyInstance(ClassLoader loader,
Class<!--?-->[] interfaces,
InvocationHandler h){
// 创建代理类$Proxy0,实现了传入的 intfs 接口,并继承了 Proxy
Class<!--?--> cl = getProxyClass0(loader, intfs);
//...
// 获取构造方法
final Constructor<!--?--> cons = cl.getConstructor(constructorParams);
//...
//调用具有 InvocationHandler 的构造方法,创建代理类并返回
return cons.newInstance(new Object[]{h});
// ...
}
目前还有一个问题没搞明白:调用 invoke
时,参数中的 method 能够争取匹配到调用的方法,该匹配是如何实现的?
CGLib
使用示例
class Hello {
public String hello(String arg) {
System.out.println("获取到参数: " + arg);
return "返回参数";
}
/**
* 通过 final 修饰,该方法不能被子类覆盖,因此 CGLib 无法代理
*/
final public String helloFinal() {
System.out.println("helloFinal");
return null;
}
}
class MyInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("代理执行前");
String res = (String) methodProxy.invokeSuper(o, objects);
System.out.println(res);
System.out.println("代理执行后");
return null;
}
}
public class Main {
public static void main(String[] args) {
// 通过CGLIB动态代理获取代理对象的过程
Enhancer enhancer = new Enhancer();
// 设置enhancer对象的父类
enhancer.setSuperclass(Hello.class);
// 设置enhancer的回调对象
enhancer.setCallback(new MyInterceptor());
// 创建代理对象
Hello proxy= (Hello)enhancer.create();
// 通过代理对象调用目标方法
proxy.hello("参数");
}
}
概括一下,CGLib 动态代理的方式一般为:
- 构造 Enhancer
- 设置代理对象(作为父类)
- 设置代理策略
- 创建代理对象
Callback
参考链接:https://blog.csdn.net/zhang6622056/article/details/87286498
Callback 可以理解成生成的代理类的方法被调用时会执行的逻辑,具有以下六种方式:
-
NoOp:不做任何操作
-
FixedValue:要求实现接口的
loadObjecd
方法,重写了被代理类的响应方法,同时,要求返回值和方法返回值相同,否则会抛出类型转换异常。此方式看不到人喝原方法的信息,也无法调用原方法。 -
MethodInterceptor:类似 AOP 的环绕增强,代理类的方法调用都会转入执行该接口的
intercept
方法。需要执行原方法可以使用参数 method 进行反射调用,或者使用参数 proxy(proxy会快一些) -
InvocationHandler:类似 MethodInterceptor,若自定义该接口的
invoke
方法,需要注意参数 method 的invoke
方法,会无限循环调用 -
LazyLoader:调用时,返回一个代理对象并存储负责所有的该代理类调用,类似 Spring 的 singleton
-
Dispatcher:每次调用都会返回一个新的代理类,类似 Spring 的 prototye
JDK 与 Cglib 的对比
JDK | Cglib | |
---|---|---|
代理目标 | 接口 | 类(被代理类会作为父类,无法处理 final) |
实现 | 反射机制 | 运行过程中修改被代理类的 class 文件字节码 |
Spring采用策略 | 目标对象实现接口时采用 | 目标对象未实现接口时采用 |
- 默认为 JDK ,使用时可以进行指定