jdk动态代理的深入了解

jdk动态代理概述

jdk动态代理的底层机制是让代理类和被代理类都实现相同的接口,通过反射创建动态代理类,然后通过反射去调用被代理类的方法,并可以在调用方法前后去增加一些增强功能,使得代码的耦合性降低,增强扩展性。

 

JDK动态代理demo

我们简单粗暴一些,接下来我们直接通过一个例子来理解jdk动态代理是如何实现的

我们用员工提交涨工资申请的例子来演示一下

1.首先定义一个公共接口Person,里面有一个方法riseSalary() 涨工资

public interface Person {
    /**
     * 涨工资
     */
    void riseSalary();
}

2.然后定义一个staff类去实现这个接口,并重写这个方法

public class Staff implements Person {
    private String name;

    @Override
    public void riseSalary() {
        System.out.println(this.name + "提交涨工资申请");
    }
}

这个时候我们想在员工提交涨工资申请前对员工进行资格审查,在提交涨工资申请后进行评估,但是又不想直接修改Staff类中的riseSalary()方法,避免代码侵入增加耦合性,所以我们这里用jdk动态代理来实现这个功能。

3.我们新增一个StaffInvokehanlder增强类。

该类继承了java反射包下的InvocationHandler类,该类下有一个invoke方法,最终通过调用invoke方法从而达到调用被代理类方法的能力,并可以在invoke方法前后增加功能,带到通过动态代理增强被代理类功能的目的。

public class StaffInvokehanlder<T> implements InvocationHandler {
    private T target;

    public StaffInvokehanlder(T target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("提交涨工资申请前对员工进行资格审查");
        Object object = method.invoke(target, args);
        System.out.println("提交涨工资申请后进行评估");
        return object;
    }
}

4.接下来我们来增加一个测试类,来演示一下动态代理的功能

public class StaffTest {
    public static void main(String[] args) {
        //将动态代理类输出到工程中com.sun.proxy目录下
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        //实例化员工王老五
        Person staff = new Staff("王老五");
        //将王老五放入StaffInvokehanlder增强类中
        StaffInvokehanlder invokehanlder = new StaffInvokehanlder(staff);

        //调用Proxy.newProxyInstance方法生成动态代理类
        Person person = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, invokehanlder);
        /**
         * 由于动态代理类也实现了Person接口,会重写riseSalary,这里调用动态代理类的riseSalary方法,调用此方法就是调用StaffInvokehanlder的中的invoke方法
         * 调用StaffInvokehanlder的invoke方法就会调用被代理类staff的riseSalary,并在方法前后进行增强
         */
        person.riseSalary();
    }
}

因为动态代理类也实现了Person接口,会重写riseSalary方法,这里调用动态代理类的riseSalary方法就是调用StaffInvokehanlder的中的invoke方法,调用StaffInvokehanlder的invoke方法就会调用被代理类staff的riseSalary方法,并在方法前后进行增强

 

至此我们就完成动态代理的demo,我们来看看程序执行的输出结果

提交涨工资申请前对员工进行资格审查
王老五提交涨工资申请
提交涨工资申请后进行评估

在王老五提交涨工资申请前后增强了资格审查和评估功能,定位到代码中就是执行invoke方法的执行,通过反射执行被代理类的riseSalary方法,并在方法前后增强功能,此时并不需要对staff中的代码进行修改,只需要增加一个invokeHanlder增强器,就可以实现这个功能

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("提交涨工资申请前对员工进行资格审查");
        Object object = method.invoke(target, args);
        System.out.println("提交涨工资申请后进行评估");
        return object;
    }

 

5.动态代理底层源码分析

那么jdk动态代理是如何通过调用代理类的方法,从而去调用Invoke方法的呢,我们接下来通过生成的动态代理类来具体分析一下

我们先看这一行代码

//调用Proxy.newProxyInstance方法生成动态代理类
Person person = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, invokehanlder);

这行代码就是通过java的Proxy生成动态代理类,传入参数为Person的类加载器,Person类元数据信息,invokeHanlder增强类实例,我们点进newProxyInstance看看

我们主要关注那么几行代码

/*
* Look up or generate the designated proxy class.
*/
Class<?> cl = getProxyClass0(loader, intfs);

这行代码就是通过反射获得动态代理类的.class信息,返回类型是Class<?>,看他源码上的注释

Look up or generate the designated proxy class.(查找或生成指定的代理类。)

接下来我们关注一下这行代码

        //通过反射获取代理类的构造器
        final Constructor<?> cons = cl.getConstructor(constructorParams);
        final InvocationHandler ih = h;
        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信息,下面的代码便是获得代理类的构造器,并通过构造器的newInstance()方法实例化生成代理类实例,并且包含参数InvocationHandler h,这个增强类实例不就是我们

Person person = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, invokehanlder);

这行代码传进来的嘛。

至此我们应该知道生成代理类的全过程了,总结一下就是:

  • 通过反射获得代理类的信息
  • 获取代理类的构造器
  • 通过构造器的newInstance()实例化代理类,代理类实例并拥有传入的增强类实例

经过这个方法后我们便获得了代理类的实例

 

接着便是对代理类方法的调用

person.riseSalary()

这时候我们便需要去深入代理类的实现细节去看看,这行代码调用是怎么实现动态代理的

 

我们在测试类中增加了这么一行代码

        //将动态代理类输出到工程中com.sun.proxy目录下
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

通过该行代码我们便可以在项com.sun.proxy目录下找到动态代理的类文件,一个.class字节码文件

我们来看看这个代理类长什么样,我把这个代理类的源码贴在下面

public final class $Proxy0 extends Proxy implements Person {
    private static Method m1;
    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 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 riseSalary() throws  {
        try {
            super.h.invoke(this, m3, (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"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("com.example.demo.designPatterns.proxy.jdk.proxy5.Person").getMethod("riseSalary");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

 

我们先来看看该类的静态块

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("com.example.demo.designPatterns.proxy.jdk.proxy5.Person").getMethod("riseSalary");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }

 

m3 = Class.forName("com.example.demo.designPatterns.proxy.jdk.proxy5.Person").getMethod("riseSalary");

是不是看到了这一行代码,这不就是通过反射获取我们类方法吗并用m3这个引用引用起来

我们接着往下看

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

这个方法不就是我们的涨工资方法吗,看一下里面的实现

super.h.invoke(this, m3, (Object[])null);

这不就是调用我们增强类的invoke方法吗,看到这是不是就有头绪了,动态代理的过程已经了然于胸。

 

这时候我们再回头看看刚开始的概述

jdk动态代理的底层机制是让代理类和被代理类都实现相同的接口,通过反射创建动态代理类,然后通过反射去调用被代理类的方法,并可以在调用方法前后去增加一些增强功能,使得代码的耦合性降低,增强扩展性。

 

进一步解释一下

代理类和被代理类实现相同的接口,代理类重写和被代理类一样的方法,代理类重写方法中调用增强类的Invoke方法,增强类拥有被代理类的实例,invoke方法中通过反射调用被代理类的真正实现方法,并在Invoke方法中实现功能的增强。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值