《知识点扫盲 · 动态代理》

📢 大家好,我是 【战神刘玉栋】,有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 动态代理。


总结陈词

💗 后续会逐步分享企业实际开发中的实战经验,有需要交流的可以联系博主。

相关推荐
城南vision6 分钟前
Docker学习—Docker核心概念总结
java·学习·docker
wyh要好好学习13 分钟前
SpringMVC快速上手
java·spring
尢词15 分钟前
SpringMVC
java·spring·java-ee·tomcat·maven
Mr. zhihao22 分钟前
享元模式在 JDK 中的应用解析
java·享元模式
茶馆大橘25 分钟前
微服务系列五:避免雪崩问题的限流、隔离、熔断措施
java·jmeter·spring cloud·微服务·云原生·架构·sentinel
wrx繁星点点25 分钟前
享元模式:高效管理共享对象的设计模式
java·开发语言·spring·设计模式·maven·intellij-idea·享元模式
真的想不出名儿29 分钟前
Java基础——反射
java·开发语言
鱼跃鹰飞30 分钟前
大厂面试真题-简单说说线程池接到新任务之后的操作流程
java·jvm·面试
菜菜-plus38 分钟前
java设计模式之策略模式
java·设计模式·策略模式
努力编程的阿伟1 小时前
【Java SE语法】抽象类(abstract class)和接口(interface)有什么异同?
java·开发语言