代理模式

概念和功能

代理模式(Proxy Pattern)是让一个类代表另一个类(作为另一个类的代理)去实现该类要求的特定功能。这种类型的设计模式属于结构型模式
代理模式和装饰器模式很像。装饰器模式是拿到一个类的实例然后再该类基础上进行添加功能;代理模式也是拿到一个类的实例然后再该类的基础上添加功能,但是代理类相对于装饰类更有“针对性”。打个比方:装饰类就好比“锦上添花”,在拿到了“美丽的锦物”之上还绣上“美丽的花”;代理类好比那“丞相”/“宰相”,拿着“皇帝授予的权利”专门来“为皇帝打理朝政”的“臣子”。一个侧重“添加和完善功能”,一个侧重“代理实施功能”。具体代码实现是什么样的下面再看。

使用场景和方法

使用场景大抵是:1、需要使用现有的类的一些功能,但是“不方便直接访问”或者“懒得访问”,需要通过他的“代理”来访问他的功能。像丞相代替皇帝行驶部分功能一样,中介代替买房人行驶获取买房信息办理买房流程等功能(决策等重要功能还是需要买房人自己实现的),代理律师替代理人行驶辩护权一样。只要涉及到部分功能需要“代理”实现的地方都可以用代理模式。

使用方法大抵是:1、持有委托人的实例去实现需要代理的功能。

实例分析

简单的实例:买房子
买(二手房)房子大抵流程是“找房子”和需要一些琐碎的时间来办理过户手续,其中比较耗时的事情是“找房子”,这部分可以很消耗时间,你要去找到在这段时间在你期望的地段有想要卖房子的人,并且他们家的户型、价格、过户手续要求等都和你期望的相差不大。那这部分耗时操作“找房子”你可以交由“中介”去做。

方法一

静态代理
需要托管出去的功能——找房子接口:

public interface FindHouses {
    void findHouses();
}

委托人类:

public class HouseBuyer implements FindHouses{
    private int money;
    private Object expect;

    @Override
    public void findHouses() {
        //find houses in many ways
    }

    private boolean decideHouse(House house){
        //decide whether or not to buy the house
        return true;
    }

    private void pay(){
        //pay the money
    }

}

中介类:

public class BuyHouseProxy implements FindHouses{
    private FindHouses findHouses;
    public BuyHouseProxy(FindHouses houseBuyer){
        this.findHouses = houseBuyer;
    }

    @Override
    public void findHouses() {
        //invoke the houseBuyers' method findHouses to find the Houses as Requested.
        findHouses.findHouses();
    }
}

自己直接找房子:

//find houses by yourself
FindHouses houseBuyer = new HouseBuyer();
houseBuyer.findHouses();

这么房子肯定还是能找到的,就是很辛苦罢了。

交由中介找房子:

//find houses by proxy
BuyHouseProxy proxy = new BuyHouseProxy(houseBuyer);
proxy.findHouses();

这样子就是中介代替委托人去辛苦的找房子了,你就只需要将相应的要求传达给中介就行了。但是我作为HouseBuyer还有很多事情需要做,比如付房租、付首付、拟购房合同等等,其中很多事情可以交给中介BuyHouseProxy但是还有很多不能交由中介去做,(1.能交给中介的功能:比如拟购房合同——接口需要新加方法,HouseBuyer需要实现该方法,BuyHouseProxy需要实现该方法;2:不能交给中介的功能:比如付首付——接口需要新建一个,HouseBuyer需要实现该接口,BuyHouseProxy需要实现该接口;)同样对于一个系统需要多个服务,每个服务对应都需要一个代理,那这种方式写代理类的工作量就太大了,维护也比较麻烦。为解决这个问题,JDK提供了一种方案:

方法二

动态代理
通过实现InvocationHandler接口来实现运行时生成代理类,我们只需要定义一个你需要“中介”去干哪些事情的接口findHouses和代理生成器的处理类就行,不需要自己新建具体的代理类。
动态代理处理类:

public class DynamicProxyHandler implements InvocationHandler {
    private Object object;
    public DynamicProxyHandler(final Object object) {
        this.object = object;
    }
    @Override
    public Object invoke(Object o, Method method, Object[] args) throws Throwable {
        Object result = method.invoke(object, args);
        return result;
    }
}

由动态代理去找房子:

    	//find houses by dynamic proxy
        FindHouses proxyBuyHouse = (FindHouses) Proxy.newProxyInstance(FindHouses.class.getClassLoader(), 
        new Class[]{FindHouses.class}, new DynamicProxyHandler(houseBuyer));
        proxyBuyHouse.findHouses();

