0

0

Java 动态代理示例代码深入理解

黄舟

黄舟

发布时间:2017-03-16 10:10:58

|

1344人浏览过

|

来源于php中文网

原创

这篇文章主要介绍了java 动态代理深入理解的相关资料,需要的朋友可以参考下

要想了解Java动态代理,首先要了解什么叫做代理,熟悉设计模式的朋友一定知道在Gof总结的23种设计模式中,有一种叫做代理(Proxy)的对象结构型模式,动态代理中的代理,指的就是这种设计模式。

在我看来所谓的代理模式,和23种设计模式中的“装饰模式”是一个东西。23种设计模式中将它们作为两种模式,网上也有些文章讲这两种模式的异同,从细节来看,确实可以人为地区分这两种模式,但是抽象到一定高度后,我认为这两种模式是完全一样的。因此学会了代理模式,也就同时掌握了装饰模式。

代理模式

代理模式简单来说,就是对一个对象进行包装,包装后生成的对象具有和原对象一样的方法列表,但是每个方法都可以是被包装过的。

立即学习Java免费学习笔记(深入)”;

静态代理

让我们先来看一段代码:


package common;

public class Test {
  static interface Subject{
    void sayHi();
    void sayHello();
  }

  static class SubjectImpl implements Subject{

    @Override
    public void sayHi() {
      System.out.println("hi");
    }

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

  static class SubjectImplProxy implements Subject{
    private Subject target;

    public SubjectImplProxy(Subject target) {
      this.target=target;
    }

    @Override
    public void sayHi() {
      System.out.print("say:");
      target.sayHi();
    }

    @Override
    public void sayHello() {
      System.out.print("say:");
      target.sayHello();
    }
  }

