Java动态代理 深度详解(一)

代理模式是设计模式中非常重要的一种类型,而设计模式又是编程中非常重要的知识点,特别是在业务系统的重构中,更是有举足轻重的地位。代理模式从类型上来说,可以分为静态代理和动态代理两种类型。

今天我将用非常简单易懂的例子向大家介绍动态代理的两种类型,接着重点介绍动态代理的两种实现方式(Java 动态代理和 CGLib 动态代理),最后深入剖析这两种实现方式的异同,最后说说动态代理在我们周边框架中的应用。

在开始之前,我们先假设这样一个场景:有一个蛋糕店,它们都是使用蛋糕机来做蛋糕的,而且不同种类的蛋糕由不同的蛋糕机来做,这样就有:水果蛋糕机、巧克力蛋糕机等。这个场景用 Java 语言描述就是下面这样:

//做蛋糕的机器
public interface CakeMachine{
    void makeCake();
}

//专门做水果蛋糕的机器
class FruitCakeMachine implements CakeMachine{
    public void makeCake() {
        System.out.println("Making a fruit cake...");
    }
}

//专门做巧克力蛋糕的机器
public class ChocolateCakeMachine implements CakeMachine{
    public void makeCake() {
        System.out.printf("making a Chocolate Cake...");
    }
}

//蛋糕店
public class CakeShop {
    public static void main(String[] args) {
        new FruitCakeMachine().makeCake();  //making a Fruit Cake...
        new ChocolateCakeMachine().makeCake();  //making a Chocolate Cake...
    }
}

上面的代码抽象出了一个 CakeMachine 接口,有各种蛋糕机(FruitCakeMachine、ChocolateCakeMachine 等)实现了该接口,最后蛋糕店(CakeShop)直接利用这些蛋糕机做蛋糕。

这样的一个例子真实地描述了实际生活中的场景。但生活中的场景往往是复杂多变的,假设这个时候来了一个顾客,他想要一个水果蛋糕,但他特别喜欢杏仁,希望在水果蛋糕上加上一层杏仁。这时候我们应该怎么做呢?

因为我们的蛋糕机只能做水果蛋糕(程序设定好了),没办法做杏仁水果蛋糕。最简单的办法是直接修改水果蛋糕机的程序,做一台能做杏仁水果蛋糕的蛋糕机。这种方式对应的代码修改也很简单,直接在原来的代码上进行修改,生成一台专门做杏仁水果蛋糕的机器就好了,修改后的 FruitCakeMachien 类应该是这样子:

//专门做水果蛋糕的机器,并且加上一层杏仁
class FruitCakeMachine implements CakeMachine{
    public void makeCake() {
        System.out.println("making a Fruit Cake...");
        System.out.println("adding apricot...");
    }
}

虽然上面这种方式实现了我们的业务需求。但是仔细想一想,在现实生活中如果我们遇到这样的一个需求,我们不可能因为一个顾客的特殊需求就去修改一台蛋糕机的硬件程序,这样成本太高!而且从代码实现角度上来说,这种方式从代码上不是很优雅,修改了原来的代码。根据代码圈中「对修改封闭、对扩展开放」的思想,我们在尝试满足新的业务需求的时候应该尽量少修改原来的代码,而是在原来的代码上进行拓展。

那我们究竟应该怎么做更加合适一些呢?我们肯定是直接用水果蛋糕机做一个蛋糕,然后再人工撒上一层杏仁啦。这其实就对应了即使模式中的代理模式,在这个业务场景中,服务员(代理人)跟顾客说没问题,可以做水果杏仁蛋糕,于是服务员充当了一个代理的角色,先让水果蛋糕机做出了水果蛋糕,之后再往上面撒了一层杏仁。在这个例子中,实际做事情的还是水果蛋糕机,服务员(撒杏仁的人)只是充当了一个代理的角色。

下面我们就来试着实现这样一个代理模式的设计。我们需要做的,其实就是设计一个代理类(FruitCakeMachineProxy),这个代理类就相当于那个撒上一层杏仁的人,之后让蛋糕店直接调用即可代理类去实现即可。

