Java动态代理

1. 代理模式

代理在我们生活中很常见,比如超市卖烟酒的柜台,他们自己并不生产烟酒,而是一些品牌烟酒的代理商,他们售卖商品,但是商品并不一定是他们直接生产的;房屋中介,房东将需要出租或售卖的房屋委托给中介,中介将房屋信息发布,发布时可能添加一些形容词,比如临街旺铺、学区房等吸引顾客上门。这些都是代理行为,那为什么不能直接和烟酒生产厂商或房东进行直接交易呢?在很多情况下我们是没法直接和这样的对象直接交易的,烟酒生产商不会因为你买一瓶酒就给你直接谈合作,房东为了省事而更想把房子交给中介去出租或售卖,只要花钱就能少操心。
那什么是代理模式呢?代理模式就是为其它对象提供了一种代理以控制这个对象的访问,顾客不是直接和某个具体对象直接打交道,而是和代理进行交互,但是最终实际做事的还是被代理的对象,比如中介卖房(租房),最终签订售房或租房合同的还是和房东进行签订。
代理模式设计的角色如下:


代理模式
  • 被代理的接口Subject,用户并不在乎具体是谁提供了功能,只关心接口的功能,比如一个想租房的人只会找租房中介而不招卖车中介,这是因为客户知道从卖车中介哪里租不来房子,用户也不会关心租房中介到底有什么样的房子出租,但是知道这个租房中介能帮我租到房子,具体租到什么样的房子取决于该中介手里的房子是不是适合我;
  • 接口的一个实现类RealSubject,这里可以是一个房东要出租的具体的房子;
  • 代理类Proxy,实现了Subject接口的类,这里是房屋中介且是具有出租房子能力的中介;
  • 客户端类,这里就是想租房的租户,直接和房屋中介打交道;
    对于代理的使用场景还有很多,比如我们不能直接访问对象A,但是有个中间对象B有直接访问对象A,那我们就让对象B帮我们去和对象A交互,这样我们就像和对象A直接交互一样,用户可能并不知道有中间对象B的存在,给用户一种直接和A交互的假象。

2. 静态代理

上面我们知道了什么是代理,对于马上要说的Java里的动态代理,我们先来看看什么是静态代理,静态代理就是代理关系在代码编译时就确定了,静态代理实现比较简单,适合那种被代理的类比较少且事先就确定的情况。我们使用代码实现房屋中介的例子;
首先我们需要有一个接口叫Rent,代表了房屋出租能力:

public interface Rent {
    void houseRent();
}

然后需要有一个实现了Rent接口的类,这个类是需要被代理的类,该类实现了houseRent方法,表示房屋出租,当调用该方法时,为租户提供房屋。

public class RealHouse implements Rent{
    @Override
    public void houseRent() {
        System.out.println("出租一间70平米次卧");
    }
}

下面是一个房屋中介类

public class HouseAgency implements Rent {
    private RealHouse house;

    public HouseAgency(RealHouse house){
        this.house = house;
    }

    @Override
    public void houseRent() {
        System.out.println("租户需要缴纳房租10%的中介费");
        house.houseRent();
        System.out.println("房租到期租户需结清水电费用");
    }
}

我们来编写测试代码:

public class TestProxy {
    public static void main(String[] args){
        RealHouse realHouse = new RealHouse();
        Rent houseProxy = new HouseAgency(realHouse);
        houseProxy.houseRent();
    }
}

输出:

租户需要缴纳房租10%的中介费
出租一间70平米次卧
房租到期租户需结清水电费用

3. 动态代理

我们上面使用传统的方式实现了代理模式,我们的代理类在事先就关联上了一间70平米的次卧,这样在需要被代理的类较少且确定的情况下是没有问题的,这样在代码编译期间代理关系就确定了,如果我们想要在运行期间动态的创建一些代理来代理不同的目标对象怎么办,动态代理就是代理类不用我们手动去创建,而是在程序运行期间动态的创建出来的,我们用一个超市卖酒的例子来学习动态代理,并分别使用JDK和CGLIB来实现。
想象一下,在超市的柜台上,摆放这各种品牌的烟酒,我们拿酒水来说,酒分为很多种类型,柜台具有售卖烟酒的能力,但是卖烟酒的柜台可能并不生产酒,只是代理商。

4. Java动态代理

4.1 JDK原生动态代理

我们首先使用JDK的动态代理来实现卖酒,之后再来回顾JDK动态代理的实现过程。
首先还是定义一个具有买酒功能的接口,表示我这个柜台具有买酒的能力

public interface SellWine {
    void sellWine();
}

我们第一款酒准备卖青岛啤酒,于是我们有了实现买酒接口的青岛啤酒类

/**
 * 青岛啤酒
 * */
public class TsingTaoBeer implements SellWine{