  public static void main(String[] args) {
    Subject subject=new SubjectImpl();
    Subject subjectProxy=new SubjectImplProxy(subject);
    subjectProxy.sayHi();
    subjectProxy.sayHello();
  }
}

这段代码中首先定义了一个Subject接口,接口中有两个方法。

然后定义了SubjectImpl类实现Subject接口并实现其中的两个方法,到这里肯定是没问题的。

现在再定义一个SubjuectImplProxy类,也实现Subject接口。这个SubjectImplProxy类的作用是包装SubjectImpl类的实例,它的内部定义一个变量target来保存一个SubjectImpl的实例。SubjectImplProxy也实现了接口规定的两个方法,并且在它的实现版本中,都调用了SubjectImpl的实现,但是又添加了自己的处理逻辑。

相信这段代码不难理解,它通过对SubjectImpl进行包装,达到了给输出内容添加前缀的功能。这种代理方式叫做静态代理。

动态代理

从上面的演示中我们不难看出静态代理的缺点:我们对SubjectImpl的两个方法,是进行的相同的包装,但是却要在SubjectImplProxy里把相同的包装逻辑写两次,而且以后如果Subject接口再添加新的方法,SubjectImplProxy也必须要添加新的实现,尽管SubjectImplProxy对所有方法的包装可能都是一样的。

下面我把上面例子的静态代理改成动态代理,我们来看一下区别:


package common;

import java.lang.invoke.MethodHandle;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class Test {
  static interface Subject{
    void sayHi();
    void sayHello();
  }

  static class SubjectImpl implements Subject{

    @Override
    public void sayHi() {
      System.out.println("hi");
    }

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

  static class ProxyInvocationHandler implements InvocationHandler{
    private Subject target;
    public ProxyInvocationHandler(Subject target) {
      this.target=target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      System.out.print("say:");
      return method.invoke(target, args);
    }

  }

  public static void main(String[] args) {
    Subject subject=new SubjectImpl();
    Subject subjectProxy=(Subject) Proxy.newProxyInstance(subject.getClass().getClassLoader(), 
    subject.getClass().getInterfaces(), new ProxyInvocationHandler(subject));
    subjectProxy.sayHi();
    subjectProxy.sayHello();

  }
}

只看main方法的话,只有第二行和之前的静态代理不同,同样是生成一个subjectProxy代理对象,只是生成的代码不同了。静态代理是直接new 一个SubjectImplProxy的实例,而动态代理则调用了java.lang.reflect.Proxy.newProxyInstance()方法,我们来看一下这个方法的源码:

Mfkiqpl旅行社旅游线路预订程序
Mfkiqpl旅行社旅游线路预订程序

升级报告:增加动态新闻功能后台添加,删除,编辑,支持UBB代码,支持上传片及文件。 增加我要入团功能散客可以自由选择加入贵社最近要出发的团队。 增加线路置顶功能置顶后的线路永远显示在最前面。 增加同行报价功能管理员在后台添加同行用户,同行用户登录后可查看贵社线路对同行的报价。同行报价在添加线路中一并添加。(感谢网友拽哥提出修改意见) 增加更多线路显示的分页功能方便大型旅行社由于线路过多而引起的部分

下载

 


  public static Object newProxyInstance(ClassLoader loader,
                     Class[] interfaces,
                     InvocationHandler h)
    throws IllegalArgumentException
  {
    if (h == null) {
      throw new NullPointerException();
    }

    /*
     * Look up or generate the designated proxy class.
     */
    Class cl = getProxyClass(loader, interfaces);  //获取代理类的Class

    /*
     * Invoke its constructor with the designated invocation handler.
     */
    try {
      Constructor cons = cl.getConstructor(constructorParams);  
      //constructorParams是写死的:{ InvocationHandler.class },上边返回的代理类Class一定是extends Proxy的,而Proxy有一个参数为InvocationHandler的构造函数
      return cons.newInstance(new Object[] { h });  
      //这里通过构造函数将我们自己定义的InvocationHandler的子类传到代理类的实例里,当我们调用代理类的任何方法时,
      实际上都会调用我们定义的InvocationHandler子类重写的invoke()函数
    } catch (NoSuchMethodException e) {
      throw new InternalError(e.toString());
    } catch (IllegalAccessException e) {
      throw new InternalError(e.toString());
    } catch (InstantiationException e) {
      throw new InternalError(e.toString());
    } catch (InvocationTargetException e) {
      throw new InternalError(e.toString());
    }
  }

上面的 Class> cl = getProxyClass(loader, interfaces);  调用的getProxyClass方法:


public static Class getProxyClass(ClassLoader loader,
                     Class... interfaces)
    throws IllegalArgumentException
  {
    if (interfaces.length > 65535) {  //因为在class文件中,一个类保存的接口数量是用2个字节来表示的,因此java中一个类最多可以实现65535个接口
      throw new IllegalArgumentException("interface limit exceeded");
    }

    Class proxyClass = null;

    /* collect interface names to use as key for proxy class cache */
    String[] interfaceNames = new String[interfaces.length];

    // for detecting duplicates
    Set> interfaceSet = new HashSet<>();
     //验证interfaces里的接口是否能被类加载器加载,是否是接口,是否有重复的 
    for (int i = 0; i < interfaces.length; i++) {
      /*
       * Verify that the class loader resolves the name of this
       * interface to the same Class object.
       */
      String interfaceName = interfaces[i].getName();
      Class interfaceClass = null;
      try {
        interfaceClass = Class.forName(interfaceName, false, loader);
      } catch (ClassNotFoundException e) {
      }
      if (interfaceClass != interfaces[i]) {
        throw new IllegalArgumentException(
          interfaces[i] + " is not visible from class loader");
      }

      /*
       * Verify that the Class object actually represents an
       * interface.
       */
      if (!interfaceClass.isInterface()) {
        throw new IllegalArgumentException(
          interfaceClass.getName() + " is not an interface");
      }

      /*
       * Verify that this interface is not a duplicate.
       */
      if (interfaceSet.contains(interfaceClass)) {
        throw new IllegalArgumentException(
          "repeated interface: " + interfaceClass.getName());
      }
      interfaceSet.add(interfaceClass);

      interfaceNames[i] = interfaceName;
    }

    /*
     * Using string representations of the proxy interfaces as
     * keys in the proxy class cache (instead of their Class
     * objects) is sufficient because we require the proxy
     * interfaces to be resolvable by name through the supplied
     * class loader, and it has the advantage that using a string
     * representation of a class makes for an implicit weak
     * reference to the class.
     */
    List key = Arrays.asList(interfaceNames);  //使用interfaces列表作为key缓存在cache里,也就是实现了相同interfaces的代理类只会创建加载一次

    /*
     * Find or create the proxy class cache for the class loader.
     */
    Map, Object> cache;
    synchronized (loaderToCache) {
      cache = loaderToCache.get(loader);
      if (cache == null) {
        cache = new HashMap<>();
        loaderToCache.put(loader, cache);
      }
      /*
       * This mapping will remain valid for the duration of this
       * method, without further synchronization, because the mapping
       * will only be removed if the class loader becomes unreachable.
       */
    }

    /*
     * Look up the list of interfaces in the proxy class cache using
     * the key. This lookup will result in one of three possible
     * kinds of values:
     *   null, if there is currently no proxy class for the list of
     *     interfaces in the class loader,
     *   the pendingGenerationMarker object, if a proxy class for the
     *     list of interfaces is currently being generated,
     *   or a weak reference to a Class object, if a proxy class for
     *     the list of interfaces has already been generated.
     */
     //看看缓存里有没有,如果有就直接取出来然后return,否则判断根据pendingGenerationMarker判断是否有其它线程正在生成当前的代理类,
     如果有则cache.wait()等待,如果没有则创建。
    synchronized (cache) {
      /*
       * Note that we need not worry about reaping the cache for
       * entries with cleared weak references because if a proxy class
       * has been garbage collected, its class loader will have been
       * garbage collected as well, so the entire cache will be reaped
       * from the loaderToCache map.
       */
      do {
        Object value = cache.get(key);
        if (value instanceof Reference) {
          proxyClass = (Class) ((Reference) value).get();
        }
        if (proxyClass != null) {
          // proxy class already generated: return it
          return proxyClass;
        } else if (value == pendingGenerationMarker) {
          // proxy class being generated: wait for it
          try {
            cache.wait();
          } catch (InterruptedException e) {
            /*
             * The class generation that we are waiting for should
             * take a small, bounded time, so we can safely ignore
             * thread interrupts here.
             */
          }
          continue;
        } else {
          /*
           * No proxy class for this list of interfaces has been
           * generated or is being generated, so we will go and
           * generate it now. Mark it as pending generation.
           */
          cache.put(key, pendingGenerationMarker);
          break;
        }
      } while (true);
    }
     //确认要生成的代理类所属的包,如果interfaces里所有接口都是public的,代理类所属包就是默认包;
     如果有interface不是public,那么所有不是public的interface必须在一个包里否则报错。
    try {
      String proxyPkg = null;   // package to define proxy class in

      /*
       * Record the package of a non-public proxy interface so that the
       * proxy class will be defined in the same package. Verify that
       * all non-public proxy interfaces are in the same package.
       */
      for (int i = 0; i < interfaces.length; i++) {
        int flags = interfaces[i].getModifiers();
        if (!Modifier.isPublic(flags)) {
          String name = interfaces[i].getName();
          int n = name.lastIndexOf('.');
          String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
          if (proxyPkg == null) {
            proxyPkg = pkg;
          } else if (!pkg.equals(proxyPkg)) {
            throw new IllegalArgumentException(
              "non-public interfaces from different packages");
          }
        }
      }

      if (proxyPkg == null) {   // if no non-public proxy interfaces,
        proxyPkg = "";     // use the unnamed package
      }

      {
        /*
         * Choose a name for the proxy class to generate.
         */
        long num;
        synchronized (nextUniqueNumberLock) {
          num = nextUniqueNumber++;
        }
        String proxyName = proxyPkg + proxyClassNamePrefix + num;  
        //生成代理类的名字,proxyPkg是上面确定下来的代理类所在的包名,proxyClassNamePrefix是写死的字符串“$Proxy”,
        num是一个全局唯一的long型数字,从0开始累积,每次生成新的代理类就+1,从这里也能看出生成的动态代理类的数量不能超过Long.maxValue
        /*
         * Verify that the class loader hasn't already
         * defined a class with the chosen name.
         */

        /*
         * Generate the specified proxy class.
         */
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
          proxyName, interfaces);  //生成一个以proxyName为类名的,实现了Interfaces里所有接口的类的字节码
        try {
          proxyClass = defineClass0(loader, proxyName,
            proxyClassFile, 0, proxyClassFile.length);  //加载生成的类
        } catch (ClassFormatError e) {
          /*
           * A ClassFormatError here means that (barring bugs in the
           * proxy class generation code) there was some other
           * invalid aspect of the arguments supplied to the proxy
           * class creation (such as virtual machine limitations
           * exceeded).
           */
          throw new IllegalArgumentException(e.toString());
        }
      }
      // add to set of all generated proxy classes, for isProxyClass
      proxyClasses.put(proxyClass, null);

    } finally {
      /*
       * We must clean up the "pending generation" state of the proxy
       * class cache entry somehow. If a proxy class was successfully
       * generated, store it in the cache (with a weak reference);
       * otherwise, remove the reserved entry. In all cases, notify
       * all waiters on reserved entries in this cache.
       */
       
       //创建成功,则将cache中该key的pendingGenerationMarker替换为实际的代理类的弱引用,否则也要清除pendingGenerationMarker标记;
       不管是否成功,都要执行cache.notifyAll(),让其它要创建相同代理类并且执行了cache.wait()的线程恢复执行。
      synchronized (cache) {
        if (proxyClass != null) {
          cache.put(key, new WeakReference>(proxyClass));
        } else {
          cache.remove(key);
        }
        cache.notifyAll();
      }
    }
    return proxyClass; //最后返回代理类Class
  }

到这里,我们已经把动态代理的java源代码都解析完了,现在思路就很清晰了:

Proxy.newProxyInstance(ClassLoader loader,Class>[] interfaces,InvocationHandler h) 方法简单来说执行了以下操作:

1.生成一个实现了参数interfaces里所有接口且继承了Proxy的代理类的字节码,然后用参数里的classLoader加载这个代理类。

2.使用代理类父类的构造函数 Proxy(InvocationHandler h)来创造一个代理类的实例,将我们自定义的InvocationHandler的子类传入。

3.返回这个代理类实例,因为我们构造的代理类实现了interfaces(也就是我们程序中传入的subject.getClass().getInterfaces())里的所有接口,因此返回的代理类可以强转成Subject类型来调用接口中定义的方法。

现在我们知道了用Proxy.newProxyInstance()返回的subjectProxy可以成功强转成Subject类型来调用接口中定义的方法了,那么在调用方法后,代理类实例怎么进行处理的呢,这就需要看一下代理类的源码了。但是代理类是程序动态生成字节码加载的,怎么看源码呢?没关系,可以在main方法中加入System.getProperties().put(“sun.misc.ProxyGenerator.saveGeneratedFiles”,”true”),这样就会把生成的代理类Class文件保存在本地磁盘上,然后再反编译可以得到代理类的源码:


package common;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy
 implements Test.Subject
{
 private static Method m4;
 private static Method m1;
 private static Method m3;
 private static Method m0;
 private static Method m2;

 static
 {
   try {
     m4 = Class.forName("Test$Subject").getMethod("sayHello", new Class[0]);
     m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
     m3 = Class.forName("Test$Subject").getMethod("sayHi", new Class[0]);
     m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
     m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
  } catch (Exception e) {
    throw new RuntimeException(e);
  }
 }

 public $Proxy0(InvocationHandler paramInvocationHandler)
 {
  super(paramInvocationHandler);
 }

 public final void sayHello()
 {
  try
  {
   this.h.invoke(this, m4, null);
   return;
  }
  catch (RuntimeException localRuntimeException)
  {
   throw localRuntimeException;
  }
  catch (Throwable localThrowable)
  {
    throw new UndeclaredThrowableException(localThrowable);
  }
 }

 public final boolean equals(Object paramObject)
 {
  try
  {
   return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
  }
  catch (RuntimeException localRuntimeException)
  {
   throw localRuntimeException;
  }
  catch (Throwable localThrowable)
  {
    throw new UndeclaredThrowableException(localThrowable);
  }
 }

 public final void sayHi()
 {
  try
  {
   this.h.invoke(this, m3, null);
   return;
  }
  catch (RuntimeException localRuntimeException)
  {
   throw localRuntimeException;
  }
  catch (Throwable localThrowable)
  {
    throw new UndeclaredThrowableException(localThrowable);
  }
 }

 public final int hashCode()
 {
  try
  {
   return ((Integer)this.h.invoke(this, m0, null)).intValue();
  }
  catch (RuntimeException localRuntimeException)
  {
   throw localRuntimeException;
  }
  catch (Throwable localThrowable)
  {
    throw new UndeclaredThrowableException(localThrowable);
  }
 }

 public final String toString()
 {
  try
  {
   return (String)this.h.invoke(this, m2, null);
  }
  catch (RuntimeException localRuntimeException)
  {
   throw localRuntimeException;
  }
  catch (Throwable localThrowable)
  {
    throw new UndeclaredThrowableException(localThrowable);
  }
 }
}

我们可以看到代理类内部实现比较简单,在调用每个代理类每个方法的时候,都用反射去调h的invoke方法(也就是我们自定义的InvocationHandler的子类中重写的invoke方法),用参数传递了代理类实例、接口方法、调用参数列表,这样我们在重写的invoke方法中就可以实现对所有方法的统一包装了。

总结

动态代理相对于静态代理在使用上的优点主要是能够对一个对象的所有方法进行统一包装,而且后期被代理的类添加方法的时候动态代理类不需要改动。

缺点是要求被代理的类必须实现了接口,因为动态代理类在实现的时候继承了Proxy类,java不支持多继承,因此动态代理类只能根据接口来定义方法。

最后动态代理之所以叫做动态代理是因为java在实现动态代理的时候,动态代理类是在运行时动态生成和加载的,相对的,静态代理类和其他普通类一下,在类加载阶段就加载了。

相关文章

java速学教程(入门到精通)
java速学教程(入门到精通)

java怎么学习?java怎么入门?java在哪学?java怎么学才快?不用担心,这里为大家提供了java速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
Java编译相关教程合集
Java编译相关教程合集

本专题整合了Java编译相关教程,阅读专题下面的文章了解更多详细内容。

7

2026.01.21

C++多线程相关合集
C++多线程相关合集

本专题整合了C++多线程相关教程,阅读专题下面的的文章了解更多详细内容。

2

2026.01.21

无人机驾驶证报考 uom民用无人机综合管理平台官网
无人机驾驶证报考 uom民用无人机综合管理平台官网

无人机驾驶证(CAAC执照)报考需年满16周岁,初中以上学历,身体健康(矫正视力1.0以上,无严重疾病),且无犯罪记录。个人需通过民航局授权的训练机构报名,经理论(法规、原理)、模拟飞行、实操(GPS/姿态模式)及地面站训练后考试合格,通常15-25天拿证。

13

2026.01.21

Python多线程合集
Python多线程合集

本专题整合了Python多线程相关教程,阅读专题下面的文章了解更多详细内容。

1

2026.01.21

java多线程相关教程合集
java多线程相关教程合集

本专题整合了java多线程相关教程,阅读专题下面的文章了解更多详细内容。

2

2026.01.21

windows激活码分享 windows一键激活教程指南
windows激活码分享 windows一键激活教程指南

Windows 10/11一键激活可以通过PowerShell脚本或KMS工具实现永久或长期激活。最推荐的简便方法是打开PowerShell(管理员),运行 irm https://get.activated.win | iex 脚本,按提示选择数字激活(选项1)。其他方法包括使用HEU KMS Activator工具进行智能激活。

2

2026.01.21

excel表格操作技巧大全 表格制作excel教程
excel表格操作技巧大全 表格制作excel教程

Excel表格操作的核心技巧在于 熟练使用快捷键、数据处理函数及视图工具,如Ctrl+C/V(复制粘贴)、Alt+=(自动求和)、条件格式、数据验证及数据透视表。掌握这些可大幅提升数据分析与办公效率,实现快速录入、查找、筛选和汇总。

6

2026.01.21

毒蘑菇显卡测试网站入口 毒蘑菇测试官网volumeshader_bm
毒蘑菇显卡测试网站入口 毒蘑菇测试官网volumeshader_bm

毒蘑菇VOLUMESHADER_BM测试网站网址为https://toolwa.com/vsbm/,该平台基于WebGL技术通过渲染高复杂度三维分形图形评估设备图形处理能力,用户可通过拖动彩色物体观察画面流畅度判断GPU与CPU协同性能;测试兼容多种设备,但中低端手机易卡顿或崩溃,高端机型可能因发热降频影响表现,桌面端需启用独立显卡并使用支持WebGL的主流浏览器以确保准确结果

10

2026.01.21

github中文官网入口 github中文版官网网页进入
github中文官网入口 github中文版官网网页进入

github中文官网入口https://docs.github.com/zh/get-started,GitHub 是一种基于云的平台,可在其中存储、共享并与他人一起编写代码。 通过将代码存储在GitHub 上的“存储库”中,你可以: “展示或共享”你的工作。 持续“跟踪和管理”对代码的更改。

7

2026.01.21

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Kotlin 教程
Kotlin 教程

共23课时 | 2.7万人学习

C# 教程
C# 教程

共94课时 | 7.2万人学习

Java 教程
Java 教程

共578课时 | 48.8万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号