Java反射机制的原理、核心类、实际应用、优缺点、具体工具类BeanUtils。

小二,来客人了
客官有请:

1. 什么是Java反射机制?

反射机制是Java中一种很强大的功能,它允许程序在运行时动态地获取和操作类的信息,通过反射,可以在运行时通过获取类的名称、方法、字段等信息。动态地创建对象,调用方法,访问和修改字段,以及执行其他与类相关的操作。

反射是Java的动态机制,允许程序在运行期间进行如下操作:

  1. 获取类的信息
  2. 创建对象实例
  3. 调用方法
  4. 访问和修改字段
  5. 获取和操作注解

这些操作都可以在不知道类的具体名称的情况下完成,使得程序具有极高的灵活性和通用性。

2. 反射的核心类有哪些?

Java反射主要涉及以下核心类,它们都位于java.lang.reflect包中:

  1. Class:表示类或接口的类型信息
  2. Constructor:表示类的构造方法
  3. Method:表示类的方法
  4. Field:表示类的字段
  5. Array:提供了动态创建和访问Java数组的方法
  6. Modifier:提供了访问修饰符信息的方法

3. 使用反射前的准备👉获取Class对象

在使用反射之前,我们首先需要获取一个类的Class对象。有三种主要方式可以获取Class对象:

  1. 通过类名.class:

    Class cls = String.class;
    
  2. 通过对象的getClass()方法:

    String str = "Hello";
    Class cls = str.getClass();
    
  3. 通过Class.forName()方法:

    Class cls = Class.forName("java.lang.String");
    

3.1 如何通过反射创建对象实例?

通过反射创建对象实例有两种主要方式:

  1. 使用Class对象的newInstance()方法(已过时,不推荐使用):

    Class cls = Class.forName("java.lang.String");
    Object obj = cls.newInstance();
    
  2. 使用Constructor对象的newInstance()方法:

    Class cls = Class.forName("java.lang.String");
    Constructor constructor = cls.getConstructor();
    Object obj = constructor.newInstance();
    

如果需要调用带参数的构造方法,可以这样做:

Class cls = Class.forName("java.lang.String");
Constructor constructor = cls.getConstructor(String.class);
Object obj = constructor.newInstance("Hello, Reflection!");

3.2 通过对象实例调用方法

通过反射调用方法的步骤如下:

  1. 获取Method对象
  2. 使用invoke()方法调用

示例代码:

Class cls = Class.forName("java.lang.String");
Object obj = cls.getConstructor(String.class).newInstance("Hello, Reflection!");
Method method = cls.getMethod("length");
int length = (int) method.invoke(obj);
System.out.println("String length: " + length);

如果方法有参数,可以在getMethod()和invoke()中传入相应的参数:

Method method = cls.getMethod("substring", int.class, int.class);
String result = (String) method.invoke(obj, 0, 5);
System.out.println("Substring: " + result);

3.3 访问对象字段

反射可以用来获取和修改对象的字段值,即使是私有字段:

Class cls = Class.forName("com.example.MyClass");
Object obj = cls.newInstance();

Field field = cls.getDeclaredField("privateField");
field.setAccessible(true);  // 允许访问私有字段
field.set(obj, "New Value");

String value = (String) field.get(obj);
System.out.println("Field value: " + value);

3.4 通过反射查看注解信息(拓展)

反射机制与注解结合使用非常强大,可以在运行时获取和处理注解信息:

Class cls = Class.forName("com.example.MyClass");
if (cls.isAnnotationPresent(MyAnnotation.class)) {
    MyAnnotation annotation = cls.getAnnotation(MyAnnotation.class);
    System.out.println("Annotation value: " + annotation.value());
}

4. 反射的实际应用

反射在很多场景下都有应用,例如:

  1. ORM(对象关系映射)框架 比如👉MyBatis
  2. 依赖注入容器
  3. 单元测试框架
  4. 序列化和反序列化
  5. 动态代理

5. 反射的优缺点

优点:

  1. 提高了程序的灵活性和扩展性
  2. 允许在运行时动态加载和使用类
  3. 是很多Java高级特性和框架的基础

缺点:

  1. 性能开销较大,反射调用比直接调用慢
  2. 可能破坏封装性,允许访问私有成员
  3. 代码可读性可能下降

6. BeanUtils.copyProperties的反射实现

**Apache Commons BeanUtils库中的copyProperties方法是一个广泛使用的工具,**用于在JavaBean之间复制属性。这个方法的底层实现就是通过反射来完成的。让我们来看看它的核心实现原理:

/**
 * 将给定源bean的属性值复制到目标bean中。
 * 
 * @param source 源bean
 * @param target 目标bean
 * @param editable 用于限制属性设置的类(或接口)
 * @param ignoreProperties 要忽略的属性名称数组
 * @throws BeansException 如果bean属性复制失败
 */
