Yang's blog Yang's blog
首页
后端开发
密码学
机器学习
命令手册
关于
友链
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

xiaoyang

尽人事,听天命
首页
后端开发
密码学
机器学习
命令手册
关于
友链
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • SpringCloud

    • 微服务架构介绍
    • SpringCloud介绍
    • Spring Cloud:生产者与消费者
    • Spring Cloud Eureka:构建可靠的服务注册与发现
    • Spring Cloud Ribbon:负载均衡
    • Spring Cloud Fegin:服务调用
    • Spring Cloud Hystrix:熔断器
    • Spring Cloud Zuul:统一网关路由
    • Spring Cloud Config:配置中心
  • Java后端框架

    • LangChain4j

      • 介绍
      • 快速开始
      • Chat and Language Models
      • Chat Memory
      • Model Parameters
      • Response Streaming
      • AI Services
      • Agent
      • Tools (Function Calling)
      • RAG
      • Structured Outputs
      • Classification
      • Embedding (Vector) Stores
      • Image Models
      • Quarkus Integration
      • Spring Boot Integration
      • Kotlin Support
      • Logging
      • Observability
      • Testing and Evaluation
      • Model Context Protocol
  • 八股文

    • 操作系统
    • JVM介绍
    • Java多线程
    • Java集合框架
    • Java反射
      • 1. 什么是代理?
      • 2. 代理的工作机制
      • 3. 静态代理
      • 4. JDK 动态代理
        • 1. 实现示例
        • 2. 动态代理的实现原理
        • 3. 优点与缺点
        • 4. 使用场景
      • 5. CGLIB 动态代理
        • 1. 什么是 CGLIB?
        • 2. CGLIB 代理的原理
        • 3. CGLIB 代理的实现步骤
        • 3.1 引入 CGLIB 依赖
        • 3.2 CGLIB 代理示例
        • (1) 目标类(被代理对象)
        • (2) CGLIB 代理工厂
        • (3) 测试 CGLIB 代理
        • (4) 运行结果
        • 4. CGLIB 代理与 JDK 代理的对比
        • 5. CGLIB 代理的优缺点
      • 6. 反射的定义与用途
      • 7. 获取Class对象的三种方式
      • 8. 使用反射操作构造方法
      • 9. 使用反射操作成员变量
      • 10. 使用反射操作成员方法
      • 11. 总结
    • JavaIO
    • Mybatis介绍
    • Spring介绍
    • SpringBoot介绍
    • Mysql
    • Redis
    • 数据结构
    • 云计算
    • 设计模式
    • 计算机网络
    • 锁核心类AQS
    • Nginx
  • 前端技术

    • 初识Vue3
    • Vue3数据双向绑定
    • Vue3生命周期
    • Vue-Router 组件
    • Pinia 集中式状态存储
  • 中间件

    • RocketMQ
  • 开发知识

    • 请求参数注解
    • 时间复杂度和空间复杂度
    • JSON序列化与反序列化
    • Timestamp vs Datetime
    • Java开发中必备能力单元测试
    • 正向代理和反向代理
    • 什么是VPN
    • 后端服务端主动推送消息的常见方式
    • 正则表达式
    • SseEmitter vs Flux 的本质区别与底层原理解析
  • 后端开发
  • 八股文
xiaoyang
2025-02-21
目录

Java反射

# Java反射

# 1. 什么是代理?

代理模式(Proxy Pattern)是一种结构型设计模式,代理对象充当真实对象的替身,控制对其的访问或添加额外的行为。在 Java 中,代理常用于日志记录、性能监控、安全检查等场景。代理可以分为静态代理和动态代理两种,主要区别在于创建方式和灵活性。

image-20250221103119717

# 2. 代理的工作机制

代理的工作流程如下:

  1. 调用者调用代理对象的方法。
  2. 代理对象在方法执行前或后添加额外的逻辑(如日志、权限检查)。
  3. 代理将请求转发给目标对象,目标对象执行实际业务逻辑。
  4. 返回结果可能经过代理的再次处理后返回给调用者。

