Java中的动态代理使用

 更新时间:2024年07月12日 11:13:03   作者:让你三行代码QAQ  
这篇文章主要介绍了Java中的动态代理使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教

动态代理

动态代理的时候,定义一个接口,需要代理人和被代理类实现这个接口,这样不够灵活,代理类能够代理的类只有实现这个接口的类。

非常不灵活,假如被代理人的类没有实现这个接口,那么就需重新写一个代理类。对于日志、事务这些操作是不区分业务的,即不需要规定都实现某接口。因此,出现了动态代理

java种的动态代理生成的方式大概有三种JDK动态代理、instrument动态代理、cglib动态代理。其中,前两种是JDK自带的,cglib是需要第三方依赖使用的。

  • JDK动态代理:在程序运行的过程种动态的生成代理类,这个代理类实现被代理类的接口,因此使用时候被代理类必须实现接口
  • instrument动态代理:是通过Class字节文件在加载至内存的过程种添加拦截器,动态的修改字节文件实现的;
  • cglib:在程序运行的过程种动态生成代理类,这个代理类是通过继承被代理类实现的,因此不能代理被final修饰的类
  • JDK动态代理和cglib动态代理的底层都是ASM

JDK动态代理

先看这样一个程序

/** 接口*/
public interface Mobile {
    void move();
}
/** 被代理类*/
public class Car implements Mobile{
    @Override
    public void move() {
        System.out.println("move...");
    }
}
/** 使用动态代理*/
public class Test {
    public static void main(String[] args) {
        Car car = new Car();
        System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles",true);
        Mobile proxyInstance = (Mobile) Proxy.newProxyInstance(Car.class.getClassLoader(), Car.class.getInterfaces(),  new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                long before = System.currentTimeMillis();
                System.out.println(method.getName()+"方法被执行前。。。");
                Object invoke = method.invoke(car, args);
                System.out.println(method.getName()+"方法被执行后。。。");
                long after = System.currentTimeMillis();
                System.out.println("耗时:" + (after - before));
                return null;
            }
        });

        proxyInstance.move();
    }
}

创建代理类需要调用Proxy.newProxyInstance()方法,其中有三个参数

  • 第一个参数:一个类加载器,一般使用被代理类的类加载器
  • 第二个参数:被代理类实现的接口
  • 第三个参数:一个InvocationHandler接口的实现类

可以把动态代理的创建改成这样:自己写一个类实现InvocationHandler。

public class Test {
    public static void main(String[] args) {
        Car car = new Car();
        System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
        Mobile proxyInstance = (Mobile) Proxy.newProxyInstance(Car.class.getClassLoader(), Car.class.getInterfaces(), new MyInvocationHandler(car));
        proxyInstance.move();
    }

}
class MyInvocationHandler implements InvocationHandler{
    private Car car;

    public MyInvocationHandler(Car car) {
        this.car = car;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        long before = System.currentTimeMillis();
        System.out.println(method.getName()+"方法被执行前。。。");
        Object invoke = method.invoke(car, args);
        System.out.println(method.getName()+"方法被执行后。。。");
        long after = System.currentTimeMillis();
        System.out.println("耗时:" + (after - before));
        return null;
    }
}

要想搞懂JDK动态代理必须查看动态生成代理类,通过设置JDK动态代理生成的类是否保存的一个属性将生成的代理类保存下来,通过在程序启动前加上: 

System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles",true);

代理类的调用过程

  • 生成的代理类
public final class $Proxy0 extends Proxy implements Mobile {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final void move() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("com.shaoby.basic.proxy.JdkProxy.Mobile").getMethod("move");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}
  • 可以看到代理类继承了Proxy,并实现了我们定义的Mobile接口
  • 构造方法传入InvocationHandler类型,并赋值给父类Proxy中的h参数,可以猜测这个就在获取代理类时候的第三个参数;
  • 代理类中通过反射生成了四个方法,除了Object中的equals、toString、hashCode外另外一个就是我们要代理的方法move;
  • 在move方法中调用了 super.h.invoke(this, m3, (Object[])null),即MyInvocationHandler中的invoke方法。
  • 因此在调用代理类的invoke方法时,调用的就是MyInvocationHandler中的invoke方法。

Method类

这个类是一个方法的类,说白了就是方法的模板,在这个类中有一个方法invoke,传入两个参数,一个是调用方法的对象,另一个是方法的入参

InvocationHandler中的invoke方法

