利用FunctionalInterface获取类、字段、方法

利用FunctionalInterface获取类、字段、方法

背景说明

最近有看到同事用mybatis-plus的LambdaQueryWrapper写查询逻辑,其中本来应该传列名的位置直接使用了lambda来完成:

在这里插入图片描述

好奇之下,对其中原理进行了一点点学习。

原理说明

提示:更多信息详见源码LambdaMetafactory类、SerializedLambda类javadoc说明。

原理

如果生成的lambda类需要实现java.io.Serializable的话,那么在生成的lambda实现类中,就会有一个名为writeReplace的方法来作该lambda类的序列化支持(直观的,可见下面的示例)。writeReplace的返回值是SerializedLambda,我们通过反射调用拿到writeReplace返回的SerializedLambda对象后,就可以获得你所写的lambda表达式中所涉及到的类、方法等信息了(看一下SerializedLambda的构造,就知道可以拿到哪些东西了):

在这里插入图片描述

原理示例

  • 首先,有一个集成了java.io.Serializable能力的FunctionalInterface功能接口:

    /**
     * 集成了{@link Serializable}能力的{@link Function}
     * <pre>
     * 当然,获得{@link SerializedLambda},不一定非要使用Function,使用其他的{@link FunctionalInterface}也行,
     * 只要保证以下两点即可:
     * 1. 会生成lambda实现类
     * 2. 生成的lambda实现类要实现Serializable
     * 注:如果只是获取SerializedLambda实例本身,那么除了上述FunctionalInterface的方式外,还可以通过其它方式获得(如:序列化/反序列化等)。
     * </pre>
     *
     * @author JustryDeng
     * @since 2022/4/9 9:24
     */
    public interface SFunction<T, R> extends Function<T, R>, Serializable {
    }
    

    注:FunctionalInterface是为了能自动生成lambda实现类;Serializable是为了保证自动生成lambda实现类对Serializable作功能支持,即:生成writeReplace方法。

  • 然后,我们有这样一个简化的lambda写法:

    // SerializedLambdaUtil.getFieldName(SFunction<T, ?> sFunction)
    // 即:Person::getName是对SFunction<T, ?>实现的一种简化的lambda写法
    SerializedLambdaUtil.getFieldName(Person::getName)
    
  • 其中,对于lambda写法Person::getName,JVM会自动生成实现类:

    final class MainTests$$Lambda$1 implements SFunction {
       @Hidden
       public Object apply(Object var1) {
          return ((Person)var1).getName();
       }
    
       private final Object writeReplace() {
          return new SerializedLambda(
    	      	MainTests.class, 
    	      	"com/example/lambda/demo/SFunction", 
    	      	"apply", 
    	      	"(Ljava/lang/Object;)Ljava/lang/Object;", 
    	      	5, 
    	      	"com/example/lambda/test/Person", 
    	      	"getName", 
    	      	"()Ljava/lang/String;", 
    	      	"(Lcom/example/lambda/test/Person;)Ljava/lang/Object;", 
    	      	new Object[0]
          	);
       }
    }
    

    注:上述展示的这个JVM自动生成的类,是我运行时直接dump class出来后反编译出来的。

可以看到,自动生成的lambda类中存在writeReplace方法,该方法返回SerializedLambda对象

实际应用

应用说明

本应用仿mybatis-plus,即:利用FunctionalInterface,获取字段名,也可称为:利用SerializedLambda,获取字段名。

