欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2023-12-30 19:00:46
...

代理模式

假如一个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方法中,调用原始实类对应的方法。

上一篇:

下一篇: