Jdk和Cglib动态代理

Jdk和Cglib动态代理

在Java编程中,我们经常会看到或使用动态代理机制,而本文将对这两个代理进行相关的介绍。

jdk动态代理

jdk动态代理只能针对接口进行代理,所以在实现jdk动态代理的时候,我们要先定义一个接口。

1
2
3
public interface JdkProxyInterface {
String sayHello();
}

然后就是我们需要代理的类,这个代理的类需要实现一个接口,这个接口是将来jdk动态代理类实现的接口,这里我们就实现JdkProxyInterface接口。

1
2
3
4
5
6
7
public class JdkTarget implements JdkProxyInterface{
@Override
public String sayHello() {
System.out.println("hello");
return "hello";
}
}

然后使用jdk自带的Proxy来创建代理对象。

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) {
Proxy.newProxyInstance(JdkProxyInterface.class.getClassLoader(),
new Class[]{JdkProxyInterface.class}, new InvocationHandler() {
// proxy为代理对象,method为执行的方法,args为传入的参数
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return null;
}
});
}

在代理执行的方法中,我们可以进行目标方法的增强,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class test {
public static void main(String[] args) {
// 待增强的对象,可以称之为需要被代理的对象
JdkTarget jdkTarget = new JdkTarget();

// 由于代理对象实现的是JdkProxyInterface接口,所以我们可以将其强制转为父类接口类型
// newProxyInstance内需要传的参数分别为:类加载器(因为代理类是直接生成字节码的,所以需要类加载器将字节码加载进内存才行,在这里我们直接获取代理类实现的接口的类加载器即可)、代理类需要实现的接口、一个InvocationHandler对象。
JdkProxyInterface jdkProxy = (JdkProxyInterface) Proxy.newProxyInstance(JdkProxyInterface.class.getClassLoader(),
new Class[]{JdkProxyInterface.class}, new InvocationHandler() {
// proxy为代理对象,method为执行的方法,args为传入的参数
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("前置增强");
// 通过反射执行方法,方法传入被代理的对象(待增强的对象)
method.invoke(jdkTarget, args);
System.out.println("后置增强");
return null;
}
});
// 代理对象执行方法
jdkProxy.sayHello();
}
}

执行结果如下:

1
2
3
前置增加
hello
后置增加

这样我们就看到了,比起原先直接执行jdkTarget.sayHello(),只输入“hello”,我们通过代理目标对象,并执行代理对象的方法来对目标对象的方法进行了增强。

接下来,我们来具体深究一下这个代理是这么实现的。

由于这个代理类是直接生成字节码的,所以我们看不到源码,在这里,我们就自己来是实现一个代理来间接了解jdk的代理是如何实现的。

我们直接定义一个代理类,命名为$Proxy0,jdk内部也是类似的名字,所以我们也取这个名字。因为jdk动态代理是基于接口的,所以这个代理类还是实现对应的接口。

1
2
3
4
5
6
7
8
9
public class $Proxy0 implements JdkProxyInterface {
@Override
public String sayHello() {
System.out.println("前置增强");
System.out.println("hello");
System.out.println("后置增强");
return null;
}
}

可以看出,这就是一个最简单的代理,我们代理的对象是jdkTarget,我们对其的方法进行了增强,但是这都已经把增强的逻辑都写死了,真正的代理是可以更具我们自己写的增强逻辑来执行增强的。而这其中的关键就在于我们传的InvocationHandler对象,这个InvocationHandler是一个接口,我们自己也来创建一个接口来模仿,在我们之前传入对象的时候是用匿名内部类的方式来传的,所以我们知道这个接口中还有个invoke方法。

1
2
3
public interface MyInvocationHandler {
Object invoke();
}

然后是在创建代理对象的时候传的InvocationHandler对象,所以我们给我们自己的代理类加一个构造方法,来接受我们自己的MyInvocationHandler对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class $Proxy0 implements JdkProxyInterface {
private MyInvocationHandler myInvocationHandler;

public $Proxy0(MyInvocationHandler myInvocationHandler) {
this.myInvocationHandler = myInvocationHandler;
}

@Override
public String sayHello() {
System.out.println("前置增强");
System.out.println("hello");
System.out.println("后置增强");
return null;
}
}

之后我们创建代理对象就变成这样了

1
2
3
4
5
6
$Proxy0 proxy0 = new $Proxy0(new MyInvocationHandler() {
@Override
public Object invoke() {
return null;
}
});

怎么样,是不是和jdk的很类似了。

那么我们这么根据自己写啥它就增强啥呢

这里我们就稍微改改逻辑就可以实现了,直接看代码吧

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
public class test {
// 测试
public static void main(String[] args) {
$Proxy0 proxy0 = new $Proxy0(new MyInvocationHandler() {
@Override
public Object invoke() {
System.out.println("前置增强");
System.out.println("hello");
System.out.println("后置增强");
return null;
}
});
proxy0.sayHello();
}
}

public class $Proxy0 implements JdkProxyInterface {
private MyInvocationHandler myInvocationHandler;

public $Proxy0(MyInvocationHandler myInvocationHandler) {
this.myInvocationHandler = myInvocationHandler;
}

@Override
public String sayHello() {
this.myInvocationHandler.invoke();
return null;
}
}

我们直接让代理执行方法直接执行所传过来的myInvocationHandler的invoke方法即可,然后在invoke方法中编写我们自己的代理增强逻辑,这样不就实现了我写啥,它就增强啥嘛。

有些同学可能对这块的逻辑转不过来,在这里解释一些,那就是在执行构造方法的时候,我们传了一个匿名内部类的对象,而这个匿名内部类是实现了MyInvocationHandler的(不懂的可以补补基础),然后我们就重写了其中的invoke方法,然后在$Proxy0这个类中的myInvocationHandler对象的值就为传过来的匿名子类对象,当我们调用invoke方法的时候,就会调用子类重写的方法,而子类重写的方法逻辑就是我们在传对象的时候写的哪个invoke逻辑。

但是还有问题,那就是对于原先的逻辑我们也是写死了,没有根据传哪个目标对象就增强哪个目标对象的哪个方法,在这里我们就可以仿照jdk的InvocationHandler的invoke方法来写就行,将invoke改写成下面这样

1
2
3
public interface MyInvocationHandler {
Object invoke(Object proxy, Method method, Object[] args);
}

我们的代理类就改写成这样

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
public class $Proxy0 implements JdkProxyInterface {
private MyInvocationHandler myInvocationHandler;
// 为了防止每次都用方法时都通过反射获取方法,我们直接将方法变为静态变量,然后用静态代码块进行初始化
private static Method method0;
// ... 如果接口还有更多方法就跟这个一样来设置method1、method2、method3.....
static {
try {
method0 = JdkProxyInterface.class.getMethod("sayHello");
} catch (NoSuchMethodException e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
}

}

public $Proxy0(MyInvocationHandler myInvocationHandler) {
this.myInvocationHandler = myInvocationHandler;
}

@Override
public String sayHello() {
Object result = this.myInvocationHandler.invoke(this, method0, null);
return (String) result;
}
// ...其他方法
}

然后proxy0就设置成这样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$Proxy0 proxy0 = new $Proxy0(new MyInvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
System.out.println("前置增强");
try {
// 通过反射执行方法,可以指定不同的对象来执行方法
String result = (String) method.invoke(new JdkTarget(), args);
System.out.println("后置增强");
return result;
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
}
}
});
proxy0.sayHello();

这样就跟jdk动态代理差不多了吧,其实jdk动态代理生成的字节码反编译后得到的代理类和我这个差不多。这样应该对jdk动态代理有了一个比较清晰的认识。

这里再提一嘴,jdk动态代理在反射调用方法达到16次后,之后调用方法不再是用反射调用方法,而是直接调用方法,这样能进行些优化,这和之后说的cglib不同

cglib动态代理

老样子,先来看看基本的使用

1
2
3
4
5
6
7
8
9
public class CglibTarget {
public void sayHello() {
System.out.println("hello");
}

public void sayWorld(int msg) {
System.out.println(msg + "world");
}
}

然后创建代理对象,对方法进行增强

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class test {
public static void main(String[] args) {
// 目标对象
CglibTarget cglibTarget = new CglibTarget();
// 代理对象,这里需要强制转换为父类,不然之后无法调用方法
// 参数分别是要继承的父类、实现的接口、和一个MethodInterceptor,这个MethodInterceptor和我们之前讲的jdk的那个是一样的
CglibTarget cglibProxy = (CglibTarget) Enhancer.create(CglibTarget.class, new Class[0], new MethodInterceptor() {
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("前置增强");
// 通过反射调用方法
method.invoke(cglibTarget, args));
System.out.println("后置增强");
return null;
}
});
cglibProxy.sayWorld(666);
}

由于代理类是直接生成字节码的,所以我们拿不到源码,所以还是自己来实现一个cglib代理来理解

