代理模式
Java代理模式即Proxy Pattern,23种java常用设计模式之一。代理模式的定义:一个类代表另一个类的功能,被代表的类不会对外部提供访问路径,只有代理类提供。这种类型的设计模式属于结构型模式。
代理模式这种设计模式是一种使用代理对象来执行目标对象的方法并在代理对象中增强目标对象方法的一种设计模式。代理对象代为执行目标对象的方法,并在此基础上进行相应的扩展。看起来是有点拗口,首先介绍一个原则:开闭原则(对扩展开放,对修改关闭)。一种好的设计模式甚至是架构,都是在不修改原有形态的基础上扩展出新的功能。
简言之,代理模式就是设置一个中间代理来控制访问原目标对象,以达到增强原对象的功能和简化访问方式。不仅可以降低模块儿之间的耦合,还能做到高复用,简化代码等。spring的AOP模块就是最直观的代理模式,使用了动态代理来实现,在spring中的许多模块中都具有动态代理的影子。
代理模式设计到的角色
① 抽象角色:声明了真实对象和代理对象共同的接口,相当于是一种契约。抽象角色往往是一个接口或者是抽象类。对比现实,比如说租房子,真实对象(房东)和代理对象(中介)都可以完成租房子的事情。
② 真实角色:实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用,是我们最终要引用的对象。对比一下,真实对象其实就是房东。
③ 代理角色:代理角色内部含有一个对真实对象的引用,目的在于可以操作真实对象,同时呢代理对象提供了与真实对象相同的接口以便在任何时候都能代替真实对象。 同时,在代理对象可以在对真实对象进行操作时候,附加一些自己的操作,相当于对真实对象进行了封装。 在这里有一个疑问,从上面的话中可以看出,真正的执行操作其实还是真实对象,我们可以想想,真正把房子租给你的其实还是房东,不是中介,因为中介是没有房子的,还有一个问题就是:附加自己的操作是什么意思呢?这个比较好理解,当中介给我们联系好房子之后,中介不是免费的,可能会收取你一定的费用。
Java的代理模式是Java中比较常用的设计模式,分为2中代理:静态代理与动态代理(JDK动态代理和cglib动态代理)
静态代理
静态代理中,我们对目标对象的每个方法的增强都是手动完成的(后面会具体演示代码),非常不灵活(比如接口一旦新增加方法,目标对象和代理对象都要进行修改)且麻烦(需要对每个目标类都单独写一个代理类)。 实际应用场景非常非常少,日常开发几乎看不到使用静态代理的场景。
上面我们是从实现和应用角度来说的静态代理,从 JVM 层面来说, 静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。
静态代理实现
假如一个A类具备做甲事情的能力,我们希望它做甲事情之前或之后做多一些操作(比如记录日志),同时又不想去修改A类的结构(因为A类可能有很多其它引用,如果一改A类,所有地方都变动了)。这时候怎么办?
针对这个情况,我们可以在A类外面包多一个类(叫它B类吧),B类同样实现甲方法,B类的甲方法里调用了A类的甲方法,除此之外,B类的甲方法中还额外加多一些操作。随后,调用B类的甲方法,就等同于调用A类的甲方法,同时也执行了我们想要扩展的操作。
一个比较形象的比喻,如果A类是明星,B类就是经纪人,我们想叫明星来演出,跟经纪人说就能实现明星演出这件事,但经纪人在安排明星演出这件事中,可以做其它一些额外的事情,比如出场费商讨、安保工作或行程安排等。经纪人就是明星的代理人,这里的B类就是A类的代理类。
定义一个接口及其实现类
定义一个明星接口StarObject
public interface StarInterface {
public void show(String name);
}
Star实现StarInterface接口,实现一些业务逻辑操作
public class Star implements StarInterface{
@Override
public void show(String name) {
System.out.println("我是明星"+name+",我在演出。");
}
}
创建一个代理类同样实现这个接口
实现经纪人类,即代理类
public class Broker implements StarInterface{
//代理角色对象内部含有对真实对象的引用
private Star star;
public Broker() {
}
public Broker(Star star) {
this.star = star;
}
@Override
public void show(String name) {
System.out.println("我是经纪人,现在去叫" + name + "来演出。");
star.show(name);
System.out.println("我是经纪人," + name + "演出结束,谢谢大家。");
}
}
将目标对象注注入进代理类,然后在代理类的对应方法调用目标类中的对应方法。这样的话,我们就可以通过代理类屏蔽对目标对象的访问,并且可以在目标方法执行前后做一些自己想做的事情。
运行代码
@Test
public void testProxy(){
StarInterface star = new Broker(new Star());
star.show("吴签");
}
这就是代理模式了。但注意,上面所述的是指静态代理模式。 静态代理就是写死了在代理对象中执行这个方法前后执行添加功能的形式,每次要在接口中添加一个新方法,则需要在目标对象中实现这个方法,并且在代理对象中实现相应的代理方法;
动态代理
动态代理是指, 程序在整个运行过程中根本就不存在目标类的代理类(在JDK内部叫 $Proxy0 ,我们看不到) ,目标对象的代理对象只是由代理生成工具(如代理工厂类) 在程序运行时由 JVM 根据反射等机制动态生成的。代理对象与目标对象的代理关系在程序运行时才确立。
相比于静态代理来说,动态代理更加灵活。我们不需要针对每个目标类都单独创建一个代理类,并且也不需要我们必须实现接口,我们可以直接代理实现类( CGLIB 动态代理机制)。
从 JVM 角度来说,动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。
说到动态代理,Spring AOP、RPC 框架应该是两个不得不的提的,它们的实现都依赖了动态代理。
动态代理在我们日常开发中使用的相对较小,但是在框架中的几乎是必用的一门技术。学会了动态代理之后,对于我们理解和学习各种框架的原理也非常有帮助。
动态代理实现方式
动态代理分为两类 : 一类是基于接口动态代理 , 一类是基于类的动态代理
- 基于接口的动态代理----JDK动态代理
- 基于类的动态代理-- CGLIB (Code Generation Library )是一个第三方代码生成类库,运行时在内存中动态生成一个子类对象从而实现对目标对象功能的扩展。
现在用的比较多的是 javasist 来生成动态代理
我们这里使用JDK的原生代码来实现,其余的道理都是一样的!
JDK 动态代理机制
代码实现
下面我们再从程序的角度来分析一下动态代理模式:
设计动态代理类(DynamicProxy)时,不需要显式实现与目标对象类(RealSubject)相同的接口,而是将这种实现推迟到程序运行时由 JVM来实现,也就是说动态代理在需要的时候才去创建,而不是提前创建好。就好比明星演出需要化妆,一名化妆师可以给多个明星化妆,化完一位接一位。 如果不能理解先来看一下实现再去分析吧。
第一步:定义抽象接口(也就是明星让化妆师做的事:makeUp)
//定义演出接口,演出前要实现化妆
public interface Show {
public void makeUp(String name);
}
第二步:定义具体对象(明星1,明星2
//明星演出前要化妆
public class Star1 implements Show {
@Override
public void makeUp(String name) {
System.out.println("我是明星"+name+",演出要化妆");
}
}
public class Star2 implements Show {
@Override
public void makeUp(String name) {
System.out.println("我是明星"+name+",演出要化妆");
}
}
第三步:定义动态代理(化妆师一名)
public class MakeUp implements InvocationHandler {
// 代理对象
private Object ProxyObject;
public Object newProxyInstance(Object ProxyObject){
this.ProxyObject =ProxyObject;
//生成动态代理类实例
return Proxy.newProxyInstance(
//指定产生代理对象的类加载器
ProxyObject.getClass().getClassLoader(),
//指定目标对象的实现接口
ProxyObject.getClass().getInterfaces(),
//指定InvocationHandler对象
this);
}
// 动态代理每次为明星化妆时都会调用这个方法
@Override
public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {
System.out.println("===我是化妆师,正在给明星化妆===");
Object result = method.invoke(ProxyObject, args);
System.out.println("=====我是化妆师,给明星化妆完毕=====");
return result;
}
}
第四步:Test(演示一个动态代理同时服务好几个)
public class MyTest {
public static void main(String[] args) {
// 1. 创建一个动态代理
MakeUp Proxy = new MakeUp();
// 2. 创建2个目标对象(明星)
Star1 star1 = new Star1();
Star2 star2 = new Star2();
// 3.动态代理拿到相应操作权限:也就是一个化妆师有给多位明星化妆的权限
Show star1Makeup = (Show) Proxy.newProxyInstance(star1);
Show star2Makeup = (Show) Proxy.newProxyInstance(star2);
star1Makeup.makeUp("吴签");
System.out.println(" ");
star2Makeup.makeUp("热狗");
}
}
注意:JDK动态代理是代理的接口,因此强制转换应该转换为接口,而不是实现类,若强制转换实现类就会抛出ClassCastException,好比ArrayList与LinkedList实现统一接口List,两者也不能相互转换,但都可以向上转型。
最后再来看一下动态代理的结果:
从结果也可以看出,我们的动态代理需要做什么的时候把相应的方法调用就好了,而不是提前完成一些事情。也就是在真正需要执行方法的时候,再去执行,而不是提前先执行。
代理模式总结
静态代理和动态代理的对比分析
下面使用一张图对他们俩进行一个对比分析:
代理模式的缺点
同样的对于整体的代理模式也有很多的缺点:
(1) 由于在客户和客户(商品)之间增加了代理对象,因此可能会造成请求的处理速度变慢
(2) 实现代理模式需要额外的工作,而且有些代理模式的实现过程较为复杂,例如远程代理。
代理模式的使用场景
代理模式的类型较多,不同类型的代理模式有不同的优缺点,它们应用于不同的场合:
(1) 当需要访问远程主机对象时使用远程代理。
(2) 当需要用一个消耗资源较少的对象来代表一个消耗资源较多的对象时可以使用虚拟代理,
(3) 频繁操作一个对象,为其设置缓冲区时可以使用缓冲代理。
(4) 为不同的对象提供访问权限时可以使用保护代理。
(5) 当需要为一个对象的访问(引用)提供一些额外的操作时可以使用智能引用代理。
写在最后
本篇博客介绍了动态代理和静态代理的概念,并对其进行了代码实现,在实际的工作中,我们会经常遇到需要代理模式的地方,希望能多多思考,促进我们形成一定的思维模式。并且动态代理作为SpringAop的实现原理,封装了动态代理,让我们实现起来更加方便,对于这部分内容可以只做了解,理解其背后的运行机制即可,并不需要具体实现,如果需要实现,直接使用spring的Aop功能即可。