java动态代理产生死循环问题排查和源码分析

解决手段

观察InvocationHandler类中的invoke方法内部利用反射调用被代理类的方法时, 是否写成了这样
在这里插入图片描述
如果是, 修改为
在这里插入图片描述
如果您感兴趣, 可以看看我下面的分析, 欢迎批评与指正.

问题复现

最近在学习jdk的动态代理, 自己写了一个Demo后, 发现程序陷入了死循环, 通过分析源码后, 成功定位并解决了问题, 也加深了自己对动态代理的理解.

书写动态代理的一般步骤(可以结合下面的代码食用)

  1. 定义接口, 如下面代码的Task接口,确定将要被代理的方法
  2. 书写实现类, 实现这个接口Task, 实现类为RealTask
  3. 实现InvocationHandler接口. 注意这个实现类要以被代理类为的数据成员, 并利用构造函数初始化它.
  4. 调用Proxy.newInstance方法获得动态代理类, 第一个参数为接口的类加载器, 第二个参数为接口列表, 第三个参数为我们实现好的InvocationHandler的实现类.
  5. 利用这个动态代理类调用接口内的方法便实现了动态代理
interface Task{
    void doSomething();
    void somethingElse(String arg);
}


class RealTask implements Task{
    @Override
    public void doSomething() {
        System.out.println("do something");
    }

    @Override
    public void somethingElse(String arg) {
        System.out.println("something else" + arg);
    }
}


public class SimpleDynamicProxy implements InvocationHandler {
    private Task task;

    public SimpleDynamicProxy(Task task) {
        this.task = task;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("代理:" + method.getName());
        return method.invoke(proxy, args);
    }

    public static void main(String[] args) {
        Task task = (Task) Proxy.newProxyInstance(Task.class.getClassLoader(),
                new Class[]{Task.class}, new SimpleDynamicProxy(new RealTask()));

        task.somethingElse("something");
    }
}

问题排查

运行以后, 悲剧就发生了, 程序陷入了死循环.
排查了半天无果后, 查阅资料发现动态代理不同于静态代理, 静态代理在编译前, 我们就已经书写好的代理类, 所以可以看到代理类的class文件 . 按照一般的经验, 动态这个字眼告诉我, 文件是运行时期生成的, 所以, 我推测在运行时, jvm帮我们生成了这个代理类, 通过搜索, 我发现在利用Proxy.newInstance前加上

System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

便可以看到这个动态代理类的源码, 于是修改是main函数为

public static void main(String[] args) {
       System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
       Task task = (Task) Proxy.newProxyInstance(Task.class.getClassLoader(),
               new Class[]{Task.class}, new SimpleDynamicProxy(new RealTask()));

       task.somethingElse("something");
   }

再次运行后, 便可以看到这个代理类的源码, 利用IDEA直接查看class文件, 是在src的同级目录下生成的
在这里插入图片描述

下面是源码

final class $Proxy0 extends Proxy implements Task {
    private static Method m1;
    private static Method m4;
    private static Method m2;
    private static Method m3;
    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 doSomething() throws  {
        try {
            super.h.invoke(this, m4, (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 void somethingElse(String var1) throws  {
        try {
            super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    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"));
            m4 = Class.forName("base.reflect.Task").getMethod("doSomething");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("base.reflect.Task").getMethod("somethingElse", Class.forName("java.lang.String"));
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}



分析源码

首先看到这个代理类叫做$Proxy0, 继承自Proxy, 实现了我们自定义的接口Task.
接着观察到构造方法, 传入了InvocationHandler的实现类, 也就是我们自定义的类SimpleDynamicProxy,
接着观察方法

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

super.h就是InvocatinHandler实例SimpleDynamicProxy, 然后调用了它的invoke方法, 自然就跳转到了SimpleDynamicProxy的invoke方法, 仔细看invoke方法的第一个参数, 竟然是this, 也就是代理对象, 然后函数内利用反射调用proxy的方法, 注意这个proxy是代理对象, 就是代理对象再次调用somethingElse(String args)方法, 好了, 问题出现了, 一开始我们调用代理对象somethingElse方法, 方法内部跳到InvocatinHandler的invoke方法, 又执行了代理对象的somethingElse方法, 这就出现了递归调用 因此将return methiod.invoke(proxy, args)修改为return methiod.invoke(task, args)

public class SimpleDynamicProxy implements InvocationHandler {
    private Task task;

    public SimpleDynamicProxy(Task task) {
        this.task = task;
    }

    // 代理类对象proxy 被代理类方法method 被代理类参数args
    // return method.invoke(proxy, args);
    // 代理类对象再次调用invoke方法
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("代理:" + method.getName());
        return method.invoke(proxy, args);
    }

总结

出现问题的原因还是没有理解SimpleDynamicProxy的invoke方法的参数的意思

  • 第一个参数proxy是代理对象, 不是被代理对象
  • 第二个参数method是接口中某个方法, 当然也有equals方法和hashcode方法
  • 第三个参数为参数列表, 当函数不需要参数时, 为null

动态代理这代码也写的太好了, 代理类Proxy内部用一个InvocatinHandler类, 调用方法的时候直接调用InvocationHandler的方法, 而InvocationHandler接口的实现全部交由用户来实现, 真的写的太好了, 真心佩服.

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值