Java-代理模式的理解
- 引言
设计模式是语言的表达方式,它能让语言轻便而富有内涵、易读却功能强大。代理模式在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-代理模式的理解的更多相关文章
- JAVA设计模式 5【结构型】代理模式的理解与使用
今天要开始我们结构型 设计模式的学习,设计模式源于生活,还是希望能通过生活中的一些小栗子去理解学习它,而不是为了学习而学习这些东西. 结构型设计模式 结构型设计模式又分为 类 结构型 对象 结构型 前 ...
- Java代理模式
java代理模式及动态代理类 1. 代理模式 代理模式的作用是:为其他对象提供一种代理以控制对这个对象的访问.在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理对象可以在客户端和目 ...
- Java代理模式示例程序
Java代理模式示例程序 当然不是我想出来的,是我看的一个网上教程里的. 模拟的是一个对电脑公司的代理 真实类的接口: public interface SaleComputer { public S ...
- Java工厂模式解耦 —— 理解Spring IOC
Java工厂模式解耦 -- 理解Spring IOC 最近看到一个很好的思想来理解Spring IOC,故记录下来. 资源获取方式 主动式:(要什么资源都自己创建) 被动式:(资源的获取不是我们创建, ...
- java 代理模式 总结
1.前言 最近舍友去面试遇到了关于java代理模式的问题. 我虽然知道怎么使用,但是没有做过正经的总结,因此有了这篇随笔,好好总结一下三大代理模式底层原理. 事实上,在开发项目的时候,基本用不上代理, ...
- 浅谈java代理模式
讲解java代理模式 目录 讲解java代理模式 何谓代理模式 静态代理 动态代理 JDK动态代理 CGLIB动态代理 何谓代理模式 代理模式,即Proxy Pattern,23种java常用设计模式 ...
- Java代理模式/静态代理/动态代理
代理模式:即Proxy Pattern,常用的设计模式之一.代理模式的主要作用是为其他对象提供一种代理以控制对这个对象的访问. 代理概念 :为某个对象提供一个代理,以控制对这个对象的访问. 代理类和委 ...
- 说说Java代理模式
代理实现可以分为静态代理和动态代理. 静态代理 静态代理模式其实很常见,比如买火车票这件小事:黄牛相当于是火车站的代理,我们可以通过黄牛买票,但只能去火车站进行改签和退票.在代码实现中相当于为一个委托 ...
- Java 代理模式
熟悉设计模式的人对于代理模式可能都不陌生.那什么事代理呢,例如我们要买一件国外的商品,但是自己买不到只能去找代购,这个代购就是我们的代理.我们来了解下java中的代理 静态代理 我们来举一个开车的例子 ...
- Java代理模式精讲之静态代理,动态代理,CGLib代理
代理(Proxy)是一种设计模式,通俗的讲就是通过别人达到自己不可告人的目的(玩笑). 如图: 代理模式的关键点是:代理对象与目标对象.代理对象是对目标对象的扩展,并会调用目标对象 这三个代理模式,就 ...
随机推荐
- 在 CentOS 下源码安装 Xen
http://www.vpsee.com/2010/04/install-xen-on-centos-from-source/ 在 CentOS 源码编译安装 Xen 的过程和在 Debian 上编译 ...
- GRUB使用说明
从Red Hat Linux 7.2起,GRUB(GRand Unified Bootloader)取代LILO成为了默认的启动装载程序.相信LILO对于大家来说都是很熟悉的.这次Red Hat Li ...
- spring4-5-事务管理
1.简单介绍 事务管理是企业级应用程序开发中必不可少的技术, 用来确保数据的完整性和一致性. 事务就是一系列的动作, 它们被当做一个单独的工作单元. 这些动作要么全部完成, 要么全部不起作用 事务的 ...
- ubuntu14.04 64 位 vmware tools 问题
当提示说open-vm-tools版本太低时可以这样解决 1.sudo apt-get autoremove open-vm-dkms open-vm-tools --purge 2.安装vmware ...
- pthread_mutex_init函数与pthread_mutexattr_init函数
直接上英文解释: pthread_mutex_init()如下: NAME pthread_mutex_init, pthread_mutex_destroy - initialise or dest ...
- python 全栈基础作业题
1.执行 Python 脚本的两种方式 1..直接使用PyCharm执行 2.python run.py 调用python 解释器来调用python脚本 2.简述位.字节的关系 数据存储是以“字节”( ...
- [GO]runtime包及gosched的使用
Gosched:让出CPU时间片 Goexit:退出当前的协程 GOMAXPROCS:设置使用最大的CPU数量(哇,牛逼了...) package main import ( "fmt&qu ...
- [GO]断言
使用if实现断言 package main import "fmt" type Student struct { name string id int } func main() ...
- CodeForces 690C2 Brain Network (medium)(树上DP)
题意:给定一棵树中,让你计算它的直径,也就是两点间的最大距离. 析:就是一个树上DP,用两次BFS或都一次DFS就可以搞定.但两次的时间是一样的. 代码如下: #include<bits/std ...
- FileInputStream和FileOutStream的使用——文件字节输入/输出流
最近又退回到java EE的学习,这篇博客就来讲解一下字节流中最重要的两个类FileInputStream和FileOutputStream的用法: FileInputStream:全称是文件字节输入 ...