从法外狂徒张三卖房引起的代理模式

写在之前

谈到代理模式,最常用的使用场景莫过于 AOP 中的利用了,在讨论 AOP 的实现之前,先来聊聊什么代理模式。

动态代理有两种形式,静态代理和动态代理,大家先不用在意两者的概念,等了解本篇你将会发现其实两者差别不大。

静态代理

用一个简单的例子来分析什么是静态代理,用买房张三卖房这件事儿为例,聊聊代理模式有何作用,为何如此使用如此频繁。

Subject 接口:用于对被访问者的抽象化(比如卖房这件事儿)

SubjectImpl:被访问者的具体实现(张三想要卖房)

SubjectProxy:被访问者的代理实现类,该类需要有一个 Subject 接口具体的实例。(比如房产中介,需要拿着张三授权才可以代理)

Client:访问者的抽象。(比如李四想买房,本身是一个顾客)

代理模式

代理类本身就是替被访问者做事的,李四想买房子,提了很多要求,比如朝南、学区房;房产中介(SubjectProxy)看张三家的房子刚好符合李四预期,将张三的房子介绍给李四;相当于当一个中间人的意思。有人可能会说,就类似于一个介绍的活儿吗?非得让中介来吗?如果只是简单的介绍,还真不需要中介,但是中介可以帮忙跑贷款、帮忙把握合同等。

这样,张三只需要授权给中介,就可以一边做别的事儿,一边更加省心地完成交易。

上面的图顺理成章变成了如下的模式

卖房理解代理模式

被代理的抽象接口——房子

public interface House {
    /**
     * 卖房子
     */
    void sell();
}

被访问的类——张三的房子

public class HouseForZhangsan implements House {


    @Override
    public void sell() {
        System.out.println("zhangsan sell house…………");
    }
}

代理类——房产中介

public class HouseProxy implements House {

    private House house;

    // 通过构造方法做到每次替不同的人代理
    public HouseProxy (House house) {
        this.house = house;
    }
    @Override
    public void sell() {
        house = new HouseForZhangsan();
        house.sell();
    }
}

测试类——交易场所

public class Test {

    public static void main(String[] args) {
        // 构造具体的卖房者
        House house = new HouseForZhangsan();
        // 将张三交给中介代理
        House houseForPerson = new HouseProxy(house);
        houseForPerson.sell();
    }
}

还是回到 Spring AOP 模式中,其中 SubjectProxy 就像是 SubjectImpl 的中介,而 SubjectImpl 本身是系统中的 JoinPoint 所在的对象(目标对象),顺理成章地为目标对象创建一个代理对象,完成切面的逻辑。

AOP 代理对象

但是各位想想,市面上并不是只有房子卖呢,张三家里有一辆空闲的车,也想卖,还能去找房产中介吗?

肯定不能了。

于是催生出了专门用于车交易的中介,比如瓜子二手车(号称没有中间商赚差价,哈哈哈)、二手车之家等等。

<img src="https://cdn.jsdelivr.net/gh/kaoyan-guide/personPic@main/imgBlog/spring/20210417205209.png" alt="二手车平台" style="zoom:50%;" />

再比如万一张三突然发现手机用久了,想卖掉二手手机,新的 iPhone,于是又出现了各种各样的二手手机平台(其实本质也是一种中介)

<img src="https://cdn.jsdelivr.net/gh/kaoyan-guide/personPic@main/imgBlog/spring/20210417205454.png" alt="二手手机" style="zoom:40%;" />

……

……

等等,那有人可能会想,能不能搞一个中介,能够啥都卖呢?于是更加全面的二手平台应运而生了。

中介平台

在这上面可以灵活的代理各种商品 (被代理对象),这就达到了一种动态中介的效果

对,没错,动态代理已经介绍完了。

<img src="https://cdn.jsdelivr.net/gh/kaoyan-guide/personPic@main/imgBlog/spring/20210417210047.png" alt="image-20210417210040941" style="zoom:50%;" />

开玩笑,继续聊聊 Spring AOP 是如何利用动态代理的。

动态代理

可以指定接口在运行期间动态的生成代理对象。(换句话说:无论你要卖什么,你来的时候都可以给你找一个对应的中介)

那么如何动态生成代理类呢?

需要借助两个工具,一个是 java.lang.reflect.Proxy 类 和 java.lang.reflect.InvocationHandler,问题的关键在于如何实时的给客户产生一个满足要去的中介。

这个就是借助 InvocationHandler来动态生成代理类,还是以上面中介为例,我们姑且讲要生成的代理类叫做 target.

如何动态产生不同类型的中介?

第一步肯定需要知道此时替什么类型客户代理,但是又不能写得太死,我们姑且在生成代理类中先声明一个 被代理的对象。

第二步:通过某种方式将 被代理对象通过传入的方式传进来

第三步:将被代理对象与中介进行绑定。

/**
 * 被代理的目标
 */
public Object target;

/**
 * 绑定委托对象,并且生成代理类
 * @param target
 * @return
 */
public Object bind(Object target) {
    this.target = target;
    //绑定该类实现的所有接口,取得代理类
    return Proxy.newProxyInstance(target.getClass().getClassLoader(), 
                                  target.getClass().getInterfaces(), this);
}   

上述几步部署完成之后,会明白中介要替什么人做事儿,中介做什么事儿,并且将中介与客户关联起来。

客户与中介绑定

最后才是真正的替客户做事儿。

public class SellInvocationHandler implements InvocationHandler {

    /**
     * 被代理的目标
     */
    public Object target;

    /**
     * 绑定委托对象,并且生成代理类
     * @param target
     * @return
     */
    public Object bind(Object target) {
        this.target = target;
        //绑定该类实现的所有接口,取得代理类
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), 
                                      target.getClass().getInterfaces(), this);
    }


    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        log.info("额外逻辑……");
        return method.invoke(target, args);
    }
}

还记得我们之前说过的吗?

动态代理解决的只是灵活产生不同代理类(换句话说灵活搭配不同类型中介)

至于做什么类型事儿,和替什么人做什么事儿这两件事儿还是得存在。

因此仍然需要申明两个类

做什么类型事儿

public interface House {
    /**
     * 卖房子
     */
    void sell();
}

替什么人做什么事儿

public class HouseForZhangsan implements House {
    @Override
    public void sell() {
        System.out.println("zhangsan sell house…………");
    }
}

然后就可以愉快地进行交易了,每次有新的顾客来,就可以叫不同类型的中介来服务。

public class DynamicProxyTest {

    public static void main(String[] args) {

        SellInvocationHandler invocationHandler = new SellInvocationHandler();

        // 将被访问类和代理类相互绑定( 将房产中介 与 房子卖者相互绑定 )
        House house = (House) invocationHandler.bind(new HouseForZhangsan());
        
        // 真正执行
        house.sell();
    }
}

至此,我们已经完成了真正的灵活代理工作。

动态代理虽好,却不能解决所有的事情。比如,动态代理只能对实现了相应接口 (Interface) 的类使用,如果某个类没有实现任何的 Interface,就无法使用动态代理机制为其生成相应的动态代理对象。

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

推荐阅读更多精彩内容