您现在的位置是:首页 >技术教程 >设计模式之代理模式(静态代理&动态代理)网站首页技术教程
设计模式之代理模式(静态代理&动态代理)
目录
1、什么是代理模式
由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。
2、代理模式的结构
代理(Proxy)模式分为三种角色:
- 抽象主题(Subject)类: 通过接口或抽象类声明真实主题和代理对象实现的业务方法。
- 真实主题(Real Subject)类: 实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
- 代理(Proxy)类 : 提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。
3、代理模式的实现
3.1 静态代理和动态代理概念
Java中的代理按照代理类生成时机不同又分为静态代理和动态代理。静态代理代理类在编译期就生成,而动态代理代理类则是在Java运行时动态生成。动态代理又有JDK代理和CGLib代理两种。
(本文中的动态代理主要讲解的是JDK代理,如果大家对CGLib代理感兴趣的话可以自行查阅网上的文章)
3.2 静态代理
我们直接通过案例来感受一下动态代理,以下是场景描述:
一般明星(对象本身)都会有一个经纪人(代理对象)来帮他处理一些事情,比如明星开演唱会之前收门票费用,预约场地,演唱会结束之后调查观众对该演唱会的反馈之类的事情肯定不能交给大明星做吧,所以这些事情一般都是交给经纪人去处理。
这个例子就是一个典型的代理例子,因此我们来看看通过静态代理如何实现:
明星类(抽象主题类):
public interface bigStar {//抽象主题类
String Sing();
void Dance();
}
坤坤类(真实主题类):
public class KunKun implements bigStar{ //真实主题类
public String Sing(){
System.out.println("大明星:坤坤开始唱歌");
return "鸡你太美"; //返回歌词
}
public void Dance(){
System.out.println("大明星:坤坤开始跳舞");
}
}
经纪人类(代理类):
public class StaticProxy implements bigStar{ //代理类
private bigStar star;
public StaticProxy(bigStar bigStar){
star=bigStar;
}
@Override
public String Sing() {
System.out.println("唱歌前收取门票费用、预约场地");
String sing = star.Sing();
System.out.println("唱歌结束后帮忙调查观众反馈");
return sing;
}
@Override
public void Dance() {
System.out.println("跳舞前收取门票费用、预约场地");
star.Dance();
System.out.println("跳舞结束后帮忙调查观众反馈");
}
}
测试类:
public class Test {
public static void main(String[] args) {
KunKun kunKun = new KunKun();
StaticProxy proxy = new StaticProxy(kunKun);
proxy.Dance();
System.out.println("------");
String sing = proxy.Sing();
System.out.println("歌词为:"+sing);
}
}
运行结果如下:
可见,我们通过“静态代理”的方式实现了“代理模式”,成功在“坤坤”唱歌、跳舞前做好了准备工作以及在结束后做好了调查观众反馈的工作。这些都是代理类来完成的,看到这里大家肯定意识到了代理模式的一个好处就是:“在被代理对象方法执行前后能做一定的处理、加强”。
但是上述静态代理模式有没有缺点呢?
有!而且很明显!那就是:“杂!乱!如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。”
因此,我们引出另一种实现代理模式的方法:“动态代理”。
3.3 动态搭理
3.3.1 代码实现
动态代理的JDK代理实现主要是通过“Proxy类”来实现的,我们直接来看代码实现,大家耐心看,代码里面有很详细的注释。看不懂可以先看底下对于里面参数的讲解部分。
大明星类(抽象主题类):
public interface bigStar { //大明星类(抽象主题类)
public String Sing();
public void Dance();
}
坤坤类(真实主题类):
public class KUN implements bigStar { //坤坤类(真实主题类)
@Override
public String Sing() {
System.out.println("坤坤在唱歌......");
//返回歌词
return "鸡你太美";
}
@Override
public void Dance() {
System.out.println("坤坤在跳舞");
}
}
经纪人(代理类):
public class StarProxyFactory { //经纪人(代理类)
public static bigStar getProxy(bigStar star){
bigStar result=(bigStar)Proxy.newProxyInstance( //newProxyInstance()方法即返回代理对象
// newProxyInstance()方法参数说明:
// ClassLoader loader : 类加载器,用于加载代理类,使用真实对象的类加载器即可
// Class<?>[] interfaces : 真实对象所实现的接口,代理模式真实对象和代理对象实现相同的接口
// InvocationHandler h : 代理对象的调用处理程序
star.getClass().getClassLoader(),
star.getClass().getInterfaces(),
new InvocationHandler() {
@Override
// InvocationHandler中invoke方法参数说明:
// proxy:代理对象
// method:对应于在代理对象上调用的接口方法的 Method 实例,比如我们调用了“Sing()”这个方法,那么对应的就是“Sing()”的Method实例
// args:对应我们方法传入的参数
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("场地布置、收取门票费用");
Object invoke = method.invoke(star, args);
System.out.println("场地打扫");
return invoke; //返回方法调用结果
}
}
);
return result;
}
}
测试类:
public class Test {
public static void main(String[] args) {
KUN kun = new KUN();
bigStar proxy = StarProxyFactory.getProxy(kun);//获取动态代理对象
//验证是否被代理
proxy.Dance();
System.out.println("--------------");
String sing = proxy.Sing();
System.out.println("歌词为:"+sing);
}
}
运行结果如下:
可见,我们通过Java提供的“Proxy类”也实现了代理的效果,但是大家看完可能会一脸懵逼,发生了什么?我在哪?我是谁?
因此接下来我们对“Proxy类”这个类做一个详细的解释。
3.3.2 Proxy类讲解
首先,我们刚刚是通过Proxy.newProxyInstance()来获取一个代理对象,它所需要的参数如下:
可见,我们如果使用Proxy.newProxyInstance()的话,需要对它传入以下参数:
①参数一:指类加载器,意思是需要我们告诉它我们需要用哪个类加载器去加载代理对象,通常我们代理对象与被代理对象可以使用同一个类加载器,因此比如上文我们是代理star对象,因此我们传入的类加载器就是:“star.getClass().getClassLoader()”。
②参数二:指真实对象所实现的接口,代理模式真实对象和代理对象实现相同的接口,因此我们也直接传入真实对象的接口即可:“star.getClass().getInterfaces()”。
③参数三:指代理对象最终调用的程序,一般代理对象调用某个方法后,都会走我们参数三写的这个方法,大家往回看代码会发现我们参数三传入了一个匿名内部类对象“new InvocationHandler()”
大家又可以发现,这个“InvocationHandler类”创建对象时,要求重写里面的invoke方法:
我们再来逐一说说这几个参数的作用:
- proxy:代理对象本身
- method:对应于在代理对象上调用的接口方法的 Method 实例,比如我们调用了“Sing()”这个方法,那么对应的就是“Sing()”的Method实例
- args:对应我们方法传入的参数
因此呢,我们可以在invoke方法里面做一些方法加强(比如我们之前的代码),也就是我们的“代理”。
以上便是动态代理实现代理模式的代码。
4、动态代理VS静态代理
动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。
如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。而动态代理不会出现该问题
5、代理模式优缺点
优点:
- 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用。
- 代理对象可以扩展目标对象的功能。
- 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度。
缺点:
- 增加了系统的复杂度。
以上是代理模式的详解,大家麻烦点个赞和关注可以嘛!现在点关注就是老粉啦!