From 7cb48f9cd21919be6d8b4c0b4259db37b7272254 Mon Sep 17 00:00:00 2001 From: Looly Date: Fri, 5 May 2023 00:21:29 +0800 Subject: [PATCH] fix code --- .../dromara/hutool/core/bean/RecordUtil.java | 123 ++++++++++++++++++ .../core/convert/CompositeConverter.java | 16 ++- .../core/convert/impl/EnumConverter.java | 3 + .../core/convert/impl/RecordConverter.java | 59 +++++++++ .../hutool/core/reflect/MethodHandleUtil.java | 49 +------ .../hutool/core/reflect/MethodUtil.java | 58 ++++++++- .../hutool/json/convert/JSONConverter.java | 9 +- .../hutool/json/mapper/JSONObjectMapper.java | 26 +++- pom.xml | 3 + 9 files changed, 288 insertions(+), 58 deletions(-) create mode 100644 hutool-core/src/main/java/org/dromara/hutool/core/bean/RecordUtil.java create mode 100644 hutool-core/src/main/java/org/dromara/hutool/core/convert/impl/RecordConverter.java diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/bean/RecordUtil.java b/hutool-core/src/main/java/org/dromara/hutool/core/bean/RecordUtil.java new file mode 100644 index 000000000..e65083b59 --- /dev/null +++ b/hutool-core/src/main/java/org/dromara/hutool/core/bean/RecordUtil.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2023 looly(loolly@aliyun.com) + * Hutool is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +package org.dromara.hutool.core.bean; + +import org.dromara.hutool.core.bean.copier.ValueProvider; +import org.dromara.hutool.core.classloader.ClassLoaderUtil; +import org.dromara.hutool.core.reflect.ConstructorUtil; +import org.dromara.hutool.core.reflect.MethodUtil; +import org.dromara.hutool.core.util.JdkUtil; + +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 6.0.0 + */ +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 = MethodUtil.getMethod(Class.class, "getRecordComponents"); + } + + final Class recordComponentClass = ClassLoaderUtil.loadClass("java.lang.reflect.RecordComponent"); + if (METHOD_COMPONENT_GET_NAME == null) { + METHOD_COMPONENT_GET_NAME = MethodUtil.getMethod(recordComponentClass, "getName"); + } + if (METHOD_COMPONENT_GET_GENERIC_TYPE == null) { + METHOD_COMPONENT_GET_GENERIC_TYPE = MethodUtil.getMethod(recordComponentClass, "getGenericType"); + } + + final Object[] components = MethodUtil.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<>( + MethodUtil.invoke(components[i], METHOD_COMPONENT_GET_NAME), + MethodUtil.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 ConstructorUtil.newInstance(recordClass, args); + } +} diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/convert/CompositeConverter.java b/hutool-core/src/main/java/org/dromara/hutool/core/convert/CompositeConverter.java index d7b8845a0..73689c5b4 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/convert/CompositeConverter.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/convert/CompositeConverter.java @@ -13,6 +13,7 @@ package org.dromara.hutool.core.convert; import org.dromara.hutool.core.bean.BeanUtil; +import org.dromara.hutool.core.bean.RecordUtil; import org.dromara.hutool.core.convert.impl.*; import org.dromara.hutool.core.reflect.TypeReference; import org.dromara.hutool.core.reflect.TypeUtil; @@ -146,11 +147,6 @@ public class CompositeConverter extends RegisterConverter { return result; } - // Kotlin Bean - if(KClassUtil.isKotlinClass(rowType)){ - return (T) KBeanConverter.INSTANCE.convert(type, value); - } - // 尝试转Bean if (BeanUtil.isBean(rowType)) { return (T) BeanConverter.INSTANCE.convert(type, value); @@ -225,6 +221,16 @@ public class CompositeConverter extends RegisterConverter { return ArrayConverter.INSTANCE.convert(type, value, defaultValue); } + // Record + if(RecordUtil.isRecord(rowType)){ + return (T) RecordConverter.INSTANCE.convert(type, value); + } + + // Kotlin Bean + if(KClassUtil.isKotlinClass(rowType)){ + return (T) KBeanConverter.INSTANCE.convert(type, value); + } + // 表示非需要特殊转换的对象 return null; } diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/convert/impl/EnumConverter.java b/hutool-core/src/main/java/org/dromara/hutool/core/convert/impl/EnumConverter.java index afdbe1afa..38d477eaa 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/convert/impl/EnumConverter.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/convert/impl/EnumConverter.java @@ -37,6 +37,9 @@ import java.util.stream.Collectors; public class EnumConverter extends AbstractConverter { private static final long serialVersionUID = 1L; + /** + * 单例 + */ public static final EnumConverter INSTANCE = new EnumConverter(); private static final WeakConcurrentMap, Map, Method>> VALUE_OF_METHOD_CACHE = new WeakConcurrentMap<>(); diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/convert/impl/RecordConverter.java b/hutool-core/src/main/java/org/dromara/hutool/core/convert/impl/RecordConverter.java new file mode 100644 index 000000000..06521d465 --- /dev/null +++ b/hutool-core/src/main/java/org/dromara/hutool/core/convert/impl/RecordConverter.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2023 looly(loolly@aliyun.com) + * Hutool is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +package org.dromara.hutool.core.convert.impl; + +import org.dromara.hutool.core.bean.BeanUtil; +import org.dromara.hutool.core.bean.RecordUtil; +import org.dromara.hutool.core.bean.copier.ValueProvider; +import org.dromara.hutool.core.bean.copier.provider.BeanValueProvider; +import org.dromara.hutool.core.bean.copier.provider.MapValueProvider; +import org.dromara.hutool.core.convert.AbstractConverter; +import org.dromara.hutool.core.convert.ConvertException; + +import java.util.Map; + +/** + * Record类的转换器,支持: + *
+ *   Map =》 Record
+ *   Bean =》 Record
+ *   ValueProvider =》 Record
+ * 
+ */ +public class RecordConverter extends AbstractConverter { + private static final long serialVersionUID = 1L; + + /** + * 单例对象 + */ + public static RecordConverter INSTANCE = new RecordConverter(); + + @SuppressWarnings("unchecked") + @Override + protected Object convertInternal(final Class targetClass, final Object value) { + ValueProvider valueProvider = null; + if (value instanceof ValueProvider) { + valueProvider = (ValueProvider) value; + } else if (value instanceof Map) { + valueProvider = new MapValueProvider((Map) value); + } else if (BeanUtil.isBean(value.getClass())) { + valueProvider = new BeanValueProvider(value); + } + + if (null != valueProvider) { + return RecordUtil.newInstance(targetClass, valueProvider); + } + + throw new ConvertException("Unsupported source type: [{}] to [{}]", value.getClass(), targetClass); + } +} diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/reflect/MethodHandleUtil.java b/hutool-core/src/main/java/org/dromara/hutool/core/reflect/MethodHandleUtil.java index 80092b12d..ce8e4b715 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/reflect/MethodHandleUtil.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/reflect/MethodHandleUtil.java @@ -12,11 +12,8 @@ package org.dromara.hutool.core.reflect; -import org.dromara.hutool.core.bean.NullWrapperBean; -import org.dromara.hutool.core.convert.Convert; import org.dromara.hutool.core.exception.HutoolException; import org.dromara.hutool.core.lang.Assert; -import org.dromara.hutool.core.lang.Console; import org.dromara.hutool.core.reflect.lookup.LookupUtil; import java.lang.invoke.MethodHandle; @@ -82,7 +79,7 @@ public class MethodHandleUtil { */ public static T invoke(final Object obj, final Method method, final Object... args) throws HutoolException{ Assert.notNull(method, "Method must be not null!"); - return invokeExact(obj, method, actualArgs(method, args)); + return invokeExact(obj, method, MethodUtil.actualArgs(method, args)); } /** @@ -123,48 +120,4 @@ public class MethodHandleUtil { throw new HutoolException(e); } } - - /** - * 检查用户传入参数: - *
    - *
  • 1、忽略多余的参数
  • - *
  • 2、参数不够补齐默认值
  • - *
  • 3、通过NullWrapperBean传递的参数,会直接赋值null
  • - *
  • 4、传入参数为null,但是目标参数类型为原始类型,做转换
  • - *
  • 5、传入参数类型不对应,尝试转换类型
  • - *
- * - * @param method 方法 - * @param args 参数 - * @return 实际的参数数组 - */ - private static Object[] actualArgs(final Method method, final Object[] args) { - final Class[] parameterTypes = method.getParameterTypes(); - if(1 == parameterTypes.length && parameterTypes[0].isArray()){ - // 可变长参数,不做转换 - return args; - } - final Object[] actualArgs = new Object[parameterTypes.length]; - if (null != args) { - for (int i = 0; i < actualArgs.length; i++) { - if (i >= args.length || null == args[i]) { - // 越界或者空值 - actualArgs[i] = ClassUtil.getDefaultValue(parameterTypes[i]); - } else if (args[i] instanceof NullWrapperBean) { - //如果是通过NullWrapperBean传递的null参数,直接赋值null - actualArgs[i] = null; - } else if (!parameterTypes[i].isAssignableFrom(args[i].getClass())) { - //对于类型不同的字段,尝试转换,转换失败则使用原对象类型 - final Object targetValue = Convert.convert(parameterTypes[i], args[i], args[i]); - if (null != targetValue) { - actualArgs[i] = targetValue; - } - } else { - actualArgs[i] = args[i]; - } - } - } - - return actualArgs; - } } diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/reflect/MethodUtil.java b/hutool-core/src/main/java/org/dromara/hutool/core/reflect/MethodUtil.java index 1bf48fd1f..ce1a18cce 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/reflect/MethodUtil.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/reflect/MethodUtil.java @@ -17,6 +17,7 @@ import org.dromara.hutool.core.bean.NullWrapperBean; import org.dromara.hutool.core.classloader.ClassLoaderUtil; import org.dromara.hutool.core.collection.set.SetUtil; import org.dromara.hutool.core.collection.set.UniqueKeySet; +import org.dromara.hutool.core.convert.Convert; import org.dromara.hutool.core.exception.HutoolException; import org.dromara.hutool.core.lang.Assert; import org.dromara.hutool.core.lang.Singleton; @@ -24,6 +25,7 @@ import org.dromara.hutool.core.map.WeakConcurrentMap; import org.dromara.hutool.core.text.StrUtil; import org.dromara.hutool.core.util.BooleanUtil; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.*; import java.util.function.Predicate; @@ -624,8 +626,18 @@ public class MethodUtil { * @throws HutoolException 一些列异常的包装 * @see MethodHandleUtil#invoke(Object, Method, Object...) */ + @SuppressWarnings("unchecked") public static T invoke(final Object obj, final Method method, final Object... args) throws HutoolException { - return MethodHandleUtil.invoke(obj, method, args); + try{ + return MethodHandleUtil.invoke(obj, method, args); + } catch (final Exception e){ + // 传统反射方式执行方法 + try { + return (T) method.invoke(ModifierUtil.isStatic(method) ? null : obj, actualArgs(method, args)); + } catch (final IllegalAccessException | InvocationTargetException ex) { + throw new HutoolException(ex); + } + } } /** @@ -742,6 +754,50 @@ public class MethodUtil { } } + /** + * 检查用户传入参数: + *
    + *
  • 1、忽略多余的参数
  • + *
  • 2、参数不够补齐默认值
  • + *
  • 3、通过NullWrapperBean传递的参数,会直接赋值null
  • + *
  • 4、传入参数为null,但是目标参数类型为原始类型,做转换
  • + *
  • 5、传入参数类型不对应,尝试转换类型
  • + *
+ * + * @param method 方法 + * @param args 参数 + * @return 实际的参数数组 + */ + public static Object[] actualArgs(final Method method, final Object[] args) { + final Class[] parameterTypes = method.getParameterTypes(); + if(1 == parameterTypes.length && parameterTypes[0].isArray()){ + // 可变长参数,不做转换 + return args; + } + final Object[] actualArgs = new Object[parameterTypes.length]; + if (null != args) { + for (int i = 0; i < actualArgs.length; i++) { + if (i >= args.length || null == args[i]) { + // 越界或者空值 + actualArgs[i] = ClassUtil.getDefaultValue(parameterTypes[i]); + } else if (args[i] instanceof NullWrapperBean) { + //如果是通过NullWrapperBean传递的null参数,直接赋值null + actualArgs[i] = null; + } else if (!parameterTypes[i].isAssignableFrom(args[i].getClass())) { + //对于类型不同的字段,尝试转换,转换失败则使用原对象类型 + final Object targetValue = Convert.convert(parameterTypes[i], args[i], args[i]); + if (null != targetValue) { + actualArgs[i] = targetValue; + } + } else { + actualArgs[i] = args[i]; + } + } + } + + return actualArgs; + } + /** * 获取方法的唯一键,结构为: *
diff --git a/hutool-json/src/main/java/org/dromara/hutool/json/convert/JSONConverter.java b/hutool-json/src/main/java/org/dromara/hutool/json/convert/JSONConverter.java
index 40e99b865..aaf723107 100644
--- a/hutool-json/src/main/java/org/dromara/hutool/json/convert/JSONConverter.java
+++ b/hutool-json/src/main/java/org/dromara/hutool/json/convert/JSONConverter.java
@@ -14,13 +14,13 @@ package org.dromara.hutool.json.convert;
 
 import org.dromara.hutool.core.array.ArrayUtil;
 import org.dromara.hutool.core.bean.BeanUtil;
+import org.dromara.hutool.core.bean.RecordUtil;
 import org.dromara.hutool.core.bean.copier.BeanCopier;
 import org.dromara.hutool.core.convert.Convert;
 import org.dromara.hutool.core.convert.ConvertException;
 import org.dromara.hutool.core.convert.Converter;
 import org.dromara.hutool.core.convert.RegisterConverter;
 import org.dromara.hutool.core.convert.impl.*;
-import org.dromara.hutool.core.lang.Console;
 import org.dromara.hutool.core.map.MapWrapper;
 import org.dromara.hutool.core.reflect.ConstructorUtil;
 import org.dromara.hutool.core.reflect.TypeReference;
@@ -228,7 +228,7 @@ public class JSONConverter implements Converter {
 		}
 
 		// 无法转换
-		throw new JSONException("Can not convert from {}: [{}] to [{}]",
+		throw new JSONException("Can not convert from '{}': {} to '{}'",
 				json.getClass().getName(), json, targetType.getTypeName());
 	}
 
@@ -280,6 +280,11 @@ public class JSONConverter implements Converter {
 			return (T) ArrayConverter.INSTANCE.convert(type, value);
 		}
 
+		// Record
+		if(RecordUtil.isRecord(rowType)){
+			return (T) RecordConverter.INSTANCE.convert(type, value);
+		}
+
 		// 表示非需要特殊转换的对象
 		return null;
 	}
diff --git a/hutool-json/src/main/java/org/dromara/hutool/json/mapper/JSONObjectMapper.java b/hutool-json/src/main/java/org/dromara/hutool/json/mapper/JSONObjectMapper.java
index 8463939d4..357fb62c4 100644
--- a/hutool-json/src/main/java/org/dromara/hutool/json/mapper/JSONObjectMapper.java
+++ b/hutool-json/src/main/java/org/dromara/hutool/json/mapper/JSONObjectMapper.java
@@ -13,10 +13,12 @@
 package org.dromara.hutool.json.mapper;
 
 import org.dromara.hutool.core.bean.BeanUtil;
+import org.dromara.hutool.core.bean.RecordUtil;
 import org.dromara.hutool.core.bean.copier.CopyOptions;
 import org.dromara.hutool.core.convert.Convert;
 import org.dromara.hutool.core.io.IoUtil;
 import org.dromara.hutool.core.lang.mutable.MutableEntry;
+import org.dromara.hutool.core.reflect.MethodUtil;
 import org.dromara.hutool.core.text.StrUtil;
 import org.dromara.hutool.json.InternalJSONUtil;
 import org.dromara.hutool.json.JSONArray;
@@ -30,6 +32,7 @@ import org.dromara.hutool.json.serialize.JSONSerializer;
 
 import java.io.InputStream;
 import java.io.Reader;
+import java.lang.reflect.Type;
 import java.util.Enumeration;
 import java.util.Map;
 import java.util.ResourceBundle;
@@ -105,7 +108,7 @@ public class JSONObjectMapper {
 		if (source instanceof JSONTokener) {
 			// JSONTokener
 			mapFromTokener((JSONTokener) source, jsonObject);
-		}else if (source instanceof Map) {
+		} else if (source instanceof Map) {
 			// Map
 			for (final Map.Entry e : ((Map) source).entrySet()) {
 				jsonObject.set(Convert.toStr(e.getKey()), e.getValue(), predicate, false);
@@ -125,11 +128,14 @@ public class JSONObjectMapper {
 		} else if (source instanceof ResourceBundle) {
 			// ResourceBundle
 			mapFromResourceBundle((ResourceBundle) source, jsonObject);
+		} else if (RecordUtil.isRecord(source.getClass())) {
+			// since 6.0.0
+			mapFromRecord(source, jsonObject);
 		} else if (BeanUtil.isReadableBean(source.getClass())) {
 			// 普通Bean
 			mapFromBean(source, jsonObject);
 		} else {
-			if(!jsonObject.config().isIgnoreError()){
+			if (!jsonObject.config().isIgnoreError()) {
 				// 不支持对象类型转换为JSONObject
 				throw new JSONException("Unsupported type [{}] to JSONObject!", source.getClass());
 			}
@@ -180,6 +186,22 @@ public class JSONObjectMapper {
 		JSONParser.of(x).parseTo(jsonObject, this.predicate);
 	}
 
+	/**
+	 * 从Record转换
+	 *
+	 * @param record     Record对象
+	 * @param jsonObject {@link JSONObject}
+	 */
+	private void mapFromRecord(final Object record, final JSONObject jsonObject) {
+		final Map.Entry[] components = RecordUtil.getRecordComponents(record.getClass());
+
+		String key;
+		for (final Map.Entry entry : components) {
+			key = entry.getKey();
+			jsonObject.set(key, MethodUtil.invoke(record, key));
+		}
+	}
+
 	/**
 	 * 从Bean转换
 	 *
diff --git a/pom.xml b/pom.xml
index f49d49693..20c902ded 100755
--- a/pom.xml
+++ b/pom.xml
@@ -179,6 +179,9 @@
 				org.apache.maven.plugins
 				maven-surefire-plugin
 				3.0.0
+				
+					--add-opens java.base/java.lang=ALL-UNNAMED
+				
 			
 			
 				org.apache.maven.plugins