一、基本概念
为某个对象提供一个代理,以控制对这个对象的访问。代理类和委托类有共同的父类或父接口,这样在任何使用委托类对象的地方都可以用代理对象来替代。代理类负责请求的预处理、过滤、将请求分派委托类处理、以及委托类执行完请求后的后续处理。
为了保持行为的一致性,代理类和委托类通常会实现相同的接口,所以在访问者看来两者没有丝毫的区别。通过代理类这中间一层,能有效控制对委托类的直接访问,也可以很好的保护和隐藏委托类对象,同时也为实现不同的策略预留了空间,从而在设计上获得了更大的灵活性。
代理主要分为静态代理和动态代理:
- 静态代理:创建代理类或特定工具自动生成源代码在对其编译,在程序运行前代理类的class文件就已经存在。
- 动态代理:动态代理是一种编程技术,允许在运行时动态创建并处理代理对象,代理对象可以作为真实对象(也称为目标对象)的替代品,对真实对象的访问请求进行拦截、过滤、增强或修改。
下面主要介绍一下动态代理技术
二、动态代理
2.1 动态代理应用场景
即使你不了解动态代理,但是相信是平时你已经在不经意间使用过动态代理,下面看下动态代理的使用场景。
- Spring AOP:AOP 使用了动态代理技术来实现对目标对象的代理和增强。
- SpringCloud OpenFeign:OpenFeign 中指定义了 FeignClient 的接口,但却能实现远程调用,OpenFeign 中只用了动态代理技术来实现远程调用。
- Mybatis 的接口绑定:定义的Mapper接口和xml文件,也是通过动态代理技术来实现的。
- RPC 框架:其实与 OpenFeign类似,比如 Dubbo、gRPC 等都是通过动态代理技术来实现远程调用的。
上面这些功能相信你在平时都应用过,动态代理的应用场景不只列举的这些,应用十分广泛。
2.2 动态代理的实现
动态代理的代理实现方式有多种,在 Java 中主要有以下四种实现方式:
- Java Proxy:是基于接口实现的,动态代理基于
java.lang.reflect.Proxy
类java.lang.reflect.InvocationHandler
实现。代理类在运行时动态生成,动态的构建全新的字节码 Bean。 - CGLIB:是一个三方库,动态构建全新的字节码Bean,不依赖接口。CGLIB通过生成目标类的子类来实现代理功能,因此即使目标类没有实现任何接口,也可以为其创建代理。
- AspectJ:AspectJ 提供了一种更为强大且全面的切面编程解决方案,它既可以静态编译时织入(compile-time weaving),也可以在运行时织入(load-time weaving 或 runtime weaving)。修改目标类的字节,在程序编译时织入,不会生成全新的class,因此性能更好一些。
- Instrumentation:是Java平台提供的一个内置 API,它主要用在 JVM 加载类的过程中进行字节码级别的操作,实现类的动态修改和增强,这与传统的动态代理机制(如Java的JDK动态代理和CGLIB动态代理)有所不同。不用再运行时创建新的class。
动态代理技术的底层实现是修改字节码
三、使用案例
下面演示一下 CGLIB 动态代理是如何工作的,加入我们有一个 HelloService 的类,要对台进行动态代理,在 sayHello 的执行前后分别做一些我们想做的事情。
public class HelloService {
public HelloService() {
System.out.println("HelloService构造");
}
/**
* 该方法不能被子类覆盖,Cglib是无法代理final修饰的方法的
*/
final public String sayOthers(String name) {
System.out.println("HelloService:sayOthers>>"+name);
return null;
}
public void sayHello() {
System.out.println("HelloService:sayHello");
}
}
public class MyMethodInterceptor implements MethodInterceptor {
/**
*
* @param sub cglib生成的代理对象
* @param method 被代理对象的方法
* @param objects 方法入参
* @param methodProxy 代理方法
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object sub, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("======插入前置通知======");
Object object = methodProxy.invokeSuper(sub, objects);
System.out.println("======插入后者通知======");
return object;
}
}
public class Client {
public static void main(String[] args) {
// 代理类class文件存入本地磁盘方便我们反编译查看源码
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\code");
// 通过CGLIB动态代理获取代理对象的过程
Enhancer enhancer = new Enhancer();
// 设置enhancer对象的父类
enhancer.setSuperclass(HelloService.class);
// 设置enhancer的回调对象
enhancer.setCallback(new MyMethodInterceptor());
// 创建代理对象
HelloService proxy = (HelloService) enhancer.create();
// 通过代理对象调用目标方法
proxy.sayHello();
}
}
运行代码就能看到动态代理的结果了。我们看到 CGLIB 等是依赖 ASM,那 ASM 又是什么呢?
3.1 ASM 介绍
ASM (Abstract Syntax Tree, bytecode manipulation and analysis framework) 是一个用于Java 字节码操作和分析的开源框架。ASM 允许开发者在运行时直接操作 Java 类的字节码,提供了低层次的 API,使得开发者可以精细地控制字节码的生成和修改,实现如字节码增强、类和方法的动态修改、AOP(面向切面编程)等功能。
ASM 在JDK 中,具体位置如图
通过 ASM 我们可以获取指定类的字节码
public class AsmUtils {
public static void main(String[] args) {
try {
ClassReader reader = new ClassReader("指定全路径");
TraceClassVisitor visitor = new TraceClassVisitor(new PrintWriter(System.out));
reader.accept(visitor, ClassReader.SKIP_FRAMES);
} catch (Exception e) {
}
}
}
ASM的特点如下:
- 高度灵活:ASM 提供了对字节码的直接访问,可以精确控制每个字节码指令,具有很高的灵活性和可定制性。
- 高性能:由于 ASM 直接操作字节码,性能优异,尤其适合需要高性能字节码操作的场合。
- 广泛使用:ASM 被许多著名的开源项目和框架所使用,如 Hibernate、CGLIB、Spring 等,用以实现 AOP、动态代理、字节码增强等功能。
- 支持广泛的 Java 版本:ASM 支持从 Java 1.0 到最新的 Java 版本,兼容性非常好。
- API设计:ASM 的API设计简洁明了,开发者可以直接操作 ClassVisitor、MethodVisitor 和FieldVisitor 等接口,来遍历、修改或生成字节码。
通过ASM,开发者可以实现例如方法调用拦截、性能监控、字节码注入等高级功能,是Java字节码操作领域的重要工具。
总结:动态代理技术因其灵活性和实用性,在现代软件开发中的应用非常广泛,几乎涵盖了从底层服务到上层业务逻辑的各个环节。