设计模式之代理模式

1.模式动机

  在某些情况下,一个客户不想或者不能直接引用一个对象,此时可以通过一个称之为"代理"的第三者来实现间接引用。代理对象可以在客户端和目标对象之间起到 中介的作用,并且可以通过代理对象去掉客户不能看到的内容和服务或者添加客户需要的额外服务。

  通过引入一个新的对象(代理对象)来实现对真实对象的操作或者将新的对象作为真实对象的一个替身,这种实现机制即为代理模式,通过引入代理对象来间接访问一 个对象,这就是代理模式的模式动机。

2.模式定义

  代理模式(Proxy Pattern):给某一个对象提供一个代理,并由代理对象控制对原对象的访问(作用)。它是一种对象结构型模式。

3.模式结构

代理模式一般涉及到的角色有
subject(抽象角色):租房子
  真实角色与代理角色的共同接口,也可以是抽象类
RealSubject(真实角色):房东
  定义了代理角色所代表的真实对象。 是我们最终要引用的对象
Proxy(代理角色):中介
  含有对真实角色的引用,代理角色通常在将客户端调用传递给真是主题对象之前或者之后执行某些操作,而不是单纯返回真实的对象。

Java动态代理类位于java.lang.reflect包下,一般主要涉及到以下两个类:
(1)Interface InvocationHandler:是代理实例的<调用处理程序>实现的接口。该接口中仅定义了一个方法
  – public object invoke(Object obj,Method method,Object[] args)
  在实际使用时, 第一个参数obj一般是指代理类,method是被代理的方法,如上例中的request(), args为该方法的参数数组。
  这个抽象方法在代理类中动态实现。
(2)Proxy: 该类即为动态代理类,作用类似于上例中的ProxySubject,其中主要包含以下内容
  protected Proxy(InvocationHandler h):构造函数,用于给内部的h赋值。
  static Class getProxyClass (ClassLoader loader,Class[] interfaces):获得一个代理类,其中loader是类装载器, interfaces是真实类所拥有的全部接口的数组
  static Object newProxyInstance(ClassLoaderloader, Class[] interfaces, InvocationHandler h):
  返回代理类的一个实例,返回后的代理类可以当作被代理类使用(可使用被代理类的在Subject接口中声明过的方法)

动态代理的步骤:
1.创建一个实现接口InvocationHandler的类,它必须实现invoke方法。
2.创建被代理的类以及接口
3.通过Proxy的静态方法
newProxyInstance(ClassLoader loader,Class[] interfaces, InvocationHandler h) 创建一个代理。
4.通过代理调用方法。

4.代码实现

静态代理实现:

//抽象角色:
public interface Subject {
    //租房子
    void request();
}

//真实角色:
//真实角色,房主
public class RealSubject implements Subject {
    public void request() {
        System.out.println("From real subject.");
    }
}
//代理角色:
public class ProxySubject implements Subject {
    // 代理角色内部引用了真实角色
    private RealSubject realSubject; 

    public void request() {
        // 在真实角色操作之前所附加的操作
        this.preRequest(); 
        if (null == realSubject) {
            realSubject = new RealSubject();
        }
        // 真实角色所完成的事情
        realSubject.request(); 
        // 在真实角色操作之后所附加的操作
        this.postRequest(); 
    }

    private void preRequest() {
        System.out.println("pre request");
    }

    private void postRequest() {
        System.out.println("post request");
    }
}
//测试:
public class Client {
    public static void main(String[] args) {
        //实际上想请求的是真实对象,但是见不到真实对象
        //客户端见到的是代理对象
        Subject subject = new ProxySubject();
        //让代理帮你完成真实对象应该完成的事情
        subject.request();
    }
}

  由以上代码可以看出,客户实际需要调用的是RealSubject类的request()方法,现在用ProxySubject来代理 RealSubject类, 同样达到目的,同时还封装了其他方法(preRequest(),postRequest()),可以处理一些其他问题。另外, 如果要按照上述的方法使用代理模式,那么真实角色必须是事先已经存在的(没有房东你出租神马),并将其作为代理对象的内部属性。但是实际使用时,一个真实角色必须对应一个代理角色(100个真实角色就需要100个代理角色),如果大量使用会导致类的急剧膨胀;此外,如果事先并不知道真实角色,该如何使用代理呢? 这个问题可以通过Java的动态代理类。
动态代理实现(抽象主题角色和真实主题角色与静态一样):

