代理模式
在代理模式(Proxy Pattern)中,一个类代表另一个类的功能。这种类型的设计模式属于结构型模式。在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。
代理模式定义: 为其他对象提供一种代理,以控制对这个对象的访问。
主要解决:在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。
何时使用:想在访问一个类时做一些控制。
如何解决:增加中间层。
关键代码:实现与被代理类组合。
应用实例:
1、Windows 里面的快捷方式。
2、猪八戒去找高翠兰结果是孙悟空变的,可以这样理解:把高翠兰的外貌抽象出来,高翠兰本人和孙悟空都实现了这个接口,猪八戒访问高翠兰的时候看不出来这个是孙悟空,所以说孙悟空是高翠兰代理类。 3、买火车票不一定在火车站买,也可以去代售点。
4、一张支票或银行存单是账户中资金的代理。支票在市场交易中用来代替现金,并提供对签发人账号
上资金的控制。
5、spring aop。
代理模式包含以下三个角色
Subject(抽象主题角色):抽象主题角色申明了真实主题和代理主题的共同接口、这样以来任何使用真实主题的地方可以都使用代理主题、客户端需要针对抽象主题角色来编程
Proxy(代理主题角色):代理主题角色包含了对真实主题的引用,从而可以在任何时候操作真实主题角色。
RealSubject(真实主题角色):真实主题角色定义了代理主题角色所代表的具体对象,真实主题角色中实现了真实的业务操作,客户端可以通过代理主题角色来间接的调用真实主题角色中定义的方法。
代理的实现主要分为两类:静态代理(编译时代理)和动态代理(运行时代理)。
静态代理
由程序员创建或特定工具自动生成源代码,也就是在编译时就已经将接口,被代理类,代理类等确定下来。在程序运行之前,代理类的.class文件就已经生成。
静态代理简单实现
根据上面代理模式,来写一个简单的静态代理的例子。我这儿举一个比较粗糙的例子,假如一个班的同学要向老师交班费,但是都是通过班长把自己的钱转交给老师。这里,班长就是代理学生上交班费,班长就是学生的代理。
public interface People {
//上交班费
public void giveMoney();
}
public class RealPeople implements People{
//实现 上交班费
public void giveMoney() {
System.out.println("班长给你我的钱,给我代交一下");
}
}
public class ProxyPeople implements People{
//接收保存目标对象
private People people;
//创建构造方法
public ProxyPeople(People people) {
this.people = people;
} //实现 上交班费
public void giveMoney() {
System.out.println("Before:在give Money之前进行一些操作");
people.giveMoney();//执行目标对象的方法
System.out.println("After:在give Money之后进行一些操作");
}
//public void preGive(){}
//public void postGive(){}
}
public class AppPeople {
public static void main(String[] args) {
//创建目标对象
RealPeople realPeople = new RealPeople();
//创建代理对象,把目标对象传给代理对象,建立代理关系
ProxyPeople proxyPeople = new ProxyPeople(realPeople);
//执行的是代理的方法
proxyPeople.giveMoney();
}
}
静态代理模式最主要的就是有一个公共接口Person,一个具体的类Student,一个代理类StudentProxyDemo,代理类持有具体类的实例,代为执行具体类实例方法。上面说到,代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种
用途。这里的间接性就是指不直接调用实际对象的方法,那么我们在代理过程中就可以加上一些其他用途。就如这个例子里的preGive()和postGive()方法。特别是在Spring AOP编程中。在切点(即一个个方法)的前后我们可以进行一系列操作。
静态代理优缺点: 为了满足软件开发的开闭原则,以及增强框架自身的灵活拓展功能。
动态代理
动态代理在底层就会为那些特定的目标类或者接口实现类进行渲染与自定义功能操作(运行阶段)。就如spring框架中的aop,底层也是通过动态代理来实现的。
一种是基于接口的代理;另一种则是基于类的代理。基于接口的代理,就是jdk自带的动态代理规则的实现方式,后者则是基于一些字节类增强的类代理,如cglib。
1、JDK动态代理
JDK中的动态代理是通过反射类Proxy以及InvocationHandler回调接口实现的,但是,JDK中所要进行动态代理的类必须要实现一个接口,也就是说只能对该类所实现接口中定义的方法进行代理,这在实际编程中具有一定的局限性,而且使用反射的效率也并不是很高。
jdk代理最主要的就是三个类:目标接口,目标类(实现了目标接口),扩展处理器InvocationHandler类
代码实例:1,接口 同静态代理。2,目标类 同静态代理。3,jdk代理类。
public class ProxyPeople implements InvocationHandler {
//invocationHandler持有的被代理对象
// 如果知道,被代理类的类型,可以在这里直接设定特定类型,需要注意的是
// 在invoke()方法中也需要转为相应类型,而不能使用O
//使用任何目标对象
private Object target;
//创建一个构造方法
public ProxyPeople(Object target) {
this.target = target;
}
/
**
* proxy:代表动态代理对象
* method:代表正在执行的方法
* args:代表调用目标方法时传入的实参
*/
//实现
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理执行" +method.getName() + "方法");
// 在方法调用之前进行系列操作,这里可以是方法
System.out.println("Before:在give Money之前进行一些操作");
// // 注意:对args进行操作之前需要进行为空的判断,因为有的方法不带参数,不进行判断,会抛出NullPointerException
if (args != null){
for (Object arg : args) {
System.out.println(" " + arg);
}
}
Object invoke = method.invoke(target, args);
System.out.println("After:在give Money之后进行一些操作");
return invoke;
}
//给任何目标对象生成代理对象
public Object getProxyInstance(){
//使用反射生成
return Proxy.newProxyInstance(
//注意该方法是在Proxy类中是静态方法,且接收的三个参数依次为:
//ClassLoader loader,:指定当前目标对象使用类加载器,获取加载器的方法是固定的
//Class<?>[] interfaces,:目标对象实现的接口的类型,使用泛型方式确认类型
//InvocationHandler h:事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this);
}
}
//测试类
public class AppPeople {
public static void main(String[] args) {
//创建目标对象
People target = new RealPeople();
//输出
System.out.println(target.getClass());
//创建代理对象,把目标对象传给代理对象,建立代理关系
People proxy = (People) new ProxyPeople(target).getProxyInstance();
//内存中动态的生成的代理对象 $Proxy0
System.out.println(proxy.getClass());
//执行方法 【代理对象】
proxy.giveMoney();
//JDK中有个规范,只要要是$开头的一般都是自动生成的
try {
//通过反编译工具可以查看源代码
byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{People.class});
FileOutputStream os = new
FileOutputStream("D://Igs/sixacproxy/Proxy0.class");//自己想把文件放哪里
os.write(bytes);
os.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
代码中字节码重组的步骤:
1、拿到被代理对象的引用,并且获取到它的所有的接口,反射获取;
2、JDK Proxy类重新生成一个新的类、同时新的类要实现被代理类所有实现的所有的接口;
3、动态生成Java代码,把新加的业务逻辑方法由一定的逻辑代码去调用(在代码中体现);
4、编译新生成的Java代码.class
5、再重新加载到JVM中运行
对于JDK 的Proxy,有以下几点:
1)Interface:对于JDK proxy,业务类是需要一个Interface的,这也是一个缺陷
2)Proxy,Proxy 类是动态产生的,这个类在调用Proxy.newProxyInstance(targetCls.getClassLoader,targetCls.getInterface,InvocationHander)之后,会产生一个Proxy类的实例。实际上这个Proxy类也是存在的,不仅仅是类的实例。这个Proxy类可以保存到硬盘上。
3) Method:对于业务委托类的每个方法,现在Proxy类里面都不用静态显示出来。
4) InvocationHandler: 这个类在业务委托类执行时,会先调用invoke方法。invoke方法再执行相应的代理操作,可以实现对业务方法的再包装。
JDK 动态代理原理分析:
Java动态代理创建出来的动态代理类Proxy。jdk为我们的生成了一个叫$Proxy0(这个名字后面的0是编号,有多个代理类会一次递增)的代理类,这个类文件时放在内存中的,我们在创建代理对象时,就是通过反射获得这个类的构造方法,然后创建的代理实例。通过对这个生成的代理类源码的查看,我们很容易能看出,动态代理实现的具体过程。
我们可以对InvocationHandler看做一个中间类,中间类持有一个被代理对象,在invoke方法中调用了被代理对象的相应方法。通过聚合方式持有被代理对象的引用,把外部对invoke的调用最终都转为对被代理对象的调用。
代理类调用自己方法时,通过自身持有的中间类对象来调用中间类对象的invoke方法,从而达到代理执行被代理对象的方法。也就是说,动态代理通过中间类实现了具体的代理功能。
生成的代理类:$Proxy0 extends Proxy implements Person,我们看到代理类继承了Proxy类,所以也就决定了java动态代理只能对接口进行代理,Java的继承机制注定了这些动态代理类们无法实现对class的动态代理。
上面的动态代理的例子,其实就是AOP的一个简单实现了,在目标对象的方法执行之前和执行之后进行了处理,对方法耗时统计。Spring的AOP实现其实也是用了Proxy和InvocationHandler这两个东西的。
jdk代理解决了静态代理需要为每个业务接口创建一个代理类的问题,虽然使用反射创建代理对象效率比静态代理稍低,但其在现代高速jvm中也是可以接受的,在Spring的AOP代理中默认就是使用的jdk代理实现的。这里jdk代理的限制也是比较明显的,即其需要被代理的对象必须实现一个接口。这里如果被代理对象没有实现任何接口,或者被代理的业务方法没有相应的接口,我们则可以使用另一种方式来
实现,即Cglib代理。
2、CGLib动态代理
什么是CGLIB?
CGLIB是一个功能强大,高性能的代码生成包。它为没有实现接口的类提供代理,为JDK的动态代理提供了很好的补充。通常可以使用Java的动态代理创建代理,但当要代理的类没有实现接口或者为了更好的性能,CGLIB是一个好的选择。
CGLib实现:
使用CGLib实现动态代理,完全不受代理类必须实现接口的限制,而且CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的方法进行代理,因为CGLib原理是动态生成被代理类的子类。
//CGLIB所需依赖包
<depedence>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</depedence>
public class RealPeople{
//上交班费
public void giveMoney() {
System.out.println("班长给你我的钱,给我代交一下");
}
}
public class ProxyPeople implements MethodInterceptor {
//使用任何目标对象
private Object target;
//创建一个构造方法
public ProxyPeople(Object target) {
this.target = target;
}
public Object intercept(Object o, Method method, Object[] objects,MethodProxy methodProxy) throws Throwable {
System.out.println("Before:在give Money之前进行一些操作");
Object invoke = methodProxy.invoke(target, objects);
System.out.println("After:在give Money之后进行一些操作");
return invoke;
}
//给任何目标对象生成代理对象
public Object getProxyInstance(){
//1.工具类
Enhancer en = new Enhancer();
//2.设置父类
en.setSuperclass(target.getClass());
//3.设置回调函数
en.setCallback(this);
//4.创建子类(代理对象)
return en.create();
}
}
public class AppPeople {
public static void main(String[] args) {
//创建目标对象
RealPeople target = new RealPeople();
//创建代理对象,把目标对象传给代理对象,建立代理关系
RealPeople proxy = (RealPeople)new
ProxyPeople(target).getProxyInstance();
System.out.println(proxy.getClass());
//执行方法 【代理对象】
proxy.giveMoney();
}
}
因为其不仅解决了静态代理需要创建多个代理类的问题,还解决了jdk代理需要被代理对象实现某个接口的问题。
首先创建了一个Enhancer对象,并且设置了父类及代理回调类对象。该Enhancer对象会为目标类创建相关的子类字节码,并且将代理代码植入该子类字节码中。
CGLIB原理:动态生成一个要代理类的子类,子类重写要代理的类的所有不是final的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。它比使用java反射的JDK动态代理要快。
CGLIB底层:使用字节码处理框架ASM,来转换字节码并生成新的类。不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。
CGLIB缺点:对于final方法,无法进行代理。
CGLIB应用:广泛的被许多AOP的框架使用,例如Spring AOP Hibernate使用CGLIB来代理单端single-ended(多对一和一对一)关联。
jdk和Cglib代理对比
使用JDK动态代理,目标类必须实现的某个接口,如果某个类没有实现接口则不能生成代理对象。Cglib原理是针对目标类生成一个子类,覆盖其中的所有方法,所以目标类和方法不能声明为final类型。从执行效率上看,Cglib动态代理效率较高。
总结
主要对代理模式的三种实现方式进行了详细讲解,并且比较了各个代理方式的优缺点,Spring主要使用的是动态代理方式实现切面编程的。这里可能会有一个疑问,即上述代理代码中,根据实现方式的不同,对客户端代码都有一定的侵入性,比如静态代理客户端需要侵入代理类的实例,jdk代理需要侵入Proxy类,而Cglib代理则需要侵入子类子类对象创建等代码。理论上,客户端只需要获取目标对象,无
论是否为代理过的,然后调用其相关方法实现特定功能即可。这其实也是工厂方法的强大之处,因为工厂方法会将对象的创建封装起来,对象的具体创建过程可以根据具体的业务处理即可,客户端只需要依赖工厂类调用相关的方法即可。同样的这也就说明了Spring IoC容器是天然支持AOP代理的原因,因为其将对象的创建过程交由容器进行了。
1 ===> 假如一个类中的方法有100个,我们想要对这些方法都做日志处理。这个时候我们就可以使用代理,简化代码,不需要在每个方法调用的时候都做日志处理。
2====> 基于委托类中的这100个方法,其中有50个方法我想做日志处理,另外40个方法我想做执行时间输出,另10个我不想做处理的时候用代理也是挻好的。
3====> 如果某个类我想隐藏,但是每些方法又想给其它人使用就可以使用代理。这些隐藏又如私有化构造,或者内部类等。
4====> 扩展功能也是常说的功能,对方法进行增强等操作,对不同的权限进行不同的增强......
代理模式效果与适用场景
代理模式是常用的结构型设计模式之一,它为对象的间接访问提供了一个解决方案,可以对对象的访问进行控制。代理模式类型较多,其中远程代理、虚拟代理、保护代理等在软件开发中应用非常广泛。
优点:
- 降低耦合度。它将请求的发送者和接收者解耦;
- 简化了对象,使得对象不需要知道链的结构;
- 增强给对象指派职责的灵活性,允许动态地新增或者删除责任链;
- 增加新的请求处理类方便;
缺点:
- 不能保证请求一定被接收;
- 系统性能将受到一定影响,调试时不方便,可能会造成循环调用;