Java中动态代理使用与原理详解

简介: Java中动态代理使用与原理详解

本篇博文介绍的是JDK的动态代理,Java中动态代理不仅仅是JDK的动态代理还有CGLIB代理。


(JDK)动态代理是指客户通过代理类来调用其它对象的方法,并且是在程序运行时根据需要动态创建目标类的代理对象。


即,不直接调用目标对象而是通过代理对象调用。代理对象不直接生成,而是程序运行时根据需要动态生成!


代理对象,不需要实现接口,但是目标对象要实现接口,否则不能用动态代理。代理对象的生成,是利用JDK 的API,动态的在内存中构建代理对象。 动态代理也叫做:JDK 代理、接口代理。

JDK 实现代理只需要使用newProxyInstance 方法,但是该方法需要接收三个参数,完整的写法是:static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h )


代理设计模式的原理:


使用一个代理将对象包装起来, 然后用该代理对象取代原始对象. 任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。

动态代理之所以被称为动态,是因为运行时才将它的类创建出来。代码开始执行时,还没有proxy类,它是根据需要从传入的接口集创建的。


【1】Proxy


专门完成代理的操作类,是所有动态代理类的父类。通过此类为一个或多个接口动态地生成实现类。提供了许多用于创建动态代理类和动态代理对象的静态方法。


① static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)


创建一个动态代理类所对应的Class对象。


② static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)


直接创建一个动态代理对象。


其他方法如下图:


【2】InvocationHandler


InvocationHandler 是一个接口,官方文档解释说,每个代理的实例都有一个与之关联的 InvocationHandler 实现类,如果代理的方法被调用,那么代理便会通知和转发给内部的 InvocationHandler 实现类的invoke方法,由它决定处理。


InvocationHandler接口源码如下:

 * //InvocationHandler是一个被代理实例的调用处理程序实现的接口。
 * // 每一个代理实例都有一个相关联的invocation handler
 * //当代理实例方法被调用时,代理会转发到相关联的invocation handler的invoke方法。
public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}


InvocationHandler 内部只有一个 invoke() 方法,当一个代理实例的方法被调用时,与代理实例相关联的invocationHhandler的invoke方法将会被调用。正是这个方法决定了怎么样处理代理传递过来的方法调用。


proxy 代理对象

method 代理对象调用的方法

args 调用的方法中的参数

因为,Proxy 动态产生的代理对象会调用 InvocationHandler 实现类,所以 InvocationHandler 是实际执行者。


故而,总的运行流程为:获取代理实例–>代理实例.方法 --> InvocationHandler.invoke() --> 接口真正实现类的方法。


InvocationHandler根本就不是proxy,它只是一个帮助proxy的类,proxy会被调用转发给它处理。Proxy本身是利用静态的Proxy.newProxyInstance()方法在运行时动态地创建的。



【3】动态代理步骤

① 父接口

父接口Human定义了两个方法:

interface Human {
  void info();
  void fly();
}



② 实现类SuperMan(被代理类)

// 被代理类
class SuperMan implements Human {
  public void info() {
    System.out.println("我是超人!我怕谁!");
  }
  public void fly() {
    System.out.println("I believe I can fly!");
  }
}



③ InvocationHandler实现类

也就是代理类的调用处理程序。

class MyInvocationHandler implements InvocationHandler {
  // 被代理类对象的声明
  Object obj;
  // 动态的创建一个代理类的对象
  public Object getProxyInstance()(Object obj){
    this.obj = obj;
    return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj
        .getClass().getInterfaces(), this);
  }
  @Override
  public Object invoke(Object proxy, Method method, Object[] args)
      throws Throwable {
    Object returnVal = method.invoke(obj, args);
    return returnVal;
  }
}



④ 测试方法

public static void main(String[] args) {
    //创建一个被代理类的对象
    SuperMan man = new SuperMan();
    //创建调用处理程序
    MyInvocationHandler handler = new MyInvocationHandler();
    //返回一个代理类的对象
    Object obj = handler.getProxyInstance()(man);
    System.out.println(obj.getClass());
    //代理实例 强转
    Human hu = (Human)obj;
    //通过代理类的对象调用重写的抽象方法
    hu.info();
    System.out.println();
    hu.fly();
  }

