JDK和Cglib动态代理的区别

1 为什么需要代理?

​ 简单来说,就是为了保证单一性原则问题,也就是每个类的功能尽可能单一,这样才能是该类被改动的可能性最小。比如,如果当我们需要为类里加上权限,日志等功能时,如果每个类都需要这种功能,那就要修改每个类,这样工作量很大,所以我们需要一个代理,让我们可以在不改动原有代码的前提下,实现一些其他功能,即增强。我们在进入目标类之前,先进入代理类,在代理类中写我们需要的额外功能,这样原有类不动,不影响原有功能。

​ 下面就是一个关于静态代理的实例,能帮助理解代理的意义

1
2
3
4
5
6
7
8
/**
* 共同接口
*/
public interface ProgrammerInterface {
void goToWork();

void goOffWork();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 程序员类
*/
public class Programmer implements ProgrammerInterface{
@Override
public void goToWork() {
System.out.print("开始上班了\n");
}

@Override
public void goOffWork() {
System.out.print("终于下班了\n");
}
}
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
/**
* 程序员代理类
*/
public class ProgrammerProxy implements ProgrammerInterface {
//目标类(程序员类)
Programmer programmer;


//构造方法,初始化目标类
public ProgrammerProxy(Programmer programmer) {
this.programmer = programmer;
}


@Override
public void goToWork() {
System.out.print("进入了上班代理\n");
programmer.goToWork();
System.out.print("上班时间为:" + new Date() + "\n\n");
}


@Override
public void goOffWork() {
System.out.print("进入了下班代理\n");
programmer.goOffWork();
System.out.print("下班时间为:" + new Date() + "\n");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ProxyDemoApplication {

public static void main(String[] args) {
// Programmer programmer = new Programmer();
// programmer.goToWork();
// programmer.goOffWork();


Programmer programmer = new Programmer();
ProgrammerProxy programmerProxy = new ProgrammerProxy(programmer);
programmerProxy.goToWork();
programmerProxy.goOffWork();
}
}

运行结果

1
2
3
4
5
6
7
进入了上班代理
开始上班了
上班时间为:Sun Feb 20 16:51:09 CST 2022

进入了下班代理
终于下班了
下班时间为:Sun Feb 20 16:51:09 CST 2022

​ 但静态代理有两个问题

  • 如果其他类需要实现同样的功能,则需要在写一个代理类,这样会很麻烦。
  • 当我们需要新增功能时,需要在接口,类,代理类上都要新增,项目规模较大时不好维护

​ 所以,就出现了动态代理,下面来聊聊Java中动态代理的几种方式和区别。

2 JDK动态代理

​ JDK动态代理主要是通过,反射包中的Porxy类InvokationHandler接口。它们结合在一起后可以创建动态代理类。Porxy类基于传递的参数创建动态代理类InvokationHandler则用于激发动态代理类的方法。这个过程是在程序执行过程中动态生成与处理的,所以叫动态代理。

​ 下面来看看使用示例

实现InvokationHandler接口

​ JDK 动态代理类必须实现反射包中的 java.lang.reflect.InvocationHandler 接口,在此接口中只有一个 invoker 方法:

image-20220220173542450

​ 在InvocationHandler#invoker中必须调用目标类被代理的方法,否则无法做到代理的实现。下面为实现 InvocationHandler 的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class TargetInvoker implements InvocationHandler {
// 代理中持有的目标类
private Object target;

public TargetInvoker(Object target) {
this.target = target;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("jdk 代理执行前");
Object result = method.invoke(target, args);
System.out.println("jdk 代理执行后");
return result;
}
}

​ 在实现InvocationHandler#invoker时,该方法里有三个参数:

  • proxy 代理目标对象的代理对象,它是真实的代理对象。
  • method 执行目标类的方法
  • args 执行目标类的方法的参数

创建JDK动态代理类

​ 创建 JDK 动态代理类实例同样也是使用反射包中的 java.lang.reflect.Proxy 类进行创建。通过调用Proxy#newProxyInstance静态方法进行创建。

1
2
3
4
5
6
7
8
9
10
11
public class DynamicProxyAnimal {

public static Object getProxy(Object target) throws Exception {
Object proxy = Proxy.newProxyInstance(
target.getClass().getClassLoader(), // 指定目标类的类加载
target.getClass().getInterfaces(), // 代理需要实现的接口,可指定多个,这是一个数组
new TargetInvoker(target) // 代理对象处理器
);
return proxy;
}
}

Proxy#newProxyInstance中的三个参数(ClassLoader loader、Class<?>[] interfaces、InvocationHandler h):

  • loader 加载代理对象的类加载器
  • interfaces 代理对象实现的接口,与目标对象实现同样的接口
  • h 处理代理对象逻辑的处理器,即上面的 InvocationHandler 实现类。

​ 最后实现执行 DynamicProxyAnimal 动态代理:

image-20220220173811698

3 Cglib动态代理

​ CGLIB 动态代理的实现机制是生成目标类的子类,通过调用父类(目标类)的方法实现,在调用父类方法时再代理中进行增强。

实现 MethodInterceptor 接口

​ 相比于 JDK 动态代理的实现,CGLIB 动态代理不需要实现与目标类一样的接口,而是通过方法拦截的方式实现代理,代码实现如下,首先方法拦截接口 net.sf.cglib.proxy.MethodInterceptor

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

@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("CGLIB 调用前");
Object result = proxy.invokeSuper(obj, args);
System.out.println("CGLIB 调用后");
return result;
}
}

通过方法拦截接口调用目标类的方法,然后在该被拦截的方法进行增强处理,实现方法拦截器接口的 intercept 方法里面有四个参数:

  • obj 代理类对象
  • method 当前被代理拦截的方法
  • args 拦截方法的参数
  • proxy 代理类对应目标类的代理方法

创建Cglib动态代理类

​ 创建 CGLIB 动态代理类使用 net.sf.cglib.proxy.Enhancer 类进行创建,它是 CGLIB 动态代理中的核心类,首先创建个简单的代理类:

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

public static Object getProxy(Class<?> clazz){
Enhancer enhancer = new Enhancer();
// 设置类加载
enhancer.setClassLoader(clazz.getClassLoader());
// 设置被代理类
enhancer.setSuperclass(clazz);
// 设置方法拦截器
enhancer.setCallback(new TargetInterceptor());
// 创建代理类
return enhancer.create();
}
}

​ 设置被代理类的信息和代理类拦截的方法的回调执行逻辑,就可以实现一个代理类。 实现 CGLIB 动态代理调用:

1
2
3
4
5
6
7
public class Main {
@Test
public void dynamicProxy() throws Exception {
Animal cat = (Animal) CglibProxy.getProxy(Cat.class);
cat.call();
}
}

image-20220220175349478

4 总结

​ 两种机制的不同实现:

  • JDK 动态代理是通过实现目标类的接口,然后将目标类在构造动态代理时作为参数传入,使代理对象持有目标对象,再通过代理对象的 InvocationHandler 实现动态代理的操作。
  • CGLIB 动态代理是通过配置目标类信息,然后利用 ASM 字节码框架进行生成目标类的子类。当调用代理方法时,通过拦截方法的方式实现代理的操作

​ 从而,我们知道:

  1. JDK动态代理只能对实现了接口的类生成代理,而不能针对类
  2. CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法

​ 后续再出一篇关于源码阅读的文章详解,这两种方式是如何实现代理的。

参考

JDK 动态代理与 CGLIB 有哪些区别? - 掘金 (juejin.cn)

你必须会的 JDK 动态代理和 CGLIB 动态代理 - 知乎 (zhihu.com)


JDK和Cglib动态代理的区别
https://2w1nd.github.io/2022/02/20/框架/Spring/JDK和Cglib动态代理的区别/
作者
w1nd
发布于
2022年2月20日
许可协议