//抽象主题角色:
public interface Subject {
    //租房子
    void request();
}

//真实主题角色:
//真实角色,房主
public class RealSubject implements Subject {
    public void request() {
        System.out.println("From real subject.");
    }
}
//代理类:
/**
 * 该代理类的内部属性是Object类型,实际使用的时候通过该类的构造方法传递进来一个对象
 * 此外,该类还实现了invoke方法,该方法中的method.invoke其实就是调用被代理对象的将要
 * 执行的方法,方法参数是sub,表示该方法从属于sub,通过动态代理类,我们可以在执行真实对象的方法前后 加入自己的一些额外方法。
 * 
 */

public class DynamicSubject implements InvocationHandler {
    //代理任何的真实对象,所以是Object类型
    private Object target;

    public DynamicSubject(Object target) {
        this.target = target;
    }
    
    //创建代理对象
    //每一个代理实例都有一个与之对应的InvocationHandler,看newProxyInstance就知道了
    //代理类调用的所有方法都是由InvocationHandler的实例进行接管处理
    //真正的方法调用是在invoke方法中进行的
    public Object createProxyInstance(){
        //第一个参数设置代码使用的类加载器,一般采用跟<目标类>相同的类加载器
        //第二个参数设置代理类实现的接口,它号称实现了真实对象所实现的接口
        //第三个参数设置回调对象,当代理对象的方法被执行时,会调用该参数指定的对象的invoke方法。
        return Proxy.newProxyInstance(//
                target.getClass().getClassLoader(),//
                target.getClass().getInterfaces(),//
                this);
    }
    /**
     * @param proxy 目标对象的代理类实例,也就是代理对象,是哪一个实例的方法被调用
     * @param method 对应于在<代理实例上调用方法>的Method的对象
     *                method是request()方法对应的Method对象
     * @param args 传入到代理实例上方法的参数值的对象数组
     *             代理对象调用的方法如果有参数,会以Object[]的形式传进来
     * @return 方法的返回值。没有返回值时返回null
     */
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        //调用真实方法之前进行的操作
        this.preRequest();
        //执行原方法,采用反射的方式
        Object obj = method.invoke(target, args);
        //调用真实方法之后进行的操作
        this.postRequest();
        return obj;
    }
    
    private void preRequest(){
        System.out.println("preRequest........");
    }
    
    private void postRequest(){
        System.out.println("postRequest......");
    }

}

测试:
public class Client {
    public static void main(String[] args) {
        //真实对象
        Subject realSubject = new RealSubject();
        //创建代理对象
        Subject subject = (Subject) new DynamicSubject(realSubject).createProxyInstance();
        //执行方法(使用的是代理对象)
        //生成代理对象之后,调用request方法,因为subject只是一个代理对象
        //此时这个请求会交给handler接管,流程立马转到了handler中的invoke方法
        subject.request();


    }

}

 5.模式优点
  1.代理模式能够协调调用者和被调用者,在一定程度上降低了系 统的耦合度。
  2.远程代理使得客户端可以访问在远程机器上的对象,远程机器 可能具有更好的计算性能与处理速度,可以快速响应并处理客户端请求。
  3.虚拟代理通过使用一个小对象来代表一个大对象,可以减少系 统资源的消耗,对系统进行优化并提高运行速度。
  4.保护代理可以控制对真实对象的使用权限。
6.模式缺点
  1.由于在客户端和真实主题之间增加了代理对象,因此 有些类型的代理模式可能会造成请求的处理速度变慢。
  2.实现代理模式需要额外的工作,有些代理模式的实现 非常复杂。
-----------------------------------

高能!

举一个代理模式使用的例子。

在权限检查的情况下,调用某一个权限方法,首先要进行权限检查,看看调用者有没有权限来调用这个方法,我们就可以使用代理模式来实现。

假设有一个Service层的实现类UserServiceImpl,它调用Dao层的方法,

UserServiceImpl{
    UserDaoImpl.select(...);
}

我们可以生成UserServiceImpl的一个代理类,

UserServiceImplProxy{
    //权限检查的方法
    checkAuthority();
    UserDaoImpl.select(...);
}

这种方式是不是有点熟悉?看静态代理

看动态代理

没错,我们采用代理模式实现了权限检查。

除了权限检查,有没有资格调用方法,还可以使用代理模式实现:

日志功能:调用了什么方法,执行了什么功能。

事务管理:调用方法的前后事务管理。

原文地址:https://www.cnblogs.com/winner-0715/p/4891497.html