• 引言

    设计模式是语言的表达方式,它能让语言轻便而富有内涵、易读却功能强大。代理模式在Java中十分常见,有为扩展某些类的功能而使用静态代理,也有如Spring实现AOP而使用动态代理,更有RPC实现中使用的调用端调用的代理服务。代理模型除了是一种设计模式之外,它更是一种思维,所以探讨并深入理解这种模型是非常有必要的。

  • 代理模式拳谱总纲

     代理模式这种设计模式是一种使用代理对象来执行目标对象的方法并在代理对象中增强目标对象方法的一种设计模式。代理对象代为执行目标对象的方法,并在此基础上进行相应的扩展。看起来是有点拗口,首先介绍一个原则:开闭原则(对扩展开放,对修改关闭)。一种好的设计模式甚至是架构,都是在不修改原有形态的基础上扩展出新的功能。

    代理模式的元素是:共同接口、代理对象、目标对象。

    代理模式的行为:由代理对象执行目标对象的方法、由代理对象扩展目标对象的方法。

    代理模式的宏观特性:对客户端只暴露出接口,不暴露它以下的架构。

    代理模式的微观特性:每个元由三个类构成,如图。

     代理模式的种类:静态代理、动态代理(jdk动态代理、cglib动态代理、Spring和AspectJ实现的动态代理)

  • 静态代理

     静态代理模式就是如上图所示,构造三个类实现他们的关系。

     首先会思考的一点就是为什么需要实现同一个接口,如果不实现同一个接口,一样可以“代理”功能,所以为什么非要实现同一个接口。我个人认为不实现统一接口的话代理方法有可能会不能完全实现(因为实现接口必须实现它的抽象方法),其次就是方法名称了,已经由接口定义的方法就是目标对象实现了的功能,也算是一种提醒,最后我能想到的就是不实现统一接口的话应该叫做聚合而不是代理。

package Proxy.Static;

public interface DAOInterface {
public void add();
public void delete();
public void update();
public void query();
}
package Proxy.Static;

public class UserDao implements DAOInterface{

    @Override
public void add() {
System.out.println("在目标对象中执行add");
} @Override
public void delete() {
System.out.println("在目标对象中执行delete");
} @Override
public void update() {
System.out.println("在目标对象中执行update");
} @Override
public void query() {
System.out.println("在目标对象中执行query");
} }
package Proxy.Static;
/**
* 代理对象
* @author ctk
*
*/
public class UserDaoProxy implements DAOInterface{
UserDao userDao = null;
public UserDaoProxy(UserDao userDao){
this.userDao = userDao;
}
@Override
public void add() {
userDao.add();
System.out.println("记录日志add");
} @Override
public void delete() {
userDao.delete();
System.out.println("记录日志delete");
} @Override
public void update() {
userDao.update();
System.out.println("记录日志update");
} @Override
public void query() {
userDao.query();
System.out.println("记录日志query");
} }

    静态代理就是写死了在代理对象中执行这个方法前后执行添加功能的形式,每次要在接口中添加一个新方法,则需要在目标对象中实现这个方法,并且在代理对象中实现相应的代理方法,幸而Java有独特的反射技术,可以实现动态代理。

  • 动态代理

     实际上在原理上讲我只认识Jdk动态代理和Cglib动态代理,Spring和AspectJ的动态代理是基于前面两种来实现的。

     Jdk的动态代理,是使用反射技术获得类的加载器并且创建实例,根据类执行的方法在执行方法的前后发送通知。

     接口和实现类还是使用上面贴出来的。

     在代理对象Proxy的新建代理实例方法中,必须要获得类的加载器、类所实现的接口、还有一个拦截方法的句柄。

     在句柄的invoke中如果不调用method.invoke则方法不会执行。在invoke前后添加通知,就是对原有类进行功能扩展了。

     创建好代理对象之后,proxy可以调用接口中定义的所有方法,因为它们实现了同一个接口,并且接口的方法实现类的加载器已经被反射框架获取到了。

