代理模式
代理模式
代理模式在设计模式中是7种结构型模式中的一种,而代理模式有分动态代理,静态代理,一般来说,动态代理更加常用一些。
代理模式的应用场景
这些应用场景除了日志记录,我也没有熟悉的,刚接触代理模式的可以直接跳过
远程代理(Remote Proxy):
虚拟代理(Virtual Proxy):
保护代理(Protection Proxy):
缓存代理(Cache Proxy):
日志记录代理(Logging Proxy):
智能引用代理(Smart Reference Proxy):
延迟加载代理(Lazy Loading Proxy):
先理解什么是代理,再理解动静态
代理模式的灵魂就是在不直接访问某个对象的情况下,通过代理对象来间接访问并控制对该对象的访问。(在这个间接访问的过程中代理对象通常会在执行代理对象里的操作先后时间段里执行一些被代理对象里没有的操作)
我们先搞清楚代理模式有几个角色,再来举例
真实对象(被代理对象): 被间接访问的对象
代理对象: 代理对象将间接访问真实对象,并且代理可以帮助你做一些额外的事情,比如检查你的权限、记录你的请求、或者缓存结果。
抽象类或接口(一般是接口): 这是代理对象和真实对象都要实现的接口(建立一个联系),这样代理才可以替代真实对象。一般这个接口里的抽象方法是代理对象访问被代理对象的关键。
举例
一天,四年级三班同学举行班级里的数学期中考试,考试时间结束后,由小明同学(数学学习委员)将试卷收好送给数学老师,数学老师批改完试卷后,小明又会将试卷拿回,并将班级数学成绩统计出来。
在了解完代理模式的三个角色后,我们尝试把上面的例子进行角色分析
真实对象(被代理对象):数学老师
代理对象:小明——数学学习委员
抽象类或者接口:期中考试
举例所用代码
这里我们先创建抽象接口
public interface IMidterm_Examination {
//批改试卷的抽象方法
void markPapers();
}
再创建具体的数学老师类,也就是真实对象类或者说被代理类
// 在实现抽象接口的前提下创建真实对象
public class MathTeacher implements IMidterm_Examination {
//基本属性
private int age=32;
private String name="李四";
private String job="数学老师";
@Override
public void markPapers() {
System.out.println("数学老师正在改试卷");
}
}
在创建代理对象,创建代理对象前需先写出代理对象类
public class MathMonitor implements IMidterm_Examination{
private int age=16;
private String name="小明";
private String job="数学学习委员";
private IMidterm_Examination target;//代理目标,即被代理对象,接口是为了更加灵活通用
public MathMonitor(IMidterm_Examination target) {
this.target = target;//在构造代理对象时,将被代理对象传入
}
void sendPaper(){
System.out.println("小明同学将试卷送给老师");
}
//小明额外的统计成绩方法
void countScores(){
System.out.println("小明同学正在统计成绩");
}
@Override
public void markPapers() {
//重写抽象接口里的方法
//显示小明同学将试卷送给数学老师
this.sendPaper();
//老师来修改试卷
target.markPapers();
//老师批改完试卷后,小明统计成绩
this.countScores();
System.out.println("期中考试流程结束");
}
}
测试主函数
public class Main {
public static void main(String[] args) {
//通过接口方式创建被代理对象,
IMidterm_Examination mathTeacher=new MathTeacher();
//再通过接口创建代理对象
IMidterm_Examination mathMonitor=new MathMonitor(mathTeacher);
//通过代理对象间接访问数学老师这个对象
mathMonitor.markPapers();
}
}
运行结果
在看完上面的例子后,我们知道代理模式中的代理就是一个类似中介的效果,就像找工作一样,小王本来想进某家电子厂的,但是需要交中介费才能进入这个厂,但是这个中介还会包你不满意该电子厂环境拒绝进厂来回的路费一样。
动静态的区别
静态代理
静态代理在上面的举例代码中已经体现出了,它有以下特点:
(也许你在读完这些特点你还是会不太理解,所以你可以在看完动态代理之后再来回顾静态代理,才能感受到这些特点。)
编译时确定:
代理类固定:
低灵活性:
性能较高:
动态代理
我们将之前那个例子稍微拓展一下,四年级三班的同学上午进行了数学期中考试后,下午又进行了英语期中考试,可是四年级三班的英语课代表生病请假了,所以英语老师也麻烦小明同学(数学学习委员)来收试卷并且把试卷送给老师,最后再把试卷送到班级里。
你先别急着否认这个静态代理做不到,静态代理同样能完成这件事情,我们再试着用静态代理来完成这件事,
我们回顾一下之前小明同学的代码:
我们的代理对象的构造函数中的参数是接口,那么理论上只要英语老师也实现这个接口,他也能传进去,那就试试。
public class MathMonitor implements IMidterm_Examination{
private int age=16;
private String name="小明";
private String job="数学学习委员";
private IMidterm_Examination target;//代理目标,即被代理对象,接口是为了更加灵活通用
public MathMonitor(IMidterm_Examination target) {
this.target = target;//在构造代理对象时,将被代理对象传入
}
我们定义的接口不变
public interface IMidterm_Examination {
//批改试卷的抽象方法
void markPapers();
}
再来创建一个英语老师的被代理对象的类实现期中考试接口:
public class EnglishTeacher implements IMidterm_Examination{
private int age=24;
private String name="王雪";
private String job="英语老师";
@Override
public void markPapers() {
System.out.println("英语老师正在批改英语试卷");
}
}
只需要在测试主函数中传入英语老师这个被代理对象,就行了
public static void main(String[] args) {
//通过接口创建被代理对象,
IMidterm_Examination mathTeacher=new MathTeacher();
//再通过接口创建代理对象
IMidterm_Examination mathMonitor=new MathMonitor(mathTeacher);
//通过代理对象间接访问数学老师这个对象
mathMonitor.markPapers();
//通过创建被代理对象,
IMidterm_Examination englishTeacher=new EnglishTeacher();
mathMonitor=new MathMonitor(englishTeacher);
//通过代理对象间接访问英语老师这个对象
mathMonitor.markPapers();
}
运行结果:
其实这么来说,静态代理也有点“动”的意思,一个代理对象也能完成多个被代理对象的代理。
但是在真正的动态代理面前,它还差远了。
前提是被代理类与代理类实现了相同的接口
动态代理的最大特色是代理类不需要实现与被代理类相同的接口就能实现代理,也就是说代理类在java中,动态代理一般有JDK接口和CGLib两种方式进行实现,这里只介绍JDK接口方法。
我们动态代理来完成上面的例子
需要大改代码的就是代理类,也就是小明这个数学学习委员的代码,
代理类不在需要我们自定义的期中考试的接口
但它需要实现官方提供的InvocationHandler接口
public class MathMonitor implements InvocationHandler {
private int age=16;
private String name="小明";
private String job="数学学习委员";
private Object target;
public MathMonitor(Object target) {
this.target = target;
}
void sendPaper(){
System.out.println("小明同学将试卷送给老师");
}
//小明额外的统计成绩方法
void countScores(){
System.out.println("小明同学正在统计成绩");
}
/*
* @param
* Object proxy 传入代理对象
* Method method 传入需要执行的方法
* Object[] args 方法需要的参数数组
* @return 返回一个Object类型的对象
**/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//在间接访问对象前可做的事情
this.sendPaper();
Object result = method.invoke(target, args);
//在间接访问对象后可做的事情
this.countScores();
return result;
//在Java的反射API中,Method 类的 invoke 方法用于动态地调用一个方法。在你提供的 invoke 方法中,
//这个 method.invoke(target, args) 调用实际上是在执行被代理对象(target)上的方法,
// 并且传递了参数(args)。
//method.invoke 的返回值就是该方法调用的结果。换句话说,它返回了被代理对象上被调用的方法的返回值。
//如果方法类型是void,那么result值是null值
}
}
测试主函数里的代码也有点变化
//创建两个被代理对象
IMidterm_Examination mathTeacher =new MathTeacher();
IMidterm_Examination englishTeacher=new EnglishTeacher();
//先代理数学老师
MathMonitor mathMonitor=new MathMonitor(mathTeacher);
IMidterm_Examination proxy = (IMidterm_Examination) Proxy.newProxyInstance(
Main.class.getClassLoader(),//获取类加载器
new Class[]{IMidterm_Examination.class},
mathMonitor
);
proxy.markPapers();
mathMonitor=new MathMonitor(englishTeacher);
proxy = (IMidterm_Examination) Proxy.newProxyInstance(
Main.class.getClassLoader(),//获取类加载器
new Class[]{IMidterm_Examination.class},
mathMonitor
);
proxy.markPapers();
}
}
类加载器(ClassLoader):Main.class.getClassLoader()
代理接口数组(Class<?>[] interfaces):new Class[]{IMidterm_Examination.class}
调用处理器(InvocationHandler):mathMonitor
动态代理的优点
动态代理是一种在运行时动态创建代理对象的机制,它允许你在调用实际对象之前或之后执行额外的操作。以下是动态代理的一些优点:
灵活性:
减少重复代码:
简化代码结构:
提高代码复用性:
动态性:
代理模式与装饰者模式的区别
在学习中,我很容易把装饰器模式和代理模式混淆,老师说在现实开发中,确实是两个都会混着用的,但是它们还是有一点小区别的。
意图不同:
关注点不同:
组合方式不同:
生命周期不同: