代理模式——用法与动态代理底层详解

代理模式作为框架中经常使用的设计模式,它对代码的拓展性提升可以说是相当高,从aop的实现,到单元测试,再到Mybatis的Mapper接口实现类,都有代理模式的影子。

本篇主要讲解代理模式的使用,动态代理的功能和JVM底层如何处理动态代理。

目录

一、静态代理的实现

1.实现一个房东

2.实现黑中介

3.租房试一试

4.静态代理如何玩出花来?

二、动态代理的实现

1.黑中介生成器(newProxyInstance方法)详解

2.InvocationHandler接口详解

3.使用动态代理

4.程序分析和整理

三、动态代理原理分析


 

一、静态代理的实现

代理是什么意思呢,举个例子,我是房东,我有一栋房子急租,但是我很忙,怎么办呢?当然是找黑……中介啦。

客户如果需要租房子,那么就去找黑中介就行了,这个中介也不一定只会做房产中介,也可以做些别的,反正就是不务正业的那种。

1、实现一个房东

房东的功能相当简单,只需要租售房子(rentHouse())就行了。

public interface Rent {

    void rentSth();
}


public class Landlord implements Rent{
    //此处实现Rent接口,之后就可以对Rent接口进行拓展,比如租车,或者py交易也行
    @Override
    public void rentSth() {
        rentHouse();
    }

    public void rentHouse(){
        System.out.println("租售房子");
    }
}

2.实现黑中介

黑中介只需要帮助房东租房子即可,但是为了方便和客户对接,因此,黑中介也需要继承Rent接口

public class Intermediary implements Rent{

    //因为中介本身是没有房子的,因此,需要有一个房东的存在,让黑中介能干活
    private Rent rent;
    //如果对静态代理理解比较深的话,这个代理的对象可以有很多个,根据业务场景的需要,用代理的不同对象处理不同的事情。

    public Intermediary(Rent rent) {
        this.rent = rent;
    }

    @Override
    public void rentSth() {
        rent.rentSth();
    }

    public void getMoney(int money){
        System.out.println("给房东"+money*0.1+"元");
        System.out.println("获利"+money*0.9+"元");
    }
}

3.租房试一试

public class Tenant {
    public static void main(String[] args) {
        //找黑中介租房子
        Intermediary intermediary = new Intermediary(new Landlord());
        intermediary.rentSth();
        intermediary.getMoney(3000);
    }
}

运行结果如下:

4.静态代理如何玩出花来?

虽然看了静态代理之后,觉得实现起来好像没啥用?但是实际上,静态代理已经可以做很多事情了,比如,可以用静态代理,让代理的对象的方法按照你设定的顺序进行执行(参考Spring AOP的功能),玩法很多。

但是静态代理有一个致命的缺点,当然,这也是所有静态代码都有的缺点:不够灵活,代理的对象必须要预先编译好

 

二、动态代理的实现

动态代理的在功能上基本上和静态代理相差无几,但是它是基于反射包下的产物,所谓反射,也就是程序在运行的过程中,使JVM加载其他class文件并且操作的一个过程。

基于反射的代理模式就解决了静态代理不够灵活的一个缺点,下面我们看下动态代理的使用方法。

1.黑中介生成器(newProxyInstance方法)详解

 

这里依旧拿上面的代码为例子,房东和Rent接口都是必须要的,而黑中介我们就不需要了,动态代理会替我们在程序运行的时候,生成一个黑中介。

Proxy.newProxyInstance();//Proxy(代理的意思)这个类提供的静态方法可以产生一个黑中介



//详细看一下这个方法需要的参数:
public static Object newProxyInstance(
ClassLoader loader,           
Class<?>[] interfaces,        
InvocationHandler h)
        throws IllegalArgumentException

 

  • ClassLoader loader:该参数是被代理对象的类加载器,如果是我们写的普通类,使用applicationClassLoader就可以了,也就是ClassLoader.getSystemClassLoader(),这个加载器也是默认加载器;有兴趣的可以了解一下Java类加载机制(双亲委派模型)。

  • Class<?>[] interfaces:该参数是被代理对象的class对象,因为可以代理多个类,因此用数组传值。

  • InvocationHandler h:虽然反射生成的黑中介可以代理房东的所有行为,但是程序员得告诉黑中介,应该怎么做。InvocationHandler是黑中介的行为处理器。(此处括号里的内容可以忽略。实际上,动态代理生成的对象,本质上重写了该对象的所有方法,使其所有方法中都只有一个功能,调用InvocationHandler对象的invoke方法。)

