代理模式 - Proxy Patterns

本篇的父博文是:设计模式 - Design Patterns


一、代理模式的意义

Design Patterns 书中对代理模式的描述是这样的:

Provide a surrogate or placeholder for another object to control access to it.
代理模式为一个对象提供一个代理对象,以控制对这个对象的访问。

代理模式很简单有趣,当然也很重要。往下看就很快明白啦~

二、应用场景

所以,为什么要用代理模式呢?或者说代理模式适用于哪些场景呢?


1. 远程代理人(remote proxy)

试想这样一个场景:你需要引用一个网络中的远程对象(对象在其它计算机JVM中)来完成功能的调用,你该怎么引用?

HelloService helloService = new 这该写啥啊?();

稍微上一点规模的系统都会有多个应用的互相调用的需求。那么,在Java 中这代码我们该如何写呢?

这里写图片描述

没错,最优的代码模板就是用代理模式 的思想去实现。

思路

在本地项目中创建一个远程对象的代理对象 - 作为远程对象的代表,并在代理对象中封装对远程程序的访问/调用细节。此时,代码中不再直接访问远程对象,而是访问这个代理对象。如此,你就如同访问一个本地对象那样访问了远程对象。

下面用一个简单写的订单服务 - OrderService作为例子来实现下。

实现步骤

  1. 首先是一个接口:订单服务,提供了一个方法创建订单。

    /**
     * 订单服务。
     */
    public interface OrderService {
    
        /**
         * 创建订单。
         * @param orderId 订单ID
         * @return
         */
        String createOrder(Integer orderId) throws Exception;
    
    }
  2. 其次呢,其它程序员在远程主机(127.0.0.1)上实现了此接口,并启动了对外服务

    public class OrderRemoteService implements OrderService {
    
        @Override
        public String createOrder(Integer orderId) throws Exception {
            return "我是127.0.0.1,你创建订单成功!ID:" + orderId;
        }
    
    }

    启动对外服务

    /**
     * 启动对外远程服务的main方法类。
     */
    public class RomteMain {
    
        @SuppressWarnings({ "resource" })
        public static void main(String[] args) throws Exception {
            // 实际实现类 - 敲黑板!这里是上面那个实际实现类
            OrderService orderSevice = new OrderRemoteService();
    
            // 下面代码:ServerSocket 监听 client 的Socket 来连接,连上了就根据约定读取client 调用的什么方法,然后转而调用实际实现类,并返回结果
            ServerSocket server = new ServerSocket(9999);// 监听端口
            while (true) {
                final Socket socket = server.accept();
                new Thread(){
                    @Override
                    public void run() {
                        try {
                            ObjectInputStream input = new ObjectInputStream(socket.getInputStream());
                            // 读方法名
                            String methodName = input.readUTF();
                            // 读参数类型
                            Class<?> paramsType = (Class<?>)input.readObject();
                            // 读参数值
                            Object params = (Object)input.readObject();
                            // 使用反射调用相关方法
                            Method method = orderSevice.getClass().getMethod(methodName, paramsType);
                            Object result = method.invoke(orderSevice, params);
                            // 将结果写回给socket 
                            ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream());
                            output.writeObject(result);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }.start();
            }
        }
    }
  3. 然后呢,在我们自己程序里写一个代理类,代理类就作为上面那个实现类在我们本地一个代表

    /**
     * 我是远程对象的代理人。
     * <p>代理对象去找幕后主使来完成任务</p>
     */
    public class OrderServiceProxy implements OrderService {
    
        @SuppressWarnings("resource")
        @Override
        public String createOrder(Integer orderId) throws Exception {
            // 1. 连接远程服务
            Socket socket = new Socket("127.0.0.1", 9999);
            ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream());
            // 自定一个写出顺序,远程对象那里也要按这个顺序读
            output.writeUTF("createOrder");// 写出方法名
            output.writeObject(Integer.class);// 写出参数类型
            output.writeObject(orderId);// 写出参数值
            // 2. 读取返回
            ObjectInputStream input = new ObjectInputStream(socket.getInputStream());
            return (String)input.readObject();
        }
    }
  4. 最后呢,我们程序中要用那个远程对象,我们没法直接引用到它,没关系我们用它的代理人

    public class LocalMain {
        public static void main(String[] args) throws Exception {
            // 客户端引用代理对象,而不是实际对象
            OrderService orderService = new OrderServiceProxy();
            // 直接调用服务就好
            System.out.println(orderService.createOrder(9527));
        }
    }

    以上。

总结
  1. 我们已经简单写了一个代理模式 - 远程代理 Java 程序。

    顺便说下,我们写这个也是个简单的RPC 框架~~

    不过有一定经验的读者会发现,上面代码还是比较挫的,这在Java 中叫所谓的“静态代理”。如果我们要多写几个代理类,多写几个方法,上面代码扩展性就极差了。

    但是,上面只是简单尝试实现了“远程代理”,如果要做一个稍微完善点的RPC ,我们当然需要用到Java 中的“动态代理”。实际上,Java 中之所以有“动态代理”,就是为了解决上面说的那类问题的。不过这里暂且不表了,这个并非主线剧情。

  2. 还有一点,是不是还会有人脑海中会有这样一种思考?这不就是个接口的实现类吗,怎么能叫代理模式呢?

    首先,再看一遍设计模式的概念。

    接下来说:你写一个实现类,是你依赖Java 提供的一种实现Interface的代码形式而写出的代码;而你写一个代理类是参照设计模式的方案和思路写出的代码;它们是两个领域两种完全不同的概念啦。


