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

微信公众号:大黄奔跑
关注我,可了解更多有趣的面试相关问题。

写在之前

谈到代理模式,最常用的使用场景莫过于 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 代理对象

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

肯定不能了。

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

二手车平台

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

二手手机

……

……

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

中介平台

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

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

image-20210417210040941

开玩笑,继续聊聊 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,就无法使用动态代理机制为其生成相应的动态代理对象。

posted @ 2021-05-11 09:09  SnailsCoffee  阅读(92)  评论(0编辑  收藏  举报