测试结果如下:

// 这里表明拿到的是代理对象
class com.web.test.$Proxy0
我是超人!我怕谁!
I believe I can fly!

【4】JDK动态代理原理

① 关键入口代码

Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj
.getClass().getInterfaces(), this);

根据类加载器,目标类的上行接口和InvocationHandler获取代理对象。这里有两个问题,第一如何获取?第二获取的代理对象是个什么样子?

先分析如何获取。


② 追踪源码到Proxy.newProxyInstance

     * //loader   定义代理类的类加载器
     * // interfaces  代理类需要实现的接口
     * // h -- 用来转发/反射目标方法的handler
     * //返回一个代理类的实例对象附带指定的代理类的invocation handler。
     * //该代理类被指定的类加载器定义且实现了指定的接口。
     */
    @CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException {
        Objects.requireNonNull(h)
         //...
         //寻找或者产生代理类的class
        Class<?> cl = getProxyClass0(loader, intfs);
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }
      // 拿到代理类的构造器--这一步很关键!
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            //如果非public,setAccessible(true);--反射中常见操作
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            // 通过构造器反射获取代理类实例对象(InvocationHandler作为参数)
            return cons.newInstance(new Object[]{h});
        }
        //...
    }

过程很清晰(参考代码注释),继续细究下去。


Class<?> cl = getProxyClass0(loader, intfs)做了什么?

源码如下:

//产生一个代理类 class。在调用该方法前必须调用checkProxyAccess 方法进行权限检查
 private static Class<?> getProxyClass0(ClassLoader loader,
                                        Class<?>... interfaces) {
     if (interfaces.length > 65535) {
         throw new IllegalArgumentException("interface limit exceeded");
     }
     //如果代理类已经存在,则直接返回缓存中的copy;
     //否则,通过ProxyClassFactory创建代理类
     return proxyClassCache.get(loader, interfaces);
 }

如果缓存中有,就直接返回proxy class;否则就要通过ProxyClassFactory获取。


这里的cache 是WeakCache,暂且不去理会,继续看ProxyClassFactory。


Proxy的静态内部类–Proxy$ProxyClassFactory源码如下:

// 通过给定的类加载器和接口,产生、定义并返回代理类。
private static final class ProxyClassFactory
    implements BiFunction<ClassLoader, Class<?>[], Class<?>>
{
    // prefix for all proxy class names--代理类前缀
    private static final String proxyClassNamePrefix = "$Proxy";
    //代理类编号
    private static final AtomicLong nextUniqueNumber = new AtomicLong();
    @Override
    public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
        Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);           
       //...省略代码
        //定义代理类所在的包
        String proxyPkg = null;    
        int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
记录非公共代理接口的包,以便在同一包中定义代理类。
验证所有非公共代理接口都在同一个包中。
        for (Class<?> intf : interfaces) {
            int flags = intf.getModifiers();
            // 如果修饰符非public,则变为final
            if (!Modifier.isPublic(flags)) {
                accessFlags = Modifier.FINAL;
                String name = intf.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, use com.sun.proxy package
            proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
        }
        long num = nextUniqueNumber.getAndIncrement();
        // 代理类的完整名字=包名+前缀+序号
        String proxyName = proxyPkg + proxyClassNamePrefix + num;
        /* Generate the specified proxy class.*/
        // 这里,产生指定的proxy class 字节数组!!!
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
            proxyName, interfaces, accessFlags);
        try {
        // 通过一个native方法获取proxy class
            return defineClass0(loader, proxyName,
                                proxyClassFile, 0, proxyClassFile.length);
        } 
        // ... 省略代码
    }
}

从ProxyClassFactory中下面的方法可以看到具体生成字节流的方法是ProxyGenerator.generateProxyClass(..).。最后通过native方法生成Class对象。同时对class对象的包名称有一些规定比如命名为com.web.test$proxy0。


获取代理类代码如下:

public c

要想得到字节码实例我们需要先下载这部分字节流,然后通过反编译得到java代码。


