add Invoker

This commit is contained in:
Looly 2024-05-15 10:58:20 +08:00
parent a1f23fc22c
commit 02c863c93c
5 changed files with 162 additions and 417 deletions

View File

@ -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接口定义了调用目标对象的方法的规范<br>
* 它允许动态地调用方法增强了代码的灵活性和扩展性<br>
* 参考org.apache.ibatis.reflection.invoker.Invoker
*
* @author MyBatis(Clinton Begin)
*/
public interface Invoker {
/**
* 调用指定目标对象的方法
*
* @param target 目标对象调用的方法属于该对象
* @param args 方法调用的参数数组
* @return 方法的返回值方法的返回类型可以是任意类型
* @param <T> 返回类型
*/
<T> T invoke(Object target, Object... args);
/**
* 获取调用方法的返回类型或参数类型
*
* @return 调用方法的返回类型作为Class对象返回
*/
Class<?> getType();
}

View File

@ -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();
}
}
/**
* 设置是否检查参数<br>
* <pre>
* 1. 参数个数是否与方法参数个数一致
* 2. 如果某个参数为null但是方法这个位置的参数为原始类型则赋予原始类型默认值
* </pre>
*
* @param checkArgs 是否检查参数
* @return this
*/
public MethodInvoker setCheckArgs(final boolean checkArgs) {
this.checkArgs = checkArgs;
return this;
}
@SuppressWarnings("unchecked")
@Override
public <T> 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);
}
}
}
}
}

View File

