diff --git a/CHANGELOG.md b/CHANGELOG.md index 53bf50c3f..d9ff86943 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ # 🚀Changelog ------------------------------------------------------------------------------------------------------------- -# 5.8.38(2025-04-26) +# 5.8.38(2025-05-07) ### 🐣新特性 * 【core 】 `PathUtil#del`增加null检查(pr#1331@Gitee) @@ -14,6 +14,7 @@ * 【extra 】 `TemplateConfig`增加`setUseCache`方法(issue#IC3JRY@Gitee) * 【extra 】 `AbstractFtp`增加`rename`方法(issue#IC3PMI@Gitee) * 【core 】 优化`PropDesc`缓存注解判断,提升性能(pr#1335@Gitee) +* 【core 】 添加`RecordUtil`支持record类(issue#3931@Github) ### 🐞Bug修复 * 【setting】 修复`Setting`autoLoad可能的加载为空的问题(issue#3919@Github) diff --git a/hutool-core/src/main/java/cn/hutool/core/bean/BeanDesc.java b/hutool-core/src/main/java/cn/hutool/core/bean/BeanDesc.java index cb23a98d3..da3b1657e 100644 --- a/hutool-core/src/main/java/cn/hutool/core/bean/BeanDesc.java +++ b/hutool-core/src/main/java/cn/hutool/core/bean/BeanDesc.java @@ -12,6 +12,7 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Collection; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; /** @@ -48,7 +49,11 @@ public class BeanDesc implements Serializable { public BeanDesc(Class beanClass) { Assert.notNull(beanClass); this.beanClass = beanClass; - init(); + if(RecordUtil.isRecord(beanClass)){ + initForRecord(); + }else{ + init(); + } } /** @@ -153,6 +158,27 @@ public class BeanDesc implements Serializable { return this; } + /** + * 针对Record类的反射初始化 + */ + private void initForRecord() { + final Class beanClass = this.beanClass; + final Map propMap = this.propMap; + + final List getters = ReflectUtil.getPublicMethods(beanClass, method -> 0 == method.getParameterCount()); + // 排除静态属性和对象子类 + final Field[] fields = ReflectUtil.getFields(beanClass, field -> !ModifierUtil.isStatic(field) && !ReflectUtil.isOuterClassField(field)); + for (final Field field : fields) { + for (final Method getter : getters) { + if (field.getName().equals(getter.getName())) { + //record对象,getter方法与字段同名 + final PropDesc prop = new PropDesc(field, getter, null); + propMap.putIfAbsent(prop.getFieldName(), prop); + } + } + } + } + /** * 根据字段创建属性描述
* 查找Getter和Setter方法时会: diff --git a/hutool-core/src/main/java/cn/hutool/core/bean/RecordUtil.java b/hutool-core/src/main/java/cn/hutool/core/bean/RecordUtil.java new file mode 100644 index 000000000..388472669 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/bean/RecordUtil.java @@ -0,0 +1,110 @@ +package cn.hutool.core.bean; + +import cn.hutool.core.bean.copier.ValueProvider; +import cn.hutool.core.util.ClassUtil; +import cn.hutool.core.util.JdkUtil; +import cn.hutool.core.util.ReflectUtil; + +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.util.AbstractMap; +import java.util.Map; + +/** + * java.lang.Record 相关工具类封装
+ * 来自于FastJSON2的BeanUtils + * + * @author fastjson2, Looly + * @since 5.8.38 + */ +public class RecordUtil { + + private static volatile Class RECORD_CLASS; + + private static volatile Method METHOD_GET_RECORD_COMPONENTS; + private static volatile Method METHOD_COMPONENT_GET_NAME; + private static volatile Method METHOD_COMPONENT_GET_GENERIC_TYPE; + + /** + * 判断给定类是否为Record类 + * + * @param clazz 类 + * @return 是否为Record类 + */ + public static boolean isRecord(final Class clazz) { + if (JdkUtil.JVM_VERSION < 14) { + // JDK14+支持Record类 + return false; + } + final Class superClass = clazz.getSuperclass(); + if (superClass == null) { + return false; + } + + if (RECORD_CLASS == null) { + // 此处不使用同步代码,重复赋值并不影响判断 + final String superclassName = superClass.getName(); + if ("java.lang.Record".equals(superclassName)) { + RECORD_CLASS = superClass; + return true; + } else { + return false; + } + } + + return superClass == RECORD_CLASS; + } + + /** + * 获取Record类中所有字段名称,getter方法名与字段同名 + * + * @param recordClass Record类 + * @return 字段数组 + */ + @SuppressWarnings("unchecked") + public static Map.Entry[] getRecordComponents(final Class recordClass) { + if (JdkUtil.JVM_VERSION < 14) { + // JDK14+支持Record类 + return new Map.Entry[0]; + } + if (null == METHOD_GET_RECORD_COMPONENTS) { + METHOD_GET_RECORD_COMPONENTS = ReflectUtil.getMethod(Class.class, "getRecordComponents"); + } + + final Class recordComponentClass = ClassUtil.loadClass("java.lang.reflect.RecordComponent"); + if (METHOD_COMPONENT_GET_NAME == null) { + METHOD_COMPONENT_GET_NAME = ReflectUtil.getMethod(recordComponentClass, "getName"); + } + if (METHOD_COMPONENT_GET_GENERIC_TYPE == null) { + METHOD_COMPONENT_GET_GENERIC_TYPE = ReflectUtil.getMethod(recordComponentClass, "getGenericType"); + } + + final Object[] components = ReflectUtil.invoke(recordClass, METHOD_GET_RECORD_COMPONENTS); + final Map.Entry[] entries = new Map.Entry[components.length]; + for (int i = 0; i < components.length; i++) { + entries[i] = new AbstractMap.SimpleEntry<>( + ReflectUtil.invoke(components[i], METHOD_COMPONENT_GET_NAME), + ReflectUtil.invoke(components[i], METHOD_COMPONENT_GET_GENERIC_TYPE) + ); + } + + return entries; + } + + /** + * 实例化Record类 + * + * @param recordClass 类 + * @param valueProvider 参数值提供器 + * @return Record类 + */ + public static Object newInstance(final Class recordClass, final ValueProvider valueProvider) { + final Map.Entry[] recordComponents = getRecordComponents(recordClass); + final Object[] args = new Object[recordComponents.length]; + for (int i = 0; i < args.length; i++) { + args[i] = valueProvider.value(recordComponents[i].getKey(), recordComponents[i].getValue()); + } + + return ReflectUtil.newInstance(recordClass, args); + } +}