深入AOP开发的基石 java动态代理

来源:互联网 发布:oracle distinct 优化 编辑:程序博客网 时间:2024/06/03 19:38

点击上方蓝字进行关注的都是靓仔仙女


概述


1. 什么是代理


我们大家都知道微商代理,简单地说就是代替厂家卖商品,厂家“委托”代理为其销售商品。关于微商代理,首先我们从他们那里买东西时通常不知道背后的厂家究竟是谁,也就是说,“委托者”对我们来说是不可见的;其次,微商代理主要以朋友圈的人为目标客户,这就相当于为厂家做了一次对客户群体的“过滤”。我们把微商代理和厂家进一步抽象,前者可抽象为代理类,后者可抽象为委托类(被代理类)。通过使用代理,通常有两个优点,并且能够分别与我们提到的微商代理的两个特点对应起来:


  • 优点一:可以隐藏委托类的实现;

  • 优点二:可以实现客户与委托类间的解耦,在不修改委托类代码的情况下能够做一些额外的处理。


2. 静态代理


若代理类在程序运行前就已经存在,那么这种代理方式被成为静态代理,这种情况下的代理类通常都是我们在Java代码中定义的。 通常情况下,静态代理中的代理类和委托类会实现同一接口或是派生自相同的父类。下面我们用Vendor类代表生产厂家,BusinessAgent类代表微商代理,来介绍下静态代理的简单实现,委托类和代理类都实现了Sell接口,Sell接口的定义如下:


public interface Sell {

    void sell();

    void ad();

}


Vendor类的定义如下:


public class Vendor implements Sell {

    public void sell() {

        System.out.println("In sell method");

    }

    public void ad() {

        System,out.println("ad method")

    }

}


代理类BusinessAgent的定义如下:


public class BusinessAgent implements Sell {

    private Vendor mVendor;

 

    public BusinessAgent(Vendor vendor) {

        mVendor = vendor;

    }

 

    public void sell() { mVendor.sell(); }

    public void ad() { mVendor.ad(); }

}


从BusinessAgent类的定义我们可以了解到,静态代理可以通过聚合来实现,让代理类持有一个委托类的引用即可。


下面我们考虑一下这个需求:给Vendor类增加一个过滤功能,只卖货给大学生。通过静态代理,我们无需修改Vendor类的代码就可以实现,只需在BusinessAgent类中的sell方法中添加一个判断即可如下所示:


public class BusinessAgent implements Sell {

    ...

    public void sell() {

        if (isCollegeStudent()) {

            vendor.sell();

        }

    }

    ...

}


这对应着我们上面提到的使用代理的第二个优点:可以实现客户与委托类间的解耦,在不修改委托类代码的情况下能够做一些额外的处理。静态代理的局限在于运行前必须编写好代理类,下面我们重点来介绍下运行时生成代理类的动态代理方式。


动态代理


1. 什么是动态代理


代理类在程序运行时创建的代理方式被成为动态代理。也就是说,这种情况下,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。相比于静态代理,动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类的函数。这么说比较抽象,下面我们结合一个实例来介绍一下动态代理的这个优势是怎么体现的。


现在,假设我们要实现这样一个需求:在执行委托类中的方法之前输出“before”,在执行完毕后输出“after”。我们还是以上面例子中的Vendor类作为委托类,BusinessAgent类作为代理类来进行介绍。首先我们来使用静态代理来实现这一需求,相关代码如下:


public class BusinessAgent implements Sell {

    private Vendor mVendor;

 

    public BusinessAgent(Vendor vendor) {

        this.mVendor = vendor;

    }

 

    public void sell() {

        System.out.println("before");

        mVendor.sell();

        System.out.println("after");

    }

 

    public void ad() {

        System.out.println("before");

        mVendor.ad();

        System.out.println("after");

    }

}


从以上代码中我们可以了解到,通过静态代理实现我们的需求需要我们在每个方法中都添加相应的逻辑,这里只存在两个方法所以工作量还不算大,假如Sell接口中包含上百个方法呢?这时候使用静态代理就会编写许多冗余代码。通过使用动态代理,我们可以做一个“统一指示”,从而对所有代理类的方法进行统一处理,而不用逐一修改每个方法。下面我们来具体介绍下如何使用动态代理方式实现我们的需求。


2. 使用动态代理


(1)InvocationHandler接口


在使用动态代理时,我们需要定义一个位于代理类与委托类之间的中介类,这个中介类被要求实现InvocationHandler接口,这个接口的定义如下:


public interface InvocationHandler {