//水果蛋糕机代理
public class FruitCakeMachineProxy implements CakeMachine{
    private CakeMachine cakeMachine;
    public FruitCakeMachineProxy(CakeMachine cakeMachine) {
        this.cakeMachine = cakeMachine;
    }
    public void makeCake() {
        cakeMachine.makeCake();
        System.out.println("adding apricot...");
    }
}
//蛋糕店
public class CakeShop {
    public static void main(String[] args) {
          FruitCakeMachine fruitCakeMachine = new FruitCakeMachine();
        FruitCakeMachineProxy fruitCakeMachineProxy = new FruitCakeMachineProxy(fruitCakeMachine);
        fruitCakeMachineProxy.makeCake();   //making a Fruit Cake...   adding apricot...
    }
}

通过代理实现这样的业务场景,这样我们就不需要在原来的类上进行修改,从而使得代码更加优雅,拓展性更强。如果下次客人喜欢葡萄干水果蛋糕了了,那可以再写一个 CurrantCakeMachineProxy 类来撒上一层葡萄干,原来的代码也不会被修改。上面说的这种业务场景就是代理模式的实际应用,准确地说这种是静态代理。

业务场景的复杂度往往千变万化,如果有另外一个客人,他也想在巧克力蛋糕上撒一层杏仁,那我们岂不是也要再写一个代理类让他做同样的一件事情。如果有客人想在抹茶蛋糕上撒一层杏仁,有客人想在五仁蛋糕上撒一层杏仁……那我们岂不是要写无数个代理类?

其实在 Java 中早已经有了针对这种情况而设计的一个接口,专门用来解决类似的问题,它就是动态代理 —— InvocationHandler。

动态代理与静态代理的区别是静态代理只能针对特定一种类型(某种蛋糕机)做某种代理动作(撒杏仁),而动态代理则可以对所有类型(所有蛋糕机)做某种代理动作(撒杏仁)。

接下来我们针对这个业务场景做一个代码的抽象实现。首先我们分析一下可以知道这种场景的共同点是希望在各种蛋糕上都做「撒一层杏仁」的动作,所以我们就做一个杏仁动态代理(ApricotHandler)。

//杏仁动态代理
public class ApricotHandler implements InvocationHandler{

    private Object object;

    public ApricotHandler(Object object) {
        this.object = object;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = method.invoke(object, args);    //调用真正的蛋糕机做蛋糕
        System.out.println("adding apricot...");
        return result;
    }
}

撒杏仁的代理写完之后,我们直接让蛋糕店开工:

public class CakeShop {
    public static void main(String[] args) {
        //水果蛋糕撒一层杏仁
        CakeMachine fruitCakeMachine = new FruitCakeMachine();
        ApricotHandler fruitCakeApricotHandler = new ApricotHandler(fruitCakeMachine);
        CakeMachine fruitCakeProxy = (CakeMachine) Proxy.newProxyInstance(fruitCakeMachine.getClass().getClassLoader(),
                fruitCakeMachine.getClass().getInterfaces(), fruitCakeApricotHandler);
        fruitCakeProxy.makeCake();
        //巧克力蛋糕撒一层杏仁
        CakeMachine chocolateCakeMachine = new ChocolateCakeMachine();
        ApricotHandler chocolateCakeApricotHandler = new ApricotHandler(chocolateCakeMachine);
        CakeMachine chocolateCakeProxy = (CakeMachine) Proxy.newProxyInstance(chocolateCakeMachine.getClass().getClassLoader(),
                chocolateCakeMachine.getClass().getInterfaces(), chocolateCakeApricotHandler);
        chocolateCakeProxy.makeCake();
    }
}

输出结果为:

making a Fruit Cake...
adding apricot...
making a Chocolate Cake...
adding apricot...

从输出结果可以知道,这与我们想要的结果是一致的。与静态代理相比,动态代理具有更加的普适性,能减少更多重复的代码。试想这个场景如果使用静态代理的话,我们需要对每一种类型的蛋糕机都写一个代理类(FruitCakeMachineProxy、ChocolateCakeMachineProxy、MatchaCakeMachineProxy等)。但是如果使用动态代理的话,我们只需要写一个通用的撒杏仁代理类(ApricotHandler)就可以直接完成所有操作了。直接省去了写 FruitCakeMachineProxy、ChocolateCakeMachineProxy、MatchaCakeMachineProxy 的功夫,极大地提高了效率。

看到这里,大家应该清楚为什么有了静态代理之后,还需要有动态代理了吧。静态代理只能针对某一种类型的实现(蛋糕机)进行操作,如果要针对所有类型的实现(所有蛋糕机)都进行同样的操作,那就必须要动态代理出马了。

欢迎加入学习交流群569772982,大家一起学习交流

你可能感兴趣的:(Java动态代理 深度详解(一))