由于cglib是基于继承来实现代理,即需要有父子关系,代理类是目标类的一个子类,所以创建出来的代理类需要继承目标类,所以,如果目标类如果加了final关键字,那么就不能为其创建代理,因为被final修饰的类不能有子类,如果是在类的方法上加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
public class $Proxy1 extends CglibTarget {

private MethodInterceptor methodInterceptor;

private static Method method0;
private static Method method1;

static {
try {
method0 = CglibTarget.class.getMethod("sayHello");
method1 = CglibTarget.class.getMethod("sayWorld", int.class);
} catch (NoSuchMethodException e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
}
}

public $Proxy1(MethodInterceptor methodInterceptor) {
this.methodInterceptor = methodInterceptor;
}

// 以下是带增强的方法
@Override
public void sayHello() {
try {
methodInterceptor.intercept(this, method0, new Object[0], null);
} catch (Throwable e) {
throw new RuntimeException(e.getMessage());
}
}

@Override
public void sayWorld(int msg) {
try {
methodInterceptor.intercept(this, method1, new Object[]{msg}, null);
} catch (Throwable e) {
throw new RuntimeException(e.getMessage());
}
}
}

然后测试一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void main(String[] args) {
CglibTarget cglibTarget = new CglibTarget();
$Proxy1 $Proxy1 = new $Proxy1(new MethodInterceptor() {
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("前置增强");
method.invoke(cglibTarget, args);
System.out.println("后置增强");
return null;
}
});
$Proxy1.sayHello();
}

测试结果:

1
2
3
前置增强
hello
后置增强

可以注意到,cglib的intercept传入的参数还有一个MethodProxy,这个是能让我们每次调用方法不是用反射进行调用,而是直接调用方法。methodProxy执行方法如下:

1
2
methodProxy.invoke(cglibTarget, args);	// 传入目标对象,结合目标对象用,不执行反射
methodProxy.invokeSuper(proxy, args); // 传入代理类对象,结合代理对象用,不执行反射

接下来我们来改造一下代理类,为其增加带原始功能的方法。

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
public class $Proxy1 extends CglibTarget {

private MethodInterceptor methodInterceptor;

private static Method method0;
private static Method method1;
public static MethodProxy methodProxy0;
public static MethodProxy methodProxy1;

static {
try {
method0 = CglibTarget.class.getMethod("sayHello");
method1 = CglibTarget.class.getMethod("sayWorld", int.class);
// 参数分别为目标类对象、代理类对象、参数类型和返回值类型、增强的方法、带原始功能的方法
methodProxy0 = MethodProxy.create(CglibTarget.class, $Proxy1.class, "()V", "sayHello", "sayHelloSuper");
methodProxy1 = MethodProxy.create(CglibTarget.class, $Proxy1.class, "(I)V", "sayWorld", "sayWorldSuper");
} catch (NoSuchMethodException e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
}
}

public $Proxy1(MethodInterceptor methodInterceptor) {
this.methodInterceptor = methodInterceptor;
}


// 带原始功能的方法
public void sayHelloSuper() {
super.sayHello();
}
public void sayWorldSuper(int msg) {
super.sayWorld(666);
}


// 以下是带增强的方法
@Override
public void sayHello() {
try {
methodInterceptor.intercept(this, method0, new Object[0], methodProxy0);
} catch (Throwable e) {
throw new RuntimeException(e.getMessage());
}
}

@Override
public void sayWorld(int msg) {
try {
methodInterceptor.intercept(this, method1, new Object[]{msg}, methodProxy1);
} catch (Throwable e) {
throw new RuntimeException(e.getMessage());
}
}
}

methodProxy在执行方法时不会使用反射,其内部创建类代理类来避免反射,一个代理类配合invoke使用,一个代理类配合invokeSuper使用,在invoke和invokeSuper生成的代理类是FastClass的子类

1
2
3
4
5
6
public abstract class FastClass {
// ...
public abstract Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException;
public abstract int getIndex(Signature var1);
// ...
}

接着我们来简单实现一下这个代理类,我们只实现FastClass中比较关键的两个方法。

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
// 当MethodProxy执行create时,就会生成这个类
public class TargetFastClass {
// 由于创建methodProxy时我们会传入方法的相关信息,在这时,它就会给每个方法进行编号
static Signature signature0 = new Signature("sayHello", "()V");
static Signature signature1 = new Signature("sayWorld", "(I)V");
/**
* sayHello 0
* sayWorld 1
* 获取方法的编号,
* 通过signature,即方法签名来获得方法,
* signature中就是包括了方法名字、参数返回值之类的信息
* 每个方法的整数编号在创建signature0和signature1说就会规定好
* @param signature
* @return
*/
public int getIndex(Signature signature) {
if (signature0.equals(signature)) {
return 0;
} else if (signature1.equals(signature)) {
return 1;
}
// 没找到方法
return -1;
}
// 根据getIndex返回的方法编号,正常调用目标对象的方法
public Object invoke(int index, Object target, Object[] args) {
if (index == 0) {
((CglibTarget) target).sayHello();
} else if (index == 1) {
((CglibTarget) target).sayWorld((int) args[0]);
} else {
throw new RuntimeException("无此方法");
}
return null;
}


}