    Object invoke(Object proxy, Method method, Object[] args);

}


从InvocationHandler这个名称我们就可以知道,实现了这个接口的中介类用做“调用处理器”。当我们调用代理类对象的方法时,这个“调用”会转送到invoke方法中,代理类对象作为proxy参数传入,参数method标识了我们具体调用的是代理类的哪个方法,args为这个方法的参数。这样一来,我们对代理类中的所有方法的调用都会变为对invoke的调用,这样我们可以在invoke方法中添加统一的处理逻辑(也可以根据method参数对不同的代理类方法做不同的处理)。因此我们只需在中介类的invoke方法实现中输出“before”,然后调用委托类的invoke方法,再输出“after”。下面我们来一步一步具体实现它。


(2)委托类的定义


动态代理方式下,要求委托类必须实现某个接口,这里我们实现的是Sell接口。委托类Vendor类的定义如下:


public class Vendor implements Sell {

    public void sell() {

        System.out.println("In sell method");

    }

    public void ad() {

        System,out.println("ad method")

    }

}


(3)中介类


上面我们提到过,中介类必须实现InvocationHandler接口,作为调用处理器”拦截“对代理类方法的调用。中介类的定义如下:


public class DynamicProxy implements InvocationHandler {

    private Object obj; //obj为委托类对象;

 

    public DynamicProxy(Object obj) {

        this.obj = obj;

    }

 

    @Override

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        System.out.println("before");

        Object result = method.invoke(obj, args);

        System.out.println("after");

        return result;

    }

}


从以上代码中我们可以看到,中介类持有一个委托类对象引用,在invoke方法中调用了委托类对象的相应方法(第11行),看到这里是不是觉得似曾相识?通过聚合方式持有委托类对象引用,把外部对invoke的调用最终都转为对委托类对象的调用。这不就是我们上面介绍的静态代理的一种实现方式吗?实际上,中介类与委托类构成了静态代理关系,在这个关系中,中介类是代理类,委托类就是委托类。


代理类与中介类也构成一个静态代理关系,在这个关系中,中介类是委托类,代理类是代理类。也就是说,动态代理关系由两组静态代理关系组成,这就是动态代理的原理。下面我们来介绍一下如何”指示“以动态生成代理类。


(4)动态生成代理类


动态生成代理类的相关代码如下:


public class Main {

    public static void main(String[] args) {

        //创建中介类实例

        DynamicProxy  inter = new DynamicProxy(new Vendor());

        //加上这句将会产生一个$Proxy0.class文件,这个文件即为动态生成的代理类文件

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

        //获取代理类实例sell

        Sell sell = (Sell)(Proxy.newProxyInstance(Sell.class.getClassLoader(),

        new Class[] {Sell.class}, inter));

        //通过代理类对象调用代理类方法,实际上会转到invoke方法调用

        sell.sell();

        sell.ad();

    }

}


在以上代码中,我们调用Proxy类的newProxyInstance方法来获取一个代理类实例。这个代理类实现了我们指定的接口并且会把方法调用分发到指定的调用处理器。这个方法的声明如下:


public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException


方法的三个参数含义分别如下:


  • loader:定义了代理类的ClassLoder;

  • interfaces:代理类实现的接口列表

  • h:调用处理器,也就是我们上面定义的实现了InvocationHandler接口的类实例


我们运行一下,看看我们的动态代理是否能正常工作。我这里运行后的输出为:


    

        

说明我们的动态代理确实奏效了。


上面我们已经简单提到过动态代理的原理,这里再简单的总结下:首先通过newProxyInstance方法获取代理类实例,而后我们便可以通过这个代理类实例调用代理类的方法,对代理类的方法的调用实际上都会调用中介类(调用处理器)的invoke方法,在invoke方法中我们调用委托类的相应方法,并且可以添加自己的处理逻辑。下面我们来看一下生成的代理类的代码究竟是怎样的。


3. 动态代理类的源码分析


通过运行Main,我们会得到一个名为“$Proxy”的class文件,这个文件即为动态生成的代理类,我们通过反编译来查看下这个代理类的源代码:


package com.sun.proxy;

 

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Method;

import java.lang.reflect.Proxy;

import java.lang.reflect.UndeclaredThrowableException;

 

public final class $Proxy0 extends Proxy implements Sell {

    //这5个Method对象分别代表equals()、toString()、ad()、sell()、hashCode()方法

    private static Method m1;

    private static Method m2;

    private static Method m4;

    private static Method m3;

    private static Method m0;

 

