静态代理和动态代理的核心机制

静态代理

(1)为工程里每个类都写一个代理类,让它与目标类实现同一个接口。图中紫色的就是代理类。

代理类和实际类去

   (2)在代理类的构造方法传参传一个实例类,然后实际调用方法的时候去调用实例类的方法。这样就达到了不需要直接去调用目标实现类的方法就可以,通过代理类就可以去调用目标类的方法的目的。

  (3)将目标类作为代理类的构造方法参数传参进去,实际调用方法去调用目标类的方法。这就是静态代理的核心思想。

静态代理的缺点

  

静态代理思路简单,但是给每个类都要写一个代理类实在是很麻烦而且代码冗余量很大,一个项目可能有成千上万的类,这个工作量实在很大。维护起来也很麻烦。

如果每个类没有实现接口怎么办呢?

好了,我们来总结一下静态代理的缺点:

  • 静态代理针对每个类都要有一个代理类,代码量打维护成本高。

  • 静态代理必须依赖于接口

既然知道了静态代理的缺点,那有没有办法实现少些或者不写代理类来实现代理功能呢?答案是有,动态代理

动态代理

在讲动态代理前先回顾一下创建对象的流程。

我们在创建对象的时候只需要new就可以了,实际的过程却是很复杂的。

  • (1)java 源文件经过编译生成字节码文件(.class结尾);

  • (2)类加载器将 class 文件加载到 JVM 内存中,就是常说的方法区,生成 Class 对象;

  • (3)执行 new,申请一块内存区域,紧接着创建一个对象放在 JVM 对象,准确地说是新生代

上面的流程中提到了 Class 对象,有两个概念初学者很容易混淆:Class 对象 和 实例对象

Class 对象简单来说就是 Class 类的实例,Class 类描述了所有的类;实例对象是通过 Class 对象创建出来的。

从上面的分析可以看出来,要想创建一个实例,最最关键的是获得 Class 对象

有些同学可能有疑问了,我写代码的时候创建对象没有用到 Class 对象呀,那是因为 Java 语言底层帮你封装了细节。Java 语言给我们提供了new 这个关键字,new 实在太好用了,一行代码就可以创建一个对象。

我们再回到前面讲的静态代理,静态代理最重要的是提前写一个代理类,有了代理类就可以 new 一个代理对象。但是每次都去写一个代理类是不是太麻烦了?!

再稍微扩展一下思路,有没有办法不写代理类还能生成一个代理对象呢?可以,上面讲的通过代理类 Class 对象就可以生成代理对象,那如何获取代理类 Class 对象呢?我们接着往下看。

Class对象包含了一个类的所有信息,如:构造方法、成员方法、成员属性等。

如果我们不写代理类,似乎无法获得代理类 Class 对象,但稍稍动一动脑:代理类和目标类实现的是同一组接口,是不是可以通过接口间接获得代理类 Class 对象。

代理类和目标类实现了同一组接口,这就说明他们大体结构都是一致的,这样我们对代理对象的操作都可以转移到目标对象身上,代理对象只需要专注于增强代码的实现。

上面说了这么多其实是在引入动态代理的概念,动态代理相对于静态代理最大的区别就是不需要事先写好代理类,一般在程序的运行过程中动态产生代理类对象。

动态代理实现之 JDK

JDK 原生提供了动态代理的实现,主要是通过java.lang.reflect.Proxyjava.lang.reflect.InvocationHandler这两个类配合使用。

Proxy类有个静态方法,传入类加载器和一组接口就可以返回代理 Class 对象。

public static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)

这个方法的作用简单来说就是,会将你传入一组接口类的结构信息"拷贝"到一个新的 Class 对象中,新的 Class对象带有构造器是可以创建对象的。

一句话总结:Proxy.getProxyClass() 这个静态方法的本质是以 Class 造 Class

拿到了 Class 对象,就可以使用反射创建实例对象了

这个方法的作用简单来说就是,会将你传入一组接口类的结构信息"拷贝"到一个新的 Class 对象中,新的 Class对象带有构造器是可以创建对象的。

一句话总结:Proxy.getProxyClass() 这个静态方法的本质是以 Class 造 Class

