什么是代理模式:
用户代码不直接调用某些功能类的方法, 而是通过代调类作为”中间层”去调用”被代理类”. 所有调用都会被代理类拦截, 我们可以利用代理类的这个特性, 在代理类里增加额外的执行代码.
使用代理可以给我们带来如下好处: 用户代码(调用者)和功能类(被调用者)解耦, 第二个好处是通过代理层可以加入一些通用的代码.
Java 代理模式的实现主要有: 静态代理, JDK 动态代理, Cglib 动态代理.
JDK 动态代理
如何使用 JDK 的动态代理:
// 1 接口 |
通过 proxy 调用 YourClass
实现自接口 YourInterface
的所有方法, 都会调用到 YourHandler
的 invoke 方法,
在 invoke 方法里可以很方便的做一些前置和后置处理(访问控制、远程通信、日志、缓存等), 在 invoke 里再通过反射调用实际类 YourClass
的方法.
- JDK动态代理的优点是, 当
YourInterface
的实现类有很多的时候, 比如有 YourClassA, YourClassB…
通过代理调用这些实现类的方法(必须是实现 YourInterface 里的方法), 都会由代理调用到InvocationHandler.invoke()
, - 如果用静态代理, 那么代理类(实现了 YourInterface 接口)必须为 YourInterface 的每一个方法都增加单独的代码.
参考: Java 动态代理作用是什么? - 知乎 @ref
实现原理
在调用 Proxy.newProxyInstance()
之后,
又调用了 ProxyGenerator.generateProxyClass()
方法生成最终代理类的字节码, 并通过 ClassLoader 把字节码转化成对象.
在最终代理类里实现了我们的 Interface 定义的所有方法, 在这些方法内部, 都通过反射调用了 InvocationHandler
接口实现类的 invoke()
方法
包
sun.misc.ProxyGenerator
提供了一个功能, 可以生成 YourInterface 的实现类的字节码:byte[] data = ProxyGenerator.generateProxyClass(name,new Class[]{YourInterface.class});
CGLIB 代理
CGLIB 是一个功能强大&高性能的字节码生成包。它为没有实现接口的类提供代理,为 JDK 的动态代理提供了很好的补充。通常可以使用 Java 的动态代理创建代理,但当要代理的类没有实现接口或者为了更好的性能,CGLIB 是一个好的选择。
CGLIB 与 JDK 动态代理不同的是, 使用 CGLIB 即使被代理类没有实现任何接口也可以实现动态代理功能。但是不能对 final 修饰的类进行代理。
JDK 动态代理通过反射类 Proxy 和 InvocationHandler 回调接口实现,要求委托类必须实现一个接口,只能对该类接口中定义的方法实现代理,这在实际编程中有一定的局限性。
CGLIB 包的底层是通过使用一个小而快的字节码处理框架 ASM,来转换字节码并生成新的类。
ASM 是一个 Java 字节码操控框架。通过分析被代理类的 class 文件, 在内存中创建被代理类的增强子类, 它能被用来动态生成类或者增强既有类的功能。
ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。
脚本语言例如 Groovy 和 BeanShell,也是使用 ASM 来生成 java 的字节码。当然不鼓励直接使用 ASM,因为它要求你必须对 JVM 内部结构包括 class 文件的格式和指令集都很熟悉。
下面通过一个例子看看使用 CGLib 如何实现动态代理:
定义业务逻辑:
public class UserServiceImpl { |
实现 MethodInterceptor 接口,定义方法的拦截器:
public class YourMethodInterceptor implements MethodInterceptor { |
利用 Enhancer
类生成 UserServiceImpl
的代理类:
Enhancer enhancer = new Enhancer(); |
Enhancer
是 CGLib 的字节码增强类, 可以生成类的字节码( 上面的例子里, 生成的是 UserServiceImpl
的子类),
其作用类似 sun.misc.ProxyGenerator
, 区别是 Enhancer
不需要被代理类实现接口, 而 ProxyGenerator
要求被代理类必须实现接口
以上参考:
@ref 说说 cglib 动态代理
Spring AOP 与代理
Spring AOP 中的一些注解 & 概念:
@Aspect: PointCut + Advice
@PointCut: 切点, 在哪里切入
@Advice: 切入的行为(在切点之前还是之后, 或者环绕切点), 以及做什么
Spring AOP 使用的动态代理,所谓的动态代理就是说 AOP 框架不会去修改字节码,而是在内存中临时为方法生成一个AOP 对象,这个 AOP 对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。
Spring AOP 中的动态代理主要有两种方式,JDK 动态代理 和 CGLIB 动态代理。
- 如果目标类(被切的类)有统一的实现接口,Spring AOP 使用 JDK 动态代理,
- 如果目标类没有实现接口,那么 Spring AOP 会选择使用 CGLIB 来动态代理目标类。
因此如果某个类被标记为 final,并且没有实现接口,那么它是无法被动态代理的,也就无法当做切点(CutPoint)