工具类封装

  • SFunction:一种FunctionalInterface能力支持接口

    import java.io.Serializable;
    import java.lang.invoke.SerializedLambda;
    import java.util.function.Function;
    
    /**
     * 集成了{@link Serializable}能力的{@link Function}
     * <pre>
     * 当然,获得{@link SerializedLambda},不一定非要使用Function,使用其他的{@link FunctionalInterface}也行,
     * 只要保证以下两点即可:
     * 1. 会生成lambda实现类
     * 2. 生成的lambda实现类要实现Serializable
     * 注:如果只是获取SerializedLambda实例本身,那么除了上述FunctionalInterface的方式外,还可以通过其它方式获得(如:序列化/反序列化等)。
     * </pre>
     *
     * @author JustryDeng
     * @since 2022/4/9 9:24
     */
    public interface SFunction<T, R> extends Function<T, R>, Serializable {
    }
    
  • SerializedLambdaUtil:SerializedLambda工具类

    import org.apache.commons.lang3.StringUtils;
    
    import java.io.Serializable;
    import java.lang.invoke.SerializedLambda;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    
    /**
     * {@link SerializedLambda}工具类
     *
     * @author JustryDeng
     * @since 2022/4/9 11:43
     */
    public class SerializedLambdaUtil {
        
        public static FieldNameParser defaultFieldNameParser = new FieldNameParser(){};
        
        /**
         * @see SerializedLambdaUtil#getFieldName(SFunction)
         */
        public static <T> String getFieldName(SFunction<T, ?> sFunction) {
            return getFieldName(sFunction, defaultFieldNameParser);
        }
        
        /**
         * 获取字段名称
         */
        public static <T> String getFieldName(SFunction<T, ?> sFunction, FieldNameParser fieldNameParser) {
            return getFieldName(getSerializedLambda(sFunction), fieldNameParser);
        }
        
        /**
         * 获取lambda表达式字段名称
         * <pre>
         * 假设你的lambda表达式部分是这样写的:<code>Person::getFirstName</code>,
         * 那么,此方法的目的就是获取到getFirstName方法对应的(Person类中的对应字段的)字段名
         * </pre>
         */
        public static String getFieldName(SerializedLambda serializedLambda, FieldNameParser fieldNameParser) {
            String implClassLongName = getImplClassLongName(serializedLambda);
            String implMethodName = getImplMethodName(serializedLambda);
            try {
                return fieldNameParser.parseFieldName(Class.forName(implClassLongName), implMethodName);
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
        }
        
        /**
         * 获取lambda表达式中,实现方法的方法名
         * <p>
         *     说明:
         *     假设你的lambda表达式部分是这样写的:<code>Person::getFirstName</code>,<br/>
         *     那么这里获取到的就是Person.getFirstName()的方法名getFirstName
         * </p>
         *
         * @param serializedLambda
         *            serializedLambda对象
         * @return  实现方法的方法名 <br />
         *          形如:getFirstName
         */
        private static String getImplMethodName(SerializedLambda serializedLambda) {
            return serializedLambda.getImplMethodName();
        }
        
        /**
         * 获取lambda表达式中,实现方法的类的全类名
         * <p>
         *     说明:
         *     假设你的lambda表达式部分是这样写的:<code>Person::getFirstName</code>,<br/>
         *     那么这里获取到的就是Person的全类名,形如:<code>com.example.lambda.test.Person</code>
         * </p>
         *
         * @param serializedLambda
         *            serializedLambda对象
         * @return  实现方法的类的全类名 <br />
         *          形如:com.example.lambda.test.Person
         */
        private static String getImplClassLongName(SerializedLambda serializedLambda) {
            return serializedLambda.getImplClass().replace("/", ".");
        }
        
        /**
         * 获取SerializedLambda实例
         *
         * @param potentialLambda
         *            lambda实例
         * @return  SerializedLambda实例
         */
        private static <T extends Serializable> SerializedLambda getSerializedLambda(T potentialLambda) {
            try{
                Class<?> potentialLambdaClass = potentialLambda.getClass();
                // lambda类属于合成类
                if (!potentialLambdaClass.isSynthetic()) {
                    throw new IllegalArgumentException("potentialLambda must be lambda-class");
                }
                Method writeReplaceMethod = potentialLambdaClass.getDeclaredMethod("writeReplace");
                boolean isAccessible = writeReplaceMethod.isAccessible();
                writeReplaceMethod.setAccessible(true);
                Object writeReplaceObject = writeReplaceMethod.invoke(potentialLambda);
                writeReplaceMethod.setAccessible(isAccessible);
                if (writeReplaceObject == null || !SerializedLambda.class.isAssignableFrom(writeReplaceObject.getClass())) {
                    throw new IllegalArgumentException("potentialLambda must be lambda-class. writeReplaceObject should not be " + writeReplaceObject);
                }
                return (SerializedLambda)writeReplaceObject;
            } catch( NoSuchMethodException | IllegalAccessException | InvocationTargetException e ){
                throw new IllegalArgumentException("potentialLambda must be lambda-class", e);
            }
        }
        
        /**
         * 字段名解析器
         *
         * @author JustryDeng
         * @since 2022/4/9 11:31
         */
        public interface FieldNameParser {
            
            /**
             * 解析字段名
             * <pre>
             *     假设你的lambda表达式部分是这样写的:<code>Person::getFirstName</code>,
             *     那么,
             *     clazz就对应Person类
             *     methodName就对应getFirstName
             * </pre>
             *
             * @param clazz
             *            字段所在的类
             * @param methodName
             *            与字段相关的方法(如:该字段的getter方法)
             * @return  解析字段名
             */
            default String parseFieldName(Class<?> clazz, String methodName) {
                return StringUtils.uncapitalize(methodName.substring("get".length()));
            }
        }
        
    }
    

测试一下

  • 测试辅助实体模型

    import lombok.Data;
    
    @Data
    @SuppressWarnings("all")
    public class Person {
        
        private String name;
        
        private String nickName;
    }
    
  • 测试类

    public class MainTests {
        
        /**
         * 测试
         */
        public static void main(String[] args) {
            /* ------------------------- 测试 SerializedLambdaUtil工具类 ------------------------- */
            System.out.println();
            System.out.println("通过FunctionalInterface方式,获得字段名:" + SerializedLambdaUtil.getFieldName(Person::getName));
            System.out.println("通过FunctionalInterface方式,获得字段名:" + SerializedLambdaUtil.getFieldName(Person::getNickName));
            
            
            /* ------------------------- dump运行时class并反编译,以观察生成的lambda实现类 ------------------------- */
            // dump class
            Map<String, Map<String, byte[]>> dumpedClasses = NonExitClassFileTransformerExecutor
                    .create("com.example.lambda.test", null, false)
                    .exec();
            List<byte[]> list = new ArrayList<>(8);
            dumpedClasses.forEach((k, map) -> list.addAll(map.values()));
            
            // 反编译
            List<Triple<String, String, String>> triples = Decompiler.defaultDecompiler().decompileAsTriple(list);
            String projectRootDir = PathUtil.getProjectRootDir(MainTests.class);
            projectRootDir = projectRootDir.replace("/target/classes/", "/src/main/resources/sourceCode/");
            IOUtil.delete(new File(projectRootDir));
            for (Triple<String, String, String> triple : triples) {
                IOUtil.toFile(triple.getRight().getBytes(StandardCharsets.UTF_8), new File(projectRootDir, triple.getLeft() + ".java"), true);
            }
        }
    }
    
  • 运行main方法,观察结果

    在这里插入图片描述

    注:dump class、反编译工具见https://gitee.com/JustryDeng/components

注意事项

SerializedLambda在很多时候确实很好用,但因为使用过程中不可避免的用到了反射,所以需要考虑性能问题。虽然高版本jdk中对反射做了很多优化,性能也接近于直接调用了,但是我们在代码层面还可以继续优化,比如考虑引入缓存之类的机制等等。

相关资料

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
CGIC 提供了一系列函数来获取用户请求字段信息,可以根据不同的数据型和需求来选择合适的函数。在使用 CGIC 获取用户请求字段信息时,需要按照以下步骤进行: 1. 引入 CGIC 库的头文件: ``` #include "cgic.h" ``` 2. 在 CGI 程序中调用 `cgiHeaderContentType` 函数设置 HTTP 响应头: ``` int main(int argc, char *argv[]) { cgiHeaderContentType("text/html"); //... } ``` 3. 使用 `cgiForm*` 函数来获取用户请求字段信息,例如: - 获取字符串型的请求字段信息: ``` char username[50]; cgiFormString("username", username, sizeof(username)); ``` 上面的代码将获取名为 "username" 的字符串型请求字段信息,并将其存储在 `username` 变量中。 - 获取整数型的请求字段信息: ``` int age; cgiFormInteger("age", &age, 18); ``` 上面的代码将获取名为 "age" 的整数型请求字段信息,并将其存储在 `age` 变量中。如果请求字段信息不存在,则使用默认值 18。 - 获取浮点数型的请求字段信息: ``` double price; cgiFormDouble("price", &price, 0.0); ``` 上面的代码将获取名为 "price" 的浮点数型请求字段信息,并将其存储在 `price` 变量中。如果请求字段信息不存在,则使用默认值 0.0。 - 获取文件型的请求字段信息: ``` cgiFilePtr file; cgiFormFile("file", &file); ``` 上面的代码将获取名为 "file" 的文件型请求字段信息,并将其存储在 `file` 指针变量中。需要注意的是,如果请求字段信息不存在或者请求的不是文件型的字段信息,则 `cgiFormFile` 函数会返回一个错误码。 4. 最后,需要根据获取到的请求字段信息进行相应的处理和输出。 综上所述,利用 CGIC 获取用户请求字段信息的方法是通过调用相应的 `cgiForm*` 函数来获取不同型的请求字段信息,然后进行相应的处理和输出。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值