代理模式

很多时候我们试图发送一个请求的时候,实际上是由代理将我们的请求转发给目标对象,这种代理方式叫做正向代理,正向代理就是客户端的代理,我们知道访问目标的真实地址,而真实目标只知道这次请求是代理发送的却不知道背后的我们;又有些时候,我们输入某url发送一个请求,实际上这个url并非真实服务器,而是服务器的一个代理,这种代理方式叫做反向代理,反向代理就是服务器的代理,我们不知道访问目标的真实地址,而只知道目标代理的地址。无论是正向代理还是反向代理,都是在实际网络中极为常用的技术,内容分发服务就是一种代理。在编程当中,代理模式也是一种很有用的程序设计模式。

 


1.代理模式

代理模式(Proxy Pattern),为其它对象提供一种代理以控制对这个对象的访问。    ----《大话设计模式》

这里重点是控制,与装饰器模式的设计意图不同,代理模式被用作实际访问对象的接口,代理可以是任何事物的接口,比如:网络连接时,你输入一行地址,大部分时候,首先访问到的其实是代理服务,通常在代理服务器中设置有缓存,以快速响应请求而不必去请求真实服务器,而客户端感知到的效果就好像是访问到了真实服务器一样。所以,我们可以尝试理解代理中的控制,与装饰器中的装饰是不同的,代理通常是将非业务代码从业务代码中分离出来,装饰器模式则是在原有的业务代码基础上,增加额外的业务代码。另一个代理的例子是反向代理,反向代理,意思就是服务器的代理,大名鼎鼎的Nginx就是这样一种代理,它可以将密集的请求,根据管理员设定的策略,转发到分布式系统中的各个真实服务器中去,以达成负载均衡的目的,此外还可以充当防火墙。

与装饰器模式很相似,代理模式的UML类图:

  • Subject: 定义了真实对象RealSubject和代理Proxy的共同接口,这样就可以在任何使用RealSubject的地方使用Proxy了;
  • RealSubject: 真实对象;
  • Proxy: 代理,保存了一个指向RealSubject的引用,这样就可以访问到真实对象的方法。

2. 应用场景和解决方案

什么时候使用代理模式比较合适,通常你有以下需求的时候可以考虑使用代理模式:

 

 

  • 你需要控制对某对象的访问的时候,比如检查本次请求是否有权限访问对象;
  • 访问某对象时,你需要增加一些额外功能时,通常,这些额外功能与访问控制有关。

创建代理模式可以按照如下方法:

  • 继承或实现真实对象的抽象类或接口,使得这个类可以替代真实对象;
  • 实现额外功能,以控制请求的访问。

 


3.代码实现

3.1 静态代理

静态代理,就是在编译阶段就已经创建了代理对象。

假设客户想要打开某一张图片,而图片保存在系统A中的数据库中,显然,每次去从数据库中读取图片是耗时的,通常地做法是第一次请求从数据库中读取图片,短时间第二次请求,就直接从距离客户较近的系统B的缓存中读取图片,这个系统B就设置了代理。

/**
 * 图片的接口,定义显示图片的方法
 */
interface Image {
    void display();
}

 

真实图片类,实现图片接口,构造器调用私有方法loadImageFromDB,从数据库汇总读取图片信息,以初始化图片

/**
 * 真实图片对象
 */
class RealImage implements Image {
    private String filename;

    public RealImage(final String filename) {
        this.filename = filename;
        loadImageFromDB();
    }

    /**
     * 从数据库中获取图片
     */
    private void loadImageFromDB() {
        System.out.println("loading " + filename);
    }


    @Override
    public void display() {
        System.out.println("Displaying " + filename);
    }
}

 

图片代理类, 其保存有真实图片对象的引用。主要display方法,是如何控制对真实对象访问的,

/**
 * 图片对象的代理
 */
class ImageProxy implements Image {
    private Image realImage;
    private String filename;

    public ImageProxy(final String filename) {
        this.filename = filename;
    }

