自定义反射工具类,方便通过lambda获取字段相关信息

666 阅读5分钟

前言

Java反射是一个强大的工具,可以在运行时动态检查和操作类、方法和字段。然而,直接使用反射可能会导致代码复杂且难以维护。为了解决这个问题,我们可以使用专门的工具类来简化反射操作。本文将介绍一个名为ReflectUtils的Java工具类,它通过Lambda表达式提供了一种简洁的方式来访问和操作对象的字段。

1. ReflectUtils简介

ReflectUtils是一个实用工具类,利用Java反射和Lambda表达式来简化字段的获取和设置操作。它通过缓存机制提高性能,并提供了多种便捷方法来处理对象的字段。

工具类完整代码

/**
 * 反射工具类
 */
@Slf4j
public class ReflectUtils {

    // 缓存Lambda表达式和对应字段的映射,避免重复计算
    private static final Map<SFunction<?>, Field> FUNCTION_CACHE = new ConcurrentHashMap<>();

    /**
     * 获取字段名称。
     *
     * @param function Lambda表达式
     * @param <T>      类型参数
     * @return 字段名称
     */
    public static <T> String getFieldName(SFunction<T> function) {
        Field field = ReflectUtils.getField(function);
        return field.getName();
    }

    /**
     * 获取字段值。
     *
     * @param obj     对象实例
     * @param function Lambda表达式
     * @param <T>      类型参数
     * @return 字段值
     */
    public static <T> Object getFieldValue(Object obj, SFunction<T> function) {
        try {
            Field field = getField(function);
            return field.get(obj);
        } catch (IllegalAccessException e) {
            throw new RuntimeException("Unable to access field value.", e);
        }
    }

    /**
     * 设置字段值。
     *
     * @param obj     对象实例
     * @param function Lambda表达式
     * @param value   要设置的值
     * @param <T>      类型参数
     */
    public static <T> void setFieldValue(Object obj, SFunction<T> function, Object value) {
        try {
            Field field = getField(function);
            field.set(obj, value);
        } catch (IllegalAccessException e) {
            throw new RuntimeException("Unable to set field value.", e);
        }
    }

    /**
     * 检查字段是否存在。
     *
     * @param function Lambda表达式
     * @param <T>      类型参数
     * @return 如果字段存在,返回true,否则返回false
     */
    public static <T> boolean fieldExists(SFunction<T> function) {
        try {
            getField(function);
            return true;
        } catch (RuntimeException e) {
            return false;
        }
    }

    /**
     * 获取所有字段。
     *
     * @param clazz 类
     * @return 字段列表
     */
    public static List<Field> getAllFields(Class<?> clazz) {
        List<Field> fields = new ArrayList<>();
        while (clazz != null) {
            fields.addAll(Arrays.asList(clazz.getDeclaredFields()));
            clazz = clazz.getSuperclass();
        }
        return fields;
    }

    /**
     * 获取字段对象。
     *
     * @param function Lambda表达式
     * @param <T>      类型参数
     * @return 字段对象
     */
    public static <T> Field getField(SFunction<T> function) {
        return FUNCTION_CACHE.computeIfAbsent(function, ReflectUtils::findField);
    }

    /**
     * 查找字段对象。
     *
     * @param function Lambda表达式
     * @param <T>      类型参数
     * @return 字段对象
     */
    public static <T> Field findField(SFunction<T> function) {
        // 第1步 获取SerializedLambda
        final SerializedLambda serializedLambda = getSerializedLambda(function);
        // 第2步 implMethodName 即为Field对应的Getter方法名
        final String implClass = serializedLambda.getImplClass();
        final String implMethodName = serializedLambda.getImplMethodName();
        final String fieldName = convertToFieldName(implMethodName);
        // 第3步 通过Spring的反射工具类获取Class中定义的Field
        final Field field = getField(fieldName, serializedLambda);

        // 第4步 如果没有找到对应的字段应该抛出异常
        if (field == null) {
            throw new RuntimeException("No such class 「" + implClass + "」 field 「" + fieldName + "」.");
        }
        // 设置字段可访问
        field.setAccessible(true);
        return field;
    }

    /**
     * 获取字段对象。
     *
     * @param fieldName        字段名称
     * @param serializedLambda 序列化的Lambda表达式
     * @return 字段对象
     */
    static Field getField(String fieldName, SerializedLambda serializedLambda) {
        try {
            // 获取的Class是字符串,并且包名是“/”分割,需要替换成“.”,才能获取到对应的Class对象
            String declaredClass = serializedLambda.getImplClass().replace("/", ".");
            Class<?> aClass = Class.forName(declaredClass, false, ClassUtils.getDefaultClassLoader());
            return ReflectionUtils.findField(aClass, fieldName);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException("get class field exception.", e);
        }
    }

