动态代理的原理及其应用

Catalogue
  1. 1. 动态代理的介绍
  2. 2. 与静态代理的比较
    1. 2.1. 静态代理
  3. 3. 动态代理的实现方式
    1. 3.1. JDK动态代理
      1. 3.1.1. 反射机制
      2. 3.1.2. JDK动态代理的实现
    2. 3.2. 利用CGLIB实现动态代理
      1. 3.2.1. CGLIB动态代理的简单例子
      2. 3.2.2. javassist
        1. 3.2.2.1. 简单使用
  4. 4. 动态代理的实际应用
    1. 4.1. 应用一: Spring AOP的动态代理实现
    2. 4.2. 应用二: RPC框架中的应用
  5. 5. 小结
  6. 6. 参考资料

动态代理的介绍

动态代理是一种在运行时动态地创建代理对象,动态地处理代理方法调用的机制。

实际上它是一种代理机制。代理可以看做是对调用目标的一个封装,直接通过代理来实现对目标代码的调用

与静态代理的比较

静态代理

提前写好代理类,每个业务类都要对应一个代理类,不灵活

  • ISubject,该接口是被访问者或者被访问的对象
  • SubjectImpl,被访问者的具体实现类
  • SubjectProxy,被访问者或被访问资源的代理实现类
  • Client:代表访问者的抽象角色,client将会访问Isubject类型的对象或者资源,通过代理类进行访问。
  • 这里的SubjectImpl和SubjectProxy都实现了ISubject的接口,SubjectProxy是将请求转发给SubjectImpl,其内部有SubjectImpl的对象,并且可以添加一些限制

以上的图解本质上就是代理模式的一个讲解,适用于静态代理和动态代理,区别在于代理对象和代理方法生成的时间和方式不同

动态代理是运行时自动生成代理对象,一个缺点是生成代理对象和调用代理方法需要耗费时间

动态代理的实现方式

动态代理主要有两种实现方式,一种是JDK动态代理,一种是CGLIB字节码机制,当然还有Javassist或ASM库,这两个在CGLIB那块一并介绍

JDK动态代理

说到JDK动态代理,就不得不提起反射机制,JDK动态代理就是通过反射机制实现的。

反射机制

反射就是通过Class类和java.lang.reflect类库在运行时获取某个类的信息。比如通过java.lang.reflect类库中Field,Method以及Constructor类就可以获取类的相关信息

JDK动态代理的实现

下面是通过反射机制实现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
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
57
58
59
60
61
62
63
/**
* 动态代理的实现
*
* @author pjmike
* @create 2018-08-04 17:42
*/
public class DynamicProxy {
public static void main(String[] args) {
IHelloImpl hello = new IHelloImpl();
MyInvocationHandler handler = new MyInvocationHandler(hello);
//获取目标用户的代理对象
IHello proxyHello = (IHello) Proxy.newProxyInstance(IHelloImpl.class.getClassLoader(), IHelloImpl.class.getInterfaces(), handler);
//调用代理方法
proxyHello.sayHello();
}
}

/**
* 被访问者接口
*/
interface IHello{
void sayHello();
}

/**
* 被访问者的具体实现类
*/
class IHelloImpl implements IHello {

@Override
public void sayHello() {
System.out.println("Hello World");
}
}

class MyInvocationHandler implements InvocationHandler {
private Object target;

/**
*
* @param target 被代理的目标对象
*/
public MyInvocationHandler(Object target) {
this.target = target;
}

/**
* 执行目标对象的方法
*
* @param proxy 代理对象
* @param method 代理方法
* @param args 方法参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("invoke method");
System.out.println("Method name : "+method.getName());
Object result = method.invoke(target, args);
return result;
}
}

从上面的例子中可以看出,代理对象的生成是通过Proxy.newProxyInstance()来完成的

1
2
3
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)

newProxyInstance()方法主要以下三个参数

  • 类加载器(ClassLoader)用来加载动态代理类
  • 一个要实现接口的数组,从这点就可以看出,要想使用JDK动态代理,必须要有接口类
  • InvocactionHandler接口的一个实现

proxy

动态代理可以将所有调用重定向到调用处理器,因此通常上会向调用处理器的构造器传递一个”实际”对象的引用,从而使得处理器在执行任务时,可以请求转发。

利用CGLIB实现动态代理

cglib是一种基于ASM的字节码生成库,用于生成和转换Java字节码.

而ASM是一个轻量但高性能的字节码操作框架。cglib是基于ASM的上层应用,对于代理没有实现接口的类,cglib非常实用。

cglib

CGLIB动态代理的简单例子

本质上说,对于需要被代理的类,它只是动态生成一个子类以覆盖非final的方法,同时绑定钩子回调自定义的拦截器。

添加CGLIB依赖

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

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
package com.pjmike.proxy;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
* CGLIB动态代理实现
*
* @author pjmike
* @create 2018-08-06 16:55
*/
public class CglibProxy {
public static void main(String[] args) {
//Enhancer是CGLIB的核心工具类,是一个字节码增强器,它可以方便的对类进行扩展
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(PersonService.class);
//设置回调所需的拦截器
enhancer.setCallback(new MyMethodInterceptor());
//通过enhancer.create()方法获取代理对象
//对代理对象所有非final的方法调用都会转发给MethodInterceptor.intercept方法,
//作用跟JDK动态代理的InvocationHandler类似
PersonService personService = (PersonService) enhancer.create();
System.out.println(personService.sayHello("pjmike"));
}
}

class PersonService {
public String sayHello(String name) {
return "Hello, " + name;
}
}

class MyMethodInterceptor implements MethodInterceptor {

@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
return methodProxy.invokeSuper(obj, args);
}
}

