diff --git a/CHANGELOG.md b/CHANGELOG.md index c08f9420e..147ffb5c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ # 5.7.6 (2021-07-28) ### 🐣新特性 +* 【core 】 增加LookupFactory和MethodHandleUtil(issue#I42TVY@Gitee) + ### 🐞Bug修复 ------------------------------------------------------------------------------------------------------------- diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/reflect/LookupFactory.java b/hutool-core/src/main/java/cn/hutool/core/lang/reflect/LookupFactory.java new file mode 100755 index 000000000..48aa5ff88 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/reflect/LookupFactory.java @@ -0,0 +1,78 @@ +package cn.hutool.core.lang.reflect; + +import cn.hutool.core.exceptions.UtilException; + +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * {@link MethodHandles.Lookup}工厂,用于创建{@link MethodHandles.Lookup}对象
+ * jdk8中如果直接调用{@link MethodHandles#lookup()}获取到的{@link MethodHandles.Lookup}在调用findSpecial和unreflectSpecial + * 时会出现权限不够问题,抛出"no private access for invokespecial"异常,因此针对JDK8及JDK9+分别封装lookup方法。 + * + * 参考: + * + * + * @author looly + * @since 5.7.7 + */ +public class LookupFactory { + + private static final int ALLOWED_MODES = MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED + | MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC; + + private static Constructor java8LookupConstructor; + private static Method privateLookupInMethod; + + static { + //先查询jdk9 开始提供的java.lang.invoke.MethodHandles.privateLookupIn方法, + //如果没有说明是jdk8的版本.(不考虑jdk8以下版本) + try { + //noinspection JavaReflectionMemberAccess + privateLookupInMethod = MethodHandles.class.getMethod("privateLookupIn", Class.class, MethodHandles.Lookup.class); + } catch (NoSuchMethodException ignore) { + //ignore + } + + //jdk8 + //这种方式其实也适用于jdk9及以上的版本,但是上面优先,可以避免 jdk9 反射警告 + if (privateLookupInMethod == null) { + try { + java8LookupConstructor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class); + java8LookupConstructor.setAccessible(true); + } catch (NoSuchMethodException e) { + //可能是jdk8 以下版本 + throw new IllegalStateException( + "There is neither 'privateLookupIn(Class, Lookup)' nor 'Lookup(Class, int)' method in java.lang.invoke.MethodHandles.", e); + } + } + } + + /** + * jdk8中如果直接调用{@link MethodHandles#lookup()}获取到的{@link MethodHandles.Lookup}在调用findSpecial和unreflectSpecial + * 时会出现权限不够问题,抛出"no private access for invokespecial"异常,因此针对JDK8及JDK9+分别封装lookup方法。 + * + * @param callerClass 被调用的类或接口 + * @return {@link MethodHandles.Lookup} + */ + public static MethodHandles.Lookup lookup(Class callerClass) { + //使用反射,因为当前jdk可能不是java9或以上版本 + if (privateLookupInMethod != null) { + try { + return (MethodHandles.Lookup) privateLookupInMethod.invoke(MethodHandles.class, callerClass, MethodHandles.lookup()); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new UtilException(e); + } + } + //jdk 8 + try { + return java8LookupConstructor.newInstance(callerClass, ALLOWED_MODES); + } catch (Exception e) { + throw new IllegalStateException("no 'Lookup(Class, int)' method in java.lang.invoke.MethodHandles.", e); + } + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/reflect/MethodHandleUtil.java b/hutool-core/src/main/java/cn/hutool/core/lang/reflect/MethodHandleUtil.java new file mode 100755 index 000000000..9511a2867 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/reflect/MethodHandleUtil.java @@ -0,0 +1,65 @@ +package cn.hutool.core.lang.reflect; + +import cn.hutool.core.exceptions.UtilException; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Method; + +/** + * 方法句柄{@link MethodHandle}封装工具类
+ * 参考: + * + * + * @author looly + * @since 5.7.7 + */ +public class MethodHandleUtil { + + /** + * jdk8中如果直接调用{@link MethodHandles#lookup()}获取到的{@link MethodHandles.Lookup}在调用findSpecial和unreflectSpecial + * 时会出现权限不够问题,抛出"no private access for invokespecial"异常,因此针对JDK8及JDK9+分别封装lookup方法。 + * + * @param callerClass 被调用的类或接口 + * @return {@link MethodHandles.Lookup} + */ + public static MethodHandles.Lookup lookup(Class callerClass) { + return LookupFactory.lookup(callerClass); + } + + /** + * 执行Interface中的default方法
+ * + *
+	 *     interface Duck {
+	 *         default String quack() {
+	 *             return "Quack";
+	 *         }
+	 *     }
+	 *
+	 *     Duck duck = (Duck) Proxy.newProxyInstance(
+	 *         ClassLoaderUtil.getClassLoader(),
+	 *         new Class[] { Duck.class },
+	 *         MethodHandleUtil::invokeDefault);
+	 * 
+ * + * @param o 接口的子对象或代理对象 + * @param method 方法 + * @param args 参数 + * @return 结果 + */ + @SuppressWarnings("unchecked") + public static T invoke(Object o, Method method, Object... args) { + final Class declaringClass = method.getDeclaringClass(); + try { + return (T) lookup(declaringClass) + .unreflectSpecial(method, declaringClass) + .bindTo(o) + .invokeWithArguments(args); + } catch (Throwable e) { + throw new UtilException(e); + } + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ReflectUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ReflectUtil.java index 60bc055d2..28b26a661 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ReflectUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ReflectUtil.java @@ -8,6 +8,7 @@ import cn.hutool.core.exceptions.UtilException; import cn.hutool.core.lang.Assert; import cn.hutool.core.lang.Filter; import cn.hutool.core.lang.SimpleCache; +import cn.hutool.core.lang.reflect.MethodHandleUtil; import cn.hutool.core.map.MapUtil; import java.lang.reflect.AccessibleObject; @@ -916,6 +917,12 @@ public class ReflectUtil { } } + if(method.isDefault()){ + // 当方法是default方法时,尤其对象是代理对象,需使用句柄方式执行 + // 代理对象情况下调用method.invoke会导致循环引用执行,最终栈溢出 + return MethodHandleUtil.invoke(obj, method, args); + } + try { return (T) method.invoke(ClassUtil.isStatic(method) ? null : obj, actualArgs); } catch (Exception e) { diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/reflect/MethodHandleUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/reflect/MethodHandleUtilTest.java new file mode 100755 index 000000000..a955dcd67 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/reflect/MethodHandleUtilTest.java @@ -0,0 +1,61 @@ +package cn.hutool.core.lang.reflect; + +import cn.hutool.core.util.ClassLoaderUtil; +import cn.hutool.core.util.ReflectUtil; +import org.junit.Assert; +import org.junit.Test; + +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +public class MethodHandleUtilTest { + + @Test + public void invokeDefaultTest(){ + Duck duck = (Duck) Proxy.newProxyInstance( + ClassLoaderUtil.getClassLoader(), + new Class[] { Duck.class }, + MethodHandleUtil::invoke); + + Assert.assertEquals("Quack", duck.quack()); + + // 测试子类执行default方法 + final Method quackMethod = ReflectUtil.getMethod(Duck.class, "quack"); + String quack = MethodHandleUtil.invoke(new BigDuck(), quackMethod); + Assert.assertEquals("Quack", quack); + + // 测试反射执行默认方法 + quack = ReflectUtil.invoke(new Duck() {}, quackMethod); + Assert.assertEquals("Quack", quack); + } + + @Test + public void invokeDefaultByReflectTest(){ + Duck duck = (Duck) Proxy.newProxyInstance( + ClassLoaderUtil.getClassLoader(), + new Class[] { Duck.class }, + ReflectUtil::invoke); + + Assert.assertEquals("Quack", duck.quack()); + } + + @Test + public void invokeTest(){ + // 测试执行普通方法 + final int size = MethodHandleUtil.invoke(new BigDuck(), + ReflectUtil.getMethod(BigDuck.class, "getSize")); + Assert.assertEquals(36, size); + } + + interface Duck { + default String quack() { + return "Quack"; + } + } + + static class BigDuck implements Duck{ + public int getSize(){ + return 36; + } + } +}