JDK和CGLIB动态代理

Java中动态代理代理包括JDK动态代理和CGLIB动态代理。前者通过实现目标类的接口,和反射技术实现。后者不需要实现接口,而是通过继承目标类,并通过方法拦截技术实现。Spring的AOP技术则是集成了这两种动态代理。本文对两种代理的使用方法做一个简要介绍。


代理模式

首先介绍下设计模式中的代理模式。代理模式是结构型模式的一种,要求代理对象实现被代理对象的接口,并持有被代理对象实例。外界只需要访问代理对象即可实现需要的功能。这种模式不会改变被代理对象的代码,但是又使其功能得到增强。

代理模式又分为两种,静态代理和动态代理。前者是在代码运行前就已存在代理类的字节码文件,相对扩展性较差。后者则是在运行时生成代理类。JDK中动态代理就是代理模式思想的体现。

image-20230207163919735

JDK动态代理

特点

被代理对象要有实现接口,否则不能被代理。

生成的代理对象默认继承了Proxy

相关类

由于动态代理是基于反射,因此动态代理集成在java.lang.reflect包下。包括两个类:InvocationHandlerProxy。前者提供了一个invoke方法,在这个方法里调用被代理对象的方法,并实现功能增强。后者提供了一个newProxyInstance静态方法,通过传入前者的实现,可以生成一个代理对象。

步骤

被代理类和接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 共同接口
*/
public interface Star {

void play();
}

/**
* 歌手类
*/
public class Singer implements Star {

public String name;

public Singer(String name) {
this.name = name;
}

@Override
public void play() {
System.out.println(name + " sing a new song!");
}
}

重写invoke方法

实现 InvocationHandler 接口,需要引入被代理对象实例,在 invoke() 方法中调用需要代理的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class MyInvocationHandler implements InvocationHandler {

// 被代理的对象
private Object object;

public MyInvocationHandler(Object object) {
this.object = object;
}

/**
* 代理方法,在这里实现代理工作
* @param proxy 被代理的对象,不用管
* @param method 被代理的方法
* @param args 被代理方法的参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("proxy start");
// 调用被代理对象的方法,返回方法执行结果
Object ret = method.invoke(this.object, args);
System.out.println("proxy end");
return ret;
}
}

创建代理对象

通过Proxy.newProxyInstance()的方法,来创建代理对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ProxyTest {
public static void main(String[] args) {
// 创建被代理对象
Singer singer = new Singer("Jay chou");
// 通过被代理对象,创建调用处理器
InvocationHandler singerHandler = new MyInvocationHandler(singer);
// 通过类加载器、被代理对象的实现接口、处理器 来创建代理对象
Object instance = Proxy.newProxyInstance(Singer.class.getClassLoader(), new Class[]{Star.class}, singerHandler);
// 代理对象类型强转
Star star1 = (Star)instance;
// 调用父类接口的方法,会转到MyInvocationHandler#invoke方法
star1.play();
}
}

输出结果

可以看到,在play()的执行前后,执行了代理对象的逻辑

1
2
3
proxy start
Jay chou sing a new song!
proxy end

源码分析

newProxyInstance方法源码

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
/*
newProxyInstance底层是通过反射方式创建的代理对象
*/
public class Proxy{

@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);

final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}

// 1. 通过类加载器和被代理方法的接口,创建代理类的Class对象
Class<?> cl = getProxyClass0(loader, intfs);

try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}

// 2. 获取代理类的构造方法,构造参数是{ InvocationHandler.class }
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}

// 3. 传入handler参数,创建代理对象
return cons.newInstance(new Object[]{h});

} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
}

CGLIB动态代理

特点

CGLIB通过动态生成被代理类的子类,重写父类的所有非final方法,在子类中通过方法拦截技术拦截所有对父类方法的调用。与JDK动态代理相比,CGLIB速度更快,并且不要求被代理对象有实现接口。

相关类

net.sf.cglib.proxy包下,包括MethodInterceptor接口和Enhancer类。前者提供intercept方法实现对父类方法的拦截,后者通过create方法创建代理对象。

步骤

依赖导入

1
2
3
4
5
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>

被代理类

不要求有实现接口

1
2
3
4
5
6
public class Actress {

public void play() {
System.out.println("actor a queen!");
}
}

重写intercept方法

cglib动态代理是基于方法拦截实现的,需要实现MethodInterceptor接口,重写intercept方法,在这里实现代理工作。

类似于InvocationHandler,区别在于,这里不需要引入被代理对象,由对父类的方法拦截完成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class MyMethodInterceptor implements MethodInterceptor {

@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("method invoke before...");
Object result = methodProxy.invokeSuper(obj, args);
System.out.println("method invoke after...");
return result;
}
}

创建代理对象

创建代理对象并调用需要代理的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import net.sf.cglib.proxy.Enhancer;

public class CglibProxyTest {
public static void main(String[] args) {
// 创建enhancer对象
Enhancer enhancer = new Enhancer();
// 设置父类
enhancer.setSuperclass(Actress.class);
// 设置回调,即前面配的方法拦截器
enhancer.setCallback(new MyMethodInterceptor());
// 通过enhancer创建代理对象
Actress actress = (Actress)enhancer.create();
// 通过代理对象执行目标方法
actress.play();
}
}

输出结果

1
2
3
method invoke before...
actor a queen!
method invoke after...

参考资料

  1. 面试必问系列之JDK动态代理
  2. CGLib动态代理
文章作者: SongGT
文章链接: http://www.songguangtao.xyz/2022/08/28/27.动态代理演示/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 SongGuangtao's Blog
大哥大嫂[微信打赏]
过年好[支付宝打赏]