前言
设计模式,是软件工程的基石脉络,如同大厦的结构一样,保证了代码的重用性、可读性、可维护性。从1995年总结出来23种设计模式,一直沿用到现在。而今天要学习的动态代理,就是23种设计模式的结构型模式中的其中一种。
既然是设计模式,就要遵循一个原则:开闭原则:对扩展开放,对修改关闭
动态代理涉及到一点点反射的知识,这里也给大家准备好啦
一、代理模式是什么?
代理模式是指:一个对象本身不做实际的操作,而是通过其他对象来得到自己想要的结果。这样做就可以实现在目标对象实现的基础上,增强额外的功能操作,扩展目标对象的功能数量等等。
在工作的时候,改源码是程序员的大忌,不到万不得已都是不会去动源码的。若此时,客户强烈要求加功能的话,就要用到 ‘’代理模式‘’了
代理模式通常分为两类,分别是静态代理以及动态代理。
二、静态代理
静态代理就是,代理的关系在编译期间就确定了,说直白一点就是,指定某个代理对象来为自己做事。
举个例子:
这天你要结婚了,你对象是一个坤坤狂热粉,于是,你就打算请坤坤来婚礼现场表演节目,活跃气氛。但是坤坤作为大明星,不能直接联系,只能联系到坤坤的经纪人,通过经纪人的安排,你终于预约到了坤坤的表演。
这里的经纪人就相当于静态代理对象。他是坤坤的御用经纪人,并且他只经营坤坤一个人,帮助坤坤处理合作、谈业务等工作,而坤坤本身只需要专注于两年半的练习就可以了。坤坤本身不需要知道自己的金主是谁,也不需要知道自己在什么地方表演。这一切都是经纪人帮忙打点好的。
放到代码中演示:
首先,创建一个明星接口,这个接口有一个skill方法,说明只要是明星就必须有点技能。
public interface Star {
void skill();
}
然后,创建坤坤对象,实现明星接口
public class GeGe implements Star {
private String name;
public GeGe() {
}
public GeGe(String name) {
this.name = name;
}
@Override
public void skill() {
System.out.println(name+"在唱歌");
System.out.println(name+"在跳舞");
System.out.println(name+"在说唱");
}
}
接下来,创建经纪人类,来帮助坤坤完成接单和变演完给粉丝送篮球
代理类也有一个保存用户的方法,但是这个方法属于代理类,这个方法不会真的保存用户,而是起到一个过渡作用,其内部有一个真实对象,进行用户保存。
public class StarProxy implements Star {
private Star star;
public StarProxy(Star star) {
this.star = star;
}
@Override
public void skill() {
System.out.println("接受预约");
star.skill();
System.out.println("表演完给粉丝送篮球");
}
}
最后,结婚当天
public class Marry {
public static void main(String[] args) {
GeGe gege = new GeGe("坤坤");
StarProxy starProxy = new StarProxy(gege);
starProxy.skill();
}
}
运行结果:
有同学可能对经纪人类有疑惑,为啥经纪人也要实现接口,不实现接口直接用组合方式也可以实现接受预约以及变演完送篮球的功能。
从功能角度来说:
首先,客户并不关心自己联系的是张三,还是李四,还是经纪人。只要最后坤坤能在结婚当天上台表演,就是达到目的了。从功能角度来说,就算不通过接口,只是类的调用也可以完成这个目的。但是这样的话,客户就只能联系经纪人来约坤坤,客户与经纪人的耦合性就太高了。假如,经纪人当天手机坏了,坤坤就一天都接不到单。但是如果代理类与真实对象有共同的实现类接口,那么客户只需要对接口操作,什么意思呢?说通俗一点,就是可以有很多方法来实现这个目的,而不是仅限于联系一个人。
从扩展性角度来说:
为了更方便扩展,使用接口最合适,减少了客户与经纪人的耦合性。
从实际角度来说:
一个接口可以有无数多个实现类,也就是说,只要坤坤愿意,随时都可以换经纪人,并且不会影响自己,假如上一个经纪人跟坤坤吵一架后就和坤坤分道扬镳了,后续有很多金主爸爸想要联系坤坤,都联系到上一个经纪人了,坤坤直接退役。
静态代理优缺点:
1、优点:以后我们需要增加什么功能的时候,直接增加一个代理类就可以了。这样就实现了不改变源码的情况下,对新功能的增加。
2、缺点:重复的代码多,一旦需要更新一个小功能,就要新建一个新的代理类,而每一个代理类都有相似的方法,重复性很高,浪费空间。
三、动态代理
静态代理有代码冗余的缺点,呐,有没有什么方法,可以实现代理的同时,只用一个类就可以完成呢?
动态代理就这样产生了~
动态代理就是,代理的关系在运行期间才确定了,说直白一点就是,自己也不知道是谁在帮我做事。
此时要是有客户找坤坤,不论这个人是粉丝也好、是金主爸爸也罢,坤坤本身都不需要知道这个人是谁,也不需要知道谈合作的人是谁。这样做,坤坤就只需要 练习 -> 表演 -> 走人 ;剩下的就交由代理公司了。
还是上面的例子,我们对代理类进行修改:
//代理类实现处理器接口
public class StarProxy implements InvocationHandler {
private Star star;
public StarProxy(Star star) {
this.star = star;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("接受预约");
Object obj = method.invoke(star, args);
System.out.println("表演完给粉丝送Basketball");
return obj;
}
}
代理类实现了处理器接口,因此创建代理对象的时候调用这个处理器,就可以完成对原有功能的填充啦~
处理器接口有一个invoke() 方法,这个方法有三个参数,分别是:
Object proxy : 创建出来的代理对象 (一般用不上)
Method method : 代表外面代理对象的方法
Object[ ] args : 代表外面代理对象方法的参数
method.invoke( ) 在反射的获取成员方法中有讲解~
测试也做了一点小改动,代码如下:
public class Marry {
public static void main(String[] args) {
GeGe gege = new GeGe("坤坤");
//创建代理公司对象
StarProxy starProxy = new StarProxy(gege);
//真实对象的类加载器
ClassLoader classLoader = gege.getClass().getClassLoader();
//真实对象实现的接口
Class<?>[] interfaces = gege.getClass().getInterfaces();
//调用处理器
Star star = (Star) Proxy.newProxyInstance(classLoader, interfaces, starProxy);
star.skill();
}
}
创建代理对象就需要用到Proxy类的静态方法:newProxyInstance()
这个方法需要有三个参数,分别是:
参数一:真实对象的类加载器
参数二:真实对象实现的接口
参数三:处理器对象
前两个参数都可以利用反射来获得,第三个参数就是我们的代理类。因为代理类实现了处理器接口。当我们创建代理对象的时候,就会调用处理器,在处理器类里面我们完成了对原有方法的升级。
测试结果如下:
学习使用动态代理,就必须用到Proxy的newProxyInstance() 方法,详记它的三个参数及用法,就可以啦~
有需要深入了解的伙伴可以new出来以后,点进去看看源码是怎么实现的。
总结
充电时刻
代理模式分为静态代理,和动态代理
静态代理:
①由程序员创建或者由第三方开发工具自动生成。在程序运行之前,代理类的class文件就已经生成存在了。
②每次更新就需要重新写一个静态代理类。
③静态代理从一开始就知道自己要帮哪个类扩充功能。
动态代理:
①动态代理是通过反射机制动态生成的。
②动态代理可以代理对象接口的所有实现类
③动态代理一开始不知道自己要帮哪个类扩充功能,只有执行的时候调用了才知道
④使用动态代理需要使用Proxy的newProxyInstance() 方法来动态创建代理类。