23种设计模式之代理模式
参考资料
- Java设计模式:23种设计模式全面解析(超级详细)
- 韩顺平老师的Java设计模式(图解+框架源码剖析)
- 秦小波老师的《设计模式之禅》
下文如有错漏之处,敬请指正
一、简介
定义
代理模式是通过代理类控制目标类的访问。即客户端可以通过代理对象间接地访问目标对象,从而限制、增强或修改目标对象。
特点
- 代理模式是一种结构型模式
优点
-
职责清晰
目标对象实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件事务,附带的结果就是编程简洁清晰。
-
高扩展性
具体主题角色是随时都会发生变化的,只要它实现了接口,甭管它如何变化,都逃不脱如来佛的手掌(接口),而我们的代理类完全就可以在不做任何修改的情况下使用,且代理模式可以限制、增强或修改目标对象。
-
安全性
代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用。
缺点
- 在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢;
- 增加了系统的复杂度;
通用类图
代理模式通过定义一个继承抽象主题类或实现抽象主题类的代理类来包含目标对象,从而实现对目标对象的访问。
-
Subject
抽象主题类
抽象主题类可以是抽象类也可以是接口,是一个最普通的业务类型定义。
-
RealSubject
目标主题类(目标对象)
实现了抽象主题类中的具体业务,是代理对象所代表的目标对象,是最终要引用的对象。
-
Proxy
代理类
提供了与目标主题类相同的接口,其内部有目标对象的引用,它可以限制、增强或修改目标对象。
应用场景
- 远程代理,这种方式通常是为了隐藏目标对象存在于不同地址空间的事实,方便客户端访问。例如,用户申请某些网盘空间时,会在用户的文件系统中建立一个虚拟的硬盘,用户访问虚拟硬盘时实际访问的是网盘空间。
- 虚拟代理,这种方式通常用于要创建的目标对象开销很大时。例如,下载一幅很大的图像需要很长时间,因某种计算比较复杂而短时间无法完成,这时可以先用小比例的虚拟代理替换真实的对象,消除用户对服务器慢的感觉。
- 安全代理,这种方式通常用于控制不同种类客户对真实对象的访问权限。
- 智能指引,主要用于调用目标对象时,代理附加一些额外的处理功能。例如,增加计算真实对象的引用次数的功能,这样当该对象没有被引用时,就可以自动释放它。
- 延迟加载,指为了提高系统的性能,延迟对目标的加载。例如Hibernate中就存在属性的延迟加载和关联表的延时加载。
二、实现分类
-
静态代理
目标类和代理类必须实现同一接口或继承同一抽象类,且一个目标类需要一个对应的代理类
-
动态代理
可以代理任意目标类,且目标类必须实现接口,利用Java反射完成动态代理类的创建。
-
Cglib代理
可以代理任意目标类,且目标类可以不实现任何接口。
Cglib代理会在内存中构建一个目标类的子类,并重写子类所有非final或static的方法(final或static 方法无法重写),在方法体中织入回调函数的调用,从而实现对目标对象的代理。
使用cglib需要导入4个包,其中cglib包版本最好为2.2.2
/** * cglib-2.2.2.jar * asm-3.3.1.jar * asm-commons-3.3.1.jar * asm-tree-3.3.1.jar */
静态代理
需求:
客户端想访问google.com,但是由于网络限制他无法访问,于是他通过某种工具成功访问了网站。
Subject:访问网站功能
RealSubject:实现访问网站功能
Proxy:通过工具访问google.com
Subject:
package proxy.staticProxy;
public interface ISubject {
public void visit();
}
RealSubject:
package proxy.staticProxy;
public class RealSubject implements ISubject {
@Override
public void visit() {
System.out.println("访问google.com");
}
}
Proxy:
package proxy.staticProxy;
public class Proxy implements ISubject{
private ISubject iSubject;
public Proxy(ISubject iSubject) {
this.iSubject = iSubject;
}
@Override
public void visit() {
System.out.println("请输入要访问的网址:");
iSubject.visit();
System.out.println("正在对访问的网站进行下载并转发给你");
}
}
Client:
package proxy.staticProxy;
public class Client {
public static void main(String[] args) {
ISubject iSubject=new Proxy(new RealSubject());
iSubject.visit();
/**
* 输出结果:
* 请输入要访问的网址:
* 访问google.com
* 正在对访问的网站进行下载并转发给你
*/
}
}
静态代理也是抽象主题的子类,如果有其他接口要代理就必须要重新写一个代理,即一个接口一个代理。
动态代理
需求:
用户想通过支付宝往绑定的银行卡里转账,用户还想使用天猫购物,但两者在使用前都需要进行网络安全检查。
Subject:转入功能和网购功能
RealSubject:实现转入功能和网购功能
Proxy:网络安全检查
Subject:
package proxy.dynamicProxy;
public interface IAliPay {
public String transfer();
}
package proxy.dynamicProxy;
public interface ITMail {
public String buy();
}
RealSubject:
package proxy.dynamicProxy;
public class IAliPayImpl implements IAliPay {
@Override
public String transfer() {
System.out.println("通过支付宝将钱转入绑定的银行卡");
return "转账成功!";
}
}
package proxy.dynamicProxy;
public class ITMailImpl implements ITMail {
@Override
public String buy() {
System.out.println("使用天猫进行网购!");
return "购买成功!";
}
}
Proxy:
package proxy.dynamicProxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyFactory {
static class Invocation implements InvocationHandler{
// 目标对象
private Object target;
public Invocation(Object target) {
this.target = target;
}
/**
* invoke() 执行增强后的目标对象的方法
* @param proxy 代理对象
* @param method 目标对象正在执行的方法,
* @param args 方法中的参数列表
* @return 方法执行后的返回值
* @throws Throwable 可能产生的各种异常信息
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
this.prepare();
Object invoke = method.invoke(target, args);
this.after();
return invoke;
}
public void prepare(){
System.out.println("进行网络安全检查");
}
public void after(){
System.out.println("操作正常,网络没有问题");
}
}
/**
* 实现目标对象的绑定然后返回代理对象
* @param target 目标对象
* @return 返回代理对象(根据接口动态定义的代理对象)
*/
public static Object getInstance(Object target){
Invocation invocation=new Invocation(target);
/**
* ClassLoader loader: 目标对象的类加载器
* Class<?>[] interfaces: 目标对象的接口,动态代理会代理接口下的所有方法
* InvocationHandler h: 目标对象所有方法的代理逻辑
*
*/
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
invocation);
}
}
Client:
package proxy.dynamicProxy;
public class Client {
public static void main(String[] args) {
// 模拟用户使用支付宝进行转账
IAliPay aliPay= (IAliPay) ProxyFactory.getInstance(new IAliPayImpl());
String transfer = aliPay.transfer();
System.out.println(transfer);
System.out.println("=================");
// 模拟用户使用天猫进行网购
ITMail TMail = (ITMail) ProxyFactory.getInstance(new ITMailImpl());
String buy = TMail.buy();
System.out.println(buy);
/**
* 输出结果:
* 进行网络安全检查
* 通过支付宝将钱转入绑定的银行卡
* 操作正常,网络没有问题
* 转账成功!
* =================
* 进行网络安全检查
* 使用天猫进行网购!
* 操作正常,网络没有问题
* 购买成功!
*/
}
}
动态代理通过实现目标对象的接口,利用Java反射完成动态代理类的创建,因此可以通过一个代理类代理所有需要被代理的接口。
Cglib代理
需求:
用户想通过支付宝往绑定的银行卡里转账,在使用前需要进行网络安全检查。
RealSubject:实现转入功能
Proxy:网络安全检查
RealSubject:
package proxy.cglib;
public class Alipay {
public String transfer(int money) {
System.out.println("通过支付宝将"+money+"元转入绑定的银行卡");
return "转账成功!";
}
}
Proxy:
package proxy.cglib;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class ProxyFactory {
/**
* 目标对象的拦截器,包含回调方法(即被代理的目标对象的方法)
*/
private static class CglibProxy implements MethodInterceptor {
// 目标对象
private Object target;
public CglibProxy(Object target) {
this.target = target;
}
/**
* @param o 目标对象
* @param method 目标对象正在执行的方法
* @param objects 方法的参数列表
* @param methodProxy cglib生成用来代替Method对象的一个对象,使用MethodProxy比调用JDK自身的Method直接执行方法效率会有提升。
* @return 方法的返回值
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
prepare();
//method.invoke() == methodProxy.invoke()
//method.invoke(target,objects)
Object returnVal = methodProxy.invoke(target, objects);
after();
return returnVal;
}
public void prepare() {
System.out.println("进行网络安全检查");
}
public void after() {
System.out.println("操作正常,网络没有问题");
}
}
// 传入目标对象返回代理对象
public static Object getInstance(Object target) {
CglibProxy cglibProxy = new CglibProxy(target);
//1. 创建一个工具类,进行代理对象的生产
Enhancer enhancer = new Enhancer();
// 2. 设置代理类的父类,即目标类
enhancer.setSuperclass(target.getClass());
// 3. 织入拦截器(或称回调方法)
enhancer.setCallback(cglibProxy);
// 4.创建并返回子类对象,即代理对象
return enhancer.create();
}
}
Client:
package proxy.cglib;
public class Client {
public static void main(String[] args) {
// 模拟用户使用支付宝进行转账
Alipay alipay= (Alipay) ProxyFactory.getInstance(new Alipay());
String transfer = alipay.transfer(1000);
System.out.println(transfer);
/**
* 输出结果:
* 进行网络安全检查
* 通过支付宝将1000元转入绑定的银行卡
* 操作正常,网络没有问题
* 转账成功!
*/
}
}
静态代理和JDK代理(动态代理)要求目标类必须实现接口,而Cglib可以在内存中动态构建目标类的子类(使用继承目标类),因此目标类可以不实现任何接口。
三、总结
- 代理模式可以控制目标类的访问(限制、增强或修改目标对象)。
- 代理模式应用得非常广泛,大到一个系统框架、企业平台,小到代码片段、事务处理;优秀的代理模式的应用有Spring AOP和AspectJ等工具。