package Proxy.jdkProxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy; public class Test {
public static void main(String[] args) {
DAOInterface userDao = new UserDao();
DAOInterface proxy = (DAOInterface) Proxy.newProxyInstance(userDao.getClass().getClassLoader(), userDao.getClass().getInterfaces()
, new InvocationHandler() {
//回调方法 拦截到目标对象的时候执行
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("在 代理对象 中拦截到:"+method.getName());
Object o = method.invoke(userDao, args);//调用拦截到的方法
return o;
}
});
proxy.delete();
}
}

     另一种动态代理的实现方式是使用Cglib,所以得先导入两个包

  

     实现方式如下

package Proxy.Cglib;

import java.lang.reflect.Method;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy; /**
* cglib代理 要求类不能使final 代理对象继承目标对象
*
* @author ctk
*
*/
public class Test {
public static void main(String[] args) {
UserDao target = new UserDao();
Enhancer en = new Enhancer();
//设置代理对象的父类
en.setSuperclass(target.getClass());
//设置回调
en.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable {
System.out.println("在 代理对象 中拦截到 "+arg1.getName());
Object obj = arg1.invoke(target, arg2);
return obj;
}
});
UserDao proxy = (UserDao) en.create();
proxy.add();
}
}

     Cglib实现代理的方式是和目标对象使用同一个父类,无论是继承还是实现接口,都是为了代理对象能直接调用目标对象的方法。

  • RPC服务分离代理模式

      

     在分布式的书上看到RPC的诞生契机,任何一种技术干学都是不长进的,只有把它的前世今生给摸透了,才能有所领悟。当你的web应用达到了编译一次1分钟或者5分钟或者更多的时候,你就该想到服务解耦这个办法了。即使是在做一个ERP,但是业务多起来也是如C++编译一样的(不是黑C++哈哈),服务解耦的意义就是想,我这个web应用调用的service能不能不运行在同一个web应用中呢?RPC由此诞生了,本地保存一个调用列表,而真正执行是在另外一个应用(或者另外一个服务器)。只要制定好通信框架序列化和反序列化的制度(私有协议栈),就做成了一个RPC框架。虽然如ActiveMQ之流的通信框架底层使用的是这种技术,但是它们的复杂程度也不是一个demo能说清楚的。

     服务调用方

package RPC;

import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.InetSocketAddress;
import java.net.Socket; public class RPCimporter<S> { @SuppressWarnings("unchecked")
public S importer(final Class<?> serviceClass,final InetSocketAddress addr){
return (S)Proxy.newProxyInstance(serviceClass.getClassLoader(), new Class<?>[]{serviceClass.getInterfaces()[0]}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Socket socket = null;
ObjectOutputStream out = null;
ObjectInputStream ins = null;
try{
socket = new Socket();
socket.connect(addr);
out = new ObjectOutputStream(socket.getOutputStream());
out.writeUTF(serviceClass.getName());
out.writeUTF(method.getName());
out.writeObject(method.getParameterTypes());
out.writeObject(args);
ins = new ObjectInputStream(socket.getInputStream());
return ins.readObject();
}finally {
if(out != null)
out.close();
if(ins != null)
ins.close();
if(socket != null)
socket.close();
} }
});
}
}

     服务调用

package RPC;