当我们创建MethodProxy对象是就会生成这个类,这个类是执行invoke方法时的代理类,我们创建MethodProxy对象时会传入一些参数,比如方法名,返回值和参数类型,之后这个类会根据那些参数来生成Signature,即方法签名,并且给这些方法签名编号,比如0、1、2、3。方法签名生成完后,当我们取执行methodProxy.invoke(…)时,就会用自己的那些参数(方法名、返回值和参数类型)生成一个前面Signature,然后传入getIndex方法中,getIndex方法根据你传的方法签名在类中找看是否有相应的签名,如果有,就放回改签名对象的方法编号,然后再调用本类中的invoke,这个invoke会拿到从getIndex传来的方法编号,目标对象和参数则是我们之前执行methodProxy.invoke(…)的时候传的,然后它就会根据你的方法编号找到对应的方法,再通过目标对象直接执行方法,这样就避免了反射。

接下来,来模拟一下这个methodProxy.invoke(…)的过程

1
2
3
4
5
6
7
8
public static void main(String[] args) {
CglibTarget cglibTarget = new CglibTarget();
// 当MethodProxy执行create时,就会生成这个类
TargetFastClass targetFastClass = new TargetFastClass();4
// methodProxy.invoke(...)调用的是哪个方法,就会用哪个方法的信息来构造Signature来传入getIndex来得到编号,然后之后会根据编号在invoke来调用对应的方法
int index = targetFastClass.getIndex(new Signature("sayHello", "()V"));
targetFastClass.invoke(index, cglibTarget, new Object[0]);
}

运行结果:

1
hello

还有一个代理类是来配合invokeSuper的,直接看吧

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
// 当MethodProxy执行create时,就会生成这个类
public class ProxyFastClass {

// 由于创建methodProxy时我们会传入方法的相关信息,在这时,它就会给每个方法进行编号
static Signature signature0 = new Signature("sayHelloSuper", "()V");
static Signature signature1 = new Signature("sayWorldSuper", "(I)V");
/**
* sayHelloSuper 1
* sayWorldSuper 2
* 获取方法的编号,
* 通过signature,即方法签名来获得方法,
* signature中就是包括了方法名字、参数返回值之类的信息
* @param signature
* @return
*/
public int getIndex(Signature signature) {
if (signature0.equals(signature)) {
return 0;
} else if (signature1.equals(signature)) {
return 1;
}
// 没找到方法
return -1;
}

// 根据getIndex返回的方法编号,正常调用目标对象的方法
public Object invoke(int index, Object proxy, Object[] args) {
if (index == 0) {
(($Proxy1) proxy).sayHelloSuper();
} else if (index == 1) {
(($Proxy1) proxy).sayWorldSuper((int) args[0]);
} else {
throw new RuntimeException("无此方法");
}
return null;
}
}

这里我们创建方法签名的方法应该是那些只带有原始功能的方法,即sayHelloSuper和sayWorldSuper,因为我们只是为了执行方法时避免反射来执行,所以没必要为重写的增强方法来创建方法签名(Signature),而且也不能这样做,因为如果我们为增强方法创建Signature,那之后调用methodProxy.invokeSuper(proxy, args)的时候会最终调用到ProxyFastClass的invoke,而这个invoke又会回去调用增强方法,就会造成死循环。

这里也简单测试一下

1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) {
$Proxy1 $Proxy1 = new $Proxy1(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
return null;
}
});
ProxyFastClass proxyFastClass = new ProxyFastClass();
int index = proxyFastClass.getIndex(new Signature("sayWorldSuper", "(I)V"));
proxyFastClass.invoke(index, $Proxy1, new Object[]{666});
}

执行结果:

1
666world

这就是cglib的避免反射调用,和jdk相比,cglib一上来就可以避免反射调用,不想jdk那样还需要多执行几次反射调用后才会避免反射调用,jdk避免反射调用原理和cglib的差不多。

jdk的避免反射调用是一个方法对应一个代理,cglib是一个代理类会对应两个FastClass,即两个代理类,一个是对应配合着代理对象使用,一个配合着目标对象使用,而且一个FastClass能对应多个方法,所以cglib产生的代理类会比jdk少。

OK,over!!