2.InvocationHandler接口详解

该接口只有一个invoke方法,这个方法我们需要实现它,才能代理我们的对象。

因为之后我们调用任何黑中介的方法,黑中介都会调用invoke方法。

public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
  • Object proxy:这个对象是黑中介自己,尽量不要去使用它,因为它的所有方法都是调用invoke方法,调用它的任何方法都会进入死递归调用。
  • Method method:因为不论你调用黑中介的哪个方法,最后都会调用invoke方法,因此,需要依靠Method对象来识别到底调用了什么方法;这个method对象也就是被调用的方法的实例对象。
  • Object[] args:这个是被黑中介调用方法的参数;
  • invoke方法的返回值就是黑中介被调用的方法的返回值。

 

3.使用动态代理

在解释完黑中介生成器之后,现在可以开始自己动手试一试了。

public class Tenant {
    public static void main(String[] args) {
        Rent rent = (Rent) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Rent.class}, new InvocationHandler() {
            //代理房东
            Rent rent = new Landlord();
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                rent.rentSth();
                System.out.println("我是黑中介,我被抓了");
                return null;
            }
        });
        rent.rentSth();
    }
}

运行结果:

4.程序分析和整理

现在我们的黑中介已经如鱼得水了,但是还是存在问题:

因为调用黑中介的任何方法都会调用invoke方法,因此,如果想代理多个方法的话,就很麻烦了,比如现在调用黑中介的toString方法,他依旧会执行invoke方法,给客户推销房子。

所以,修改程序:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
       String menthodName = method.getName();
        //根据方法的名字选择做什么事情。
       if(menthodName.equals("rentSth")){
             method.invoke(rent,null);
             System.out.println("我是黑中介,我被抓了");
             return null;
       }
       return null;
}

 

 

三、动态代理原理分析

从二中我们可以发现,整个动态代理对我们来说几乎都是透明的,唯一的黑匣子便是newProxyInstance方法。

不过Proxy既然是位于reflet包下,那么它的实现必定离不开反射技术。

事实上在最后,它会调用sun.misc.ProxyGenerator.generateProxyClass()方法来完成生成字节码的动作,并且在运行时产生一个描述代理类的字节码byte[]数组。

我们可以在main()方法中添加下面的代码,将这个类文件写入到本地。

        byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{Rent.class});
        try(
                FileOutputStream fos =new FileOutputStream(new File("E:\\$Proxy0.class"))
        ){
            fos.write(bytes);
            fos.flush();
        }catch (Exception e){
            e.printStackTrace();
        }

再次运行main方法后,在E盘下会多出一个字节码$Proxy0文件

通过反编译可以得出下面的代码(为了方便观看,我去掉了trycatch代码块):

点击此处获取反编译工具jad

public final class $Proxy extends Proxy
    implements Rent
{

    public $Proxy(InvocationHandler invocationhandler)
    {
        super(invocationhandler);
    }

    public final boolean equals(Object obj)
    {
        return ((Boolean)super.h.invoke(this, m1, new Object[] {
                obj
            })).booleanValue();
    }

    public final String toString()
    {
        
        return (String)super.h.invoke(this, m2, null);
    }

    public final void rentSth()
    {
        super.h.invoke(this, m3, null);
    }

    public final int hashCode()
    {
        return ((Integer)super.h.invoke(this, m0, null)).intValue();
    }

    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    static 
    {
        m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] {
                Class.forName("java.lang.Object")
            });
        m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
        m3 = Class.forName("proxy.Rent").getMethod("rentSth", new Class[0]);
        m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      
    }
}

不难发现实际上动态生成的对象在调用任何方法的时候,都会交给成员对象InvocationHandler的invoke方法去处理,动态代理也就不那么神秘了。

 

 

jad工具使用方法:

在命令行下输入:

jad -o -r -s java -d 生成Java文件路径位置 字节码文件位置/类名.class

例如:jad -o -r -s java -d src *.class

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值