可以看到在代理类中invoke方法的调用为 super.h.invoke(this, m3, (Object[])null);

MyInvocationHandler中的invoke方法:

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        long before = System.currentTimeMillis();
        System.out.println(method.getName()+"方法被执行前。。。");
        Object invoke = method.invoke(car, args);
        System.out.println(method.getName()+"方法被执行后。。。");
        long after = System.currentTimeMillis();
        System.out.println("耗时:" + (after - before));
        return null;
    }

参数:

  • 第一个参数:this,即代理类本身
  • 第二个参数:m3,即move方法
  • 第三个参数:第二个方法的入参

返回值:

这个返回值其实是被代理方法的返回值,如果没有返回值就返回null;

这里最重要的是method.invoke(car, args),就是用car对象调用move方法。

ASM

在上述分析中,动态代理类的构造方法中参数是我们猜测的。其实这里是用ASM实现的。在生成代理类时调用的Proxy.newInstance()传入了InvocationHandler接口的实现类,Proxy.neInstance()会动态的生成代理类,并产生一个代理对象。这里后续再研究。

ASM是Java字节码操控框架,它能够以二进制形式修改已有类,或动态生成类,ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。ASM 从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。

cglib动态代理

public class Test {

    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Plane.class);
        enhancer.setCallback(new MyMethodInterceptor());
        Plane o = (Plane)enhancer.create();
        o.move();


    }
}
class MyMethodInterceptor implements MethodInterceptor{
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        long before = System.currentTimeMillis();
        System.out.println(method.getName()+"方法被执行前。。。");
        Object result = methodProxy.invokeSuper(o, objects);
        System.out.println(method.getName()+"方法被执行后。。。");
        long after = System.currentTimeMillis();
        System.out.println("耗时:" + (after - before));
        return result;
    }
}

cglib动态代理的原理是:

  • 指定代理类的父类
  • 生成代理类
  • 调用代理类父类的method

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • 详细介绍Java函数式接口

    详细介绍Java函数式接口

    函数式接口在Java中是指有且仅有一个抽象方法的接口。当然接口中可以包含其他的方法默认、静态、私有,具体内容请参考下面文章内容
    2021-09-09
  • idea中创建多module的maven工程的方法

    idea中创建多module的maven工程的方法

    这篇文章主要介绍了idea中创建多module的maven工程的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-10-10
  • 关于Java中Json的各种处理

    关于Java中Json的各种处理

    这篇文章主要介绍了关于Java中Json的各种处理方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-06-06
  • java实现折半排序算法

    java实现折半排序算法

    折半插入排序法,又称二分插入排序法,是直接插入排序法的改良版,也需要执行i-1趟插入,不同之处在于,第i趟插入,先找出第i+1个元素应该插入的的位置,假定前i个数据是已经处于有序状态。
    2015-04-04
  • Windows Zookeeper安装过程及启动图解

    Windows Zookeeper安装过程及启动图解

    这篇文章主要介绍了Windows Zookeeper安装过程及启动图解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-12-12
  • 浅谈异常结构图、编译期异常和运行期异常的区别

    浅谈异常结构图、编译期异常和运行期异常的区别

    下面小编就为大家带来一篇浅谈异常结构图、编译期异常和运行期异常的区别。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2016-09-09
  • 你知道Spring如何解决所有循环依赖的吗

    你知道Spring如何解决所有循环依赖的吗

    这篇文章主要来和大家聊聊Spring 能解决所有循环依赖吗,文中的示例代码讲解详细,对我们学习Spring有一定的帮助,需要的小伙伴可以参考一下
    2023-07-07
  • 解析Spring 漏洞及其修复方案

    解析Spring 漏洞及其修复方案

    官宣了最近网传的Spring漏洞。攻击者利用该漏洞,可在未授权的情况下远程执行命令,今天通过本文给大家普及下漏洞分析影响范围及解决方案,感兴趣的朋友跟随小编一起看看吧
    2022-04-04
  • maven配置阿里仓库的方法步骤

    maven配置阿里仓库的方法步骤

    这篇文章主要介绍了maven配置阿里仓库的方法步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-12-12
  • 一篇文章教你如何用多种迭代写法实现二叉树遍历

    一篇文章教你如何用多种迭代写法实现二叉树遍历

    这篇文章主要介绍了C语言实现二叉树遍历的迭代算法,包括二叉树的中序遍历、先序遍历及后序遍历等,是非常经典的算法,需要的朋友可以参考下
    2021-08-08

最新评论