@ -73,6 +73,12 @@ public class MethodReflect {
allMethods = null;
}
// region ----- getMathod
// endregion
// region ----- getMathods
/**
* 获取当前类及父类的所有公共方法等同于{@link Class#getMethods()}

View File

@ -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;
/**
* <p>方法查找工具类用于从指定的类或类层级结构中根据特定规则搜索方法
*
* <p><strong>查找方式与查找范围</strong>
* <p>支持四种语义的查找
* <ul>
* <li><i>get</i>: 获取首个符合条件的方法</li>
* <li><i>getWithMetadata</i>: 获取首个带有指定元数据的方法与该元数据</li>
* <li><i>find</i>: 获得所有符合条件的方法</li>
* <li><i>findWithMetadata</i>: 获取所有带有指定元数据的方法与这些元数据</li>
* </ul>
* 基于上述四种语义的查找提供四种查找范围
* <ul>
* <li><i>xxxFromSpecificMethods</i>在用户给定的方法列表中查找匹配的方法</li>
* <li><i>xxxFromMethods</i>{@link Class#getMethods}的范围中查找匹配的方法</li>
* <li><i>xxxFromDeclaredMethods</i>{@link Class#getDeclaredMethods}的范围中查找匹配的方法</li>
* <li><i>xxxFromAllMethods</i>在类的层级结构中的所有类的所有方法的范围中查找匹配的方法</li>
* </ul>
* 比如我们希望获取{@link Class#getMethods()}中所有匹配条件的方法则应当调用{@link #findFromMethods}
* 若我们希望获得类所有方法范围中首个匹配的方法则应当调用{@link #getFromAllMethods}
*
* <p><strong>匹配规则</strong>
* <p>方法查找的规则由{@link Function}实现<br>
* 规定{@link Function#apply(Object)}方法返回元数据不为{@code null}则认为方法与其匹配返回结果时将同时返回匹配的方法与元数据<br>
* 因此我们可以通过实现{@link Function}接口来同时实现方法的查找与元数据的获取<br>
* 比如我们希望查找所有方法上带有{@code Annotation}注解的方法则可以实现如下
* <pre>{@code
* Map<Method, Annotation> methods = MethodScanner.findFromAllMethods(Foo.class, method -> method.getAnnotation(Annotation.class));
* }</pre>
* 此外对于一些无需获取元数据的查找我们可以使用{@link MethodMatcherUtil}提供的一些内置实现
* <pre>{@code
* // 查找所有静态公开方法
* Set<Method> methods = MethodScanner.findFromAllMethods(Foo.class, MethodMatcherUtils.isPublicStatic());
* // 按照方法名与参数类型查找方法
* Method method = MethodScanner.getFromAllMethods(Foo.class, MethodMatcherUtils.forNameAndParameterTypes("foo", String.class));
* }</pre>
*
* <p><strong>缓存</strong>
* <p>对于{@link #getDeclaredMethods}{@link #getMethods}方法与基于这两个方法实现的
* 所有{@code xxxFromMethods}{@code xxxFromDeclaredMethods}方法
* 都提供了缓存基于{@link WeakConcurrentMap}的缓存支持<br>
* {@link #getAllMethods}与所有{@code xxxFromAllMethods}方法都基于{@link #getDeclaredMethods}实现
* 但是每次全量查找都需要重新遍历类层级结构因此会带来一定的额外的性能损耗<br>
* 缓存在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<Class<?>, Method[]> METHODS_CACHE = new WeakConcurrentMap<>();
/**
* 直接声明的方法缓存
*/
private static final WeakConcurrentMap<Class<?>, 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);
}
/**
* <p>获取当前类层级结构中的所有方法<br>
* 等同于按广度优先遍历类及其所有父类与接口并依次调用{@link Class#getDeclaredMethods()}<br>
* 返回的方法排序规则如下
* <ul>
* <li>{@code type}距离越近则顺序越靠前</li>
* <li>{@code type}距离相同则父类优先于接口</li>
* <li>{@code type}距离相同的接口则顺序遵循接口在{@link Class#getInterfaces()}的顺序</li>
* </ul>
*
* @param type
* @return 当前类及父类的所有公共方法
* @see ClassUtil#traverseTypeHierarchyWhile(Class, Predicate)
*/
public static Method[] getAllMethods(final Class<?> type) {
if (Objects.isNull(type)) {
return EMPTY_METHODS;
}
final List<Method> 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 <T> 结果类型
*/
public static <T> Map<Method, T> findWithMetadataFromSpecificMethods(final Method[] methods, final Function<Method, T> lookup) {
if (ArrayUtil.isEmpty(methods)) {
return Collections.emptyMap();
}
final Map<Method, T> 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<Method> findFromSpecificMethods(final Method[] methods, final Function<Method, ?> lookup) {
return findWithMetadataFromSpecificMethods(methods, lookup).keySet();
}
/**
* 从指定方法列表中筛选所有方法上带有指定元数据方法的方法与对应元数据
*
* @param methods 方法列表
* @param lookup 查找器
* @return 方法与对应的元数据
* @param <T> 值类型
*/
public static <T> Map.Entry<Method, T> getWithMetadataFromSpecificMethods(final Method[] methods, final Function<Method, T> 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<Method, ?> lookup) {
final Map.Entry<Method, ?> result = getWithMetadataFromSpecificMethods(methods, lookup);
return Objects.isNull(result) ? null : result.getKey();
}
// endregion
// region ============= methods =============
/**
* 获取方法上带有指定元数据的方法与对应元数据
*
* @param type 类型
* @param lookup 查找器
* @return 方法与对应的元数据集合
* @param <T> 值类型
*/
public static <T> Map<Method, T> findWithMetadataFromMethods(final Class<?> type, final Function<Method, T> lookup) {
return findWithMetadataFromSpecificMethods(getMethods(type), lookup);
}
/**
* 获取方法上带有指定元数据的方法
*
* @param type 类型
* @param lookup 查找器
* @return 方法集合
*/
public static Set<Method> findFromMethods(final Class<?> type, final Function<Method, ?> lookup) {
return findFromSpecificMethods(getMethods(type), lookup);
}
/**
* 获取首个方法上带有指定元数据的方法及元数据
*
* @param type 类型
* @param lookup 查找器
* @return 方法及元数据若无任何匹配的结果则返回{@code null}
* @param <T> 值类型
*/
public static <T> Map.Entry<Method, T> getWithMetadataFromMethods(final Class<?> type, final Function<Method, T> lookup) {
return getWithMetadataFromSpecificMethods(getMethods(type), lookup);
}
/**
* 获取首个方法上带有指定元数据的方法及元数据
*
* @param type 类型
* @param lookup 查找器
* @return 方法若无任何匹配的结果则返回{@code null}
*/
public static Method getFromMethods(final Class<?> type, final Function<Method, ?> lookup) {
return getFromSpecificMethods(getMethods(type), lookup);
}
// endregion
// region ============= declared methods =============
/**
* 获取方法上带有指定元数据的方法与对应元数据
*
* @param type 类型
* @param lookup 查找器
* @return 方法与对应的元数据集合
* @param <T> 值类型
*/
public static <T> Map<Method, T> findWithMetadataFromDeclaredMethods(final Class<?> type, final Function<Method, T> lookup) {
return findWithMetadataFromSpecificMethods(getDeclaredMethods(type), lookup);
}
/**
* 获取方法上带有指定元数据的方法
*
* @param type 类型
* @param lookup 查找器
* @return 方法集合
*/
public static Set<Method> findFromDeclaredMethods(final Class<?> type, final Function<Method, ?> lookup) {
return findFromSpecificMethods(getDeclaredMethods(type), lookup);
}
/**
* 获取首个方法上带有指定元数据的方法及元数据
*
* @param type 类型
* @param lookup 查找器
* @return 方法及元数据若无任何匹配的结果则返回{@code null}
* @param <T> 值类型
*/
public static <T> Map.Entry<Method, T> getWithMetadataFromDeclaredMethods(final Class<?> type, final Function<Method, T> lookup) {
return getWithMetadataFromSpecificMethods(getDeclaredMethods(type), lookup);
}
/**
* 获取首个方法上带有指定元数据的方法及元数据
*
* @param type 类型
* @param lookup 查找器
* @return 方法若无任何匹配的结果则返回{@code null}
*/
public static Method getFromDeclaredMethods(final Class<?> type, final Function<Method, ?> lookup) {
return getFromSpecificMethods(getDeclaredMethods(type), lookup);
}
// endregion
// region ============= all methods =============
/**
* 获取方法上带有指定元数据的方法与对应元数据
*
* @param type 类型
* @param lookup 查找器
* @return 方法与对应的元数据集合
* @param <T> 值类型
*/
public static <T> Map<Method, T> findWithMetadataFromAllMethods(final Class<?> type, final Function<Method, T> lookup) {
return findWithMetadataFromSpecificMethods(getAllMethods(type), lookup);
}
/**
* 获取方法上带有指定元数据的方法
*
* @param type 类型
* @param lookup 查找器
* @return 方法集合
*/
public static Set<Method> findFromAllMethods(final Class<?> type, final Function<Method, ?> lookup) {
return findFromSpecificMethods(getAllMethods(type), lookup);
}
/**
* 获取首个方法上带有指定元数据的方法及元数据
*
* @param type 类型
* @param lookup 查找器
* @return 方法及元数据若无任何匹配的结果则返回{@code null}
* @param <T> 值类型
*/
public static <T> Map.Entry<Method, T> getWithMetadataFromAllMethods(final Class<?> type, final Function<Method, T> lookup) {
if (Objects.isNull(type)) {
return null;
}
final Mutable<Map.Entry<Method, T>> result = new MutableObj<>();
ClassUtil.traverseTypeHierarchyWhile(type, t -> {
final Map.Entry<Method, T> 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<Method, ?> lookup) {
final Map.Entry<Method, ?> target = getWithMetadataFromAllMethods(type, lookup);
return Objects.isNull(target) ? null : target.getKey();
}
// endregion
}

View File

@ -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> 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> 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);
}
/**