JAVA设计模式之代理模式

代理模式是Java常见的设计模式之一。所谓代理模式是指客户端并不直接调用实际的对象,而是通过调用代理,来间接的调用实际的对象。
为什么要采用这种间接的形式来调用对象呢?一般是因为客户端不想直接访问实际的对象,或者访问实际的对象存在困难,因此通过一个代理对象来完成间接的访问。
在现实生活中,这种情形非常的常见,比如请一个律师代理来打官司。

代理模式的UML图
在这里插入图片描述

动态代理
在这里插入图片描述

从UML图中,可以看出代理类与真正实现的类都是继承了抽象的主题类,这样的好处在于代理类可以与实际的类有相同的方法,可以保证客户端使用的透明性。

为了保持行为的一致性,代理类和委托类通常会实现相同的接口,所以在访问者看来两者没有丝毫的区别。通过代理类这中间一层,能有效控制对委托类对象的直接访问,也可以很好地隐藏和保护委托类对象,同时也为实施不同控制策略预留了空间,从而在设计上获得了更大的灵活性。

更通俗的说,代理解决的问题当两个类需要通信时,引入第三方代理类,将两个类的关系解耦,让我们只了解代理类即可,而且代理的出现还可以让我们完成与另一个类之间的关系的统一管理,但是切记,代理类和委托类要实现相同的接口,因为代理真正调用的还是委托类的方法。

代理模式的实现

使用场合举例:

如果需要委托类处理某一业务,那么我们就可以先在代理类中统一处理然后在调用具体实现类

按照代理的创建时期,代理类可以分为两种:

静态代理:由程序员创建代理类或特定工具自动生成源代码再对其编译。在程序运行前代理类的.class文件就已经存在了。

动态代理:在程序运行时运用反射机制动态创建而成

代码实例:

1、接口

public interface AppService {
    String createApp(String name);
    void deleteApp(String name);
}


2、实现类

/**
 * 具体实现类
 */
public class AppServiceImpl implements AppService {
    @Override
    public String createApp(String name) {
        System.out.println("1----App["+name+"] has been created.");
        return name;
    }

    @Override
    public void deleteApp(String name) {
        System.out.println("2------App["+name+"] has been delete.");
    }
}


静态代理实现:

代理类:

/**
 * 代理类--代理实现类
 */
public class AppServiceProxy implements AppService {
    //目标对象
    private AppService appService;
    //通过构造方法传入目标对象
    public AppServiceProxy(AppService appService){
        this.appService = appService;
    }


    @Override
    public String createApp(String name) {
        //在代理对象前我们可以添加一些自己的操作
        System.out.println("before operation");
        String result = appService.createApp(name);
        //在代理对象后我们也可以添加一些自己的操作
        System.out.println("after operation");
        return name;
    }

    @Override
    public void deleteApp(String name) {
        appService.deleteApp(name);
    }
}



客户端:

public class StaticClient {
    public static void main(String[] args) {
        AppService target = new AppServiceImpl();
        AppServiceProxy proxy = new AppServiceProxy(target);
        proxy.createApp("static proxy");
    }
}


运行结果:

before operation
1----App[static proxy] has been created.
after operation

静态代理类优缺点

优点:

代理使客户端不需要知道实现类是什么,怎么做的,而客户端只需知道代理即可(解耦合),对于如上的客户端代码,new AppServiceImpl()可以应用工厂将它隐藏,如上只是举个例子而已。

缺点:

1)代理类和委托类实现了相同的接口,代理类通过委托类实现了相同的方法。这样就出现了大量的代码重复。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。

2)代理对象只服务于一种类型的对象,如果要服务多类型的对象。势必要为每一种对象都进行代理,静态代理在程序规模稍大时就无法胜任了。如上的代码是只为AppServiceProxy类的访问提供了代理,但是如果还要为其他类如Department类提供代理的话,就需要我们再次添加代理Department的代理类。

举例说明:代理可以对实现类进行统一的管理,如在调用具体实现类之前,需要打印日志等信息,这样我们只需要添加一个代理类,在代理类中添加打印日志的功能,然后调用实现类,这样就避免了修改具体实现类。满足我们所说的开闭原则。但是如果想让每个实现类都添加打印日志的功能的话,就需要添加多个代理类,以及代理类中各个方法都需要添加打印日志功能(如上的代理方法中删除,增加都可以添加上打印日志的功能)