    /**
     * 将getter方法名转换为字段名。
     *
     * @param getterMethodName Getter方法名
     * @return 字段名称
     */
    static String convertToFieldName(String getterMethodName) {
        // 获取方法名
        String prefix = null;
        if (getterMethodName.startsWith("get")) {
            prefix = "get";
        } else if (getterMethodName.startsWith("is")) {
            prefix = "is";
        }

        if (prefix == null) {
            throw new IllegalArgumentException("invalid getter method: " + getterMethodName);
        }

        // 截取get/is之后的字符串并转换首字母为小写
        return Introspector.decapitalize(getterMethodName.replace(prefix, ""));
    }

    /**
     * 获取SerializedLambda对象。
     *
     * @param function Lambda表达式
     * @param <T>      类型参数
     * @return SerializedLambda对象
     */
    static <T> SerializedLambda getSerializedLambda(SFunction<T> function) {
        try {
            Method method = function.getClass().getDeclaredMethod("writeReplace");
            method.setAccessible(Boolean.TRUE);
            return (SerializedLambda) method.invoke(function);
        } catch (Exception e) {
            throw new RuntimeException("get SerializedLambda exception.", e);
        }
    }
}

2. 核心功能

2.1 获取字段名称

public static <T> String getFieldName(SFunction<T> function) {
    Field field = ReflectUtils.getField(function);
    return field.getName();
}

该方法使用Lambda表达式获取字段名称。通过getField方法获取字段对象,然后返回字段的名称。

2.2 获取字段值

public static <T> Object getFieldValue(Object obj, SFunction<T> function) {
    try {
        Field field = getField(function);
        return field.get(obj);
    } catch (IllegalAccessException e) {
        throw new RuntimeException("Unable to access field value.", e);
    }
}

此方法通过反射获取对象中指定字段的值。它接受一个对象实例和一个Lambda表达式,返回字段的值。

2.3 设置字段值

public static <T> void setFieldValue(Object obj, SFunction<T> function, Object value) {
    try {
        Field field = getField(function);
        field.set(obj, value);
    } catch (IllegalAccessException e) {
        throw new RuntimeException("Unable to set field value.", e);
    }
}

该方法用于设置对象字段的值。通过反射获取字段对象,并调用Field.set()方法设置新值。

2.4 检查字段是否存在

public static <T> boolean fieldExists(SFunction<T> function) {
    try {
        getField(function);
        return true;
    } catch (RuntimeException e) {
        return false;
    }
}

此方法检查字段是否存在。通过捕获异常来判断字段的存在性。

2.5 获取所有字段

public static List<Field> getAllFields(Class<?> clazz) {
    List<Field> fields = new ArrayList<>();
    while (clazz != null) {
        fields.addAll(Arrays.asList(clazz.getDeclaredFields()));
        clazz = clazz.getSuperclass();
    }
    return fields;
}

该方法返回类及其父类的所有字段。通过迭代类的层次结构,收集所有声明的字段。

3. 实现细节

3.1 字段缓存

private static final Map<SFunction<?>, Field> FUNCTION_CACHE = new ConcurrentHashMap<>();

使用缓存机制存储Lambda表达式与字段对象的映射,避免重复计算,提高性能。

3.2 获取字段对象

public static <T> Field getField(SFunction<T> function) {
    return FUNCTION_CACHE.computeIfAbsent(function, ReflectUtils::findField);
}

通过缓存机制获取字段对象,若缓存中不存在,则使用findField方法查找。

3.3 查找字段对象

public static <T> Field findField(SFunction<T> function) {
    final SerializedLambda serializedLambda = getSerializedLambda(function);
    final String implClass = serializedLambda.getImplClass();
    final String implMethodName = serializedLambda.getImplMethodName();
    final String fieldName = convertToFieldName(implMethodName);
    final Field field = getField(fieldName, serializedLambda);

    if (field == null) {
        throw new RuntimeException("No such class 「" + implClass + "」 field 「" + fieldName + "」.");
    }
    field.setAccessible(true);
    return field;
}

通过获取SerializedLambda解析Lambda表达式,提取字段名称并获取相应的字段对象。

4. 使用示例

假设有一个简单的类:

public class User {
    private String name;
    private int age;

    // Getter and Setter
}

可以使用ReflectUtils来获取和设置字段值:

User user = new User();
ReflectUtils.setFieldValue(user, User::getName, "Alice");
String name = (String) ReflectUtils.getFieldValue(user, User::getName);

System.out.println("Name: " + name); // 输出: Name: Alice

5. 总结

ReflectUtils类通过结合Java反射和Lambda表达式,提供了一种简洁高效的方式来操作对象的字段。它不仅提高了代码的可读性,还通过缓存机制优化了性能。掌握这个工具类的使用,将有助于提升Java开发的灵活性和效率。

通过这篇博文,我们希望你能更好地理解如何使用ReflectUtils来简化Java反射操作,并在实际项目中应用这一工具类来提高开发效率。