承接上文

三.计数代理

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

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

1.定义书籍基本类Book

public class Book {
private String No;
private String name; public Book(String no, String name) {
No = no;
this.name = name;
} public String getNo() {
return No;
} public void setNo(String no) {
No = no;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
}
}

2.定义抽象主题接口IBorrow

public interface IBorrow {  //借阅过程
boolean borrow();
}

3.定义借阅实现类Borrow

public class Borrow implements IBorrow{
private Book book;
public void setBook(Book book){
this.book = book;
}
public Book getBook(){
return book;
}
public boolean borrow(){
//保存信息到数据库等功能,代码就不写了哈哈哈
return true;
}
}

4.定义借阅代理类BorrowProxy

public class BorrowProxy implements IBorrow {
private Borrow obj;
private Map<String,Integer> map;
public BorrowProxy(Borrow obj){
this.obj = obj;
map = new HashMap();
}
public boolean borrow(){
if(!obj.borrow()){ //借阅失败
return false; //返回
}
Book book = obj.getBook();
Integer i = map.get(book.getNo());
i = (i == null) ? 1 : i+1;
map.put(book.getNo(),i);//保存书号-次数 键值对
return true;
}
public void log() throws Exception{
Set<String> set = map.keySet();
String key = "";
String result = "";
Iterator<String> it = set.iterator();
while(it.hasNext()){
key = it.next();
result += key + "\t" + map.get(key) + "\r\n";
}
Calendar c = Calendar.getInstance();
RandomAccessFile fa = new RandomAccessFile("D:/log.txt","rw");
fa.seek(fa.length());
fa.writeBytes(+c.get(Calendar.YEAR) + "-" + (c.get(Calendar.MONTH) +1) +
"-" + c.get(Calendar.DAY_OF_MONTH) + "\r\n");//记录日志时间
fa.writeBytes(result);
fa.close();
}
}

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

5.简单测试类

public class Test {
public static void main(String[] args) throws Exception{
Borrow br = new Borrow();
BorrowProxy bp = new BorrowProxy(br); Book book = new Book("1000","计算机应用");
br.setBook(book);
bp.borrow(); book = new Book("1001","计算机应用2");
br.setBook(book);
bp.borrow();
bp.log();
}
}

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

四.动态代理

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

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

1.创建服务器工程URmiServer

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

public interface ICalc {
float calc(String s) throws Exception;
}

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

package BridgeModel.Proxy.dynamicProxy;

/**
* Created by lenovo on 2017/4/22.
*/
public class ServerCalc implements ICalc{
public float calc(String s) throws Exception{
s += "+0";
float result = 0;
float value = 0;
char opcur = '+';
char opnext;
int start = 0;
if(s.charAt(0)=='-'){
opcur = '-';
start = 1;
}
for(int i=start;i<s.length();i++){
if(s.charAt(i)=='+' || s.charAt(i)=='-'){
opnext = s.charAt(i);
value = Float.parseFloat(s.substring(start,i));
switch (opcur){
case '+': result += value; break;
case '-': result -= value; break;
}
start = i+1;
opcur = opnext;
i = start;
}
}
return result;
}
}

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

public class ServerProxy {
public static Map<String,Object> map = new HashMap();
public void registry(String key,Object value){ //远程对象注册功能
map.put(key,value); //注册到HashMap映射中
}
public void process(int socketNO) throws Exception{
ServerSocket s = new ServerSocket(socketNO);
while(true){
Socket socket = s.accept();
if(socket != null){
MySocket ms = new MySocket(socket);
ms.start();
}
}
}
}

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

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

4)MySocket类

public class MySocket extends Thread{
Socket socket;
public MySocket(Socket socket){
this.socket = socket;
}
public Object invoke(String registname,String methodname,Object para[]) throws Exception{
Object obj = ServerProxy.map.get(registname); //获得注册对象
//形成函数参数列表
Class classType = Class.forName(obj.getClass().getName());
Class c[] = new Class[para.length];
for(int i=0;i<para.length;i++){
c[i] = para[i].getClass();
}
Method mt = classType.getMethod(methodname,c);
return mt.invoke(obj,para);
}
public void run(){
while(true){
try{
InputStream ins = socket.getInputStream();
if(ins == null || ins.available() == 0)
continue;
//前处理
ObjectInputStream in = new ObjectInputStream(ins);
ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
String registname = (String)in.readObject(); //获得远程对象注册名称
String methodname = (String)in.readObject(); //获得远程调用方法名称
Object[] para = (Object[])in.readObject(); //获得远程对象方法参数列表 //动态调用主题对象
Object result = invoke(registname,methodname,para);
//后处理
out.writeObject(result); //将结果返回给客户端
out.flush();
}
catch (Exception ex){
ex.printStackTrace();
}
}
}
}

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

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

5)简单测试类

public class URmiServer {
public static void main(String[] args) throws Exception{
ServerCalc obj = new ServerCalc();
ServerProxy spoobj = new ServerProxy();
spoobj.registry("calc",obj);
spoobj.process(4000);
}
}

