JDK 与 Cglib 的使用和对比

Spring AOP 依靠 JDK 和 CGLib 进行动态代理实现。在此对两种实现方式的一些知识进行整理。

JDK

使用示例

1
2
3
4
5
6
7
Java复制代码/**
* 需要被代理的接口
*/
interface Iinterface {

String proxyMethod(String gift);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Java复制代码/**
* 实现 InvocationHandler 接口,对 invoke 方法进行重写
*/
class MyHandler implements InvocationHandler {

/**
* @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;
}
}
1
2
3
4
5
6
7
8
9
10
11
Java复制代码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 的呢?

采用其他资料生成的案例,以下是代理类的反编译代码。

参考链接:链接1 链接2

首先来看看代理类的继承关系:

可以看到代理类继承了 Proxy ,再来看看代理类中的方法调用,其中 teach() 是被代理接口的方法声明,内部只是简单地调用了父类即 Proxy 的 h 属性的 invoke 方法

再回看 Proxy 类的属性

可以猜测,对被代理方法进行调用时,会转而由被代理类中继承自 Proxy 的 InvocationHandler 执行,从而 invoke方法被调用。再来看看调用的 newProxyInstance 的逻辑,代码进行了省略,添加了简单注释

1
2
3
4
5
6
7
8
9
10
11
12
13
java复制代码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});
// ...
}

CGLib

使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java复制代码class Hello {

public String hello(String arg) {
System.out.println("获取到参数: " + arg);
return "返回参数";
}

/**
* 通过 final 修饰,该方法不能被子类覆盖,因此 CGLib 无法代理
*/
final public String helloFinal() {
System.out.println("helloFinal");
return null;
}
}
1
2
3
4
5
6
7
8
9
10
java复制代码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;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
java复制代码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

参考链接:blog.csdn.net/zhang662205…

Callback 可以理解成生成的代理类的方法被调用时会执行的逻辑,具有以下六种方式:

  • NoOp:不做任何操作
  • FixedValue:要求实现接口的 loadObjecd 方法,重写了被代理类的响应方法,同时,要求返回值和方法返回值相同,否则会抛出类型转换异常。此方式看不到人喝原方法的信息,也无法调用原方法。
  • MethodInterceptor:类似 AOP 的环绕增强,代理类的方法调用都会转入执行该接口的 intercept 方法。需要执行原方法可以使用参数 method 进行反射调用,或者使用参数 proxy(proxy会快一些)
  • InvocationHandler:类似 MethodInterceptor,若自定义该接口的 invoke 方法,需要注意参数 methodinvoke 方法,会无限循环调用
  • LazyLoader:调用时,返回一个代理对象并存储负责所有的该代理类调用,类似 Spring 的 singleton
  • Dispatcher:每次调用都会返回一个新的代理类,类似 Spring 的 prototye

JDK 与 Cglib 的对比

JDK Cglib
代理目标 接口 类(被代理类会作为父类,无法处理 final)
实现 反射机制 运行过程中修改被代理类的 class 文件字节码
Spring采用策略 目标对象实现接口时采用 目标对象未实现接口时采用
  • 默认为 JDK ,使用时可以进行指定

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%