设计模式之代理模式
1 静态代理
有些时候,我们想做一些事但是自己没有资源或者自己做不好,就会想着花点钱请专业的人帮我们做。这是一种代理模式。比如我们要买车,都会去4S店,让他们帮我们办理繁琐的手续。抽象成代码如下:
买车的行为抽象成接口Something:
public interface Something {
void buyCar();
}
买车的人Person类:
public class Person implements Something {
private String name;
public Person(String name){
this.name = name;
}
@Override
public void buyCar() {
System.out.println(name + "是用户,要买车...");
}
}
实现Something接口,表明要买车。
代理用户买车的4S店Agent类:
public class Agent implements Something {
List<Person> personList;
public Agent(List<Person> personList){
this.personList = personList;
}
@Override
public void buyCar() {
System.out.println("我是中介,我可以帮助用户购车...");
for (Person person : personList) {
person.buyCar();
}
System.out.println("我是中介,买车流程结束...");
}
}
代理类也实现接口Something。
客户端Client:
public class Client {
public static void main(String[] args) {
Person person = new Person("xiaoMing");
Person person2 = new Person("xiaoLi");
List<Person> personList = new ArrayList<Person>();
personList.add(person);
personList.add(person2);
Agent agent = new Agent(personList);
agent.buyCar();
}
}
运行结果:
我是中介,我可以帮助用户购车...
xiaoMing是用户,要买车...
xiaoLi是用户,要买车...
我是中介,买车流程结束...
上面的整个过程就是代理模式的一种。因为代理类Agent包含目标类Person的引用,在编译时已经确定代理的目标类,因此称为静态代理模式。其UML图如下所示:
静态代理角色:
行为抽象接口Something:抽象接口,代表要完成的任务。
目标类Person:买主类Person是实际想买车的,称为目标类,实现Something接口;
代理类Agent:代理类Agent代理目标类执行买车的过程,代理类含有目标类的对象,并实现Something接口的目的就是为了使代理类拥有目标类的功能,可用于替换目标类,即使用目标类的地方都可以使用代理类。
PS:此处是否可以把代理类内目标类的引用换成接口的引用?大家可以思考下。
由于在编译器就确定了代理的目标,因此每一个目标类都需要一个代理类来对应,在实际情况中则显得冗余。究其根本原因在于代理类和目标类共同实现一个接口,因此要把代理类和目标类解耦。解耦常用的方法是动态代理。
2 动态代理
动态代理,顾名思义就是动态生成目标类的代理类;如何动态的生成是关键。
此处,则需要用到java反射reflect包中的API;因此动态代理也成称为JDK代理。
动态代理的过程如下:
接口Something和目标类Person依旧不变。
代理类变成代理工厂:
public class ProxyFactory {
private Object object;
public ProxyFactory(Object object) {
this.object = object;
}
public Object getProxyInstance(){
return Proxy.newProxyInstance(object.getClass().getClassLoader(),
object.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object returnValue = method.invoke(object, args);
return returnValue;
}
});
}
}
客户端Client:
public class Client {
public static void main(String[] args) {
Something person = new Person("xiaoShao");
System.out.println(person.getClass());
Something personProxy = (Something)new ProxyFactory(person).getProxyInstance();
System.out.println(personProxy.getClass());
personProxy.buyCar();
}
}
运行结果:
class com.design.proxy.Person
class com.sun.proxy.$Proxy0
xiaoShao是用户,要买车...
针对代理工厂ProxyFactory有以下几点说明:
① 之所以称为代理工厂,是因为此处的代理类与目标类完全解耦,任何实现Something接口的目标类的代理都可以通过ProxyFactory生成,故就把此类当做生产代理类的工厂,需要的时候就可以来取。
② ProxyFactory类中使用java.lang.reflect.Proxy的newProxyInstance(arg…)方法返回一个代理的对象;方法中的三个参数分别为:类加载器、接口类型和代理实例需要执行的任务。也正是这些参数确定了代理类的类别,具体如何实现则是使用反射,关于反射后续会讲到。
③ 根据上面三个参数可知,生成目标类的代理类需要目标类的加载器和目标类的接口类型,因此动态动态代理是针对实现接口的类生成代理类的,故也称接口代理。关于类加载器后续会讲到。
动态代理的UML图:
可知,目标类和代理工厂是在客户端才有依赖的,是一个完全解耦的结果。
虽然动态代理克服了静态代理模式的缺点,能够动态的生成所有类的代理类,避免了代理类的冗余度,但同时有一个缺点,那就是目标类必须要有一个接口。在实际项目中,没有接口的类也需要生成代理,此时静态代理和动态代理都不好使。解决这个问题的方法是cglib代理。
3 CGLIB代理
CGLIB代理也称子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展。
首先看看是如何实现的吧
没有接口的目标类Target:
public class Target {
public void doSomething(){
System.out.println("do something...");
}
}
代理工厂类:
public class ProxyFactory implements MethodInterceptor {
private Object object;
public ProxyFactory(Object object) {
this.object = object;
}
public Object getProxyInstance(){
// 工具类
Enhancer enhancer = new Enhancer();
// 设置父类,即目标类的Class对象
enhancer.setSuperclass(object.getClass());
// 设置回调函数
enhancer.setCallback(this);
// 返回代理对象,子类
return enhancer.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
Object target = method.invoke(object, args);
return target;
}
}
客户端类:
public class Client {
public static void main(String[] args) {
Target target = new Target();
Target targetPrxoy = (Target)new ProxyFactory(target).getProxyInstance();
targetPrxoy.doSomething();
}
}
代理工厂ProxyFactory类实现net.sf.cglib.proxy.MethodInterceptor接口,实现拦截方法intercept(),并使用Enhancer工具类来生成代理类。在使用cglib代理需要注意以下几点:
① net.sf.cglib.proxy.MethodInterceptor接口位于spring-core.jar包中。
② 拦截方法intercept()是用于执行代理类中的方法,如果目标对象的方法为final/static,那么就不会被拦截;
③ cglib是在内存中创建目标类的子类来实现代理,因此目标类不能为final类型;
关于CGLIB知识后续会讲到。
CGLIB代理的UML类图:
4 代理模式的使用场景
代理模式本身分为三种,可以根据不同的情况选择不同的模式。三种模式对比如下:
模式 | 实现原理 | 优点 | 缺点 | 使用场景 |
---|---|---|---|---|
静态代理 | 代理类和目标类实现同一个接口,且代理类包含目标类的引用,使得代理类可替换目标类 | 从代理模式上来讲,代理模式通过代理类访问目标类,能访问目标类的地方都能访问代理类,在不方便使用目标类的情况下可以用代理类。 | 代理类内部含有目标类的对象,因此代理类与目标类一一对应;当目标类较多时,代理类则显得冗余。 | 业务简单的场景,目标类稳定的场景。 |
动态代理 | 通过目标类的类加载器和接口类型,运行期生成一个代理类 | 动态代理解决了静态代理的问题。通过代理工厂生成代理类,使得代理工厂与目标类解耦。 | 动态代理适用于实现接口的目标类,但实际情况中很多目标类都没有实现接口,这限制了动态代理的使用。 | 适用于为实现接口的目标类生成代理类。spring框架中AOP都有使用。 |
CGLIB代理 | 在内存中生成目标类的字节码 | CGLIB代理解决了动态代理的问题。通过CGLIB包为没有接口的目标类生成代理类。 | 不能为final类型的类生成代理类;对于static/final类型的方法无法拦截并执行。 | 适用于为没有接口的目标类生成代理类。spring框架中AOP都有使用。 |
动态代理和CGLIB代理互相补充。
5 参考资料
http://www.cnblogs.com/cenyu/p/6289209.html
https://blog.csdn.net/maoyuanming0806/article/details/80186248
- 开发 | 深度神经网络可视化工具集锦
- CSS3三维变形,其实很简单!
- 使用 pandas处理股票数据并作分析
- 用R语言做时间序列分析(附数据集和源码)
- 【android开发】Android GUI系统学习1:Gralloc
- 【kaggle实战】从KNN,LR,SVM,RF到深度学习
- 开发 | 训练一个AI给颜值打分,公平公正!
- 【android开发】Android HAL模块实现
- 让剁手党洞察物体细节,“放大镜”当之无愧
- CSS3过渡,不再为JS动画而犯愁
- 【编程基础】c语言中获取整数和浮点数的符号位
- 前端特效开发 | JS实现聚光灯看图效果
- 【专业知识】Android主线程的消息系统(Handler\Looper)
- CSS3渐变,就是这么玩
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Array - 217. Contains Duplicate
- Array - 299. Bulls and Cows
- Array - 189. Rotate Array
- Array - 277 Find the Celebrity
- Array - 80. Remove Duplicates from Sorted Array II
- Array - 508. Wiggle Sort
- Array - 376. Wiggle Subsequence
- Array - 283. Move Zeroes
- Array - 88. Merge Sorted Array
- Array - 228. Summary Ranges
- Array - 152. Maximum Product Subarray
- Focal Loss和它背后的男人RetinaNet
- Array - 53. Maximum Subarray
- Array - 295. Find Median from Data Stream
- Array - 239. Sliding Window Maximum