0%

java动态代理:java-Proxy和CGLIB

一直想了解一下 Spring AOP 的实现,所以看了下 java 动态代理(jdk 8)。

java Proxy 实现动态代理

Proxy 原理是在运行期创建指定的被代理接口的一个实现类,这个类是 Proxy 的子类,并使用反射机制,对接口中声明的方法,用 InvocationHandler 转发函数调用。

Proxy 只能代理接口的实现,会被转发到从其原理不难看出,这是因为生成的代理类已经继承了 Proxy,因为 java 单继承的机制,所以只能实现接口作为其代理。

假设有接口 Target:

1
2
3
4
5
public interface Target {

void fun(String s);
}

及其实现 RealTarget:

1
2
3
4
5
6
7
8
9
10
11
public class RealTarget implements Target {

@Override
public void fun(String s) {
System.out.println(this.getClass().getSimpleName() + " fun " + s);
}

public void fun2(String s) {
System.out.println(this.getClass().getSimpleName() + " fun2 " + s);
}
}

要使用 Proxy 对 RealTarget 动态代理,首先,创建一个类实现 InvocationHandler,作为方法调用的处理器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class InvokeHandler implements InvocationHandler {

private Object proxiedObj;

public InvokeHandler(Object proxiedObj) {
this.proxiedObj = proxiedObj;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(this.getClass().getSimpleName() + " invoke " + Arrays.deepToString(args));
return method.invoke(proxiedObj, args);
}
}

然后可使用以下方式创建和使用代理:

1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) {
Target t = new RealTarget();
Target proxy = (Target) Proxy.newProxyInstance(
Target.class.getClassLoader(),
new Class[]{Target.class},
new InvokeHandler(t));
proxy.fun("ma");
System.out.println(proxy.getClass());
System.out.println(Proxy.class.isAssignableFrom(proxy.getClass()));
System.out.println(proxy instanceof RealTarget);
}

以上代码的输出为:

1
2
3
4
5
InvokeHandler invoke [ma]
RealTarget fun ma
class com.sun.proxy.$Proxy0
true
false

另,Proxy 会把从 Object 中继承的非 final 方法,包括 hashCode()、equals()、toString() 转发到 InvocationHandler,但是其他从 Object 中继承的 final 方法不会转发。

运行时创建的代理类如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
public final class $Proxy0 extends Proxy implements Target {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;

public $Proxy0(InvocationHandler var1) throws {
super(var1);
}

public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}

public final void fun(String var1) throws {
try {
super.h.invoke(this, m3, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}

public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}

public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}

static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m3 = Class.forName("Target").getMethod("fun", Class.forName("java.lang.String"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}

了解了其原理,不难理解这种情况:假设 fun() 和 fun2() 都是实现的接口中的方法,如果在 fun() 的实现中调用了 fun2(),因为实际是用 this 即被代理类引用来调用的,所以这一 fun2() 的调用不会代理。

Spring CGLIB 实现动态代理

以上对 jdk Proxy 的了解可以知道,Proxy 只能代理接口的实现,想对别的类动态代理,可以用 CGLIB 实现。这里我们使用spring-core包内置的 CGLIB 实现进行实例。

CGLIB 的原理是动态创建一个被代理类的子类,覆盖其方法,实现代理。可以实现 MethodInterceptor 接口,作用与 jdk Proxy 的 InvocationHandler 类似。如果不为代理设置 MethodInterceptor,将默认调用代理类的父类(被代理类)的方法。

显然,CGLIB 对于 final 类无法代理;类中的 final 方法无法代理。

而因为 CGLIB 是通过继承被代理类来实现动态代理的,如果在 MethodInterceptor 中使用代理对象调用方法,由于多态,那么在被代理类内部的方法的相互调用,也是经过代理对象来调用的,都会被代理。

CGLIB 的使用可见下例。

首先假设有被代理类 RealSubject:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.tianma.resTest;

public class RealSubject {

public void fun(String s) {
System.out.println(this.getClass().getSimpleName() + " fun " + s);
fun2();
}

public void fun2() {
System.out.println(this.getClass().getSimpleName() + " fun2");
}

public final void fun3(String s) {
System.out.println(this.getClass().getSimpleName() + " fun3 " + s);
}
}

定义 MethodInterceptor 的实现,其中,第一个参数 o,是代理对象的实例,methodProxy.invokeSuper(o, objects),是使用代理对象调用了父类的方法。

1
2
3
4
5
6
7
8
9
10
public class MyMethodInterceptor implements MethodInterceptor {

@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println(o.getClass().getSimpleName());
System.out.println(method.toString());
System.out.println(methodProxy.getSignature().toString());
return methodProxy.invokeSuper(o, objects);
}
}

