代理(Proxy)模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。

  所谓代理,可以理解为一个人或者机构代表另一个人或者机构采取行动。

1.  种类     

按照使用目的划分,分为以下几种:   

远程(Remote)代理:为一个位于不同地址空间的对象提供一个局域代表对象。这个不同的地址空间可以是在本机器中,也可是在另一台机器。远程代理又叫做大使。

虚拟代理:根据需要创建一个资源消耗较大的对象,使得此对象只在需要时才会被真正创建。

Copy-on-Write代理:虚拟代理的一种,把复制拖延到客户端需要时才真正采取行动。

保护代理:控制一个对象的访问,如果需要,可以给不同的用户不同级别的访问权。

Cache代理:为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。

防火墙代理:保护目标,不让恶意用户接近。

同步化代理:使几个用户能够同时使用一个对象而不会冲突。

智能引用:当一个对象呗调用时,提供一些额外的操作,比如将此对象调用的次数记录下来。

  在所有的代理模式中,虚拟代理、远程代理、智能引用代理和保护代理模式最为常见。

2.  代理模式

UML类图如下:

代理(Proxy)模式_模式

 

设计到的角色:

抽象主题角色:声明了真实主题和代理主题的共同接口,这样一来在任何使用真实主题的地方都可以使用代理主题。

代理主题角色:代理主题角色内部含有真实主题的引用,从而可以在任何时候操作真实主题对象;代理主题角色提供一个与真实主题角色相同的接口,以便在任何时候可以代理主题角色;

真实主题角色:定义了代理角色所代表的真实对象。

 

 代码如下:

package cn.qlq.proxy;

public abstract class Subject {
    
    abstract void request();
    
}

 

package cn.qlq.proxy;

public class RealSubject extends Subject {

    @Override
    void request() {
        System.out.println("request");
    }

}

 

package cn.qlq.proxy;

public class ProxySubject extends Subject {

    private RealSubject realSubject;

    public ProxySubject() {
        realSubject = new RealSubject();
    }

    @Override
    void request() {
        preRequest();

        realSubject.request();

        postRequest();
    }

    private void postRequest() {
        System.out.println("postRequest");
    }

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

}

 

客户端代码:

package cn.qlq.proxy;

public class Client {

    public static void main(String[] args) {
        Subject subject = new ProxySubject();
        subject.request();
    }

}

 

 代理模式工作过程:

  首先,代理主题并不改变主题的接口,因为模式的用意是不让客户端感觉到代理的存在;

  其次,代理事宜委派将客户端的调用委派给真实的主题对象,换言之,代理主题起到的是一个传递请求的作用;

  最后,代理主题在传递请求之前和之后都可以执行特定的操作,而不是单纯传递请求。

 

3. Java中对代理模式的支持

3.1    静态代理

  静态代理类似于装饰模式,被代理对象与代理对象一起实现相同的接口或者是继承相同父类。其实上面就可以理解为一个静态代理。

 

3.2  JDK代理(接口代理)-动态代理

  从JDK1.3以来,Java在java.lang.reflect包下提供下面三个类支持代理模式:Proxy、InvocationHandler和Method。

  Proxy类使得设计师能够在运行时间创建代理对象。当系统有了代理对象之后,对原方法的调用会首先被分派给一个调用处理器(InvocationHandler)。程序可以在调用处理器的invoke()方法中进行额外的操作。

  显然,Java语言所提供的这一支持是建立在反射基础上的。

  创建代理对象如下:

(1)指明一系列的接口来创建一个代理对象。

 (2)创建一个调用处理器(InvocationHandler)对象。

(3)将这个代理指定为某个其他对象的引用。

(4)在调用处理器的invoke方法中采取代理,一方面将调用传递给真实对象,另一方面执行所需的操作。

 

注意:此模式是基于接口的代理,所以被代理对象必须有接口,代码如下:

package cn.qlq.proxy;

public interface Subject {
    
    void request();
    
}

 

package cn.qlq.proxy;

public class RealSubject implements Subject {

    @Override
    public void request() {
        System.out.println("request");
    }

}

 

package cn.qlq.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * 创建动态代理对象 动态代理不需要实现接口,但是需要指定接口类型
 */
public class ProxyFactory {

    // 维护一个目标对象
    private Object target;

    public ProxyFactory(Object target) {
        this.target = target;
    }

    // 给目标对象生成代理对象
    public Object getProxyInstance() {
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("开始执行之前");
                        // 运用反射执行目标对象方法
                        Object returnValue = method.invoke(target, args);
                        System.out.println("开始执行之后");
                        return returnValue;
                    }
                });
    }

}

 

客户端代码:

package cn.qlq.proxy;

public class Client {

    public static void main(String[] args) {
        Subject target = new RealSubject();

        Subject proxyInstance = (Subject) new ProxyFactory(target).getProxyInstance();

        proxyInstance.request();
    }

}

结果:

开始执行之前
request
开始执行之后

 

3.2  cglib代理(继承代理)-动态代理

 需要的jar包:

代理(Proxy)模式_模式_02

 

  上面的静态代理和动态代理模式都是要求目标对象是实现一个接口的目标对象,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候就可以使用以目标对象子类的方式类实现代理,这种方法就叫做:Cglib代理

Cglib代理,也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展.

  • JDK的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口,如果想代理没有实现接口的类,就可以使用Cglib实现.
  • Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口.它广泛的被许多AOP的框架使用,例如Spring AOP和synaop,为他们提供方法的interception(拦截)
  • Cglib包的底层是通过使用一个小而块的字节码处理框架ASM来转换字节码并生成新的类.不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉.

Cglib子类代理实现方法:

1.需要引入cglib的jar文件,但是Spring的核心包中已经包括了Cglib功能,所以直接引入pring-core-3.2.5.jar即可.
2.引入功能包后,就可以在内存中动态构建子类
3.代理的类不能为final,否则报错
4.目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法.
5.如果方法为static,private则无法进行代理。

 

例如:(RealSubject不实现接口)

package cn.qlq.proxy;

public class RealSubject {

    public void request() {
        System.out.println("request");
    }

}

 

代理工厂:

package cn.qlq.proxy;

import java.lang.reflect.Method;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

/**
 * 创建动态代理对象 动态代理不需要实现接口,但是需要指定接口类型
 */
public class ProxyFactory implements MethodInterceptor {

    // 维护一个目标对象
    private Object target;

    public ProxyFactory(Object target) {
        this.target = target;
    }

    // 给目标对象生成代理对象
    public Object getProxyInstance() {
        // 1.工具类
        Enhancer en = new Enhancer();
        // 2.设置父类
        en.setSuperclass(target.getClass());
        // 3.设置回调函数
        en.setCallback(this);
        // 4.创建子类(代理对象)
        return en.create();
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("执行前...");

        // 执行目标对象的方法
        Object returnValue = method.invoke(target, args);

        System.out.println("执行后...");

        return returnValue;
    }

}

或者:

package cn.qlq.proxy;

import java.lang.reflect.Method;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

/**
 * 创建动态代理对象 动态代理不需要实现接口,但是需要指定接口类型
 */
public class ProxyFactory2 {

    // 维护目标对象
    private Object target;

    public ProxyFactory2(Object target) {
        this.target = target;
    }

    // 给目标对象创建一个代理对象
    public Object getProxyInstance() {
        // 1.工具类
        Enhancer en = new Enhancer();
        // 2.设置父类
        en.setSuperclass(target.getClass());
        // 3.设置回调函数
        en.setCallback(new MethodInterceptor() {
            public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                System.out.println("执行前...");

                // 执行目标对象的方法
                Object returnValue = method.invoke(target, args);

                System.out.println("执行后...");

                return returnValue;
            }
        });
        // 4.创建子类(代理对象)
        return en.create();

    }

}

 

客户端:

package cn.qlq.proxy;

public class Client {

    public static void main(String[] args) {
        RealSubject target = new RealSubject();

        RealSubject proxyInstance = (RealSubject) new ProxyFactory(target).getProxyInstance();

        System.out.println(proxyInstance.getClass());

        proxyInstance.request();
    }

}

结果:

class cn.qlq.proxy.RealSubject$$EnhancerByCGLIB$$2f221c0c
执行前...
request
执行后...

 

总结:

意图:为其他对象提供一种代理以控制对这个对象的访问。

主要解决:在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。

何时使用:想在访问一个类时做一些控制。

如何解决:增加中间层。

关键代码:实现与被代理类组合。

应用实例: 1、Windows 里面的快捷方式。 2、猪八戒去找高翠兰结果是孙悟空变的,可以这样理解:把高翠兰的外貌抽象出来,高翠兰本人和孙悟空都实现了这个接口,猪八戒访问高翠兰的时候看不出来这个是孙悟空,所以说孙悟空是高翠兰代理类。 3、买火车票不一定在火车站买,也可以去代售点。 4、一张支票或银行存单是账户中资金的代理。支票在市场交易中用来代替现金,并提供对签发人账号上资金的控制。 5、spring aop。

优点: 1、职责清晰。 2、高扩展性。 3、智能化。

缺点: 1、由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。 2、实现代理模式需要额外的工作,有些代理模式的实现非常复杂。

使用场景:按职责来划分,通常有以下使用场景: 1、远程代理。 2、虚拟代理。 3、Copy-on-Write 代理。 4、保护(Protect or Access)代理。 5、Cache代理。 6、防火墙(Firewall)代理。 7、同步化(Synchronization)代理。 8、智能引用(Smart Reference)代理。

注意事项: 1、和适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口。 2、和装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制。

 

【当你用心写完每一篇博客之后,你会发现它比你用代码实现功能更有成就感!】