add LookupFactory and MethodHandleUtil

This commit is contained in:
Looly 2021-07-28 21:07:55 +08:00
parent 62df0171a4
commit 05495ba8ed
5 changed files with 213 additions and 0 deletions

View File

@ -6,6 +6,8 @@
# 5.7.6 (2021-07-28)
### 🐣新特性
* 【core 】 增加LookupFactory和MethodHandleUtilissue#I42TVY@Gitee
### 🐞Bug修复
-------------------------------------------------------------------------------------------------------------

View File

@ -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}对象<br>
* jdk8中如果直接调用{@link MethodHandles#lookup()}获取到的{@link MethodHandles.Lookup}在调用findSpecial和unreflectSpecial
* 时会出现权限不够问题抛出"no private access for invokespecial"异常因此针对JDK8及JDK9+分别封装lookup方法
*
* 参考
* <ul>
* <li>https://blog.csdn.net/u013202238/article/details/108687086</li>
* </ul>
*
* @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<MethodHandles.Lookup> 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);
}
}
}

View File

@ -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}封装工具类<br>
* 参考
* <ul>
* <li>https://stackoverflow.com/questions/22614746/how-do-i-invoke-java-8-default-methods-reflectively</li>
* </ul>
*
* @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方法<br>
*
* <pre class="code">
* interface Duck {
* default String quack() {
* return "Quack";
* }
* }
*
* Duck duck = (Duck) Proxy.newProxyInstance(
* ClassLoaderUtil.getClassLoader(),
* new Class[] { Duck.class },
* MethodHandleUtil::invokeDefault);
* </pre>
*
* @param o 接口的子对象或代理对象
* @param method 方法
* @param args 参数
* @return 结果
*/
@SuppressWarnings("unchecked")
public static <T> 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);
}
}
}

View File

@ -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) {

View File

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