2.创建客户端工程URmiClient

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

public interface ICalc {
float calc(String s) throws Exception;
}

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

public class ClientProxy implements ICalc {
ClientComm comm;
public ClientProxy(String IP, int socketNO) throws Exception{
comm = new ClientComm(IP,socketNO);
}
public float calc(String s) throws Exception{
Float result = (Float)comm.invoke("calc","calc",new Object[]{s});
return result.floatValue();
}
}

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

public class ClientComm {
Socket socket;
public ClientComm(String IP,int socketNO) throws Exception{
socket = new Socket(IP,socketNO);
}
Object invoke(String registname,String methodname,Object[] para) throws Exception{
//前处理
ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
ObjectInputStream in = new ObjectInputStream(socket.getInputStream());
out.writeObject(registname);
out.writeObject(methodname);
out.writeObject(para);
return in.readObject(); //后处理
}
}

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

3)一个简单测试类

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

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

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

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

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

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

1.定义抽象主题IRegist

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

2.定义两个具体主题

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

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

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

1)定义计数实现类CountInvoke

public class CountInvoke implements InvocationHandler {
private int count = 0;
private Object obj;
public CountInvoke(Object obj){
this.obj = obj;
} public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
count ++;
method.invoke(obj,args); //对主题对象obj应用反射技术调用相应方法
return null;
} public int getCount(){
return count;
} }

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

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

2)创建代理类GenericProxy

public class GenericProxy {
public static Object createProxy(Object obj, InvocationHandler invokeObj){
Object proxy = Proxy.newProxyInstance(obj.getClass().getClassLoader(),
obj.getClass().getInterfaces(),invokeObj);
return proxy;
}
}

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

3)简单测试类Test

public class Test {
public static void main(String[] args) {
IRegist email = new fromEmail();
IRegist post = new fromPost();
CountInvoke emailinvoke = new CountInvoke(email);
CountInvoke postinvoke = new CountInvoke(post); IRegist emailproxy = (IRegist)GenericProxy.createProxy(email,emailinvoke);
IRegist postproxy = (IRegist)GenericProxy.createProxy(post,postinvoke); emailproxy.regist("email1");
postproxy.regist("post1");
System.out.println(emailinvoke.getCount());
System.out.println(postinvoke.getCount());
}
}

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. Maximum Depth of Binary Tree leetcode

    Given a binary tree, find its maximum depth. The maximum depth is the number of nodes along the long ...

  2. markown编辑器截图粘贴预览,并将图片传至七牛云

    最近在做一个项目,需要实现类似QQ截图后,就是能够在富文本编辑器中粘贴截图并预览. 先看一下效果: 分析一下实现步骤: QQ截图后在编辑器中粘贴,肯定会有一个粘贴事件,即 paste 事件 在事件回调 ...

  3. android 透明状态栏方法及其适配键盘上推(二)

    在上一篇文章中介绍了一种设置透明状态栏及其适配键盘上推得方法.但是上一篇介绍的方法中有个缺点,就是不能消除掉statusbar的阴影.很多手机如(三星,Nexus都带有阴影).即使我用了: <a ...

  4. eclipse中以debug方式启动tomcat报错

    在eclipse中debug  Tomcat报错,错误如下: FATAL ERROR in native method: JDWP No transports initialized, jvmtiEr ...

  5. web CSS的知识- 关于后代选择器,子选择器,兄弟选择器的使用

    1. 后代选择器官方解释:后代选择器可以选择作为某元素后代的元素.理解:选择某一标签的后代中,所有的此标签标记例:ul em {color:red;}就是选择,h1标签后代中中,所有的em.代码如下: ...

  6. Python实现Windows定时关机

    是最初的几个爬虫,让我认识了Python这个新朋友,虽然才刚认识了几天,但感觉有种莫名的默契感.每当在别的地方找不到思路,总能在Python找到解决的办法.自动关机,在平时下载大文件,以及跑程序的时候 ...

  7. 集合框架(HashSet存储自定义对象保证元素唯一性)

    HashSet如何保证元素唯一性的原理 1.HashSet原理 a. 我们使用Set集合都是需要去掉重复元素的, 如果在存储的时候逐个equals()比较, 效率较低,哈希算法提高了去重复的效率, 降 ...

  8. [LeetCode] Decode String 题解

    题目 题目 s = "3[a]2[bc]", return "aaabcbc". s = "3[a2[c]]", return " ...

  9. nginx+lua安装配置

    1.选定源码目录选定目录 /usr/local/ cd /usr/local/ 2.安装PCRE库cd /usr/local/wget ftp://ftp.csx.cam.ac.uk/pub/soft ...

  10. 数据结构(三) 用java实现七种排序算法。

    很多时候,听别人在讨论快速排序,选择排序,冒泡排序等,都觉得很牛逼,心想,卧槽,排序也分那么多种,就觉得别人很牛逼呀,其实不然,当我们自己去了解学习后发现,并没有想象中那么难,今天就一起总结一下各种排 ...