private static void copyProperties(Object source, Object target, @Nullable Class editable,
        @Nullable String... ignoreProperties) throws BeansException {
    // 步骤1: 验证输入参数
    Assert.notNull(source, "Source must not be null");
    Assert.notNull(target, "Target must not be null");

    // 步骤2: 确定实际的可编辑类
    Class actualEditable = target.getClass();
    if (editable != null) {
        if (!editable.isInstance(target)) {
            throw new IllegalArgumentException("Target class [" + target.getClass().getName() +
                    "] not assignable to Editable class [" + editable.getName() + "]");
        }
        actualEditable = editable;
    }

    // 步骤3: 获取目标类的属性描述符
    PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
    List ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);

    // 步骤4: 遍历目标的每个属性描述符
    for (PropertyDescriptor targetPd : targetPds) {
        Method writeMethod = targetPd.getWriteMethod();
        if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
            // 步骤5: 从源中获取相应的属性描述符
            PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
            if (sourcePd != null) {
                Method readMethod = sourcePd.getReadMethod();
                if (readMethod != null) {
                    // 步骤6: 检查属性类型是否可赋值
                    ResolvableType sourceResolvableType = ResolvableType.forMethodReturnType(readMethod);
                    ResolvableType targetResolvableType = ResolvableType.forMethodParameter(writeMethod, 0);

                    // 如果任一ResolvableType具有无法解析的泛型,则在可赋值检查中忽略泛型类型
                    boolean isAssignable =
                        (sourceResolvableType.hasUnresolvableGenerics() || targetResolvableType.hasUnresolvableGenerics() ?
                            ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType()) :
                            targetResolvableType.isAssignableFrom(sourceResolvableType));

                    if (isAssignable) {
                        try {
                            // 步骤7: 如果需要,设置方法为可访问
                            if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
                                readMethod.setAccessible(true);
                            }
                            // 步骤8: 从源对象读取属性值
                            Object value = readMethod.invoke(source);
                            if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
                                writeMethod.setAccessible(true);
                            }
                            // 步骤9: 将属性值写入目标对象
                            writeMethod.invoke(target, value);
                        }
                        catch (Throwable ex) {
                            throw new FatalBeanException(
                                "Could not copy property '" + targetPd.getName() + "' from source to target", ex);
                        }
                    }
                }
            }
        }
    }
}

步骤说明:

  1. 验证输入参数:确保源对象和目标对象不为空。

  2. 确定实际的可编辑类:如果提供了editable参数,验证目标对象是否为该类的实例,并将actualEditable设置为editable或目标对象的类。

  3. 获取目标类的属性描述符:使用getPropertyDescriptors方法获取目标类的所有属性描述符。

  4. 遍历目标的每个属性描述符:对每个非忽略的、具有写方法的属性进行处理。

  5. 从源中获取相应的属性描述符:根据属性名在源对象中查找对应的属性描述符。

  6. 检查属性类型是否可赋值:使用ResolvableType来处理泛型情况,确保源属性类型可以赋值给目标属性类型。

  7. 如果需要,设置方法为可访问:对于非公共方法,设置其可访问性。

  8. 从源对象读取属性值:使用反射调用源对象的读方法获取属性值。

  9. 将属性值写入目标对象:使用反射调用目标对象的写方法,将属性值设置到目标对象中。

这个方法的主要目的是实现对象之间的属性复制,同时考虑了类型兼容性、访问控制和异常处理等因素。

这里的反射主要体现在以下几个方面:

  • 使用PropertyDescriptor获取属性的读写方法(getter和setter)。
  • 使用Method.invoke()方法来调用getter和setter方法。
  • 使用属性的Class对象进行类型检查和转换。

通过反射,BeanUtils.copyProperties可以在不知道具体类型的情况下,动态地复制对象属性,这使得它能够适用于各种不同的JavaBean类。

然而,正如前面提到的,反射操作的性能开销较大。因此,在频繁调用或处理大量数据时,使用反射实现的BeanUtils.copyProperties可能会成为性能瓶颈。在这种情况下,可以考虑使用基于字节码生成的工具(如Cglib的BeanCopier)来提高性能。

谈谈你对反射的理解(高频面试题)

反射机制是Java中一种很强大的功能,它允许程序在运行时动态地获取和操作类的信息,通过反射,可以在运行时通过获取类的名称、方法、字段等信息。动态地创建对象,调用方法,访问和修改字段,以及执行其他与类相关的操作。

它的核心时java.lang.reflect包中的一组类和接口。常用的反射类和接口:

  • Class类:代表一个类或者接口,在运行时可以通过它获取和操作类的信息,通过class类的newInstance()方法可以创建类的实例,相当于调用该类的无参构造函数。
  • Constructors类:代表类的构造函数,在运行时可以通过它创建对象
  • Method类:代表类的方法,在运行时可以通过它调用方法。通过调用它的invoke()方法,可以传递参数并获取返回值。
  • Filed类:代表类的字段,在运行时可以通过它访问和修改字段。

使用反射技术可以在运行时动态的创建对象、调用方法、访问和修改字段,反射技术使得程序具有更高的灵活性和扩展性。它也带来了一定的性能开销,在性能敏感的场景下需要谨慎使用。

如果问你用过哪些反射的工具类?

BeanUtils.copyProperties(Object o,Object o1),前面提到的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值