动态代理的介绍
动态代理是一种在运行时动态地创建代理对象,动态地处理代理方法调用的机制。
实际上它是一种代理机制。代理可以看做是对调用目标的一个封装,直接通过代理来实现对目标代码的调用
与静态代理的比较
静态代理
提前写好代理类,每个业务类都要对应一个代理类,不灵活

- 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
|
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;
public MyInvocationHandler(Object target) { this.target = target; }
@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;
public class CglibProxy { public static void main(String[] args) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(PersonService.class); enhancer.setCallback(new MyMethodInterceptor()); 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.*;
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(); }
|
动态代理的实际应用
动态代理实际上有很多应用,比如spring aop的实现,rpc框架的实现,一些第三方工具库的内部使用等等。这里简单介绍动态代理在spring aop和RPC框架中的应用
应用一: Spring AOP的动态代理实现
Spring AOP的动态代理实现主要有两种方式,JDK动态代理和CGLIB字节码生成。
默认情况下,如果Spring AOP发现目标对象后实现了相应的interface,则采用JDK动态代理机制为其生成代理对象。如果没有发现接口,则采用CGLIB的方式为目标对象生成动态的代理对象实例
应用二: RPC框架中的应用
RPC即远程过程调用,它的实现中使用到了动态代理,关于RPC的具体原理参照你应该知道的RPC原理等文章
rpc
实际上RPC框架要解决的一个问题就是: 如何调用他人的远程服务?像调用本地服务一样调用远程服务。
如何封装数据,通过网络传输给远程服务,使远程接口透明,这就需要动态代理的帮助了。所以透明化远程服务调用就是要利用动态代理,在代理层对数据进行封装,网络传输等操作。
小结
实际开发中,我们很多时候都是利用现成的框架和开源库,其中就包含动态代理的应用。只有真正了解了动态代理的知识,才能更好地理解其在框架中的设计,也有利于更好的去使用框架。
参考资料