8

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

 3 years ago
source link: https://segmentfault.com/a/1190000039978438
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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

发布于 6 分钟前

谈到代理模式,最常用的使用场景莫过于 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,就无法使用动态代理机制为其生成相应的动态代理对象。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK