承接上文

三.计数代理

计数代理的应用场景是:当客户程序需要在调用服务提供者对象的方法之前或之后执行日志或者计数等额外功能时,就可以用到技术代理模式。计数代理模式并不是把额外操作的代码直接添加到原服务中,而是把它们封装成一个单独的对象,这就是计数代理。

考虑这样一个应用,用计数代理统计图书馆中每天借阅书籍的具体次数。

1.定义书籍基本类Book

  1. public class Book {
  2. private String No;
  3. private String name;
  4.  
  5. public Book(String no, String name) {
  6. No = no;
  7. this.name = name;
  8. }
  9.  
  10. public String getNo() {
  11. return No;
  12. }
  13.  
  14. public void setNo(String no) {
  15. No = no;
  16. }
  17.  
  18. public String getName() {
  19. return name;
  20. }
  21.  
  22. public void setName(String name) {
  23. this.name = name;
  24. }
  25. }

2.定义抽象主题接口IBorrow

  1. public interface IBorrow { //借阅过程
  2. boolean borrow();
  3. }

3.定义借阅实现类Borrow

  1. public class Borrow implements IBorrow{
  2. private Book book;
  3. public void setBook(Book book){
  4. this.book = book;
  5. }
  6. public Book getBook(){
  7. return book;
  8. }
  9. public boolean borrow(){
  10. //保存信息到数据库等功能,代码就不写了哈哈哈
  11. return true;
  12. }
  13. }

4.定义借阅代理类BorrowProxy

  1. public class BorrowProxy implements IBorrow {
  2. private Borrow obj;
  3. private Map<String,Integer> map;
  4. public BorrowProxy(Borrow obj){
  5. this.obj = obj;
  6. map = new HashMap();
  7. }
  8. public boolean borrow(){
  9. if(!obj.borrow()){ //借阅失败
  10. return false; //返回
  11. }
  12. Book book = obj.getBook();
  13. Integer i = map.get(book.getNo());
  14. i = (i == null) ? 1 : i+1;
  15. map.put(book.getNo(),i);//保存书号-次数 键值对
  16. return true;
  17. }
  18. public void log() throws Exception{
  19. Set<String> set = map.keySet();
  20. String key = "";
  21. String result = "";
  22. Iterator<String> it = set.iterator();
  23. while(it.hasNext()){
  24. key = it.next();
  25. result += key + "\t" + map.get(key) + "\r\n";
  26. }
  27. Calendar c = Calendar.getInstance();
  28. RandomAccessFile fa = new RandomAccessFile("D:/log.txt","rw");
  29. fa.seek(fa.length());
  30. fa.writeBytes(+c.get(Calendar.YEAR) + "-" + (c.get(Calendar.MONTH) +1) +
  31. "-" + c.get(Calendar.DAY_OF_MONTH) + "\r\n");//记录日志时间
  32. fa.writeBytes(result);
  33. fa.close();
  34. }
  35. }

该代理类定义了Map成员变量map,键是书号,每天借阅次数是值。日志文件中首先存放当前时间,然后保存“书号和借阅次数信息”,一行一条记录。

5.简单测试类

  1. public class Test {
  2. public static void main(String[] args) throws Exception{
  3. Borrow br = new Borrow();
  4. BorrowProxy bp = new BorrowProxy(br);
  5.  
  6. Book book = new Book("1000","计算机应用");
  7. br.setBook(book);
  8. bp.borrow();
  9.  
  10. book = new Book("1001","计算机应用2");
  11. br.setBook(book);
  12. bp.borrow();
  13. bp.log();
  14. }
  15. }

可以看出示例并没有实现一天记录日志一次,因为从设计思想角度来说,时间控件应该在主框架实现,到了约定时间,调用代理类的log()方法即可。

四.动态代理

上述所说的代理模式都是静态代理,所谓静态代理就是 一个主题类与一个代理类一一对应。而动态代理,则是多个主题对应了一个代理类。它们共享“前处理,后处理”功能,动态调用所需主题,大大减少了程序规模,这就是动态代理的特显。实现动态而代理的关键是反射。程序框架如下图所示

在JAVA设计模式:动态代理(一)提到的RMI代理模式的内容,该功能完全可以由动态代理模式来实现,具体过程如下

1.创建服务器工程URmiServer

1)定义抽象主题远程接口ICalc

  1. public interface ICalc {
  2. float calc(String s) throws Exception;
  3. }

2)定义具体远程主题实现ServerCalc

  1. package BridgeModel.Proxy.dynamicProxy;
  2.  
  3. /**
  4. * Created by lenovo on 2017/4/22.
  5. */
  6. public class ServerCalc implements ICalc{
  7. public float calc(String s) throws Exception{
  8. s += "+0";
  9. float result = 0;
  10. float value = 0;
  11. char opcur = '+';
  12. char opnext;
  13. int start = 0;
  14. if(s.charAt(0)=='-'){
  15. opcur = '-';
  16. start = 1;
  17. }
  18. for(int i=start;i<s.length();i++){
  19. if(s.charAt(i)=='+' || s.charAt(i)=='-'){
  20. opnext = s.charAt(i);
  21. value = Float.parseFloat(s.substring(start,i));
  22. switch (opcur){
  23. case '+': result += value; break;
  24. case '-': result -= value; break;
  25. }
  26. start = i+1;
  27. opcur = opnext;
  28. i = start;
  29. }
  30. }
  31. return result;
  32. }
  33. }

3)定义服务器端远程代理类ServerProxy

  1. public class ServerProxy {
  2. public static Map<String,Object> map = new HashMap();
  3. public void registry(String key,Object value){ //远程对象注册功能
  4. map.put(key,value); //注册到HashMap映射中
  5. }
  6. public void process(int socketNO) throws Exception{
  7. ServerSocket s = new ServerSocket(socketNO);
  8. while(true){
  9. Socket socket = s.accept();
  10. if(socket != null){
  11. MySocket ms = new MySocket(socket);
  12. ms.start();
  13. }
  14. }
  15. }
  16. }

该类是远程代理通信功能的核心类。通过registry()方法,把主题对象注册到成员变量map映射中,方便将来获得该对象,并利用反射机制执行该对象的方法。

Process()方法表明ServerProxy是一个多线程类,常规线程负责监听客户端的链接,若有连接,则获得连接并创建MySocket线程对象,启动该线程。

4)MySocket类

  1. public class MySocket extends Thread{
  2. Socket socket;
  3. public MySocket(Socket socket){
  4. this.socket = socket;
  5. }
  6. public Object invoke(String registname,String methodname,Object para[]) throws Exception{
  7. Object obj = ServerProxy.map.get(registname); //获得注册对象
  8. //形成函数参数列表
  9. Class classType = Class.forName(obj.getClass().getName());
  10. Class c[] = new Class[para.length];
  11. for(int i=0;i<para.length;i++){
  12. c[i] = para[i].getClass();
  13. }
  14. Method mt = classType.getMethod(methodname,c);
  15. return mt.invoke(obj,para);
  16. }
  17. public void run(){
  18. while(true){
  19. try{
  20. InputStream ins = socket.getInputStream();
  21. if(ins == null || ins.available() == 0)
  22. continue;
  23. //前处理
  24. ObjectInputStream in = new ObjectInputStream(ins);
  25. ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
  26. String registname = (String)in.readObject(); //获得远程对象注册名称
  27. String methodname = (String)in.readObject(); //获得远程调用方法名称
  28. Object[] para = (Object[])in.readObject(); //获得远程对象方法参数列表
  29.  
  30. //动态调用主题对象
  31. Object result = invoke(registname,methodname,para);
  32. //后处理
  33. out.writeObject(result); //将结果返回给客户端
  34. out.flush();
  35. }
  36. catch (Exception ex){
  37. ex.printStackTrace();
  38. }
  39. }
  40. }
  41. }

线程运行run方法中包含了远程代理服务端的主要功能,“前处理”主要是三次读网络,第一次获取远程对象注册名称registname,第二次获得方法名methodname,第三次获得方法参数para,根据获得这三个值,利用反射机制完成“动态主题调用”,主要体现在invoke()方法中,“后处理”负责把结果写回客户端。

对于某功能来说,若使用动态代理,那么“前处理和后处理”功能的划分是最为关键的!

5)简单测试类

  1. public class URmiServer {
  2. public static void main(String[] args) throws Exception{
  3. ServerCalc obj = new ServerCalc();
  4. ServerProxy spoobj = new ServerProxy();
  5. spoobj.registry("calc",obj);
  6. spoobj.process(4000);
  7. }
  8. }

2.创建客户端工程URmiClient

1)定义抽象主题远程接口ICalc

  1. public interface ICalc {
  2. float calc(String s) throws Exception;
  3. }

2)定义客户端计算代理ClientProxy

  1. public class ClientProxy implements ICalc {
  2. ClientComm comm;
  3. public ClientProxy(String IP, int socketNO) throws Exception{
  4. comm = new ClientComm(IP,socketNO);
  5. }
  6. public float calc(String s) throws Exception{
  7. Float result = (Float)comm.invoke("calc","calc",new Object[]{s});
  8. return result.floatValue();
  9. }
  10. }

该类从接口ICalc派生,但calc方法中并没有真正细线求表达式的具体过程。只是把参数负责向远程服务器端传送,由ClientComm类对象具体完成,也就是说发送任意远程方法所需相关参数是由该类完成,是共享的。

  1. public class ClientComm {
  2. Socket socket;
  3. public ClientComm(String IP,int socketNO) throws Exception{
  4. socket = new Socket(IP,socketNO);
  5. }
  6. Object invoke(String registname,String methodname,Object[] para) throws Exception{
  7. //前处理
  8. ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
  9. ObjectInputStream in = new ObjectInputStream(socket.getInputStream());
  10. out.writeObject(registname);
  11. out.writeObject(methodname);
  12. out.writeObject(para);
  13. return in.readObject(); //后处理
  14. }
  15. }

Invoke方法中有三个参数,第一个是远程对象注册名称,第二个是方法名称,第三个方法参数,因此向网络写三次,然后读网络,等待返回结果。

3)一个简单测试类

  1. public class URmiClient {
  2. public static void main(String[] args) throws Exception{
  3. ICalc obj = new ClientProxy("localhost",4000);
  4. System.out.println(obj.calc("1+5+10"));
  5. System.out.println(obj.calc("1+5+20"));
  6. }
  7. }

先运行URmiServer,再运行URmiClient,就会得到26和36了!

希望通过上面的demo,读者可以对动态代理有一个初步的理解。当然,这个demo中还有一些缺陷例如客户端执行完没有通知服务器端断开socket通信等。

下面简单再讲讲JDK动态代理

动态代理工具是java.lang.reflect包的一部分。它允许程序创建代理对象。他能实现一个或多个已知接口,并用反射机制动态执行相应主题对象。

下面一用JDK动态代理仿真这样一个应用:有两种渠道接受信件,一种是通过Email方式,另一种是通过传统邮寄方式。对来的信件都要进行登记,现在要求利用代理模式增加功能,对不同的方式来的信件统计。

1.定义抽象主题IRegist

  1. public interface IRegist {
  2. void regist(String msg); //对来的信件进行登记
  3. }

2.定义两个具体主题

  1. public class fromEmail implements IRegist { //Email信件登记类
  2. public void regist(String msg){
  3. System.out.println("from Email");
  4. }
  5. }
  1. public class fromPost implements IRegist {
  2. public void regist(String msg){
  3. System.out.println("from post");
  4. }
  5. }

3.定义动态代理相关类以及接口

上面以及定义了两个主题类fromEmail,fromPost。如果是静态代理,那么一定需要两个代理类。由于功能相同,如果采用JDK动态代理,那么只需要一个代理类,但是必须严格规范编程。具体步骤如下

1)定义计数实现类CountInvoke

  1. public class CountInvoke implements InvocationHandler {
  2. private int count = 0;
  3. private Object obj;
  4. public CountInvoke(Object obj){
  5. this.obj = obj;
  6. }
  7.  
  8. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
  9. count ++;
  10. method.invoke(obj,args); //对主题对象obj应用反射技术调用相应方法
  11. return null;
  12. }
  13.  
  14. public int getCount(){
  15. return count;
  16. }
  17.  
  18. }

注意,该类并不是动态代理类,它是动态代理类所调用的接口实现类,因此接口不能随意,有系统指定,接口名为InvocationHandler

Invoke是接口定义的方法,必须实现,该方法完成了代理所需要的功能,包括前处理,利用反射技术调用主题方法,后处理等。一般来说,实现动态代理,主要是完成接口方法invoke的编写。

2)创建代理类GenericProxy

  1. public class GenericProxy {
  2. public static Object createProxy(Object obj, InvocationHandler invokeObj){
  3. Object proxy = Proxy.newProxyInstance(obj.getClass().getClassLoader(),
  4. obj.getClass().getInterfaces(),invokeObj);
  5. return proxy;
  6. }
  7. }

主要通过createProxy()产生代理对象,该方法有两个参数,第一个参数是obj表示具体代理对象,第二个参数是invokeOBj表示代理调用的接口实现类对象。简单来说,就是为了主题对象obj创建代理对象,并与调用接口invokeobj对象建立关联,以便将来代理对象能调用接口方法。

3)简单测试类Test

  1. public class Test {
  2. public static void main(String[] args) {
  3. IRegist email = new fromEmail();
  4. IRegist post = new fromPost();
  5. CountInvoke emailinvoke = new CountInvoke(email);
  6. CountInvoke postinvoke = new CountInvoke(post);
  7.  
  8. IRegist emailproxy = (IRegist)GenericProxy.createProxy(email,emailinvoke);
  9. IRegist postproxy = (IRegist)GenericProxy.createProxy(post,postinvoke);
  10.  
  11. emailproxy.regist("email1");
  12. postproxy.regist("post1");
  13. System.out.println(emailinvoke.getCount());
  14. System.out.println(postinvoke.getCount());
  15. }
  16. }

