设计模式——动态代理

143 阅读4分钟

什么是代理模式

在我们现实生活中的购物场景中,我们通常是从代理商那里购买产品,而不是从厂家那里购买。

而代理模式的思想也是类似的,它分为几个角色:

  • 抽象角色:一般是接口或抽象类
  • 真实角色 : 被代理的角色
  • 代理角色 : 代理真实角色 ; 它在代理真实角色的基础上 , 一般会做一些附属的操作。
  • 客户:使用代理角色进行一些操作。

静态代理

以电影上映为例:电影是电影公司委托给电影院进行播放的,而电影院在提供播放电影的服务之外,还可以提供其他服务:例如售卖零食等。现在我们用代理模式来实现这个过程

(1)抽象角色:电影接口,它描述了它的功能——播放电影

public interface Movie {
    void play();
}

(2)被代理类:实现电影接口的动作片电影:

public class ActionMovie implements Movie {
    public void play() {
        System.out.println("正在播放动作电影《终结者》");
    }
}

(3)代理类:电影院:

public class Cinema implements Movie{
    ActionMovie am;
    public Cinema(ActionMovie am) {
        this.am = am;
    }


    public void play() {
        playAdvertisement(true);
        am.play();
        playAdvertisement(false);
    }

    public void playAdvertisement(boolean isStart){
        if ( isStart ) {
            System.out.println("电影马上开始,开始售卖零食");
        } else {
            System.out.println("电影即将结束,开始关门");
        }
    }

}

(4)测试方法:

@Test
public void testProxy() {
    Movie movie = new Cinema(new ActionMovie());
    movie.play();
}

可以发现,代理模式可以让代理类(电影院)在不修改被代理对象(动作片电影类)的基础上,加上一些新功能。而所谓的静态代理是指:代理类(电影院类)的.class文件在运行前就已经存在。

静态代理的好处

  • 公共业务由代理完成,实现了业务的分工。并且公共业务发生扩展时更加方便和集中;
  • 真实角色不需要关注公共事情。

缺点

如果真实对象增加,比如此处电影院还可以代理播放其他类型的电影,那么就要在代理类Cinema里原有的基础上添加代码,这使得工作量大大增加。

动态代理

在动态代理中,代理类的字节码在程序运行时由Java反射机制动态生成,无需程序员手动编写源码。

同样以电影院上映为例,保持电影接口,动作片电影类不变,修改一下代理类:

(1)代理类不再是实现Movie接口,而是继承一个InvocationHandler类,表明它是一个动态代理类

public class Cinema implements InvocationHandler {
   Object movie;

    public Cinema(Object movie) {
        this.movie = movie;
    }

    public void playAdvertisement(boolean isStart){
        if ( isStart ) {
            System.out.println("即将开始播放,开始售卖零食");
        } else {
            System.out.println("即将结束播放,开始关门");
        }
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        playAdvertisement(true);
        method.invoke(movie, args);
        playAdvertisement(false);
        return null;
    }

}

(2)测试方法

    @Test
    public void testProxy() {
        InvocationHandler movie = new Cinema(new ActionMovie());
        //代理类
        Movie movieProxy  = (Movie) Proxy.newProxyInstance(ActionMovie.class.getClassLoader(),
                ActionMovie.class.getInterfaces(), movie);
        movieProxy.play();
    }

相关类

上面的代码是通过Proxy类的静态方法newProxyInstance ()来动态创建代理类。

Proxy提供了创建动态代理类和实例的静态方法,它同时也是由这些方法创建而来的所有动态代理类的父类。

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
//loader对象表示类加载器;interfaces对象表示代理接口;

InvocationHandler类是什么

InvocationHandler 是一个接口,每个代理的实例都有一个与之关联的InvocationHandler实现类(本例的电影院对象)。当代理的方法被调用时,代理便会通知和转发给内部的InvocationHandler实现类。其实现类可以通过接口方法invoke来调用被代理类(本例的动作电影类)的方法。

public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
    //代理对象proxy;代理对象调用的方法method;调用方法的参数args
}

扩展案例

在原来案例的基础上,我们再增加需求:电影院还可以播放悬疑片:

public class SuspenseMovie implements Movie {
    public void play() {
        System.out.println("正在播放《惊魂记》");
    }
}

测试方法:

    @Test
    public void testProxy() {
        InvocationHandler actionMovie = new Cinema(new ActionMovie());
        InvocationHandler suspenseMovie = new Cinema(new SuspenseMovie());
        //代理类
        Movie actionMovieProxy   = (Movie) Proxy.newProxyInstance(ActionMovie.class.getClassLoader(),
                ActionMovie.class.getInterfaces(), actionMovie);
        Movie susMovieProxy = (Movie) Proxy.newProxyInstance(
                SuspenseMovie.class.getClassLoader(),
                SuspenseMovie.class.getInterfaces(), suspenseMovie);

        actionMovieProxy .play();
        susMovieProxy.play();
    }

相比于只能代理某一角色的静态代理,动态代理可以代理某一类业务(多个类),比如本例中Cinema代理的是电影接口的实现类,它不会更改原有代码。