    //构造方法接收一个InvocationHandler对象为参数,这个对象就是代理类的“直接委托类”(真正的委托类可以看做代理类的“间接委托类”)

    public $Proxy0(InvocationHandler var1) throws  {

        super(var1);

    }

 

    //对equals方法的调用实际上转为对super.h.invoke方法的调用,父类中的h即为我们在构造方法中传入的InvocationHandler对象,以下的toString()、sell()、ad()、hashCode()等方法同理

    public final boolean equals(Object var1) throws  {

        try {

            return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();

        } 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 ad() throws  {

        try {

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

        } catch (RuntimeException | Error var2) {

            throw var2;

        } catch (Throwable var3) {

            throw new UndeclaredThrowableException(var3);

        }

    }

 

    public final void sell() 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)).intValue();

        } catch (RuntimeException | Error var2) {

            throw var2;

        } catch (Throwable var3) {

            throw new UndeclaredThrowableException(var3);

        }

    }

 

    //这里完成Method对象的初始化(通过反射在运行时获得Method对象)

    static {

        try {

            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]);

            m4 = Class.forName("Sell").getMethod("ad", new Class[0]);

            m3 = Class.forName("Sell").getMethod("sell", new Class[0]);

            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);

        } catch (NoSuchMethodException var2) {

            throw new NoSuchMethodError(var2.getMessage());

        } catch (ClassNotFoundException var3) {

            throw new NoClassDefFoundError(var3.getMessage());

        }

    }

}


我们可以看到,以上代码的逻辑十分简单,我们在注释中也做出了相关的说明。


现在,我们已经了解了动态代理的使用,也搞清楚了它的实现原理,更进一步的话我们可以去了解动态代理类的生成过程,只需要去阅读newProxyInstance方法的源码即可,这个方法的逻辑也没有复杂的地方,这里就不展开了。



但是 部落会告诉大家


就在今天晚上  8 : 30


在腾讯课堂


动脑学院的


Java高级免费公开课中


动脑学院  lison大神


将会带你一起学习


深入AOP开发的基石 java动态代理》



你只需今晚8:30分


点击最下方阅读原文即可




推荐阅读  

高并发与分布式系统的基石--数据库读写分离实战

这就是学编程的下场...

论程序员与产品经理是怎么互掐起来的

如何假装成为一名好的程序员

来自部落的邀请

Java框架 Spring 核心机制

至程序员的情书

Java高级部落送你ofo小黄车60天免费骑行,还不来?

Facebook研发的Cassandra你用过吗?

给 Java开发者的10个大数据工具和框架


推荐程序员必备微信号 

Java高级部落

微信号:javagaojibuluo



在部落里,将会分享程序员相关的的技术,职场生活,行业热点资讯....以及更多好玩的IT趣文和趣图都在此部落中.....这....只属于我们程序猿.....


 ▼长按下方↓↓↓二维码识别关注


推荐学习资料获取微信号 

长按下方二维码识别关注


原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 15个月宝宝拉稀怎么办 孩子学习不认真该怎么办 孩子学习不自觉该怎么办 孩子学习压力大该怎么办 分手发信息不回怎么办 两岁宝宝不会回答问题怎么办 分手了我还想他怎么办 两岁宝宝特别不听话怎么办 2岁宝宝争东西怎么办 两岁宝宝钙吸收不好怎么办 1岁宝宝害羞胆小怎么办 2岁宝宝害羞胆小怎么办 6个月的宝宝胆小怎么办 3岁半宝宝很胆小怎么办 分手了想他了怎么办 分手了还想联系怎么办 2岁多宝宝不长肉怎么办 2个月宝宝尿裤子怎么办 四岁宝宝脾气大怎么办 两周宝宝换奶粉怎么办 两周半宝宝不喝奶粉怎么办 2岁半宝宝太调皮怎么办 2岁宝宝晚上睡觉晚怎么办 三周岁半宝宝入园后不合群怎么办 数学懒于思考的孩子怎么办 2岁宝宝爱哭不讲道理怎么办 孩子不讲道理一直哭怎么办 白天不烧晚上烧怎么办 两岁宝宝出虚汗怎么办 两岁宝宝出水痘怎么办 两岁宝宝爱看手机怎么办 两岁宝宝太好动怎么办 五岁宝宝不会数数怎么办 四岁宝宝算数不好怎么办 两个月宝宝体内有火怎么办 两个月宝宝有火怎么办 2岁宝宝起眼屎怎么办 一周岁的宝宝皮肤过敏怎么办 两岁宝宝脾气倔不听话怎么办 22个月宝宝打人怎么办 两岁宝宝会打人怎么办