From 02c863c93ceef17cafce9375b74c88e2a5778007 Mon Sep 17 00:00:00 2001 From: Looly Date: Wed, 15 May 2024 10:58:20 +0800 Subject: [PATCH] add Invoker --- .../dromara/hutool/core/reflect/Invoker.java | 41 ++ .../core/reflect/method/MethodInvoker.java | 113 +++++ .../core/reflect/method/MethodReflect.java | 6 + .../core/reflect/method/MethodScanner2.java | 391 ------------------ .../core/reflect/method/MethodUtil.java | 28 +- 5 files changed, 162 insertions(+), 417 deletions(-) create mode 100644 hutool-core/src/main/java/org/dromara/hutool/core/reflect/Invoker.java create mode 100644 hutool-core/src/main/java/org/dromara/hutool/core/reflect/method/MethodInvoker.java delete mode 100644 hutool-core/src/main/java/org/dromara/hutool/core/reflect/method/MethodScanner2.java diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/reflect/Invoker.java b/hutool-core/src/main/java/org/dromara/hutool/core/reflect/Invoker.java new file mode 100644 index 000000000..fbb25d09e --- /dev/null +++ b/hutool-core/src/main/java/org/dromara/hutool/core/reflect/Invoker.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2024. 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: + * https://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.reflect; + +/** + * Invoker接口定义了调用目标对象的方法的规范。
+ * 它允许动态地调用方法,增强了代码的灵活性和扩展性。
+ * 参考:org.apache.ibatis.reflection.invoker.Invoker + * + * @author MyBatis(Clinton Begin) + */ +public interface Invoker { + + /** + * 调用指定目标对象的方法。 + * + * @param target 目标对象,调用的方法属于该对象。 + * @param args 方法调用的参数数组。 + * @return 方法的返回值,方法的返回类型可以是任意类型。 + * @param 返回类型 + */ + T invoke(Object target, Object... args); + + /** + * 获取调用方法的返回类型或参数类型。 + * + * @return 调用方法的返回类型,作为Class对象返回。 + */ + Class getType(); +} + diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/reflect/method/MethodInvoker.java b/hutool-core/src/main/java/org/dromara/hutool/core/reflect/method/MethodInvoker.java new file mode 100644 index 000000000..8f4bd0fa1 --- /dev/null +++ b/hutool-core/src/main/java/org/dromara/hutool/core/reflect/method/MethodInvoker.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2024. 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: + * https://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.reflect.method; + +import org.dromara.hutool.core.exception.HutoolException; +import org.dromara.hutool.core.lang.Assert; +import org.dromara.hutool.core.reflect.ClassUtil; +import org.dromara.hutool.core.reflect.Invoker; +import org.dromara.hutool.core.reflect.ModifierUtil; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * 方法调用器,通过反射调用方法。 + * + * @author Looly + */ +public class MethodInvoker implements Invoker { + + private final Method method; + private final Class[] paramTypes; + private final Class type; + private boolean checkArgs; + + /** + * 构造 + * + * @param method 方法 + */ + public MethodInvoker(final Method method) { + this.method = method; + + this.paramTypes = method.getParameterTypes(); + if (paramTypes.length == 1) { + type = paramTypes[0]; + } else { + type = method.getReturnType(); + } + } + + /** + * 设置是否检查参数
+ *
+	 * 1. 参数个数是否与方法参数个数一致
+	 * 2. 如果某个参数为null但是方法这个位置的参数为原始类型,则赋予原始类型默认值
+	 * 
+ * + * @param checkArgs 是否检查参数 + * @return this + */ + public MethodInvoker setCheckArgs(final boolean checkArgs) { + this.checkArgs = checkArgs; + return this; + } + + + + @SuppressWarnings("unchecked") + @Override + public T invoke(final Object target, final Object... args) throws HutoolException{ + if(this.checkArgs){ + checkArgs(args); + } + + try { + return MethodHandleUtil.invoke(target, method, args); + } catch (final Exception e) { + // 传统反射方式执行方法 + try { + return (T) method.invoke(ModifierUtil.isStatic(method) ? null : target, MethodUtil.actualArgs(method, args)); + } catch (final IllegalAccessException | InvocationTargetException ex) { + throw new HutoolException(ex); + } + } + } + + @Override + public Class getType() { + return this.type; + } + + /** + * 检查传入参数的有效性。 + * + * @param args 传入的参数数组,不能为空。 + * @throws IllegalArgumentException 如果参数数组为空或长度为0,则抛出此异常。 + */ + private void checkArgs(final Object[] args) { + final Class[] paramTypes = this.paramTypes; + if (null != args) { + Assert.isTrue(args.length == paramTypes.length, "Params length [{}] is not fit for param length [{}] of method !", args.length, paramTypes.length); + Class type; + for (int i = 0; i < args.length; i++) { + type = paramTypes[i]; + if (type.isPrimitive() && null == args[i]) { + // 参数是原始类型,而传入参数为null时赋予默认值 + args[i] = ClassUtil.getDefaultValue(type); + } + } + } + } +} diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/reflect/method/MethodReflect.java b/hutool-core/src/main/java/org/dromara/hutool/core/reflect/method/MethodReflect.java index aace3e633..a56d5bf01 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/reflect/method/MethodReflect.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/reflect/method/MethodReflect.java @@ -73,6 +73,12 @@ public class MethodReflect { allMethods = null; } + // region ----- getMathod + + + + // endregion + // region ----- getMathods /** * 获取当前类及父类的所有公共方法,等同于{@link Class#getMethods()} diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/reflect/method/MethodScanner2.java b/hutool-core/src/main/java/org/dromara/hutool/core/reflect/method/MethodScanner2.java deleted file mode 100644 index cc21bb9aa..000000000 --- a/hutool-core/src/main/java/org/dromara/hutool/core/reflect/method/MethodScanner2.java +++ /dev/null @@ -1,391 +0,0 @@ -/* - * 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: - * https://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.reflect.method; - -import org.dromara.hutool.core.array.ArrayUtil; -import org.dromara.hutool.core.lang.mutable.Mutable; -import org.dromara.hutool.core.lang.mutable.MutableObj; -import org.dromara.hutool.core.map.MapUtil; -import org.dromara.hutool.core.map.reference.WeakConcurrentMap; -import org.dromara.hutool.core.reflect.ClassUtil; - -import java.lang.reflect.Method; -import java.util.*; -import java.util.function.Function; -import java.util.function.Predicate; - -/** - *

方法查找工具类,用于从指定的类或类层级结构中,根据特定规则搜索方法。 - * - *

查找方式与查找范围 - *

支持四种语义的查找: - *

    - *
  • get: 获取首个符合条件的方法;
  • - *
  • getWithMetadata: 获取首个带有指定元数据的方法与该元数据;
  • - *
  • find: 获得所有符合条件的方法;
  • - *
  • findWithMetadata: 获取所有带有指定元数据的方法与这些元数据;
  • - *
- * 基于上述四种语义的查找,提供四种查找范围: - *
    - *
  • xxxFromSpecificMethods:在用户给定的方法列表中查找匹配的方法;
  • - *
  • xxxFromMethods:在{@link Class#getMethods}的范围中查找匹配的方法;
  • - *
  • xxxFromDeclaredMethods:在{@link Class#getDeclaredMethods}的范围中查找匹配的方法;
  • - *
  • xxxFromAllMethods:在类的层级结构中的所有类的所有方法的范围中查找匹配的方法;
  • - *
- * 比如,我们希望获取{@link Class#getMethods()}中所有匹配条件的方法,则应当调用{@link #findFromMethods}, - * 若我们希望获得类所有方法范围中首个匹配的方法,则应当调用{@link #getFromAllMethods}。 - * - *

匹配规则 - *

方法查找的规则由{@link Function}实现。
- * 规定,当{@link Function#apply(Object)}方法返回元数据不为{@code null}时,则认为方法与其匹配,返回结果时将同时返回匹配的方法与元数据。
- * 因此,我们可以通过实现{@link Function}接口来同时实现方法的查找与元数据的获取:
- * 比如,我们希望查找所有方法上带有{@code Annotation}注解的方法,则可以实现如下: - *

{@code
- * 		Map methods = MethodScanner.findFromAllMethods(Foo.class, method -> method.getAnnotation(Annotation.class));
- * }
- * 此外,对于一些无需获取元数据的查找,我们可以使用{@link MethodMatcherUtil}提供的一些内置实现: - *
{@code
- * 		// 查找所有静态公开方法
- * 		Set methods = MethodScanner.findFromAllMethods(Foo.class, MethodMatcherUtils.isPublicStatic());
- * 		// 按照方法名与参数类型查找方法
- * 		Method method = MethodScanner.getFromAllMethods(Foo.class, MethodMatcherUtils.forNameAndParameterTypes("foo", String.class));
- * }
- * - *

缓存 - *

对于{@link #getDeclaredMethods}与{@link #getMethods}方法与基于这两个方法实现的, - * 所有{@code xxxFromMethods}与{@code xxxFromDeclaredMethods}方法, - * 都提供了缓存基于{@link WeakConcurrentMap}的缓存支持。
- * {@link #getAllMethods}与所有{@code xxxFromAllMethods}方法都基于{@link #getDeclaredMethods}实现, - * 但是每次全量查找,都需要重新遍历类层级结构,因此会带来一定的额外的性能损耗。
- * 缓存在GC时会被回收,但是也可以通过{@link #clearCaches}手动清除缓存。 - * - * @author huangchengxing - * @see MethodMatcherUtil - * @since 6.0.0 - */ -public class MethodScanner2 { - /* - TODO 替换{@link MethodUtil}中的部分方法实现 - */ - - /** - * 空方法列表 - */ - private static final Method[] EMPTY_METHODS = new Method[0]; - - /** - * 方法缓存 - */ - private static final WeakConcurrentMap, Method[]> METHODS_CACHE = new WeakConcurrentMap<>(); - - /** - * 直接声明的方法缓存 - */ - private static final WeakConcurrentMap, Method[]> DECLARED_METHODS_CACHE = new WeakConcurrentMap<>(); - - // region ============= basic ============= - - /** - * 获取当前类及父类的所有公共方法,等同于{@link Class#getMethods()} - * - * @param type 类 - * @return 当前类及父类的所有公共方法 - */ - public static Method[] getMethods(final Class type) { - if (Objects.isNull(type)) { - return EMPTY_METHODS; - } - return METHODS_CACHE.computeIfAbsent(type, Class::getMethods); - } - - /** - * 获取当前类直接声明的所有方法,等同于{@link Class#getDeclaredMethods()} - * - * @param type 类 - * @return 当前类及父类的所有公共方法 - */ - public static Method[] getDeclaredMethods(final Class type) { - if (Objects.isNull(type)) { - return EMPTY_METHODS; - } - return DECLARED_METHODS_CACHE.computeIfAbsent(type, Class::getDeclaredMethods); - } - - /** - *

获取当前类层级结构中的所有方法。
- * 等同于按广度优先遍历类及其所有父类与接口,并依次调用{@link Class#getDeclaredMethods()}。
- * 返回的方法排序规则如下: - *

    - *
  • 离{@code type}距离越近,则顺序越靠前;
  • - *
  • 与{@code type}距离相同,则父类优先于接口;
  • - *
  • 与{@code type}距离相同的接口,则顺序遵循接口在{@link Class#getInterfaces()}的顺序;
  • - *
- * - * @param type 类 - * @return 当前类及父类的所有公共方法 - * @see ClassUtil#traverseTypeHierarchyWhile(Class, Predicate) - */ - public static Method[] getAllMethods(final Class type) { - if (Objects.isNull(type)) { - return EMPTY_METHODS; - } - final List methods = new ArrayList<>(); - ClassUtil.traverseTypeHierarchyWhile(type, clazz -> { - methods.addAll(Arrays.asList(getDeclaredMethods(clazz))); - return true; - }); - return methods.isEmpty() ? EMPTY_METHODS : methods.toArray(new Method[0]); - } - - /** - * 清空缓存 - */ - public static void clearCaches() { - METHODS_CACHE.clear(); - DECLARED_METHODS_CACHE.clear(); - } - - // endregion - - // region ============= specific methods ============= - - /** - * 从指定方法列表中筛选所有方法上带有指定元数据方法的方法与对应元数据 - * - * @param methods 方法列表 - * @param lookup 查找器 - * @return 方法与对应的元数据集合 - * @param 结果类型 - */ - public static Map findWithMetadataFromSpecificMethods(final Method[] methods, final Function lookup) { - if (ArrayUtil.isEmpty(methods)) { - return Collections.emptyMap(); - } - final Map results = new LinkedHashMap<>(); - for (final Method method : methods) { - final T result = lookup.apply(method); - if (Objects.nonNull(result)) { - results.put(method, result); - } - } - return results; - } - - /** - * 从指定方法列表中筛选所有方法上带有指定元数据方法的方法 - * - * @param methods 方法列表 - * @param lookup 查找器 - * @return 方法集合 - */ - public static Set findFromSpecificMethods(final Method[] methods, final Function lookup) { - return findWithMetadataFromSpecificMethods(methods, lookup).keySet(); - } - - /** - * 从指定方法列表中筛选所有方法上带有指定元数据方法的方法与对应元数据 - * - * @param methods 方法列表 - * @param lookup 查找器 - * @return 方法与对应的元数据 - * @param 值类型 - */ - public static Map.Entry getWithMetadataFromSpecificMethods(final Method[] methods, final Function lookup) { - for (final Method method : methods) { - final T result = lookup.apply(method); - if (Objects.nonNull(result)) { - return MapUtil.entry(method, result); - } - } - return null; - } - - /** - * 从指定方法列表中筛选所有方法上带有指定元数据方法的方法 - * - * @param methods 方法列表 - * @param lookup 查找器 - * @return 方法 - */ - public static Method getFromSpecificMethods(final Method[] methods, final Function lookup) { - final Map.Entry result = getWithMetadataFromSpecificMethods(methods, lookup); - return Objects.isNull(result) ? null : result.getKey(); - } - - // endregion - - // region ============= methods ============= - - /** - * 获取方法上带有指定元数据的方法与对应元数据 - * - * @param type 类型 - * @param lookup 查找器 - * @return 方法与对应的元数据集合 - * @param 值类型 - */ - public static Map findWithMetadataFromMethods(final Class type, final Function lookup) { - return findWithMetadataFromSpecificMethods(getMethods(type), lookup); - } - - /** - * 获取方法上带有指定元数据的方法 - * - * @param type 类型 - * @param lookup 查找器 - * @return 方法集合 - */ - public static Set findFromMethods(final Class type, final Function lookup) { - return findFromSpecificMethods(getMethods(type), lookup); - } - - /** - * 获取首个方法上带有指定元数据的方法及元数据 - * - * @param type 类型 - * @param lookup 查找器 - * @return 方法及元数据,若无任何匹配的结果则返回{@code null} - * @param 值类型 - */ - public static Map.Entry getWithMetadataFromMethods(final Class type, final Function lookup) { - return getWithMetadataFromSpecificMethods(getMethods(type), lookup); - } - - /** - * 获取首个方法上带有指定元数据的方法及元数据 - * - * @param type 类型 - * @param lookup 查找器 - * @return 方法,若无任何匹配的结果则返回{@code null} - */ - public static Method getFromMethods(final Class type, final Function lookup) { - return getFromSpecificMethods(getMethods(type), lookup); - } - - // endregion - - // region ============= declared methods ============= - - /** - * 获取方法上带有指定元数据的方法与对应元数据 - * - * @param type 类型 - * @param lookup 查找器 - * @return 方法与对应的元数据集合 - * @param 值类型 - */ - public static Map findWithMetadataFromDeclaredMethods(final Class type, final Function lookup) { - return findWithMetadataFromSpecificMethods(getDeclaredMethods(type), lookup); - } - - /** - * 获取方法上带有指定元数据的方法 - * - * @param type 类型 - * @param lookup 查找器 - * @return 方法集合 - */ - public static Set findFromDeclaredMethods(final Class type, final Function lookup) { - return findFromSpecificMethods(getDeclaredMethods(type), lookup); - } - - /** - * 获取首个方法上带有指定元数据的方法及元数据 - * - * @param type 类型 - * @param lookup 查找器 - * @return 方法及元数据,若无任何匹配的结果则返回{@code null} - * @param 值类型 - */ - public static Map.Entry getWithMetadataFromDeclaredMethods(final Class type, final Function lookup) { - return getWithMetadataFromSpecificMethods(getDeclaredMethods(type), lookup); - } - - /** - * 获取首个方法上带有指定元数据的方法及元数据 - * - * @param type 类型 - * @param lookup 查找器 - * @return 方法,若无任何匹配的结果则返回{@code null} - */ - public static Method getFromDeclaredMethods(final Class type, final Function lookup) { - return getFromSpecificMethods(getDeclaredMethods(type), lookup); - } - - // endregion - - - // region ============= all methods ============= - - /** - * 获取方法上带有指定元数据的方法与对应元数据 - * - * @param type 类型 - * @param lookup 查找器 - * @return 方法与对应的元数据集合 - * @param 值类型 - */ - public static Map findWithMetadataFromAllMethods(final Class type, final Function lookup) { - return findWithMetadataFromSpecificMethods(getAllMethods(type), lookup); - } - - /** - * 获取方法上带有指定元数据的方法 - * - * @param type 类型 - * @param lookup 查找器 - * @return 方法集合 - */ - public static Set findFromAllMethods(final Class type, final Function lookup) { - return findFromSpecificMethods(getAllMethods(type), lookup); - } - - /** - * 获取首个方法上带有指定元数据的方法及元数据 - * - * @param type 类型 - * @param lookup 查找器 - * @return 方法及元数据,若无任何匹配的结果则返回{@code null} - * @param 值类型 - */ - public static Map.Entry getWithMetadataFromAllMethods(final Class type, final Function lookup) { - if (Objects.isNull(type)) { - return null; - } - final Mutable> result = new MutableObj<>(); - ClassUtil.traverseTypeHierarchyWhile(type, t -> { - final Map.Entry target = getWithMetadataFromDeclaredMethods(t, lookup); - if (Objects.nonNull(target)) { - result.set(target); - return false; - } - return true; - }); - return result.get(); - } - - /** - * 获取首个方法上带有指定元数据的方法及元数据 - * - * @param type 类型 - * @param lookup 查找器 - * @return 方法,若无任何匹配的结果则返回{@code null} - */ - public static Method getFromAllMethods(final Class type, final Function lookup) { - final Map.Entry target = getWithMetadataFromAllMethods(type, lookup); - return Objects.isNull(target) ? null : target.getKey(); - } - - // endregion - -} diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/reflect/method/MethodUtil.java b/hutool-core/src/main/java/org/dromara/hutool/core/reflect/method/MethodUtil.java index 78d2fc372..9fd6c24dd 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/reflect/method/MethodUtil.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/reflect/method/MethodUtil.java @@ -28,7 +28,6 @@ import org.dromara.hutool.core.stream.StreamUtil; 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.Set; import java.util.function.Predicate; @@ -530,20 +529,7 @@ public class MethodUtil { * @throws HutoolException 一些列异常的包装 */ public static T invokeWithCheck(final Object obj, final Method method, final Object... args) throws HutoolException { - final Class[] types = method.getParameterTypes(); - if (null != args) { - Assert.isTrue(args.length == types.length, "Params length [{}] is not fit for param length [{}] of method !", args.length, types.length); - Class type; - for (int i = 0; i < args.length; i++) { - type = types[i]; - if (type.isPrimitive() && null == args[i]) { - // 参数是原始类型,而传入参数为null时赋予默认值 - args[i] = ClassUtil.getDefaultValue(type); - } - } - } - - return invoke(obj, method, args); + return (new MethodInvoker(method)).setCheckArgs(true).invoke(obj, args); } /** @@ -566,18 +552,8 @@ 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 { - 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); - } - } + return (new MethodInvoker(method)).invoke(obj, args); } /**