用ProxyGenerator.generateProxyClass(..)方法生成字节流,然后写进硬盘.假设我把proxyName定义为Human$ProxyCode。

public class TestProxyJava {
 public static void generateClassFile(Class clazz,String proxyName) throws Exception
    {
        //根据类信息和提供的代理类名称,生成字节码
        byte[] classFile = ProxyGenerator.generateProxyClass(proxyName,new Class[]{clazz});
        String paths = clazz.getResource(".").getPath();  
        System.out.println(paths);  
        FileOutputStream out = null;     
        //保留到硬盘中  
        out = new FileOutputStream(paths+proxyName+".class");    
        out.write(classFile);    
        out.flush(); 
    }
 public static void main(String[] args) {
   try {
    generateClassFile(Human.class,"Human$ProxyCode");
  } catch (Exception e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
  }
}
}

获取到的代理类源码如下:

public final class Human$ProxyCode extends Proxy implements Human{
  private static Method m1;
  private static Method m3;
  private static Method m4;
  private static Method m2;
  private static Method m0;
  public Human$ProxyCode(InvocationHandler paramInvocationHandler)
    throws 
  {
    super(paramInvocationHandler);
  }
  public final boolean equals(Object paramObject)
    throws 
  {
    try
    {
      return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
//final fly
  public final void fly()
    throws 
  {
    try
    {
      this.h.invoke(this, m3, null);
      return;
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  public final void info()
    throws 
  {
    try
    {
    /调用InvokeHandler的invoke方法,并将具体方法传了进去
      this.h.invoke(this, m4, null);
      return;
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  public final String toString()
    throws 
  {
    try
    {
      return (String)this.h.invoke(this, m2, null);
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  public final int hashCode()
    throws 
  {
    try
    {
      return ((Integer)this.h.invoke(this, m0, null)).intValue();
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  static
  {
    try
    {
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m3 = Class.forName("com.web.test.Human").getMethod("fly", new Class[0]);
      m4 = Class.forName("com.web.test.Human").getMethod("info", new Class[0]);
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException)
    {
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }
    catch (ClassNotFoundException localClassNotFoundException)
    {
      throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
    }
  }
}

可以发现这个类extends Proxy实现了我们需要代理的接口Human,且他的构造函数确实是需要传递一个InvocationHandler对象。


那么现在的情况就是我们的生成了一个代理类,这个代理类是我们需要代理的接口的实现类。我们的接口中定义的info和fly方法,在这个代理类中帮我们实现了,并且全部变成了final的。同时覆盖了一些Object类中的方法。


以info这个方法举例,方法中会调用InvocationHandler类中的invoke方法(也就是我们实现的逻辑的地方),同时把自己的Method对象,参数列表等传入进去。


再回头看上面的测试main方法:

public static void main(String[] args) {
  SuperMan man = new SuperMan();//创建一个被代理类的对象
  MyInvocationHandler handler = new MyInvocationHandler();
  Object obj = handler.bind(man);//返回一个代理类的对象
  System.out.println(obj.getClass());
  // 现在知道为什么可以强转了吧
  Human hu = (Human)obj;
  //通过代理类的对象调用重写的抽象方法
  hu.info();
  System.out.println();
  hu.fly();
}

【5】动态代理与AOP


在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。


AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。


主要功能:日志记录,性能统计,安全控制,事务处理,异常处理等等。


简而言之,在不改变原有代码基础上进行功能增强!


添加HumanUtil模拟增强方法:

class HumanUtil {
  public void method1() {
    System.out.println("=======方法一=======");
  }
  public void method2() {
    System.out.println("=======方法二=======");
  }
}

修改MyInvocationHandler如下:

class MyInvocationHandler implements InvocationHandler {
  // 被代理类对象的声明
  Object obj;
  // 动态的创建一个代理类的对象
  public Object bind(Object obj){
    this.obj = obj;
    return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj
        .getClass().getInterfaces(), this);
  }
  @Override
  public Object invoke(Object proxy, Method method, Object[] args)
      throws Throwable {
    HumanUtil h = new HumanUtil();
    h.method1();
    Object returnVal = method.invoke(obj, args);
    h.method2();
    return returnVal;
  }
}

再次测试如下:

class com.web.test.$Proxy0
=======方法一=======
我是超人!我怕谁!
=======方法二=======
=======方法一=======
I believe I can fly!
=======方法二=======

使用Proxy生成一个动态代理时,往往并不会凭空产生一个动态代理,这样没有太大的意义。通常都是为指定的目标对象生成动态代理。


这种动态代理在AOP中被称为AOP代理,AOP代理可代替目标对象,AOP代理包含了目标对象的全部方法。但AOP代理中的方法与目标对象的方法存在差异:AOP代理里的方法可以在执行目标方法之前、之后插入一些通用处理。


如下图所示:


【6】JDK动态代理总结


① 概率总结


① JDK动态代理只能代理有接口的类,并且只能代理接口方法,不能代理一般的类中自定义的方法


因为代理类是一个实现了被代理对象的上行接口的类,所以类必须有接口。而且不能代理被代理对象中自定义的方法!


② 提供了一个使用InvocationHandler作为参数的构造方法


在代理类中做一层包装,业务逻辑在invoke方法中实现。


③ 重写了Object类的equals、hashCode、toString


它们都只是简单的调用了InvocationHandler的invoke方法,即可以对其进行特殊的操作,也就是说JDK的动态代理还可以代理上述三个方法。


④ 在invoke方法中我们甚至可以不用Method.invoke方法调用实现类就返回


这种方式常常用在RPC框架中,在invoke方法中发起通信调用远端的接口等


② 动态代理在框架中的应用

① 在mybatis中的应用


如在MyBatis原理分析之获取SqlSession一文中获取executor 实例就应用了代理模式。在MyBatis原理分析之获取Mapper接口的代理对象也应用了java的动态代理思想。

③ 代理模式的几种变异体

① 远程代理

远程代理可以作为另一个JVM上对象的本地代表。调用代理的方法,会被代理利用网络转发到远程执行,并且结果会通过网络返回给代理,再由代理将结果转给客户。

②虚拟代理

虚拟代理作为创建开销大的对象的代表。虚拟代理经常直到我们真正需要一个对象的时候才创建它。当对象在创建前和创建中时,由虚拟代理来扮演对象的替身。对象创建后,代理就会将请求直接委托给对象。


③ 缓存代理

缓存代理会维护之前创建的对象,当收到请求时,在可能的情况下返回缓存的对象。它也允许许多个客户共享结果,以减少计算或网络延迟。


④ 保护代理

保护代理可以根据客户的角色来决定是否允许客户访问特定的方法。所以保护代理可能只提供给客户部分接口。保护代理通常是由Java的动态代理技术实现。

⑤ 同步代理

在多线程的情况下为主题提供安全的访问。

⑥ 复杂隐藏代理

用来隐藏一个类的复杂集合的复杂度,并进行访问控制。有时候也称为外观代理(Facade Proxy),这不难理解。复杂隐藏代理和外观模式是不一样的,因为代理控制访问,而外观模式只提供另一组接口。



【7】获取代理类class文件的简单方法


在【4】中通过ProxyGenerator.generateProxyClass(proxyName,new Class[]{clazz});获取class文件,比较繁琐,这里介绍一种简单方式。


关键代码如下:

生成$Proxy0的class文件
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

修改main方法如下:

public static void main(String[] args) {
    //生成$Proxy0的class文件
      System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
    SuperMan man = new SuperMan();//创建一个被代理类的对象
    MyInvocationHandler handler = new MyInvocationHandler();
    Object obj = handler.bind(man);//返回一个代理类的对象
    System.out.println(obj.getClass());
    Human hu = (Human)obj;
    hu.info();//通过代理类的对象调用重写的抽象方法
    System.out.println();
    hu.fly();
  }


生成的$Proxy0路径在项目根目录下com/web/test–该包名为测试代码所在的包。

总结:


java动态代理是利用反射机制生成一个实现代理接口的代理类,在调用具体方法前调用内部InvokeHandler来处理,InvokeHandler将会调用invoke方法并处理目标实际方法。


而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。


如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP

如果目标对象实现了接口,可以强制使用CGLIB实现AOP

如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换

目录
相关文章
|
9月前
|
存储 缓存 Java
我们来详细讲一讲 Java NIO 底层原理
我是小假 期待与你的下一次相遇 ~
290 2
|
8月前
|
监控 Java API
现代 Java IO 高性能实践从原理到落地的高效实现路径与实战指南
本文深入解析现代Java高性能IO实践,涵盖异步非阻塞IO、操作系统优化、大文件处理、响应式网络编程与数据库访问,结合Netty、Reactor等技术落地高并发应用,助力构建高效可扩展的IO系统。
233 0
|
10月前
|
存储 缓存 Java
【高薪程序员必看】万字长文拆解Java并发编程!(5):深入理解JMM:Java内存模型的三大特性与volatile底层原理
JMM,Java Memory Model,Java内存模型,定义了主内存,工作内存,确保Java在不同平台上的正确运行主内存Main Memory:所有线程共享的内存区域,所有的变量都存储在主存中工作内存Working Memory:每个线程拥有自己的工作内存,用于保存变量的副本.线程执行过程中先将主内存中的变量读到工作内存中,对变量进行操作之后再将变量写入主内存,jvm概念说明主内存所有线程共享的内存区域,存储原始变量(堆内存中的对象实例和静态变量)工作内存。
299 0
|
9月前
|
存储 算法 安全
Java中的对称加密算法的原理与实现
本文详细解析了Java中三种常用对称加密算法(AES、DES、3DES)的实现原理及应用。对称加密使用相同密钥进行加解密,适合数据安全传输与存储。AES作为现代标准,支持128/192/256位密钥,安全性高;DES采用56位密钥,现已不够安全;3DES通过三重加密增强安全性,但性能较低。文章提供了各算法的具体Java代码示例,便于快速上手实现加密解密操作,帮助用户根据需求选择合适的加密方案保护数据安全。
563 58
|
8月前
|
人工智能 安全 Java
Go与Java泛型原理简介
本文介绍了Go与Java泛型的实现原理。Go通过单态化为不同类型生成函数副本,提升运行效率;而Java则采用类型擦除,将泛型转为Object类型处理,保持兼容性但牺牲部分类型安全。两种机制各有优劣,适用于不同场景。
325 24
|
9月前
|
XML JSON Java
Java 反射:从原理到实战的全面解析与应用指南
本文深度解析Java反射机制,从原理到实战应用全覆盖。首先讲解反射的概念与核心原理,包括类加载过程和`Class`对象的作用;接着详细分析反射的核心API用法,如`Class`、`Constructor`、`Method`和`Field`的操作方法;最后通过动态代理和注解驱动配置解析等实战场景,帮助读者掌握反射技术的实际应用。内容翔实,适合希望深入理解Java反射机制的开发者。
743 13
|
8月前
|
存储 缓存 安全
深入讲解 Java 并发编程核心原理与应用案例
本教程全面讲解Java并发编程,涵盖并发基础、线程安全、同步机制、并发工具类、线程池及实际应用案例,助你掌握多线程开发核心技术,提升程序性能与响应能力。
315 0
|
9月前
|
算法 Java 索引
说一说 Java 并发队列原理剖析
我是小假 期待与你的下一次相遇 ~
|
9月前
|
安全 Java 编译器
JD-GUI,java反编译工具及原理: JavaDecompiler一个Java反编译器
Java Decompiler (JD-GUI) 是一款由 Pavel Kouznetsov 开发的图形化 Java 反编译工具,支持 Windows、Linux 和 Mac Os。它能将 `.class` 文件反编译为 Java 源代码,支持多文件标签浏览、高亮显示,并兼容 Java 5 及以上版本。JD-GUI 支持对整个 Jar 文件进行反编译,可跳转源码,适用于多种 JDK 和编译器。其原理基于将字节码转换为抽象语法树 (AST),再通过反编译生成代码。尽管程序可能带来安全风险,但可通过代码混淆降低可读性。最新版修复了多项识别错误并优化了内存管理。
6673 1