即静态代理类只能为特定的接口(Service)服务。如想要为多个接口服务则需要建立很多个代理类。

动态代理实现:

我们就要定义一个动态代理类了

public class LoggerHandler implements InvocationHandler {
    //这个就是我们要代理的真实对象
    private Object target;

    //构造方法,给我们要代理的真实对象赋初值
    public LoggerHandler(Object target){
        this.target = target;
    }

    //Object proxy:被代理的对象
    //Method method:要调用的方法
    //Object[] args:方法调用时所需要参数
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {
        System.out.println("Entered "+target.getClass().getName()+"-"+method.getName()
                                                    +",with arguments{"+args[0]+"}");

        //在代理真实对象前我们可以添加一些自己的操作
        System.out.println("before invoke");

        //当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
        Object result = method.invoke(target, args);
        //调用目标对象的方法 (调用厂家的方法(createApp)及参数(Kevin Test))
        System.out.println("Before return:"+result);

        //在代理真实对象后我们也可以添加一些自己的操作
        System.out.println("after invoke");

        return result;
    }
}


Client类

public class Client {
    public static void main(String[] args) {
        //我们要代理的真实对象
        AppService target = new AppServiceImpl();
        // 我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的
        InvocationHandler invocationHandler = new LoggerHandler(target);

        /**
         * 通过Proxy的newProxyInstance方法来创建我们的代理对象,我们来看看其三个参数
         * 第一个参数 handler.getClass().getClassLoader() ,我们这里使用handler这个类的ClassLoader对象来加载我们的代理对象
         * 第二个参数realSubject.getClass().getInterfaces(),我们这里为代理对象提供的接口是真实对象所实行的接口,表示我要代理的是该真实对象,这样我就能调用这组接口中的方法了
         * 第三个参数handler, 我们这里将这个代理对象关联到了上方的 InvocationHandler 这个对象上
         */
        AppService proxy =
                (AppService) Proxy.newProxyInstance(
                                                    target.getClass().getClassLoader(),
                                                    target.getClass().getInterfaces(),
                                                    invocationHandler);
        System.out.println(proxy.getClass().getName());
//        for(Method m:proxy.getClass().getDeclaredMethods()){
//            System.out.println(m.getName());
//
//        }
        proxy.createApp("proxy Test1");
        proxy.deleteApp("proxy Test2");
    }
}



运行结果:

com.sun.proxy.$Proxy0
Entered com.boss.design.structuralModel.Proxy.AppServiceImpl-createApp,with arguments{proxy Test1}
before invoke
1----App[proxy Test1] has been created.
Before return:proxy Test1
after invoke
Entered com.boss.design.structuralModel.Proxy.AppServiceImpl-deleteApp,with arguments{proxy Test2}
before invoke
2------App[proxy Test2] has been delete.
Before return:null
after invoke


可以看到,我们可以通过LoggerHandler代理不同类型的对象,如果我们把对外的接口都通过动态代理来实现,那么所有的函数调用最终都会经过invoke函数的转发,因此我们就可以在这里做一些自己想做的操作,比如日志系统、事务、拦截器、权限控制等。这也就是AOP(面向切面编程)的基本原理。

动态代理优点:
动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。而且动态代理的应用使我们的类职责更加单一,复用性更强

总结:

其实所谓代理,就是一个人或者一个机构代表另一个人或者另一个机构采取行动。在一些情况下,一个客户不想或者不能够直接引用一个对象,而代理对象可以在客户端和目标对象之前起到中介的作用。

代理对象就是把被代理对象包装一层,在其内部做一些额外的工作,比如用户需要上facebook,而普通网络无法直接访问,网络代理帮助用户先翻墙,然后再访问facebook。这就是代理的作用了。

纵观静态代理与动态代理,它们都能实现相同的功能,而我们看从静态代理到动态代理的这个过程,我们会发现其实动态代理只是对类做了进一步抽象和封装,使其复用性和易用性得到进一步提升而这不仅仅符合了面向对象的设计理念,其中还有AOP的身影,这也提供给我们对类抽象的一种参考。关于动态代理与AOP的关系,个人觉得AOP是一种思想,而动态代理是一种AOP思想的实现!


版权声明:本文为weixin_36586564原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。