动态代理的意义在于生成一个代理对象,来代理真实对象,从而控制对真实对象的访问。

为了理解代理模式,我们假设一个场景,软件公司。里面包括客户,产品经理和软件工程师。客户需要什么产品并不会直接找软件工程师谈,而是会去找产品经理,客户会与产品经理谈论软件的价格、交付、进度时间节点等,此时产品经理就是代理对象,软件工程师就是真实对象。

代理对象的作用就是:在真实对象访问之前或之后加入对应的逻辑,或者根据其他的规则控制是否使用真实对象。

我们需要在调用这个对象之前产生一个代理对象,而这个代理对象需要和真实对象建立代理关系,所以代理分为两个步骤:

  • 建立代理对象和真实对象的关系
  • 实现代理对象的代理逻辑方法

Java 中由多种动态代理技术,比如 JDK、CGLIB、Javassist、ASM,其中最常用的动态代理技术有两种:

  • JDK 动态代理,这是 JDK 自带的功能
  • 另一种是 CGLIB,这是第三方提供的技术

一、JDK 动态代理

JDK 动态代理必须借助一个接口才能产生代理对象,所以我们先定义接口:

1
2
3
4
5
6
7
public interface HelloWorld {

void sayHelloWorld(String str);

void sayHelloWorld();

}

然后提供接口的实现类:

1
2
3
4
5
6
7
8
9
10
11
public class HelloWorldImpl implements HelloWorld {

public void sayHelloWorld(String str) {
System.out.println("hello world "+str);
}

public void sayHelloWorld() {
System.out.println("hello world");
}

}

在 JDK 动态代理中,要实现代理逻辑类必须要去实现 java.lang.reflect.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
25
26
27
28
29
30
31
32
33
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class JdkProxyExample implements InvocationHandler {

private Object target = null;

/**
* 建立真实对象与代理对象的对应关系,并返回代理对象
* @param target 真实对象
* @return 代理对象
*/
public Object bind(Object target) {
this.target = target;
System.out.println("根据代理对象返回真实对象");
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}

/**
* @param proxy 代理对象
* @param method 当前调度方法
* @param args 当前方法参数
* @return 代理结果返回
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("进入代理逻辑方法");
System.out.println("调度真实对象之前的服务");
Object obj = method.invoke(target, args);
System.out.println("调度真实对象之后的服务");
return obj;
}
}

第一步,建立代理对象和真实对象的关系

这是使用 bind 方法实现的,该方法返回代理对象,newProxyInstance() 参数如下:

  • 第一个是类加载器,采用 target 本身的类加载器
  • 把生成的动态代理对象下挂到那些接口下,如上,下挂在 HelloWorld 接口下,代理对象就可以这样声明:HelloWorld proxy = ......
  • 第三个是定义实现方法逻辑的代理类,它必须实现 InvocationHandler 接口的 invoke 方法,它就是代理逻辑方法的

第二步,实现代理逻辑方法

invoke 方法可以实现代理逻辑,当我们使用了代理对象调度方法之后,程序就会进入到 invoke 方法里,invoke 方法参数含义如下:

  • proxy,代理对象,bind 方法生成的对象
  • method,当前调度方法
  • args,调度方法的参数
1
Object obj = method.invoke(target, args);

这行代码通过反射实现调度真实对象的方法。

下面是测试类:

1
2
3
4
5
6
7
public class JdkProxyTest {
public static void main(String[] args) {
JdkProxyExample jdkProxyExample = new JdkProxyExample();
HelloWorld helloworld = (HelloWorld) jdkProxyExample.bind(new HelloWorldImpl());
helloworld.sayHelloWorld("HanMeimei");
}
}

运行结果如下:

根据代理对象返回真实对象
进入代理逻辑方法
调度真实对象之前的服务
hello world HanMeimei
调度真实对象之后的服务

二、CGLIB 动态代理

CGLIB(Code Generation Library)是一个强大的、高性能的用于生成字节码并操作字节码的类库。它广泛用于实现类似动态代理的功能。

它可以在运行期扩展 Java 类与实现 Java 接口。Hibernate 用它来实现 PO 对象字节码的动态生成,Spring AOP 用它提供方法的拦截功能。

CGLIB 主要通过生成目标类的子类来实现动态代理,这与 Java 标准库中的 JDK 动态代理使用接口的方式不同。CGLIB 可以代理那些没有实现接口的类。

1、CGLIB 原理

动态生成一个要代理类的子类,子类重写要代理的类的所有不是 final 的方法。在子类中采用方法拦截拦截所有弗雷方法的调用,顺势织入横切逻辑。它比使用 Java 反射的 JDK 动态代理模块要快。

使用字节码处理框架 ASM 来转换字节码并生成新的类。

2、CGLIB 组成结构

图片加载失败
  • 最底层的是字节码字节码,字节码是 Java 为了保证“一次编译、到处运行”而产生的一种虚拟指令格式。
  • 位于字节码之上的是 ASM,这是一种直接操作字节码的框架。其使用类似 SAX 的解析器来实现高性能。
  • 位于 ASM 之上的是 CGLIB、Groovy、BeanShell,后两者并不是 Java 体系中的内容,而是脚本语言,他们通过 ASM 框架生成字节码变相执行了 Java 代码,这说明在 JVM 中执行程序并不一定非要写 Java 代码,只要能生成 Java 字节码,JVM 并不关心字节码的来源。当然,通过 Java 代码生成的 JVM 字节码是通过编译器直接生成的,算是“最正统”的 JVM 字节码
  • 再向上,就是与应用程序紧密相关的框架了,比如 Spring AOP

CGLIB 创建动态代理类的过程

  1. 查找目标类上定义的所有非 final 的 public 类型的方法
  2. 将符合条件的方法定义转换为字节码
  3. 将组成的字节码转换成相应的代理的 class 对象
  4. 实现 MethodInterceptor 接口,用来处理对代理类上所有方法的请求

三、JDK 动态代理和 CGLIB 动态代理对比

1、JDK 动态代理优缺点

1.1 优点

JDK 动态代理是 JDK 原生的,不需要任何依赖即可使用。

通过反射机制生成代理类的速度比 CGLIB 操作字节码生成代理类的速度更快。

1.2 缺点

如果要使用 JDK 动态代理,被代理的类必须实现了接口,否则无法代理。

JDK 动态代理无法为没有在接口中定义的方法实现代理,假设有一个实现了接口的类,为它的一个不属于接口中的方法配置了切面,Spring 仍然会使用 JDK 动态代理,但是由于配置了切面的方法不属于接口,为这个方法配置的切面将不会被织入。

JDK 动态代理执行代理方法时,需要通过反射机制进行回调,此时方法执行的效率比较低。

2、CGLIB 实现动态代理的优缺点

2.1 优点

无需实现接口,达到代理类的无侵入。

2.2 缺点

相关链接

[[代理模式]]

OB tags