Published on

什么是反射机制?RocketMQ中是如何使用的?

Authors

什么是反射机制?


反射机制指的是程序在运行时能够获取自身的信息。在java中,只要给定类的名字,那么就可以通过反射机制来获得类的所有属性和方法。Java的反射可以:

  1. 在运行时判断任意一个对象所属的类。

  2. 在运行时判断任意一个类所具有的成员变量和方法。

  3. 在运行时任意调用一个对象的方法

  4. 在运行时构造任意一个类的对象

Object obj=//...任意对象;
Class<?> clazz = obj.getclass();
//获取成员变量
Field[] fields =clazz.getDeclaredFields();
for(Field field : fields){
	System.out.println("成员变量: "+ field.getName());
}
//获取方法
Method[] methods =clazz.getDeclaredMethods();
for(Method method:methods){
    System.out.println("方法:" + method.getName));
}

Method method = clazz.getDeclaredMethod("methodName"// 方法参数类型...)
method.setAccessible(true);//如果方法是私有的
method.invoke(obj,//方法参数...);

// 默认构造函数
Object obj=clazz.newInstance();
//或者使用特定的构造函数
Constructor<?> constructor = clazz.getconstructor(// 参数类型...);
Object obj= constructor.newInstance(// 构造函数参数...);

反射的好处是可以提升程序的灵活性和扩展性,比较容易在运行期间干很多事情。但是也存在很多问题:

  1. 代码可读性低。
  2. 反射代码执行的性能低
  3. 反射破环了封装性

所以,我们应该在业务代码中尽量避免使用反射。但在部分场景中,反射的用处是很大的。

反射为什么慢

  • 动态解析 :反射涉及动态解析的类型,导致无法进行某些Java虚拟机优化,如JIT优化。
  • 参数包装 :反射调用时,参数需要被包装成Object[]类型,执行时又需要拆包,这个过程消耗时间。
  • 方法查找 :反射调用方法时需要遍历方法数组并检查可见性,这些都是耗时的操作。

反射常见的应用场景

  1. 动态代理 :可以在运行时创建代理类。

  2. JDBC:使用Class.forName动态加载数据库驱动。

  3. BeanUtils:用于属性值的拷贝。

  4. RPC框架 :实现远程调用的动态处理。

  5. ORM框架 :如Hibernate,使用反射操作数据库表。

  6. Spring的IOC/DI:通过反射实现依赖注入。

反射和Class的关系

Java的Class类是java反射机制的基础,通过Class类我们可以获得关于一个类的相关信息。Java.lang.Class是一个比较特殊的类,它用于封装被装入到IM中的类(包括类和接口)的信息。当一个类或接口被装入到IVM时便会产生一个与之关联的java.lang.Class对象,可以通过这个Class对象对被装入类的详细信息进行访问。虚拟机为每种类型管理一个独一无二的Class对象。也就是说,每个类(型)都有一个Class对象。运行程序时,Java虚拟机(JVM)首先检查是否所要加载的类对应的Class对象是否已经加载。如果没有加载,JVM就会根据类名查找.class文件,并将其Class对象载入。

如何通过反射破坏单例模式?

https://www.txlink.me/blog/202409/如何破坏单例模式?

RocketMQ中哪里使用到了反射?

1 MixAll中打印参数

image-20240904152430373

如图所示,printObjectProperties函数用于打印object的所有非静态字段值到指定的logger中,可选择仅打印标注为@ImportantField的字段。

在broker启动时,BrokerStartup中便调用了该方法将brokerConfig、nettyServerConfig等配置类的属性打印到日志中。

2 分层存储中动态选择文件存储

分层存储是什么?

参考https://github.com/apache/rocketmq/tree/develop/tieredstore

消息队列 RocketMQ 分层存储允许用户将消息数据从本地磁盘卸载到其他更便宜、更大的存储介质上。这样用户就可以以更低的成本延长消息保留时间。并且不同的 topic 可以根据需要灵活指定不同的 TTL。

Tiered storage architecture

这里的FileSegment就像是插件,通过配置tieredBackendServiceProvider属性,默认为“"org.apache.rocketmq.tieredstore.provider.MemoryFileSegment"”,通过反射动态创建实例。

public FileSegmentFactory(MetadataStore metadataStore, MessageStoreConfig storeConfig) {
    try {
        this.storeConfig = storeConfig;
        this.metadataStore = metadataStore;
        Class<? extends FileSegment> clazz =
            Class.forName(storeConfig.getTieredBackendServiceProvider()).asSubclass(FileSegment.class);
        fileSegmentConstructor = clazz.getConstructor(
            MessageStoreConfig.class, FileSegmentType.class, String.class, Long.TYPE);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

结论

反射机制在Java编程中是一个强大的工具,它提供了极大的灵活性和动态性。然而,使用反射时需要谨慎,因为它可能导致性能下降和代码可读性降低。在实际开发中,建议在必要时使用反射,避免在业务代码中频繁使用。

FAQs

  1. 反射机制的主要用途是什么?
    • 反射机制主要用于在运行时获取类的信息,动态调用方法,和构造对象。
  2. 反射会影响程序性能吗?
    • 是的,反射会导致性能下降,因为它涉及动态解析和多次检查。
  3. Java中的反射如何实现?
    • 反射通过java.lang.Class类和相关的方法,如getDeclaredMethods()getDeclaredFields()来实现。
  4. 反射的缺点有哪些?
    • 反射的缺点包括代码可读性低、性能较差以及可能破坏封装性。
  5. 在什么情况下应该使用反射?
    • 当需要动态加载类、调用方法或构造对象时,可以使用反射,但应尽量避免在业务逻辑中频繁使用。