Java设计模式:代理模式(二)的更多相关文章

  1. Java设计模式-代理模式之动态代理(附源代码分析)

    Java设计模式-代理模式之动态代理(附源代码分析) 动态代理概念及类图 上一篇中介绍了静态代理,动态代理跟静态代理一个最大的差别就是:动态代理是在执行时刻动态的创建出代理类及其对象. 上篇中的静态代 ...

  2. JAVA 设计模式 代理模式

    用途 代理模式 (Proxy) 为其他对象提供一种代理以控制对这个对象的访问. 代理模式是一种结构型模式. 结构

  3. Java设计模式の代理模式

    目录  代理模式 1.1.静态代理   1.2.动态代理 1.3.Cglib代理 代理模式 代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样做的好处是 ...

  4. Java设计模式——代理模式实现及原理

    简介 Java编程的目标是实现现实不能完成的,优化现实能够完成的,是一种虚拟技术.生活中的方方面面都可以虚拟到代码中.代理模式所讲的就是现实生活中的这么一个概念:中介. 代理模式的定义:给某一个对象提 ...

  5. Java设计模式 - 代理模式

    1.什么是代理模式: 为另一个对象提供一个替身或占位符以访问这个对象. 2.代理模式有什么好处: (1)延迟加载 当你需要从网络上面查看一张很大的图片时,你可以使用代理模式先查看它的缩略图看是否是自己 ...

  6. Java设计模式—代理模式

    代理模式(Proxy Pattern)也叫做委托模式,是一个使用率非常高的模式. 定义如下:     为其他对象提供一种代理以控制对这个对象的访问. 个人理解:        代理模式将原类进行封装, ...

  7. Java设计模式-代理模式(Proxy)

    其实每个模式名称就表明了该模式的作用,代理模式就是多一个代理类出来,替原对象进行一些操作,比如我们在租房子的时候回去找中介,为什么呢?因为你对该地区房屋的信息掌握的不够全面,希望找一个更熟悉的人去帮你 ...

  8. Java设计模式--代理模式+动态代理+CGLib代理

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

  9. Java设计模式——代理模式

    public interface People { public void work(); } public class RealPeople implements People { public v ...

  10. Java设计模式(十二) 策略模式

    原创文章,同步发自作者个人博客,http://www.jasongj.com/design_pattern/strategy/ 策略模式介绍 策略模式定义 策略模式(Strategy Pattern) ...

随机推荐

  1. java 使用Stack来判断Valid Parentheses

    假如定义形如"{}[]()"或者"{[()]}"的模式为valid,"[{]"或者"(("的模式为invalid,那么我 ...

  2. Asp.net MVC 视图使用像Ajax,ViewBag提示为找到上下文

    不知是什么原因,所有的视图中Ajax,ViewBag之类的都提示为找到上下文(由于换了个版本Vs,猜测应该是Vs的原因),然后顺利在网上找到了解决方案. 给地址链接:https://social.ms ...

  3. 1754: [Usaco2005 qua]Bull Math

    1754: [Usaco2005 qua]Bull Math Time Limit: 5 Sec  Memory Limit: 64 MBSubmit: 398  Solved: 242[Submit ...

  4. 关于EasyUI 1.5版Datagrid组件在空数据时无法显示"空记录"提示的BUG解决方法

    问题:jQuery easyUI中Datagrid,在表格数据加载无数据的时候,如何显示"无记录"的提示语? 解决jQuery EasyUI 1.5.1版本的Datagrid,在处 ...

  5. 企业架构(TOGAF)学习

    自从听了公司内部的一堂<企业架构设计>培训,顿时觉得如获至宝. 先说下笔者,笔者是一名二流本科毕业,工作三年,基层的软件开发工程师,梦想着有朝一日成长成一名架构师.可是笔者对于如何成长成一 ...

  6. python实现TCP/UDP通信

    一.说明 对于TCP/udp的说明已经很多了,我在这里只是简单的说明一下 二.套接字scoket 套接字是一种具有之前所说的"通信端点"概念的计算网络数据结构.相当于电话插口,没它 ...

  7. Win10上编译CoreCLR的Windows和Linux版本

    一.编译环境 首先,不管是Windows还是Linux版本CoreCLR的编译,都是在Windows10上进行的. 二.CoreCLR for Windows 在Windows上做编译怎么能少得了Vi ...

  8. iOS开发之通过代码自定义一个控件

    关于控件的继承关系(面试重点): (1)所有的控件都继承自UIView. (2)能监听事件的都是先继承自UIControl,UIControl再继承自UIView.比如UIButton. (3)能整体 ...

  9. Unix环境编程基础下

    Unix出错处理 当UNIX系统的函数出错时,通常会返回一个负值.我们判断函数的返回值小于0表示出错了,注意我们并不知道为什么出错.例如我们open一个文件,返回值-1表示打开失败,但是为什么打开失败 ...

  10. QQ第三方登录教程

    教程戳这里