设计模式-结构型之代理模式

模式动机

       在某些时候,有些对象出于权限、安全等各方面的因素不想直接被用户访问,此时需要通过设置一个“代理”对象作为第三者来实现间接访问。代理对象在客户端和目标对象中起到一个中介作用,并且通过代理对象可以去掉客户端不能访问的内容和服务或者增加客户需要的一些额外服务。这便是代理对象的动机。就好比那些明星的经纪人,经纪人就是一个代理对象,想找明星拍广告、拍电影等等,一般都是通过经纪人这个第三者来进行勾兑!

模式定义

       给一个对象提供一个代理,由代理对象包含一个目标对象的引用,负责对该对象的访问、控制等等。

模式结构

模式结构
       工厂模式主要由以下四部分组成:
  抽象目标对象:如Target
  具体目标对象:如TargetImpl
  代理对象:如Proxy

代理模式常见的几种类型

  根据代理模式的使用目的,代理模式有以下几种类型:

  • 保护(Protect or Access)代理:控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限。
  • 缓冲(Cache)代理:为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。
  • 防火墙(Firewall)代理:保护目标不让恶意用户接近。
  • 同步化(Synchronization)代理:使几个用户能够同时使用一个对象而没有冲突。
  • 智能引用(SmartReference)代理:当一个对象被引用时,提供一些额外的操作,如将此对象被调用的次数记录下来等。

代码实现

  下面我们就用明星、经纪人来作为例子,进行一个代理模式的简单实现:
  抽象目标对象,我们例子中的明星类

//明星接口,定义明星的公共方法
public interface Star {
    public void shootFilm();//拍电影
}

  具体目标对象,我们例子中的成龙

public class ChengLong implements Star {
    @Override
    public void shootFilm() {
        System.out.println("成龙拍电影...");
    }

}

  代理对象,我们例子中的经纪人

public class ChengLongProxy implements Star {
    private ChengLong cl = new ChengLong();
    @Override
    public void shootFilm() {
        System.out.println("第一步,跟成龙确认后续的档期,看是否有空?");
        System.out.println("第二步,跟成龙确认片酬,看是否满意?");
        cl.shootFilm();//成龙拍电影
        System.out.println("拍摄完成,帮成龙处理善后工作!");
    }
}

  客户端,即找成龙拍电影的人

public class Client {
    public static void main(String[] args) {
        ChengLongProxy proxy = new ChengLongProxy();
        proxy.shootFilm();
    }
}

  运行客户端测试类,输出为

第一步,跟成龙确认后续的档期,看是否有空?
第二步,跟成龙确认片酬,看是否满意?
成龙拍电影...
拍摄完成,帮成龙处理善后工作!

       从上面的例子中,可以看出,当有人想找成龙拍电影时,并不是直接找成龙(ChengLong)本人,而是通过他的经纪人(ChengLongProxy),经纪人在跟成龙确认了档期以及片酬等相关问题后(显然,这些问题都是比较隐私或者机密的,一般人是不能直接去跟成龙沟通的,所以这也算得上是我们上面所说的“保护代理”),如果没有问题,则成龙进行电影的拍摄,最终完成电影拍摄。

动态代理

       上面的代码是静态代理的实现方式,可以看出这种方式灵活性是很差的!试想一下,如果当接口增加一个方法时,比如Star接口突然需要增加一个拍广告shootAds()的方法,那么我们的代理类也需要增加一个代理方法shootAds(),如果我们Star接口里的方法很多,那么我们的代理类也会很多,复杂程度可想而知。

       动态代理则可以解决这个问题。动态代理是指在运行时,动态生成代理类,代理类的字节码将在运行时通过反射机制生成,并载入当前的classloader。动态代理与普通的代理相比较,最大的好处是接口中声明的所有方法都被转移到一个集中的方法中处理,这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。下面以JDK自带动态代理举例:
       jdk动态代理主要涉及一个接口和一个类
