This commit is contained in:
Looly 2023-05-05 00:21:29 +08:00
parent 3b327ec7c3
commit 7cb48f9cd2
9 changed files with 288 additions and 58 deletions

View File

@ -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 相关工具类封装<br>
* 来自于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<String, Type>[] 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<Object> 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<String, Type>[] 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<String> valueProvider) {
final Map.Entry<String, Type>[] 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);
}
}

View File

@ -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;
}

View File

@ -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<Class<?>, Map<Class<?>, Method>> VALUE_OF_METHOD_CACHE = new WeakConcurrentMap<>();

View File

@ -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类的转换器支持
* <pre>
* Map = Record
* Bean = Record
* ValueProvider = Record
* </pre>
*/
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<String> valueProvider = null;
if (value instanceof ValueProvider) {
valueProvider = (ValueProvider<String>) value;
} else if (value instanceof Map) {
valueProvider = new MapValueProvider((Map<String, ?>) 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);
}
}

View File

@ -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> 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);
}
}
/**
* 检查用户传入参数
* <ul>
* <li>1忽略多余的参数</li>
* <li>2参数不够补齐默认值</li>
* <li>3通过NullWrapperBean传递的参数,会直接赋值null</li>
* <li>4传入参数为null但是目标参数类型为原始类型做转换</li>
* <li>5传入参数类型不对应尝试转换类型</li>
* </ul>
*
* @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;
}
}

View File

@ -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> 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 {
}
}
/**
* 检查用户传入参数
* <ul>
* <li>1忽略多余的参数</li>
* <li>2参数不够补齐默认值</li>
* <li>3通过NullWrapperBean传递的参数,会直接赋值null</li>
* <li>4传入参数为null但是目标参数类型为原始类型做转换</li>
* <li>5传入参数类型不对应尝试转换类型</li>
* </ul>
*
* @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;
}
/**
* 获取方法的唯一键结构为:
* <pre>

View File

@ -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;
}

View File

@ -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<String, Type>[] components = RecordUtil.getRecordComponents(record.getClass());
String key;
for (final Map.Entry<String, Type> entry : components) {
key = entry.getKey();
jsonObject.set(key, MethodUtil.invoke(record, key));
}
}
/**
* 从Bean转换
*

View File

@ -179,6 +179,9 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0</version>
<configuration>
<argLine>--add-opens java.base/java.lang=ALL-UNNAMED</argLine>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>