Browse Source

feat: 增加对代理生成的 MethodHandleProxies 的 lambda 调试支持

hanchunlin 4 years ago
parent
commit
175e71a8a8

+ 4 - 4
mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/conditions/AbstractLambdaWrapper.java

@@ -72,10 +72,10 @@ public abstract class AbstractLambdaWrapper<T, Children extends AbstractLambdaWr
      * @throws com.baomidou.mybatisplus.core.exceptions.MybatisPlusException 获取不到列信息时抛出异常
      */
     protected ColumnCache getColumnCache(SFunction<T, ?> column) {
-        LambdaMeta lambda = LambdaUtils.extract(column);
-        String fieldName = PropertyNamer.methodToProperty(lambda.getImplMethodName());
-        tryInitCache(lambda.getInstantiatedClass());
-        return getColumnCache(fieldName, lambda.getInstantiatedClass());
+        LambdaMeta meta = LambdaUtils.extract(column);
+        String fieldName = PropertyNamer.methodToProperty(meta.getImplMethodName());
+        tryInitCache(meta.getInstantiatedClass());
+        return getColumnCache(fieldName, meta.getInstantiatedClass());
     }
 
     private void tryInitCache(Class<?> lambdaClass) {

+ 3 - 4
mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/toolkit/LambdaUtils.java

@@ -18,14 +18,12 @@ package com.baomidou.mybatisplus.core.toolkit;
 import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
 import com.baomidou.mybatisplus.core.metadata.TableInfo;
 import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
-import com.baomidou.mybatisplus.core.toolkit.support.ColumnCache;
-import com.baomidou.mybatisplus.core.toolkit.support.LambdaMeta;
-import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
-import com.baomidou.mybatisplus.core.toolkit.support.SerializedLambdaMeta;
+import com.baomidou.mybatisplus.core.toolkit.support.*;
 
 import java.lang.invoke.SerializedLambda;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 
@@ -56,6 +54,7 @@ public final class LambdaUtils {
             Method method = func.getClass().getDeclaredMethod("writeReplace");
             return new SerializedLambdaMeta((SerializedLambda) ReflectionKit.setAccessible(method).invoke(func));
         } catch (NoSuchMethodException e) {
+            if (func instanceof Proxy) return new ProxyLambdaMeta((Proxy) func);
             String message = "Cannot find method writeReplace, please make sure that the lambda composite class is currently passed in";
             throw new MybatisPlusException(message);
         } catch (InvocationTargetException | IllegalAccessException e) {

+ 12 - 0
mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/toolkit/support/LambdaMeta.java

@@ -1,12 +1,24 @@
 package com.baomidou.mybatisplus.core.toolkit.support;
 
 /**
+ * Lambda 信息
+ * <p>
  * Created by hcl at 2021/5/14
  */
 public interface LambdaMeta {
 
+    /**
+     * 获取 lambda 表达式实现方法的名称
+     *
+     * @return lambda 表达式对应的实现方法名称
+     */
     String getImplMethodName();
 
+    /**
+     * 实例化该方法的类
+     *
+     * @return 返回对应的类名称
+     */
     Class<?> getInstantiatedClass();
 
 }

+ 60 - 0
mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/toolkit/support/ProxyLambdaMeta.java

@@ -0,0 +1,60 @@
+package com.baomidou.mybatisplus.core.toolkit.support;
+
+import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
+import com.baomidou.mybatisplus.core.toolkit.ReflectionKit;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Proxy;
+
+/**
+ * Create by hcl at 2021/5/17
+ */
+public class ProxyLambdaMeta implements LambdaMeta {
+    private static final Field FIELD_MEMBER_NAME;
+    private static final Field FIELD_MEMBER_NAME_CLAZZ;
+    private static final Field FIELD_MEMBER_NAME_NAME;
+
+    static {
+        try {
+            Class<?> classDirectMethodHandle = Class.forName("java.lang.invoke.DirectMethodHandle");
+            FIELD_MEMBER_NAME = ReflectionKit.setAccessible(classDirectMethodHandle.getDeclaredField("member"));
+            Class<?> classMemberName = Class.forName("java.lang.invoke.MemberName");
+            FIELD_MEMBER_NAME_CLAZZ = ReflectionKit.setAccessible(classMemberName.getDeclaredField("clazz"));
+            FIELD_MEMBER_NAME_NAME = ReflectionKit.setAccessible(classMemberName.getDeclaredField("name"));
+        } catch (ClassNotFoundException | NoSuchFieldException e) {
+            throw new MybatisPlusException(e);
+        }
+    }
+
+    private final Class<?> clazz;
+    private final String name;
+
+    public ProxyLambdaMeta(Proxy func) {
+        InvocationHandler handler = Proxy.getInvocationHandler(func);
+        try {
+            Object dmh = ReflectionKit.setAccessible(handler.getClass().getDeclaredField("val$target")).get(handler);
+            Object member = FIELD_MEMBER_NAME.get(dmh);
+            clazz = (Class<?>) FIELD_MEMBER_NAME_CLAZZ.get(member);
+            name = (String) FIELD_MEMBER_NAME_NAME.get(member);
+        } catch (IllegalAccessException | NoSuchFieldException e) {
+            throw new MybatisPlusException(e);
+        }
+    }
+
+    @Override
+    public String getImplMethodName() {
+        return name;
+    }
+
+    @Override
+    public Class<?> getInstantiatedClass() {
+        return clazz;
+    }
+
+    @Override
+    public String toString() {
+        return clazz.getSimpleName() + "::" + name;
+    }
+
+}

+ 17 - 2
mybatis-plus-core/src/test/java/com/baomidou/mybatisplus/test/toolkit/LambdaUtilsTest.java

@@ -21,6 +21,11 @@ import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
 import lombok.Getter;
 import org.junit.jupiter.api.Test;
 
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandleProxies;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertSame;
 
@@ -33,8 +38,18 @@ class LambdaUtilsTest {
      * 测试解析
      */
     @Test
-    void testExtract() {
+    @SuppressWarnings("unchecked")
+    void testExtract() throws IllegalAccessException, NoSuchMethodException {
         SFunction<TestModel, Object> function = TestModel::getName;
+        test(function);
+        MethodHandles.Lookup lookup = MethodHandles.lookup();
+        MethodHandle getter = lookup.findVirtual(TestModel.class, "getId", MethodType.methodType(int.class));
+        function = (SFunction<TestModel, Object>) MethodHandleProxies.asInterfaceInstance(SFunction.class, getter);
+        test(function);
+    }
+
+    private void test(SFunction<TestModel, Object> function) {
+        function.apply(new TestModel());
         LambdaMeta meta = LambdaUtils.extract(function);
         assertNotNull(meta);
         assertSame(TestModel.class, meta.getInstantiatedClass());
@@ -44,7 +59,7 @@ class LambdaUtilsTest {
      * 用于测试的 Model
      */
     @Getter
-    private static class TestModel extends Parent implements Named {
+    public static class TestModel extends Parent implements Named {
         private String name;
     }