一文带你理解JDK动态代理原理

本文详细介绍了动态代理在实际编程中的应用,通过实例演示如何使用Java的Proxy和InvocationHandler创建代理对象,增强惠普打印机的方法并解释其实现原理。

 

写在开头,为什么要写这篇文章。

为了面试的时候有话说,以及记录自己对动态代理的理解。

1.为什么要有动态代理?

动态代理可能在平时编程中很少看到,但是在使用的各种框架底层都使用了动态代理。它主要是对我们编写的代码逻辑进行一个增强的工具。

2.举例说明

假如有个打印接口,不同的打印机厂商有具体的实现。但是在打印的过程中需要记录日志信息、打印前后的信息。

打印机接口

public interface IPrintr {
    /**
     * 打印
     */
    void print();

    /**
     * 根据打印id获取信息
     * @param id
     * @return
     */
    String getPrintInfo(String id);

}

惠普打印机具体实现

public class HPPrinter implements IPrintr {
    @Override
    public void print() {
        System.out.println("调用打印机...");
    }

    @Override
    public String getPrintInfo(String id) {
        return "ID为:"+id+"的打印信息";
    }
}

如果需要给惠普打印机的每个方法加上日志记录和打印反馈记录信息,如果直接写死在每个方法中这样的做法显然是不合理的。我们需要使用一个横切面直接插入到我们的代码中,给每个方法增强。具体的做法就是使用动态代理。

直接写入代码的方式:

public class HPPrinter implements IPrintr {
    @Override
    public void print() {
        System.out.println("记录日志信息");
        System.out.println("调用打印机...");
        System.out.println("记录反馈信息");
    }

    @Override
    public String getPrintInfo(String id) {
        System.out.println("记录日志信息");
        System.out.println("获取打印信息");
        System.out.println("记录反馈信息");
        return "ID为:"+id+"的打印信息";
    }
}

使用动态代理:

第一步:实现InvocationHandler接口,并实现invoke方法

public class ProxyHandler implements InvocationHandler {

    private Object targetObject;//被代理的对象

    //将被代理的对象传入获得它的类加载器和实现接口作为Proxy.newProxyInstance方法的参数。
    public Object newProxyInstance(Object targetObject) {
        this.targetObject = targetObject;
        //targetObject.getClass().getClassLoader():被代理对象的类加载器
        //targetObject.getClass().getInterfaces():被代理对象的实现接口
        //this 当前对象,该对象实现了InvocationHandler接口所以有invoke方法,通过invoke方法可以调用被代理对象的方法
        return Proxy.newProxyInstance(
                targetObject.getClass().getClassLoader(),
                targetObject.getClass().getInterfaces(),
                this);
    }

    /**
     * @param proxy  被代理的对象
     * @param method 被代理的方法
     * @param args   方法执行的参数
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
       
        System.out.println("记录日志");
        Object obj = method.invoke(targetObject, args);
        System.out.println("记录反馈信息");
        return obj;
    }
}

第二步:使用Java中的proxy类生成代理对象。

这一步在第一步中写了一个方法生成,需要从外部传入一个需要被增强的类(本例中是HPPrinter )。

第三步:在主类中使用

public static void main(String[] args) {
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        ProxyHandler proxyHandler = new ProxyHandler();
        IPrintr proxy = (IPrintr)proxyHandler.newProxyInstance(new HPPrinter());
        proxy.print();
        System.out.println("------------");
        String printInfo = proxy.getPrintInfo("2019210516036");
        System.out.println(printInfo);
    }

 输出结果:

记录日志
调用打印机...
记录反馈信息
------------
记录日志
获取打印信息
记录反馈信息
ID为:2019210516036的打印信息

3.原理解释

主要是下面的一句代码。为我们的代理类生成了一个新类,$Proxy0。

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

 所以我们最终使用的是$Proxy0类。

要生成该类,需要使用下面这行代码

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

这个是动态代理过程中生成的代码,具体代码如下:

public final class $Proxy0 extends Proxy implements IPrintr {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m4;
    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 print() 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 String getPrintInfo(String var1) throws  {
        try {
            return (String)super.h.invoke(this, m4, 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"));
            m3 = Class.forName("dynamic_proxy.IPrintr").getMethod("print");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m4 = Class.forName("dynamic_proxy.IPrintr").getMethod("getPrintInfo", 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类持有我们代理对象的方法,而且在执行具体的方法前,都会执行InvocationHandler 接口中的invoke方法,这就是为什么我们的方法会被增强的原因。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值