📢 大家好,我是 【战神刘玉栋】,有10多年的研发经验,致力于前后端技术栈的知识沉淀和传播。 💗
🌻 CSDN入驻不久,希望大家多多支持,后续会继续提升文章质量,绝不滥竽充数,欢迎多多交流。👍
文章目录
写在前面的话
之前几篇文章介绍 Spring 和 SpringMVC 源码中,经常看到反射机制和动态代理的源码,本篇展开介绍一下。
推荐文章 - 程序猿入职必会:
《程序猿入职必会(1) · 搭建拥有数据交互的 SpringBoot 》
《程序猿入职必会(2) · 搭建具备前端展示效果的 Vue》
《程序猿入职必会(3) · SpringBoot 各层功能完善 》
《程序猿入职必会(4) · Vue 完成 CURD 案例 》
《程序猿入职必会(5) · CURD 页面细节规范 》
《程序猿入职必会(6) · 返回结果统一封装》
《程序猿入职必会(7) · 前端请求工具封装》
《程序猿入职必会(8) · 整合 Knife4j 接口文档》
《程序猿入职必会(9) · 用代码生成器快速开发》
《程序猿入职必会(10) · 整合 Redis(基础篇)》
《程序猿入职必会(11) · 整合 Redis 实战运用》
推荐文章 - 学会 SpringMVC 系列
《学会 SpringMVC 系列 · 基础篇》
《学会 SpringMVC 系列 · 剖析篇(上)》
《学会 SpringMVC 系列 · 剖析入参处理》
《学会 SpringMVC 系列 · 剖析出参处理》
《学会 SpringMVC 系列 · 返回值处理器》
《学会 SpringMVC 系列 · 消息转换器 MessageConverters》
《学会 SpringMVC 系列 · 写入拦截器 ResponseBodyAdvice》
《学会 SpringMVC 系列 · 剖析初始化》
《学会 SpringMVC 系列 · 参数解析器 ArgumentResolvers》
动态代理
技术简介
动态代理在Java中有着广泛的应用,比如 Spring AOP、RPC 远程调用、Java 注解对象获取、日志、用户鉴权、全局性异常处理、性能监控,甚至事务处理等。
代理模式
当我们谈及 Java 的 动态代理 或者是 静态代理 ,我们都很容易谈及到设计模式中的代理模式
。
代理模式给某一个对象提供一个代理,并由代理对象来控制对真实对象的访问,起到对代理对象已有功能的增强。
代理模式存在三个角色:
1、抽象主题角色(Subject)
抽象主题角色指的是 真实对象
与 代理对象
的公共的法所抽象出来的一个接口或者是抽象类。
在 Java 编程成,我们就可以认为是一个 接口
或者是 抽象类
。
2、真实主题角色(RealSubject)
真实主题角色指的是被代理对象。
3、代理主题角色(ProxySubject)
代理主题角色指的是代理对象。
两种代理
JDK 动态代理
适用场景:JDK 动态代理仅适用于接口代理。如果你的 Bean 实现了一个或多个接口,Spring 会使用 JDK 动态代理。
工作原理:JDK 动态代理通过反射机制创建一个实现了指定接口的代理类,并在调用方法时将调用转发到目标对象。
CGLIB 代理
适用场景:CGLIB 代理适用于没有实现接口的类,或者当你明确指定使用 CGLIB 代理时。CGLIB 通过生成目标类的子类来实现代理。
工作原理:CGLIB 通过字节码生成技术在运行时创建一个目标类的子类,并重写其方法,以实现代理功能。
JDK 动态代理
这里也来一个简单示例:
java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 定义一个接口
interface Hello {
void sayHello(String name);
}
// 实现 InvocationHandler 接口
class HelloInvocationHandler implements InvocationHandler {
private final Hello target;
public HelloInvocationHandler(Hello target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before method: " + method.getName());
Object result = method.invoke(target, args); // 调用目标对象的方法
System.out.println("After method: " + method.getName());
return result;
}
}
// 目标类实现 Hello 接口
class HelloImpl implements Hello {
@Override
public void sayHello(String name) {
System.out.println("Hello, " + name);
}
}
// 使用动态代理
public class ProxyExample {
public static void main(String[] args) {
Hello hello = new HelloImpl();
Hello proxyInstance = (Hello) Proxy.newProxyInstance(
hello.getClass().getClassLoader(),
new Class<?>[]{Hello.class},
new HelloInvocationHandler(hello)
);
proxyInstance.sayHello("World");
}
}
上面的示例可以看出,核心就是 Proxy.newProxyInstance 方法,下面介绍一下。
【Proxy.newProxyInstance】
Proxy.newProxyInstance 是 Java 提供的一个静态方法,用于创建动态代理对象。这个方法属于java.lang.reflect.Proxy
类,允许你在运行时创建一个实现了一组给定接口的新代理实例。这个代理实例可以在调用方法时,将调用转发到一个指定的处理器,通常是实现了 InvocationHandler 接口的对象。
java
//参数1:ClassLoader loader:用于加载代理类的类加载器。通常可以使用目标类的类加载器。
//参数2:Class<?>[] interfaces:一个接口数组,代理对象将实现这些接口。
//参数3:InvocationHandlerh:一个实现了 InvocationHandler 接口的对象,负责处理代理实例上调用的方法。
//返回一个代理对象,该对象实现了指定的接口。
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
【与反射机制的关系】
Proxy.newProxyInstance 方法与 Java 的反射机制密切相关。以下是它们之间的关系:
1、动态方法调用:在 InvocationHandler 的 invoke 方法中,使用反射来调用目标对象的方法。具体来说,Method.invoke(Object obj, Object... args) 方法是反射的一部分,它允许在运行时调用对象的方法。
2、接口的动态实现:通过 Proxy.newProxyInstance 创建的代理对象在运行时动态实现了指定的接口,而不是在编译时确定。这种动态特性是反射机制的一个重要应用。
3、方法信息:在 invoke 方法中,可以通过 Method 对象获取方法的元数据(如方法名、参数类型等),这也是反射机制的一部分。
CGLIB 动态代理
参考示例代码,如下所示:
java
public class UserService {
public void addUser(String username) {
System.out.println("User " + username + " added.");
}
}
public class CglibProxy implements MethodInterceptor {
public Object createProxy(Class<?> clazz) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("Before method: " + method.getName());
Object result = proxy.invokeSuper(obj, args); // 调用父类的方法
System.out.println("After method: " + method.getName());
return result;
}
}
public class Client2 {
public static void main(String[] args) {
CglibProxy cglibProxy = new CglibProxy();
UserService userServiceProxy = (UserService) cglibProxy.createProxy(UserService.class);
userServiceProxy.addUser("John Doe");
}
}
JDK17需要添加如下VM选项:
--add-opens java.base/java.lang=ALL-UNNAMED
SpringBoot 与动态代理
Spring Boot 默认情况下使用 JDK 动态代理,除非满足以下条件才会使用 CGLib 动态代理:
1、目标类不是接口:
JDK 动态代理只能代理接口,如果目标类不是接口,Spring Boot 会使用 CGLib 动态代理。
2、proxyTargetClass 属性设置为 true:
在 @EnableAspectJAutoProxy 注解中,可以设置 proxyTargetClass 属性为 true,强制使用 CGLib 动态代理。
为什么 Spring Boot 默认使用 JDK 动态代理?
性能: JDK 动态代理通常比 CGLib 动态代理性能更高,因为它不需要生成额外的字节码。
简洁: JDK 动态代理更简洁,因为它只需要代理接口,而 CGLib 动态代理需要代理类。
为什在某些情况下使用 CGLib 动态代理?
代理非接口类: JDK 动态代理无法代理非接口类,而 CGLib 动态代理可以。
需要更强大的功能: CGLib 动态代理提供了更强大的功能,例如:可以代理私有方法。
总结:
Spring Boot 默认使用 JDK 动态代理,因为它性能更高、更简洁。但在某些情况下,例如代理非接口类或需要更强大的功能时,Spring Boot 会使用 CGLib 动态代理。
总结陈词
💗 后续会逐步分享企业实际开发中的实战经验,有需要交流的可以联系博主。