java.lang.reflect.InvocationHandler接口:代理类需要实现的接口,通过它的invoke方法可以处理具体实现类的方法调用并返回结果

/*
    Object proxy:指被代理的对象。 
    Method method:要调用的方法 
    Object[] args:方法调用时所需要的参数 
*/
public interface InvocationHandler { 
public Object invoke(Object proxy,Method method,Object[] args) throws Throwable; 
}

       java.lang.reflect.Proxy类:提供用于创建动态代理类,它的newProxyInstance()可以返回一个指定接口的代理类实例。

/*
    ClassLoader loader:类加载器 
    Class<?&#160; &#160; &#160; &#160;[] interfaces:得到全部的接口 
    InvocationHandler h:得到InvocationHandler接口的子类实例 
*/
public static Object newProxyInstance(ClassLoader loader, Class<?&#160; &#160; &#160; &#160;[] interfaces,InvocationHandler h)  throws IllegalArgumentException 

       JDK动态代理-代码示例

  抽象接口

//明星接口,定义明星的公共方法
public interface Star {
    public void shootFilm();//拍电影
    public void shootAds();//拍广告
}

  具体实现类

public class ChengLong implements Star {
    @Override
    public void shootFilm() {
        System.out.println("成龙拍电影...");
    }
    @Override
    public void shootAds() {
        System.out.println("成龙拍广告...");
    }

}

  动态代理类

public class ChengLongProxy implements InvocationHandler {
    private Object target;
    //绑定委托对象(与接口的具体的实现类绑定),并返回一个代理类实例
    //Proxy.newProxyInstance的第三个参数是表明这些被拦截的方法执行时需要执行哪个InvocationHandler的invoke方法
    public Object bind(Object target){
        this.target = target;
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }
    //当绑定的实现类中的方法被调用时,invoke方法将会被执行
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        System.out.println(method.getName()+"()方法被调用");
        System.out.println("第一步,跟成龙确认后续的档期,看是否有空?");
        System.out.println("第二步,跟成龙确认片酬,看是否满意?");
        Object result = method.invoke(target, args);
        System.out.println("拍摄完成,帮成龙处理善后工作!");
        return result;
    }
}

  客户端

public class Client {
    public static void main(String[] args) {
        ChengLongProxy proxy = new ChengLongProxy();//创建代理类实例
        Star star = (Star) proxy.bind(new ChengLong());//把具体实现类(ChengLong)的一个实例传入bind,进行绑定
        //调用方法
        star.shootFilm();
        star.shootAds();
    }
}

  运行客户端测试类,输出为

shootFilm()方法被调用
第一步,跟成龙确认后续的档期,看是否有空?
第二步,跟成龙确认片酬,看是否满意?
成龙拍电影...
拍摄完成,帮成龙处理善后工作!
shootAds()方法被调用
第一步,跟成龙确认后续的档期,看是否有空?
第二步,跟成龙确认片酬,看是否满意?
成龙拍广告...
拍摄完成,帮成龙处理善后工作!

       从上面的代码可以看出,我们的代理类不再需要覆盖对应接口里的shootFilm、shootAds等方法,所有的方法调用通过反射机制全部放到了invoke()里面。以后如果接口增加新的方法时,并不需要修改代理类。

       在jdk动态代理中,必须实现接口(implements Star)才能实现动态代理,如果不想实现接口,那还可以使用其他的动态代理方式。动态代理的实现方式并不止这一种,除了jdk自带的动态代理之外,还有cglib,javassist等都可以实现动态代理,且cglib、javassist不需要实现接口也可以达到动态代理的效果,并且性能、功能上有一定提升。有兴趣的可以查阅相关资料。

总结

       主要介绍了代理模式的含义以及达到的目标,重点介绍了静态代理、动态代理的各自实现以及区别。代理模式是一个非常重要和常见的模式,很值得我们借鉴和学习,很多优秀的框架中,都有动态代理的体现。比如spring aop、hibernate延迟加载都是代理模式的经典应用。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值