Java设计模式

Java代理模式中的静态代理和动态代理

2018-09-09  本文已影响3人  Geeny

代理模式

假如一个A类具备做甲事情的能力,我们希望它做甲事情之前或之后做多一些操作(比如记录日志),同时又不想去修改A类的结构(因为A类可能有很多其它引用,如果一改A类,所有地方都变动了)。这时候怎么办?

针对这个情况,我们可以在A类外面包多一个类(叫它B类吧),B类同样实现甲方法,B类的甲方法里调用了A类的甲方法,除此之外,B类的甲方法中还额外加多一些操作。随后,调用B类的甲方法,就等同于调用A类的甲方法,同时也执行了我们想要扩展的操作。

一个比较形象的比喻,如果A类是明星,B类就是经纪人,我们想叫明星来演出,跟经纪人说就能实现明星演出这件事,但经纪人在安排明星演出这件事中,可以做其它一些额外的事情,比如出场费商讨、安保工作或行程安排等。经纪人就是明星的代理人,这里的B类就是A类的代理类。

这就是代理模式的概念。来个代码Demo。

定义一个明星接口StarObject

public interface StarInterface {

    public void show(String name);
    
}

Star实现StarInterface接口,实现一些业务逻辑操作

public class Star implements StarInterface {
    
    @Override
    public void show(String name) {
        System.out.println("我是明星" + name + ",我在演出。");
    }

}

实现经纪人类,即代理类

public class Broker implements StarInterface {

    private StarInterface star;
    
    
    public Broker(StarInterface star) {
        this.star = star;
    }
    
    @Override
    public void show(String name) {
        System.out.println("我是经纪人,现在去叫" + name + "来演出。");
        star.show(name);
        System.out.println("我是经纪人," + name + "演出结束,谢谢大家。");
    }

}

运行代码

StarInterface star = new Broker(new Star());
star.show("Leslie Zhang");

得到结果:

我是经纪人,现在去叫Leslie Zhang来演出。
我是明星Leslie Zhang,我在演出。
我是经纪人,Leslie Zhang演出结束,谢谢大家。

这就是代理模式了。但注意,上面所述的是指静态代理模式。有静态就会有动态,那动态代理是什么?

动态代理

上面的方式对于一个类扩展出一个代理类的情况来说,是可以解决问题了。但如果之后对“明星”类要实现更多的“经纪人”类时,我们都需要再写多个代理类,这会变得麻烦和复杂。JDK为此给出了动态代理的方案。

先看Demo。

实现一个动态代理类

public class BrokerDynamic implements InvocationHandler {

    private Object star;
    
    public BrokerDynamic(Object star) {
        this.star = star;  // 传入实现类。
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        
        System.out.println("这里是经纪人在明星演出前的前置处理。");
        
        Object result = method.invoke(star, args);  // 得到并返回方法值
        
        System.out.println("这里是经纪人在明星演出后的后置处理。");
        
        return  result;
    }
    
    // 值得注意的是,这个动态代理类中,对象成员、方法调用返回值类型、方法参数都是Object类型
    // 这表示这个代理类能适用多种类型。
    // 在使用时通过强制转换类型就可以得到指定的对象了。
}

运行代码

StarInterface star = (StarInterface) Proxy.newProxyInstance(StarInterface.class.getClassLoader()
                        ,new Class[] {StarInterface.class}
                        , new BrokerDynamic(new Star()));

star.show("Leslie Zhang");

// 在调用star的show()方法时,代理类会转为去调用BrokerDynamic类invoke方法,
// invoke方法的参数proxy就是发起调用的代理类star实例,
// method就是指show()方法,
// args就是调用show()方法时传的参数"Leslie Zhang"

得到运行结果

这里是经纪人在明星演出前的前置处理。
我是明星Leslie Zhang,我在演出。
这里是经纪人在明星演出后的后置处理。

Proxy.newProxyInstanc是JDK提供的得到一个动态代理类的静态方法。
第一个参数为类加载器,我们这里使用StarInterface接口,就直接得到它对应的类加载器;
第二个参数是指实类所实现的接口列表,我们这里只实现StarInterface接口,故只传一个;
第三个参数为一个实现了调用处理器InvocationHandler接口的实例。

Proxy.newProxyInstanc返回对应类型的代理类,因为返回值为Object类型,所以需要强转一下。

得到代理类后,调用代理类对应的方法,这一个代理类就会自动地调用InvocationHandler实例的invoke方法。这样,我们就可以在invoke里面实现一些扩展操作了。

这就是动态代理的基本概念。

也许你还有这样的问题

  1. 上面的例子倒是很动态,其它的类一传进来都可以生成它对应的代理类,但前置处理和后置处理的操作写死了,并不灵活。
  2. InvocatioHandler中的invoke方法第一个参数proxy就是代理类实例了,在BrokerDynamic类中invoke方法里,直接调用proxy的方法不就可以了,为什么在构造方法中还要另外存一个star实例。

第一个问题确实存在一点局限,或许我们可以用两个抽象方法在BrokerDynamic类的invoke方法中分别作为前置和后置处理,之后再创建子类实类把这两个“坑”补上,便可实现前置后置处理的不同需求。

第二个问题中,形参proxy是指代理类(经纪人),即调用show方法时的代理类实例本身,不是原来的实类(明星),如果我们在invoke方法中,把Object result = method.invoke(star, args); 中的star换为proxy中进行调用,就会再执行一次invoke方法,invoke方法里又是proxy在调用show方法,会形成递归循环。所以,在BrokerDynamic里,我们还是需要另存一个原始实类(明星),在invoke方法中,调用原始实类对应的方法。

上一篇下一篇

猜你喜欢

热点阅读