阴阳大论之代理模式
目录
目录
代理模式
定义
在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。
优缺点
- 1、职责清晰。
- 2、高扩展性。
- 3、智能化。
- 1、由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。
- 2、实现代理模式需要额外的工作,有些代理模式的实现非常复杂。
使用场景
- 远程代理
为一个位于不同的地址空间的对象提供一个局域代表对象。这个不同的地址空间可以是本电脑中,也可以在另一台电脑中。最典型的例子就是——客户端调用Web服务或WCF服务。
- 虚拟代理(Virtual PRoxy)会
推迟真正所需对象实例化时间. 在需要真正的对象工作之前, 如果代理对象能够处理, 那么暂时不需要真正对象来出手.
优点: 在应用程序启动时,由于不需要创建和装载所有的对象,因此加速了应用程序的启动。
- Copy-on-Write 代理
虚拟代理的一种,把复制(或者叫克隆)拖延到只有在客户端需要时,才真正采取行动。
- 保护(Protect or Access)代理
控制一个对象的访问,可以给不同的用户提供不同级别的使用权限。
- Cache代理
为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以这些结果。
- 防火墙(Firewall)代理
保护目标不让恶意用户接近
- 同步化(Synchronization)代理
使几个用户能够同时使用一个对象而没有冲突。
- 智能引用(Smart Reference)代理
当一个对象被引用时,提供一些额外的操作,比如将对此对象调用的次数记录下来等
与其它模式的区别
- 和适配器模式的区别
适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口。
- 和装饰器模式的区别
装饰器模式为了增强功能,而代理模式是为了加以控制。
静态代理
JDK实现动态代理
实现流程
- 实现InvocationHandler接口,创建自己的调用处理器
- 调用Proxy的静态方法,创建代理类并生成相应的代理对象
- 源码demo
/**
* 经纪人,动态代理
*
* @author 石玉森
* @create 2018-10-19 11:51
**/
public class JDKDynamicProxyFactory<T> implements InvocationHandler {
/**
* 委托类实例:被代理的真实对象实例
*/
private T target;
public JDKDynamicProxyFactory(T target) {
this.target = target;
}
/**
*
* @param proxy 生成的代理类实例
* @param method
* @param args
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("调用被被代理的方法 before");
Object result = method.invoke(target,args);
System.out.println("调用被被代理的方法 after");
return result;
}
/**
* 创建代理类实例
* @return
*/
public T getProxyInstance(){
ClassLoader classLoader = target.getClass().getClassLoader();
Class[] interfaces = target.getClass().getInterfaces();
Object proxy = Proxy.newProxyInstance(classLoader,interfaces,this);
return (T) proxy;
}
}
实现原理
JDK动态代理是基于java反射来实现。
- 创建代理类的源码;
拿到被代理类实现的接口类对象,遍历里面的方法,以字符串的形式拼凑出代理类源码(动态代理类与被代理类实现同一接口在此体现),将代理类的源码写到本地java文件
- 将源码进行编译成字节码;
读取源码,编译java文件,得到.class字节码文件(的路径)
- 将字节码加载到内存;
- 实例化代理类对象并返回给调用者。
CGLIB实现动态代理
定义
- CGLIB是一个高性能的代码生成类库,被Spring广泛应用。其底层是通过ASM字节码框架生成类的字节码,达到动态创建类的目的。
实现流程
- 实现MethodInterceptor接口,创建自己的调用处理器
- 通过Enhancer类增强工具, 创建代理类并生成相应的代理对象
- 源码demo
**
* cglib动态代理实现
*
* @author 石玉森
* @create 2018-10-19 17:01
**/
public class CglibDynamicProxyFactory<T> implements MethodInterceptor {
/**
* 被代理类:委托类
*/
private T target;
/**
* @param o 代理的对象(生成的被代理类的子类实例)
* @param method 被代理的方法
* @param objects 被代理的方法的参数
* @param methodProxy 代理的对的方法(生成的被代理类的子类实例)
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
Object result = null;
System.out.println("调用被被代理的方法 before");
// result = method.invoke(o,objects);//不对
result = method.invoke(this.target, objects);//可以
// result = methodProxy.invoke(this.target, objects);//可以
// result = methodProxy.invokeSuper(o, objects);//可以
System.out.println("调用被被代理的方法 after");
return result;
}
public T getProxyInstance(T target) {
this.target=target;
Enhancer enhancer = new Enhancer();
//设置创建子类的类,即指定为哪个类产生代理类
enhancer.setSuperclass(target.getClass());
/*设置回调函数 setCallback设置被代理类的public非final方法被调用时的处理类
* */
enhancer.setCallback(this);
//通过字节码技术动态创建子类实例
return (T) enhancer.create();
}
}
实现原理
- 生成代理类的二进制字节码文件;
- 加载二进制字节码,生成Class对象( 例如使用Class.forName()方法 );
- 通过反射机制获得实例构造,并创建代理类对象
静态代理、CGLIB与JDK实现动态代理的区别
代理模式 | 优点 | 缺点 |
---|---|---|
静态代理 | 简单 | 代码不能复用 |
JDK动态代理 | 动态代码生成快 | 执行慢,强制实现接口 |
CGLIB动态代理 | 生成代码慢 | 执行快,不需实现接口 |
代理模式在spring中的应用
spring AOP默认使用JDK动态代理实现。可以通过配置强制使用cglib代理。
CopyOnWriteArrayList类使用代理模式实现
/***
* 静态代理:实现List
* 随机存取:实现RandomAccess
* 克隆:实现Cloneable
*/
public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
final transient ReentrantLock lock = new ReentrantLock();
//委托类。初始化时即实例化,不像虚拟代理懒加载
private transient volatile Object[] array;
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
}
代理模式在Struts2中的应用
struts2中的拦截器实现
代理模式在RPC中的应用
/**
* 服务生产者
* 服务生产者接受到消费方的调用,通过反射调起实际接口
*/
ObjectInputStream input = new ObjectInputStream(socket.getInputStream());
try {
String interfaceName = input.readUTF();
String methodName = input.readUTF();
Class<?>[] parameterTypes = (Class<?>[]) input.readObject();
Object[] arguments = (Object[]) input.readObject();
ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream());
try {
if (!interfaceName.equals(interfaceClazz.getName())) {
throw new IllegalAccessException("Interface wrong, export:" + interfaceClazz + " refer:" + interfaceName);
}
Method method = service.getClass().getMethod(methodName, parameterTypes);
Object result = method.invoke(service, arguments);
output.writeObject(result);
} catch (Throwable t) {
output.writeObject(t);
} finally {
output.close();
}
} finally {
input.close();
}
/**
* 服务消费方
* 服务消费方在本地通过代理的模式实例化代理类,代理类内部通过socket调用远程生产者
*/
return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class<?>[]{interfaceClass},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Socket socket =null;
try {
socket = new Socket(host, port);
ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream());
try {
output.writeUTF(method.getName());
output.writeObject(method.getParameterTypes());
output.writeObject(args);
ObjectInputStream input = new ObjectInputStream(socket.getInputStream());
try {
Object result = input.readObject();
return result;
} finally {
input.close();
}
} finally {
output.close();
}
} finally {
socket.close();
}
}
});