至于上面提到的javassist也是需要直接操作字节码,跟ASM类似,所以这两者使用门槛比较高,一般用于框架的底层实现。比如hibernate底层使用了javassist和cglib.

javassist

javassist是一个运行时编译库,可以动态的生成或修改类的字节码。它有两种实现动态代理的方案: javassist提供的动态代理接口和javassist字节码。

因为javassist提供动态代理接口比较慢,所以这里主要分析javassist字节码,先不考虑其动态代理接口。至于这几种代理方案的性能比较问题,参考动态代理方案性能对比

javassist主要由CtClass,CtMethod,CtField几个类组成,与JDK反射中的Class,Method,Field相似。

简单使用

添加依赖

1
2
3
4
5
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.21.0-GA</version>
</dependency>

代码

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
package com.pjmike.proxy;

import javassist.*;

/**
* javassist字节码
*
* @author pjmike
* @create 2018-08-07 0:07
*/
public class JavassistByteCode {
public static void main(String[] args) throws IllegalAccessException, CannotCompileException, InstantiationException, NotFoundException {
ByteCodeAPI byteCodeApi = createJavassistBycodeDynamicProxy();
System.out.println(byteCodeApi.sayHello());
}
public static ByteCodeAPI createJavassistBycodeDynamicProxy() throws CannotCompileException, IllegalAccessException, InstantiationException, NotFoundException {
//获取运行时类的上下文
ClassPool pool = ClassPool.getDefault();
//动态创建类
CtClass cc = pool.makeClass(ByteCodeAPI.class.getName()+"demo");
cc.addInterface(pool.get(ByteCodeAPI.class.getName()));
//创建属性
CtField field = CtField.make("private String a;", cc);
cc.addField(field);
//创建方法
CtMethod method = CtMethod.make("public String sayHello() {return \"hello\";}", cc);
cc.addMethod(method);
//添加构造器
cc.addConstructor(CtNewConstructor.defaultConstructor(cc));
Class<?> pc = cc.toClass();
ByteCodeAPI byteCodeApi = (ByteCodeAPI) pc.newInstance();
return byteCodeApi;
}
}
interface ByteCodeAPI {
public String sayHello();
}

//结果:输出 hello

动态代理的实际应用

动态代理实际上有很多应用,比如spring aop的实现,rpc框架的实现,一些第三方工具库的内部使用等等。这里简单介绍动态代理在spring aop和RPC框架中的应用

应用一: Spring AOP的动态代理实现

Spring AOP的动态代理实现主要有两种方式,JDK动态代理和CGLIB字节码生成。

默认情况下,如果Spring AOP发现目标对象后实现了相应的interface,则采用JDK动态代理机制为其生成代理对象。如果没有发现接口,则采用CGLIB的方式为目标对象生成动态的代理对象实例

应用二: RPC框架中的应用

RPC即远程过程调用,它的实现中使用到了动态代理,关于RPC的具体原理参照你应该知道的RPC原理等文章

rpc

实际上RPC框架要解决的一个问题就是: 如何调用他人的远程服务?像调用本地服务一样调用远程服务。

如何封装数据,通过网络传输给远程服务,使远程接口透明,这就需要动态代理的帮助了。所以透明化远程服务调用就是要利用动态代理,在代理层对数据进行封装,网络传输等操作。

小结

实际开发中,我们很多时候都是利用现成的框架和开源库,其中就包含动态代理的应用。只有真正了解了动态代理的知识,才能更好地理解其在框架中的设计,也有利于更好的去使用框架。

参考资料

Bagikan Komentar