    @Override
    public void sellWine() {
        System.out.println("售卖青岛啤酒");
    }
}

现在我们有了具体某个品牌的酒,我们还需要一个柜台来摆放我们的酒,于是定义一个实现了InvocationHandler接口的柜台类,实现了该接口的invoke方法

public class Booth implements InvocationHandler {
    private Object brand;

    public Booth(Object brand){
        this.brand = brand;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("本柜台销售有烟酒副食");
        method.invoke(brand, args);
        System.out.println("销售结束");
        return null;
    }
}

最后我们来编写测试代码,销售青岛啤酒

public class TestProxy {
    public static void main(String[] args){
        TsingTaoBeer beer = new TsingTaoBeer();
        InvocationHandler booth1 = new Booth(beer);

        SellWine dynamicProxy = (SellWine) Proxy.newProxyInstance(TsingTaoBeer.class.getClassLoader(),
                TsingTaoBeer.class.getInterfaces(), booth1);
        dynamicProxy.sellWine();
    }

输出:

本柜台销售有烟酒副食
售卖青岛啤酒
销售结束

我们在这个例子中,没有定义代理类,而是通过Proxy类动态生成的代理对象。JDK动态代理的语法:
JDK动态代理一个非常重要的类是Proxy,通过该类的静态方法nnewProxyInstance()来创建动态代理,下面是该静态方法的定义:

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
  • loader是一个类加载器
  • interfaces是要代理的接口,我们这里是SellWine()接口
  • 实现InvocationHandler 接口的具体类的对象
    InvocationHandler 是一个接口,每一个代理的实例都会有一个与之关联的InvocationHandler实现类,如果代理的方法被调用,那么代理就会将转发给内部的InvocationHandler实现类来处理,其实就是调用invoke方法,
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {}
  • proxy是代理的对象
  • method调用代理对象的方法
  • args为调用方法的参数
    调用代理的方法其实真正的执行者就是InvocationHandler实现类。
    卖更多商品
    我们柜台既然能卖酒,我还想卖烟怎么办?我们再定义一个卖烟的接口:
public interface SellSmoke {
    void sellSmoke();
}

我们打算售卖宇宙牌香烟

public class CosmicCigarette implements SellSmoke {
    @Override
    public void sellSmoke() {
        System.out.println("售卖宇宙牌香烟");
    }
}

接着编写测试代码:

public class TestProxy {
        public static void main(String[] args){
            TsingTaoBeer beer = new TsingTaoBeer();
            InvocationHandler booth1 = new Booth(beer);

            CosmicCigarette cigarette = new CosmicCigarette();
            InvocationHandler booth2 = new Booth(cigarette);

            SellWine dynamicProxy = (SellWine) Proxy.newProxyInstance(TsingTaoBeer.class.getClassLoader(),
                    TsingTaoBeer.class.getInterfaces(), booth1);
            dynamicProxy.sellWine();

            SellSmoke dynamicProxy2 = (SellSmoke) Proxy.newProxyInstance(CosmicCigarette.class.getClassLoader(),
                    CosmicCigarette.class.getInterfaces(), booth2);
            dynamicProxy2.sellSmoke();
        }
}

输出:

本柜台销售有烟酒副食
售卖青岛啤酒
销售结束
本柜台销售有烟酒副食
售卖宇宙牌香烟
销售结束

从运行结果可以看到,我们通过Proxy的newProxyInstance方法动态的产生了SellWine和SellSmoke两种接口的实现类代理,而不需要我们去手动编写对应的代理类。
JDK的动态代理依赖接口,被代理的类必须实现接口,下面我们介绍实现动态代理的另一种方式,使用CGLIB库,它能对类进行代理,而不强制类实现实现接口。

4.2 CGLIB动态代理

对于上面的例子,我们使用CGLIB进行改写,CGLIB实现动态代理需要实现MethonInterceptor接口的类,对方法进行拦截。
同样我们定义需要被代理的类,青岛啤酒类

public class TsingTaoBeer {
    public void sellWine(){
        System.out.println("售卖青岛啤酒");
    }
}

然后定义一个实现了MethodInterceptor接口的类

public class SellInterceptor implements MethodInterceptor {

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("本柜台售卖烟酒");
        methodProxy.invokeSuper(o, objects);
        System.out.println("售卖结束");
        return null;
    }
}

编写测试代码

public class TestProxy {
    public static void main(String[] args){
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(TsingTaoBeer.class);
        enhancer.setCallback(new SellInterceptor());

        TsingTaoBeer beerProxy = (TsingTaoBeer) enhancer.create();
        beerProxy.sellWine();

    }
}

使用CGLIB实现动态代理不需要实现接口。

5. 待完善

分析JDK动态代理和CGLIB动态代理详细过程。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容