动态代理的原理及其应用

深碍√TFBOYSˉ_ 2022-03-16 14:56 312阅读 0赞

动态代理的介绍

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

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

与静态代理的比较

静态代理

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

  • 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. * @author pjmike
  5. * @create 2018-08-04 17:42
  6. */
  7. public class DynamicProxy {
  8. public static void main(String[] args) {
  9. IHelloImpl hello = new IHelloImpl();
  10. MyInvocationHandler handler = new MyInvocationHandler(hello);
  11. //获取目标用户的代理对象
  12. IHello proxyHello = (IHello) Proxy.newProxyInstance(IHelloImpl.class.getClassLoader(), IHelloImpl.class.getInterfaces(), handler);
  13. //调用代理方法
  14. proxyHello.sayHello();
  15. }
  16. }
  17. /**
  18. * 被访问者接口
  19. */
  20. interface IHello{
  21. void sayHello();
  22. }
  23. /**
  24. * 被访问者的具体实现类
  25. */
  26. class IHelloImpl implements IHello {
  27. @Override
  28. public void sayHello() {
  29. System.out.println("Hello World");
  30. }
  31. }
  32. class MyInvocationHandler implements InvocationHandler {
  33. private Object target;
  34. /**
  35. *
  36. * @param target 被代理的目标对象
  37. */
  38. public MyInvocationHandler(Object target) {
  39. this.target = target;
  40. }
  41. /**
  42. * 执行目标对象的方法
  43. *
  44. * @param proxy 代理对象
  45. * @param method 代理方法
  46. * @param args 方法参数
  47. * @return
  48. * @throws Throwable
  49. */
  50. @Override
  51. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  52. System.out.println("invoke method");
  53. System.out.println("Method name : "+method.getName());
  54. Object result = method.invoke(target, args);
  55. return result;
  56. }
  57. }
  58. 复制代码

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

  1. public static Object newProxyInstance(ClassLoader loader,
  2. Class<?>[] interfaces,
  3. InvocationHandler h)
  4. 复制代码

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

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

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

利用CGLIB实现动态代理

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

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

CGLIB动态代理的简单例子

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

添加CGLIB依赖

  1. <dependency>
  2. <groupId>cglib</groupId>
  3. <artifactId>cglib</artifactId>
  4. <version>3.2.4</version>
  5. /dependency>
  6. 复制代码
  7. package com.pjmike.proxy;
  8. import net.sf.cglib.proxy.Enhancer;
  9. import net.sf.cglib.proxy.MethodInterceptor;
  10. import net.sf.cglib.proxy.MethodProxy;
  11. import java.lang.reflect.Method;
  12. /**
  13. * CGLIB动态代理实现
  14. *
  15. * @author pjmike
  16. * @create 2018-08-06 16:55
  17. */
  18. public class CglibProxy {
  19. public static void main(String[] args) {
  20. //Enhancer是CGLIB的核心工具类,是一个字节码增强器,它可以方便的对类进行扩展
  21. Enhancer enhancer = new Enhancer();
  22. enhancer.setSuperclass(PersonService.class);
  23. //设置回调所需的拦截器
  24. enhancer.setCallback(new MyMethodInterceptor());
  25. //通过enhancer.create()方法获取代理对象
  26. //对代理对象所有非final的方法调用都会转发给MethodInterceptor.intercept方法,
  27. //作用跟JDK动态代理的InvocationHandler类似
  28. PersonService personService = (PersonService) enhancer.create();
  29. System.out.println(personService.sayHello("pjmike"));
  30. }
  31. }
  32. class PersonService {
  33. public String sayHello(String name) {
  34. return "Hello, " + name;
  35. }
  36. }
  37. class MyMethodInterceptor implements MethodInterceptor {
  38. @Override
  39. public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
  40. return methodProxy.invokeSuper(obj, args);
  41. }
  42. }
  43. 复制代码

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

javassist

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

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

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

简单使用

添加依赖

  1. <dependency>
  2. <groupId>org.javassist</groupId>
  3. <artifactId>javassist</artifactId>
  4. <version>3.21.0-GA</version>
  5. </dependency>
  6. 复制代码

代码

  1. package com.pjmike.proxy;
  2. import javassist.*;
  3. /**
  4. * javassist字节码
  5. *
  6. * @author pjmike
  7. * @create 2018-08-07 0:07
  8. */
  9. public class JavassistByteCode {
  10. public static void main(String[] args) throws IllegalAccessException, CannotCompileException, InstantiationException, NotFoundException {
  11. ByteCodeAPI byteCodeApi = createJavassistBycodeDynamicProxy();
  12. System.out.println(byteCodeApi.sayHello());
  13. }
  14. public static ByteCodeAPI createJavassistBycodeDynamicProxy() throws CannotCompileException, IllegalAccessException, InstantiationException, NotFoundException {
  15. //获取运行时类的上下文
  16. ClassPool pool = ClassPool.getDefault();
  17. //动态创建类
  18. CtClass cc = pool.makeClass(ByteCodeAPI.class.getName()+"demo");
  19. cc.addInterface(pool.get(ByteCodeAPI.class.getName()));
  20. //创建属性
  21. CtField field = CtField.make("private String a;", cc);
  22. cc.addField(field);
  23. //创建方法
  24. CtMethod method = CtMethod.make("public String sayHello() {return \"hello\";}", cc);
  25. cc.addMethod(method);
  26. //添加构造器
  27. cc.addConstructor(CtNewConstructor.defaultConstructor(cc));
  28. Class<?> pc = cc.toClass();
  29. ByteCodeAPI byteCodeApi = (ByteCodeAPI) pc.newInstance();
  30. return byteCodeApi;
  31. }
  32. }
  33. interface ByteCodeAPI {
  34. public String sayHello();
  35. }
  36. //结果:输出 hello
  37. 复制代码

动态代理的实际应用

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

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

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

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

应用二: RPC框架中的应用

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

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

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

小结

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

参考资料

  • JDK dynamic proxy
  • Java编程思想
  • CGLIB动态代理介绍
  • Javassist Tutorial-1

发表评论

表情:
评论列表 (有 0 条评论,312人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Java动态代理原理应用示例

    Java动态代理是一种在运行时生成对象的机制,它主要用于实现接口代理、行为拦截等功能。以下是动态代理的原理和一个简单的示例。 原理: 1. 使用`Proxy`类创建一个代理对

    相关 Java动态代理原理应用实例

    Java动态代理是Java语言特性的一种体现,它能够不修改原有代码的情况下,增加新的功能。其原理主要基于Java的反射机制和代理模式。 具体步骤如下: 1. 通过反射创建目

    相关 动态代理原理及其应用

    动态代理的介绍 动态代理是一种在运行时动态地创建代理对象,动态地处理代理方法调用的机制。 实际上它是一种代理机制。代理可以看做是对调用目标的一个封装,直接通过代理来实现