然后可以通过以下方式来创建和使用代理:

1
2
3
4
5
6
7
8
9
10
11
12
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(RealSubject.class);
enhancer.setCallback(new MyMethodInterceptor());

RealSubject subject = (RealSubject) enhancer.create();
System.out.println("----------------------");

subject.fun("ma");
System.out.println("---------");
subject.fun2();
System.out.println("---------");
subject.fun3("tian");

其输出为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
----------------------
RealSubject$$EnhancerByCGLIB$$9e951628
public void com.tianma.resTest.RealSubject.fun(java.lang.String)
fun(Ljava/lang/String;)V
RealSubject$$EnhancerByCGLIB$$9e951628 fun ma
RealSubject$$EnhancerByCGLIB$$9e951628
public void com.tianma.resTest.RealSubject.fun2()
fun2()V
RealSubject$$EnhancerByCGLIB$$9e951628 fun2
---------
RealSubject$$EnhancerByCGLIB$$9e951628
public void com.tianma.resTest.RealSubject.fun2()
fun2()V
RealSubject$$EnhancerByCGLIB$$9e951628 fun2
---------
RealSubject$$EnhancerByCGLIB$$9e951628 fun3 tian

使用 java HSDB 工具可以查看 CGLIB 运行时生成的字节码,可以看下 fun() 部分的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class RealSubject$$EnhancerByCGLIB$$9e951628 extends RealSubject implements Factory {

......

public final void fun(String var1) {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}

if (var10000 != null) {
var10000.intercept(this, CGLIB$fun$0$Method, new Object[]{var1}, CGLIB$fun$0$Proxy);
} else {
super.fun(var1);
}
}

......

}

可见,被代理类中的可以被 override 的方法(非 private、非 final、非 static),被动态创建的子类覆盖,我们可以实现 MethodInterceptor 接口对调用进行自定义处理,如果不设置 MethodInterceptor,将默认调用代理类的父类(被代理类)的方法。

同时,从被反编译的代码中可以看到,Object 类中可 override 的方法:hashCode()、toString()、equals()、clone() 也可以代理。

java 查看运行时动态创建的 class

使用 java 的 HSDB 工具

简记下查看动态生成类的方式:

  1. 启动项目,使用断点等使不要退出
  2. cmd 执行 java -classpath “%JAVA_HOME%/lib/sa-jdi.jar” sun.jvm.hotspot.HSDB,启动 HSDB
  3. cmd 执行 jps,查看要调试的项目的 id
  4. HSDB 中 attach 到此 id 的项目,查看类,搜索要查看的类的类名即可搜到
  5. create,然后 class 会被保存到执行 2 中命令的目录中

HSDB 工具还可以查看运行时 Thread 等信息。

使用 ProxyGenerator 生成和保存 java Proxy 动态生成的 class 文件

使用下面代码,我们可以用 ProxyGenerator 生成 class 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 获取代理类的字节码
byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", RealTarget.class.getInterfaces());

FileOutputStream out = null;

try {
out = new FileOutputStream(path);
out.write(classFile);
out.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}