import java.io.IOException;
import java.net.InetSocketAddress; /**
* 测试RPCdemo
* @author ctk
*
*/
public class RPCTest {
public static void main(String[] args) {
new Thread(new Runnable() { @Override
public void run() {
try {
RPCExporter.exporter("localhost", 10000);
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
RPCimporter<EchoService> importer = new RPCimporter<>();
EchoService echo = importer.importer(EchoServiceImpl.class, new InetSocketAddress("localhost", 10000));
System.out.println(echo.echo("hello world"));
}
}

     服务列表用接口来实现再好不过了,在调用importer这个方法的时候,会往提供方请求一个执行服务的类和方法并要求返回一个执行结果。

     服务提供方

package RPC;
/**
* RPC服务代理接口
* @author MacBook
*
*/
public interface EchoService {
public String echo(String ping);
}
package RPC;

public class EchoServiceImpl implements EchoService{

    @Override
public String echo(String ping) {
return ping != null ? ping + " ---> I am ok.":"I am ok";
} }
package RPC;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors; public class RPCExporter {
static Executor executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
public static void exporter(String hostName,int port) throws IOException{
ServerSocket server = new ServerSocket();
server.bind(new InetSocketAddress(hostName, port));
try{
while(true){
executor.execute(new ExportTask(server.accept())); }
}catch (Exception e) { } finally {
server.close();
} } }
package RPC;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Method;
import java.net.Socket; public class ExportTask implements Runnable {
Socket client = null;
public ExportTask(Socket socket){
client = socket;
} @Override
public void run() {
ObjectInputStream oins = null;
ObjectOutputStream oous = null;
try{
oins = new ObjectInputStream(client.getInputStream());
String interfaceName = oins.readUTF();
Class<?> service = Class.forName(interfaceName);
String methodName = oins.readUTF();
Class<?>[] parameterTypes = (Class<?>[])oins.readObject();
Object[] arguments = (Object[])oins.readObject();
Method method = service.getMethod(methodName, parameterTypes);
Object result = method.invoke(service.newInstance(), arguments);
oous = new ObjectOutputStream(client.getOutputStream());
oous.writeObject(result);
}catch (Exception e) { }finally { try {
if(oins != null)
oins.close();
if(oous != null)
oous.close();
if(client != null)
client.close();
} catch (IOException e) {
e.printStackTrace();
}
}
} }

     服务器架构和BIO的多线程结构没有什么区别,只是使用了线程池而已,把accept到的socket交给线程实现类去完成任务。在提供服务的时候先读取请求的对象名和方法名,并通过反射创建实例,最后接受到参数,运行结束之后返回result结果。

  • 一些感想

     以前觉得,设计模式只是为了代码更加简洁,或者实现功能更为有条理而设置的,现在慢慢感悟到框架的设计也可设计模式有紧密的关系。就比如生产者消费者模型和消息中间件的架构是密切相关的,相似的还有观察者模型、发布订阅模型。而RPC服务实现服务解耦也有点像代理模式的思想,虽然它没有使用反射进行动态代理,没有AOP那么直接。还有一点,就是不同场景下的设计模式需要做不同的思考,比如单例模式在高并发环境下应该如何达到高效、安全。

     多思考,多挖掘,干巴爹。

Java-代理模式的理解的更多相关文章

  1. JAVA设计模式 5【结构型】代理模式的理解与使用

    今天要开始我们结构型 设计模式的学习,设计模式源于生活,还是希望能通过生活中的一些小栗子去理解学习它,而不是为了学习而学习这些东西. 结构型设计模式 结构型设计模式又分为 类 结构型 对象 结构型 前 ...

  2. Java代理模式

    java代理模式及动态代理类 1.      代理模式 代理模式的作用是:为其他对象提供一种代理以控制对这个对象的访问.在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理对象可以在客户端和目 ...

  3. Java代理模式示例程序

    Java代理模式示例程序 当然不是我想出来的,是我看的一个网上教程里的. 模拟的是一个对电脑公司的代理 真实类的接口: public interface SaleComputer { public S ...

  4. Java工厂模式解耦 —— 理解Spring IOC

    Java工厂模式解耦 -- 理解Spring IOC 最近看到一个很好的思想来理解Spring IOC,故记录下来. 资源获取方式 主动式:(要什么资源都自己创建) 被动式:(资源的获取不是我们创建, ...

  5. java 代理模式 总结

    1.前言 最近舍友去面试遇到了关于java代理模式的问题. 我虽然知道怎么使用,但是没有做过正经的总结,因此有了这篇随笔,好好总结一下三大代理模式底层原理. 事实上,在开发项目的时候,基本用不上代理, ...

  6. 浅谈java代理模式

    讲解java代理模式 目录 讲解java代理模式 何谓代理模式 静态代理 动态代理 JDK动态代理 CGLIB动态代理 何谓代理模式 代理模式,即Proxy Pattern,23种java常用设计模式 ...

  7. Java代理模式/静态代理/动态代理

    代理模式:即Proxy Pattern,常用的设计模式之一.代理模式的主要作用是为其他对象提供一种代理以控制对这个对象的访问. 代理概念 :为某个对象提供一个代理,以控制对这个对象的访问. 代理类和委 ...

  8. 说说Java代理模式

    代理实现可以分为静态代理和动态代理. 静态代理 静态代理模式其实很常见,比如买火车票这件小事:黄牛相当于是火车站的代理,我们可以通过黄牛买票,但只能去火车站进行改签和退票.在代码实现中相当于为一个委托 ...

  9. Java 代理模式

    熟悉设计模式的人对于代理模式可能都不陌生.那什么事代理呢,例如我们要买一件国外的商品,但是自己买不到只能去找代购,这个代购就是我们的代理.我们来了解下java中的代理 静态代理 我们来举一个开车的例子 ...

  10. Java代理模式精讲之静态代理,动态代理,CGLib代理

    代理(Proxy)是一种设计模式,通俗的讲就是通过别人达到自己不可告人的目的(玩笑). 如图: 代理模式的关键点是:代理对象与目标对象.代理对象是对目标对象的扩展,并会调用目标对象 这三个代理模式,就 ...

随机推荐

  1. space defender,太空版植物大战僵尸 游戏基本框架的设计

  2. 【LA3415 训练指南】保守的老师 【二分图最大独立集,最小割】

    题意 Frank是一个思想有些保守的高中老师.有一次,他需要带一些学生出去旅行,但又怕其中一些学生在旅行中萌生爱意.为了降低这种事情发生的概率,他决定确保带出去的任意两个学生至少要满足下面四条中的一条 ...

  3. 微信小程序(一)基本知识初识别

    最近微信圈里小程序很火的样子,以前小程序刚开始的时候研究了一下,多日没关注发现一些东西都淡忘了,最后决定还是记录下来的好.    毕竟好记星比不上烂笔头嘛~

  4. 7-set用法详解

    C++中set用法详解 转载 http://blog.csdn.net/yas12345678/article/details/52601454 C++ / set 更详细见:http://www.c ...

  5. SqlServer性能瓶颈分析

    SqlServer性能瓶颈分析 一.内存瓶颈分析--SQLServer:Buffer Manager SELECT TOP 312 * FROM  sys.dm_os_performance_coun ...

  6. Docker for mac安装

    Mac安装Docker docker下载地址: https://hub.docker.com/editions/community/docker-ce-desktop-mac docker for m ...

  7. Rabbit MQ参考资料

    https://github.com/ServiceStack/rabbitmq-windows/blob/master/README.md https://github.com/rabbitmq/r ...

  8. Oracle 递归

      当对象存在父节点.子节点时,通过特定的方式获取父节点.子节点数据构建树状结构或其它形式结构时,通常都会使用递归,如:一个公司有多个部门.每个部门下可能有多个小部门,小部门下面又有组-.为了数据容易 ...

  9. [GO]使用go语言实现比特币的工作量证明

    之前的博文已经实现了区块连的基本的工作原理,但在比特币系统中有一个很重要的概念:工作量证明POW,在比特币系统中它的作用就是在十分钟左右的时间内只有一个有能够记帐并得到奖励 在之前的博文中,区块的哈希 ...

  10. HDU 3361 ASCII (水题)

    题意: 析:不说话. #include <cstdio> #include <string> #include <cstdlib> #include <cma ...