    /**
     * 控制对真实对象的访问:
     * 1.第一次访问时,图片不存在,创建图片类
     * 2.第二次访问时,已有图片,直接调用真实图片的display方法以减少请求耗时
     */
    @Override
    public void display() {
        if (realImage == null) {
            realImage = new RealImage(filename);
        }
        realImage.display();
    }
}

 

客户端调用, 首先创建代理对象,先后两次通过代理对象访问图片资源,这是正向代理。

public class StaticProxyDemo {
    public static void main(String[] args) {
        Image proxy = new ImageProxy("10MB_Image.jpg");
        System.out.println("第一次访问");
        proxy.display();
        System.out.println("第二次访问");
        proxy.display();
    }
}

 

输出结果,可以看到,第二次访问时,没有再次去加载图片了。

第一次访问
loading 10MB_Image.jpg
Displaying 10MB_Image.jpg
第二次访问
Displaying 10MB_Image.jpg

 

3.2 动态代理

动态代理,就是运行时,应用反射机制动态创建的代理对象。java.lang.reflect包中提供了创建动态代理的方法,通常,使用如下步骤创建动态代理:

  1. 实现InvocationHandler接口,代理对象调用的任何方法,都会重定向到InvocationHandler.invoke()中;
  2. 使用Proxy.newProxyInstance(ClassLoader classLoader, Class<?>[] interfaces, InvocationHandler handler)来创建代理对象,其中interfaces是代理类需要实现的一系列接口;
  3. 使用代理对象调用真实对象方法。

还是使用上面的例子,改为动态代理实现,首先实现InvocationHandler接口,invoke方法是需要重写的。所有proxy代理对象调用的方法,都会重定向到invoke中,所以这里特别适合做方法调用的统计。

proxied属性是真实对象的引用,可以看到,invoke中,利用反射技术,最终proxy调用的一切方法都会转发给真实对象proxied执行。

class ImageInvocationHandler implements InvocationHandler {
    private Object proxied;
    private static int count = 0;

    public ImageInvocationHandler(Object proxied) {
        this.proxied = proxied;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("第" + ++count  + "次调用方法: " + method.getName());
        if (null != args) {
            for (Object arg : args) {
                System.out.println(" " + arg);
            }
        }

        return method.invoke(proxied, args);
    }
}

 

实例化InvocationHandler,并将其作为参数,获取到代理对象,注意,为了调用真实对象的方法,一般Proxy.newProxyInstance()之后需要将类型强转为真实对象的接口,之所以能强转,是因为代理proxy实现了真实对象实现的接口Image. 

public class DynamicProxyDemo {
    public static void main(String[] args) {
        ImageInvocationHandler invocationHandler =
                new ImageInvocationHandler(new RealImage("10MB_PNG.png"));
        Image proxy = (Image) Proxy.newProxyInstance(RealImage.class.getClassLoader(),
                new Class[]{Image.class}, invocationHandler);
        proxy.display();
        proxy.display();
    }
}

 

proxy代理对象调用了两次display方法,可以看到结果与静态代理一致。

loading 10MB_PNG.png
第1次调用方法: display
Displaying 10MB_PNG.png
第2次调用方法: display
Displaying 10MB_PNG.png

 

 


4.总结

读懂了上面的内容,也就对什么叫代理有了基本的认识,AOP(面向切面编程)就是采用动态代理来实现的,我认为这是代理模式在我们编程中最为优雅的应用,她实现了业务逻辑和其它逻辑之间的高层次的解耦,使代码更加易维护、易扩展、灵活性更强,所以,我们没有任何理由不去深入理解代理。才外,动态代理利用反射技术,可以在运行时动态地创建代理对象并指定实现的接口,由于动态代理对象调用的一切方法都会重定向到InvocationHandler.invoke方法中,所以动态代理在JavaEE中广泛地应用于日志、统计等框架中,AOP技术就是动态代理技术的应用体现。

posted @ 2019-11-20 09:56  杨浪  阅读(230)  评论(0编辑  收藏  举报