看动态代理处理类就可以发现这个类很像上面的代理类BuyHouseProxy.java,获取到实例之后需要invoke相应参数的方法。而方法Proxy.newProxyInstance前面两个参数[FindHouses.class.getClassLoader(), new Class[]{FindHouses.class}]主要是获取接口的结构来构造动态代理类。那这样我就通过DynamicProxyHandler来生成相关代理类的处理方法了。细分一下newProxyInstance第一个参数是用来定义代理类的classLoader,第二个参数是代理类需要实现的那些接口列表,第三个参数是代理类调用方法的处理类。

两个方法对比: 这个栗子体现不出动态代理的优势,现在我们假设我们很“土豪”——找房子和收集资料都“外包”给“中介”去帮我们去做,那我会有两个service需要proxy去代理。按照我们方法一静态代理去做的话只能新建两个代理类分别“代理实现”两个功能,一个代理类去实现接口“找房子”一个代理类去实现接口“收集购房资料”,他们都是在被代理类实现的基础上“再度实现”相关功能的。针对这种情况,我需要再加一个interface CollectMaterial,一个代理类CollectMaterialProxy,原来的HouseBuyer需要再implements CollectMaterial重写相关的方法。如果按照我们方法二动态代理去做的话,interface CollectMaterial肯定是要加的,但后面我们只需要让原来的HouseBuyer implements CollectMaterial重写相关的方法,然后调用的地方稍微修改下:

//find houses and collect materials by dynamic proxy
        Object proxyInstance = Proxy.newProxyInstance(HouseBuyer.class.getClassLoader(), new
                Class[]{FindHouses.class, CollectMaterial.class}, new DynamicProxyHandler(houseBuyer));
        FindHouses findHouses = (FindHouses) proxyInstance;
        CollectMaterial collectMaterial = (CollectMaterial) proxyInstance;
        findHouses.findHouses();
        collectMaterial.collect();

调用相关方法运行就可以了,不需要再创建具体的代理类了。
动态代理的方法是很灵活的,你可以在一个接口定义多个方法,然后在InvocationHandler里面对method name判断进行不同处理;也可以写多个接口,然后也是在InvocationHandler里面对method name判断进行不同处理;看业务划分去做不同的处理方式。

方法三

CGLIB代理
方法二是“省去了新增proxy类的步骤”,因为这个proxy只是为了“重写一个方法”那它完全可以去掉,方法处理这一步可以提取出来,那就不需要各个proxy类了,只需要这个handler。那那些个“接口”呢?如果没有那些接口行不行?可以!就是方法三:CGLIB代理。CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。但因为采用的是继承,所以不能对final修饰的类进行代理。JDK动态代理与CGLib动态代理均是实现Spring AOP的基础。下面还是看我们的例子

CGLIB代理的处理类:

public class CglibProxy implements MethodInterceptor {
    private Object target;

    public Object getInstance(final Object target) {
        this.target = target;
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(this.target.getClass());
        enhancer.setCallback(this);
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("do sth before");
        Object result = methodProxy.invoke(o, args);
        System.out.println("do sth after");
        return result;
    }
}

由CGLIB代理类去找房子和搜集资料:

		//find houses and collect materials by cglibProxy
		HouseBuyer houseBuyer = new HouseBuyer();
        CglibProxy cglibProxy = new CglibProxy();
        HouseBuyer buyHouseCglibProxy = (HouseBuyer) cglibProxy.getInstance(houseBuyer);
        buyHouseCglibProxy.findHouses();
        buyHouseCglibProxy.collect();

但是这个实现方式有一个缺点:创建代理对象需要花费较长的时间,比JDK多得多,虽然CGLIB创建的动态代理对象比JDK创建的动态代理对象的性能更高。所以对于单例的对象,因为无需频繁创建对象,用CGLIB合适,反之使用JDK方式要更为合适一些。同时由于CGLib由于是采用动态创建子类的方法,对于final修饰的方法无法进行代理。

结构模式之间的比较

结合之前两个结构型模式可以发现:代理模式是增强功能,装饰器模式是增加功能(某种程度上的增强功能),适配器模式是让功能灵活起来。或者说代理模式专注的是增强功能或者让功能更成熟更专业,装饰器模式专注的是增加功能让被装饰类的功能根据需要拆开开发,适配器模式专注的是增加功能的灵活性让被适配类更加灵活、使用更方便;如果你想说装饰器模式的方法也能实现代理模式呀,在两种模式实现的第一种方式中都是拿到类的实例然后再基础上“包装”方法。确实是,从这种实现方式来说,他们很像。这些模式都是抽象的,实现方式也是多样的,不能以一种实现方式很相近就说他们“一样”。就像你给你女票送饭来表达爱意,这和外卖小哥送饭给你女票概念完全不一样。但是你想让“你送外卖”的这个行为显得更专业那你就可以用“外卖小哥”来代替你送这个外卖。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值