这种机制的核心是接口一致性:代理对象必须实现与目标对象相同的接口,确保调用者无感知地使用代理。

# 3. 静态代理

静态代理是你手动编写的一个类,实现与真实对象相同的接口,然后在方法中调用真实对象的方法,并可能添加额外逻辑。例如:

  • 你有一个 User 接口,RealUser 实现它,UserProxy 是静态代理,调用 RealUser 的方法前打印日志。
  • 优点:运行快,因为是编译时生成的类。
  • 缺点:不够灵活,每个接口都需要写一个代理类。

静态代理是指手动编写一个代理类,实现与真实对象相同的接口,并在方法中调用真实对象的方法,同时可能添加额外的逻辑。

实现示例

考虑以下代码示例,展示一个简单的静态代理:

public interface User {
    void doSomething();
}

public class RealUser implements User {
    @Override
    public void doSomething() {
        System.out.println("Doing something");
    }
}

public class UserProxy implements User {
    private User realUser;

    public UserProxy(User realUser) {
        this.realUser = realUser;
    }

    @Override
    public void doSomething() {
        System.out.println("Before doing something");
        realUser.doSomething();
        System.out.println("After doing something");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

在这个例子中,UserProxy 是 RealUser 的静态代理,实现了 User 接口,在调用 doSomething 方法前和后添加了打印日志。

# 4. JDK 动态代理

动态代理是在运行时通过 Java 的反射 API 创建的代理,不需要手动编写代理类。它利用 java.lang.reflect.Proxy 类生成实现指定接口的代理实例,所有方法调用都会路由到一个调用处理器(Invocation Handler)。

# 1. 实现示例

以下是一个动态代理的示例,展示如何为 User 接口创建代理并添加日志功能:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;

public class LoggingProxyHandler implements InvocationHandler {
    private Object realObject;

    public LoggingProxyHandler(Object realObject) {
        this.realObject = realObject;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Calling method: " + method.getName());
        try {
            return method.invoke(realObject, args);
        } catch (IllegalAccessException | InvocationTargetException e) {
            throw new RuntimeException("Error invoking method", e);
        }
    }
}

public class DynamicProxyExample {
    public static void main(String[] args) {
        User realUser = new RealUser();
        User proxy = (User) Proxy.newProxyInstance(
            realUser.getClass().getClassLoader(),
            new Class[] { User.class },
            new LoggingProxyHandler(realUser)
        );

        proxy.doSomething();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

在这个例子中,Proxy.newProxyInstance 创建了一个动态代理实例,实现了 User 接口。所有对代理的方法调用都会通过 LoggingProxyHandler 的 invoke 方法处理,打印方法名后调用真实对象的对应方法。

# 2. 动态代理的实现原理

动态代理的核心在于反射:

  1. 代理类的创建:Proxy.newProxyInstance 使用反射生成一个实现指定接口的类,类名通常以 $Proxy 开头,加载到 JVM 中。
  2. 方法调用的处理:调用处理器(InvocationHandler)的 invoke 方法接收所有方法调用,通过 Method.invoke 使用反射调用真实对象的方法。

# 3. 优点与缺点

  • 优点:灵活性高,可以在运行时为任意接口创建代理,无需为每个接口编写类,减少代码重复,适合框架开发。
  • 缺点:由于使用反射,性能稍慢,存在一定的运行时开销,且只能代理接口,不能直接代理具体类。如果需要代理具体类,可以使用其他库如 CGLIB,通过字节码操作实现,但这超出了标准 Java 反射的范围。

# 4. 使用场景

动态代理常用于需要通用拦截逻辑的场景,例如:

  • 框架开发,如 Spring AOP 中的事务管理。
  • 测试场景,创建模拟对象(Mock)来代替真实对象。
  • 添加横切关注点,如日志记录、性能监控。

# 5. CGLIB 动态代理

# 1. 什么是 CGLIB?

CGLIB(Code Generation Library)是一种基于字节码增强的动态代理技术,它可以在运行时 动态生成一个类的子类,并 重写父类方法 以实现 AOP(面向切面编程)。

CGLIB 代理是 Spring AOP 的核心技术之一,主要用于 代理没有实现接口的类,弥补了 JDK 动态代理的局限性。


# 2. CGLIB 代理的原理

CGLIB 通过 继承目标类 并 重写目标方法 来增强功能,因此:

  • 它不能代理 final 类,因为 final 类不能被继承。
  • 它不能代理 final 方法,因为 final 方法不能被重写。

CGLIB 的核心原理是使用 ASM(Java 字节码操作框架) 来修改目标类的字节码,并生成一个新的代理子类。


# 3. CGLIB 代理的实现步骤

# 3.1 引入 CGLIB 依赖

如果你使用的是 Maven,可以添加以下依赖:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>
1
2
3
4
5

# 3.2 CGLIB 代理示例

我们以一个 UserService 类为例,实现 CGLIB 代理。

# (1) 目标类(被代理对象)
public class UserService {
    public void addUser(String name) {
        System.out.println("Add user: " + name);
    }
}
1
2
3
4
5
# (2) CGLIB 代理工厂
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

public class CGLIBProxy implements MethodInterceptor {
    private Object target;

    public CGLIBProxy(Object target) {
        this.target = target;
    }

    public Object getProxyInstance() {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(target.getClass()); // 生成目标类的子类
        enhancer.setCallback(this); // 设置拦截器
        return enhancer.create(); // 创建代理对象
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("Before method...");
        Object result = proxy.invokeSuper(obj, args); // 调用父类方法
        System.out.println("After method...");
        return result;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# (3) 测试 CGLIB 代理
public class CGLIBTest {
    public static void main(String[] args) {
        UserService userService = new UserService();
        CGLIBProxy cgProxy = new CGLIBProxy(userService);
        UserService proxyObject = (UserService) cgProxy.getProxyInstance();
        proxyObject.addUser("Alice");
    }
}
1
2
3
4
5
6
7
8
# (4) 运行结果
Before method...
Add user: Alice
After method...
1
2
3

CGLIB 成功在 addUser 方法前后插入了额外的逻辑,实现了 AOP 代理。


# 4. CGLIB 代理与 JDK 代理的对比

特性 JDK 动态代理 CGLIB 代理
代理方式 生成接口代理类 生成子类代理
是否需要接口 需要 不需要
是否能代理 final 方法 可以 不可以
是否能代理 final 类 可以 不可以
性能 代理接口时性能更好 代理类时性能更好
实现方式 反射 (Proxy + InvocationHandler) 字节码增强 (Enhancer + MethodInterceptor)
  • JDK 代理的优点:基于接口,使用 Proxy.newProxyInstance 实现,性能在代理接口时优于 CGLIB。
  • CGLIB 代理的优点:不需要目标类实现接口,适用于代理普通类,Spring AOP 默认使用 CGLIB 来代理非接口的 Bean。

# 5. CGLIB 代理的优缺点

优点

✅ 不需要接口:相比 JDK 动态代理,CGLIB 可以直接代理类,不要求实现接口。 ✅ 性能较高:CGLIB 直接操作字节码,在代理类时性能优于 JDK 代理。 ✅ Spring AOP 的重要支撑:Spring 默认使用 CGLIB 来代理没有实现接口的 Bean。

缺点

❌ 不能代理 final 方法:因为 CGLIB 通过继承实现代理,final 方法无法被重写。 ❌ 不能代理 final 类:如果目标类被 final 修饰,CGLIB 无法生成子类。 ❌ 比 JDK 代理占用更多内存:因为 CGLIB 需要动态生成子类,创建代理对象时会占用更多内存。

# 6. 反射的定义与用途

在Java编程中,反射(Reflection)是一种强大的机制,可以通过编程的方式访问类的信息例如成员变量、成员方向和构造方法的信息,并进行一些操作。这包括创建对象、调用方法,甚至访问私有成员。反射广泛应用于框架开发(如Spring AOP)、动态代理等场景。它通过java.lang.reflect包提供的一系列类(如Class、Method、Field、Constructor)实现。反射的核心在于Class对象,它代表运行时的类信息。

image-20250221111728493

# 7. 获取Class对象的三种方式

获取Class对象是反射的起点,有三种常见方法:

方法 描述 使用场景
Class.forName("全类名") 通过类名字符串加载类,抛出ClassNotFoundException 运行时从配置文件或用户输入获取类
类名.class 直接使用类的.class属性 编译时已知类,代码简洁
对象.getClass() 从实例对象获取其类信息 有对象实例时,查看其类型

# 8. 使用反射操作构造方法

通过Class对象的getConstructors()或getDeclaredConstructors()方法,可以获取类的构造方法。getConstructors()返回所有公共构造方法,getDeclaredConstructors()返回所有构造方法(包括私有)。

以下是使用反射创建对象的步骤:

  1. 获取Class对象。
  2. 获取Constructor对象,例如getDeclaredConstructor()。
  3. 如果构造方法是私有的,调用setAccessible(true)以绕过访问检查。
  4. 使用newInstance()创建对象。

代码示例:

Class<?> cls = Class.forName("com.example.MyClass");
Constructor<?> constructor = cls.getDeclaredConstructor(String.class);
constructor.setAccessible(true); // 允许访问私有构造方法
Object instance = constructor.newInstance("example");
1
2
3
4

这允许创建私有构造方法的对象,打破了封装,适合测试场景。

# 9. 使用反射操作成员变量

成员变量(字段)通过getFields()或getDeclaredFields()获取。getFields()返回所有公共字段(包括继承的),getDeclaredFields()返回类中声明的所有字段(包括私有)。

要获取字段的值,必须提供实例对象,并使用Field.get()方法。如果字段是私有的,需先调用setAccessible(true)。

示例:

Class<?> cls = Class.forName("com.example.MyClass");
Field field = cls.getDeclaredField("myField");
field.setAccessible(true);
Object instance = cls.getDeclaredConstructor().newInstance();
Object value = field.get(instance);
1
2
3
4
5

这展示了反射如何动态访问字段值,需注意性能开销和安全问题。

# 10. 使用反射操作成员方法

成员方法通过getMethods()或getDeclaredMethods()获取。getMethods()返回所有公共方法(包括继承的),getDeclaredMethods()返回类中声明的所有方法(包括私有)。

具体方法如下:

  • getMethods():返回Method[],包含所有公共方法。
  • getDeclaredMethods():返回Method[],包含所有声明方法,不包括继承的。
  • getMethod(String name, Class<?>... parameterTypes):返回单个公共方法。
  • getDeclaredMethod(String name, Class<?>... parameterTypes):返回单个声明方法。

使用Method.invoke(Object obj, Object... args)运行方法,参数包括调用对象和方法参数。

示例:

Class<?> cls = Class.forName("com.example.MyClass");
Method method = cls.getDeclaredMethod("myMethod", int.class);
method.setAccessible(true); // 如果是私有方法
Object instance = cls.getDeclaredConstructor().newInstance();
Object result = method.invoke(instance, 42);
1
2
3
4
5

这允许动态调用方法,适合框架如Spring的AOP实现。

# 11. 总结

反射提供了强大的动态能力,但也带来性能开销(由于使用反射调用比直接调用慢)和安全风险(可访问私有成员)。因此,在高性能场景或安全敏感场景下需谨慎使用。

编辑 (opens new window)
上次更新: 2025/05/12, 03:06:46

← Java集合框架 JavaIO→

最近更新
01
SseEmitter vs Flux 的本质区别与底层原理解析
05-12
02
操作系统
03-18
03
Nginx
03-17
更多文章>
Theme by Vdoing | Copyright © 2023-2025 xiaoyang | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式