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

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

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

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

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

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