拿到了 Class 对象,就可以使用反射创建实例对象了:

// Proxy.getProxyClass 默认会生成一个带参数的构造方法,这里指定参数获取构造方法
Constructor<A> constructor = aClazz.getConstructor(InvocationHandler.class);
// 使用反射创建代理对象
A a1 = constructor.newInstance(new InvocationHandler() {});

眼尖的同学已经看到了,创建实例的时候需要传入一个 InvocationHandler 对象,说明代理对象中必然有一个成员变量去接收。在调用代理对象的方法时实际上会去执行 InvocationHandler 对象的 invoke方法,画个图理解一下:

 

invoke 方法里可以写增强代码,然后调用目标对象 work 方法。

总结一下流程:

(1)通过 Proxy.getProxyClass() 方法获取代理类 Class 对象;

(2)通过反射 aClazz.getConstructor() 获取构造器对象;

(3)定义InvocationHandler类并实例化,当然也可以直接使用匿名内部类;

(4)通过反射 constructor.newInstance() 创建代理类对象;

(5)调用代理方法;

看了上面的流程,是不是觉得比静态代理还要繁琐,有没有更加优雅的方法?当然有!

为了尽量简化操作,JDK Proxy 类直接提供了一个静态方法:

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

这个方法传入类加载器、一组接口和 InvocationHandler 对象直接就可以返回代理对象了,有了代理对象就可以调用代理方法了,是不是 so easy?!

newProxyInstance方法本质上帮我们省略了获取代理类对象通过代理类对象创建代理类的过程,这些细节全部隐藏了

所以真正在项目中直接使用newProxyInstance这个方法就好了,上面讲的那些流程是为了方便大家理解整个过程。

看到这里我相信大家应该能看懂JDK 原生动态代理了。

动态代理实现之 cglib

JDK 动态代理,一旦目标类有了明确的接口,完全可以通过接口生成一个代理 Class 对象,通过代理 Class 对象就可以创建代理对象。

这里可以看出 JDK 动态代理有个限制必须要求目标类实现了接口,那加入一个目标类没有实现接口,那岂不是不能使用动态代理了?

cglib 就是为了实现这个目标而出现的,利用asm开源包对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。

JDK动态代理与 cglib 动态代理对比

我们通过几个问题简单对比一下 JDK 和 cglib 动态代理的区别。

问题 1:cglib 和 JDK 动态代理的区别?

  • JDK 动态代理:利用 InvocationHandler 加上反射机制生成一个代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理

  • cglib 动态代理:利用ASM框架,将目标对象类生成的class文件加载进来,通过修改其字节码生成代理子类

问题 2:cglib 比 JDK快?

  • cglib底层是ASM字节码生成框架,在 JDK 1.6 前字节码生成要比反射的效率高

  • 在 JDK 1.6 之后 JDK 逐步对动态代理进行了优化,在 1.8 的时候 JDK 的效率已经高于 cglib

问题 3:Spring框架什么时候用 cglib 什么时候用 JDK 动态代理?

  • 目标对象生成了接口默认用 JDK 动态代理

  • 如果目标对象没有实现接口,必须采用cglib

  • 当然如果目标对象使用了接口也可以强制使用cglib

小结

使用代理模式可以避免侵入式修改原有代码。代理分为:静态代理和动态代理。

静态代理要求目标类必须实现接口,通过新建代理类并且与目标类实现同一组接口,最终实现通过代理类间接调用目标类的方法。

关于代理类,可以用一个公式总结一下:代理类 = 增强代码 + 目标实现类 。

静态代理必须要求提前写好代理类,使用起来比较繁琐,这就引入了动态代理。

动态代理是在程序运行的过程中动态生成代理类,根据实现方式的不同进而分为:JDK原生动态代理和CGLIB动态代理。

JDK 动态代理通过反射+InvocationHandler 机制动态生成代理类来实现,要求目标类必须实现接口。cglib 不要求目标类实现接口,通过修改字节码方式生成目标类的子类,这就是代理类。

动态代理不仅在 RPC 框架中被使用,还在其他地方有着广泛的应用场景,比如:Spring AOP、测试框架 mock、用户鉴权、日志、全局异常处理、事务处理等。

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值