2. 虚拟代理(virtual proxy)

思路

如果一个对象的创建成本非常高且并不是随时需要它,那么在程序启动时我们就不需要创建此对象,而是仅仅创建此对象的一个代理,让客户端引用这个代理就好了。当客户端实际需要使用这个对象的时候,我们再创建这个对象。

实现步骤

/**
 * 4. 某个客户程序。
 */
public class ClientMain {

    /** 只给客户端持有一个视频服务的代理类,而不是真实服务类 */
    private static VideoService videoService = new MP4VideoProxyService();

    public static void main(String[] args) throws Exception {
        // 客户端程序启动了
        System.out.println("程序启动了--------");
        System.out.println("程序运行很久很久--------");
        Thread.sleep(1000l);

        // 很久后才突然被触发,需要执行以下代码
        System.out.println("N小时候触发了某个事件,我们需要获取视频了--------");
        String result = videoService.getVideo("长城");
        System.out.println(result);
    }
}

/**
 * 1. 视频服务“接口”。
 */
interface VideoService {
    String getVideo(String videoName);
}
/**
 * 2. 视频服务“真实服务类”。
 * <p>初始化很消耗资源,很耗时</p>
 */
class MP4VideoService implements VideoService {

    public MP4VideoService() {
        // 很耗时、耗资源的初始化操作
        System.out.println("我是很耗资源的真实服务,我被创建了!");
    }

    @Override
    public String getVideo(String videoName) {
        return "找到视频:[" + videoName + "]并返回";
    }
}

/**
 * 3. 视频服务一个“代理类”。
 */
class MP4VideoProxyService implements VideoService {

    /** 真实服务类的引用,当真正需要时才给其赋值 */
    private VideoService mp4VideoService;

    @Override
    public String getVideo(String videoName) {
        // 代理类的getVideo方法
        if (mp4VideoService == null) {
            mp4VideoService = new MP4VideoService();
        }
        return mp4VideoService.getVideo(videoName);
    }
}

3. 保护代理(protection proxy)

思路:

使用一个代理对象来控制一个实际对象的访问权限。这种用处就先不写代码了,很好想到,就是保护一个对象,使不同的客户端有不同的访问权限。


4. 智能引用(smart reference)

思路:

智能引用取代了简单的指针引用,在对象被访问时能让我们做一些额外的操作。

实现步骤:

这个最大名鼎鼎的应用就是AOP 了吧。
正好,接下来我使用上面没有使用的,Java动态代理 的方式来实现一个AOP 代码。

  1. 首先,我们创建一个interfaceCalculateService ,用来表示计算服务,并定义加、减、乘和除四个方法

    public interface CalculateService {
    
        int calculateAdd(int a, int b);
    
        int calculateSubtract(int a, int b);
    
        int calculateMultiply(int a, int b);
    
        int calculateDivide(int a, int b);
    }
  2. 然后,我们用一个类来实现这个接口的方法,这个类就是作为一个真是实现类

    public class CalculateServiceImpl implements CalculateService {
    
        public int calculateAdd(int a, int b) {
            return a + b;
        }
    
        public int calculateSubtract(int a, int b) {
            return a - b;
        }
    
        public int calculateMultiply(int a, int b) {
            return a * b;
        }
    
        public int calculateDivide(int a, int b) {
            return a / b;
        }
    
    }
  3. 最后,我们用动态代理的API,来实现AOP ,我们统计下耗费的时间。

    public class MainAOP {
    
        // 现在我们有一个服务CalculateService,它提供了基本的加减乘除。
        // * 突然的,你多了个需求,想在执行加减乘除方法时,都计算一下它执行所耗费的时间。
        // ** 咋办?去改CalculateServiceImpl吗?有时候你没有权限改这个/你不应该侵入人家的代码/其它调用这个服务的人可能并不需要计时
        // ** 那代码怎么写能很好的解决这个问题呢?
        // *** 灯~灯~灯~灯…… 所以你学了设计模式,你想到“可以用aop的思想 + JavaSE中提供的Proxy等类的功能”来写代码。开始!
    
        /** 计算服务 */
        private static CalculateService calculateService = getCalculateService();
    
        /************************************ 模拟使用↓ **************************************/
    
        public static void main(String[] args) {
            // 牛!
            calculateService.calculateAdd(1, 1);
        }
    
        /**
         * 获得计算服务。<br>
         * 你利用你的aop思想,将代码写成了下面这样。<br>
         * 你不费吹灰之力,没有写代理类.java的情况下,把功能给完成了。还对原代码无侵入,还能随意判断,以根据不同的方法去决定是否要计算时间。牛!
         * @return
         */
        private static CalculateService getCalculateService() {
            return (CalculateService) Proxy.newProxyInstance(
                    MainAOP.class.getClassLoader(), 
                    new Class[] { CalculateService.class }, 
                    new InvocationHandler() {
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            long startTime = System.currentTimeMillis();
                            // 核心调用实现类
                            Object result = method.invoke(new CalculateServiceImpl(), args);
                            long endTime = System.currentTimeMillis();
                            // 此次aop的目的:输出耗时
                            System.out.println("耗时:" + (endTime - startTime));
                            return result;
                        }
                    });
        }
    }

    需要解释的点,代码中的注释都写上啦。


以上我们了解了:设计模式之 > 结构模式之 > 代理模式

请持续关注我的博客哈,会继续推出设计模式系列。


转载注明出处:http://blog.csdn.net/u010297957/article/details/53885454

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值