通过Lambda函数的方式获取属性名称

这篇具有很好参考价值的文章主要介绍了通过Lambda函数的方式获取属性名称。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

最近在使用mybatis-plus框架, 常常会使用lambda的方法引用获取实体属性, 避免出现大量的魔法值.

public List<User> listBySex() {
  LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
  // lambda方法引用
  queryWrapper.eq(User::getSex, "男");
  return userServer.list(wrapper);
}

那么在我们平时的开发过程中, 常常需要用到java bean的属性名, 直接写死属性名字符串的形式容易产生bug, 比如属性名变化, 编译时并不会报错, 只有在运行时才会报错该对象没有指定的属性名称. 而lambda的方式不仅可以简化代码, 而且可以通过getter方法引用拿到属性名, 避免潜在bug.

期望的效果

String userName = BeanUtils.getFieldName(User::getName);
System.out.println(userName);
// 输出: name

实现步骤

  1. 定义一个函数式接口, 用来接收lambda方法引用

    注意: 函数式接口必须继承Serializable接口才能获取方法信息

    @FunctionalInterface
    public interface SFunction<T> extends Serializable {
      Object apply(T t);
    }
    
  2. 定义一个工具类, 用来解析获取属性名称

    import lombok.extern.slf4j.Slf4j;
    import org.springframework.util.ClassUtils;
    import org.springframework.util.ReflectionUtils;
    
    import java.beans.Introspector;
    import java.lang.invoke.SerializedLambda;
    import java.lang.reflect.Field;
    import java.lang.reflect.Method;
    import java.util.Map;
    import java.util.concurrent.ConcurrentHashMap;
    
    @Slf4j
    public class BeanUtils {
        private static final Map<SFunction<?>, Field> FUNCTION_CACHE = new ConcurrentHashMap<>();
     
        public static <T> String getFieldName(SFunction<T> function) {
            Field field = BeanUtils.getField(function);
            return field.getName();
        }
     
        public static <T> Field getField(SFunction<T> function) {
            return FUNCTION_CACHE.computeIfAbsent(function, BeanUtils::findField);
        }
     
        public static <T> Field findField(SFunction<T> function) {
            // 第1步 获取SerializedLambda
            final SerializedLambda serializedLambda = getSerializedLambda(function);
            // 第2步 implMethodName 即为Field对应的Getter方法名
            final String implClass = serializedLambda.getImplClass();
            final String implMethodName = serializedLambda.getImplMethodName();
            final String fieldName = convertToFieldName(implMethodName);
            // 第3步  Spring 中的反射工具类获取Class中定义的Field
            final Field field = getField(fieldName, serializedLambda);
    
            // 第4步 如果没有找到对应的字段应该抛出异常
            if (field == null) {
                throw new RuntimeException("No such class 「"+ implClass +"」 field 「" + fieldName + "」.");
            }
    
            return field;
        }
    
        static Field getField(String fieldName, SerializedLambda serializedLambda) {
            try {
                // 获取的Class是字符串,并且包名是“/”分割,需要替换成“.”,才能获取到对应的Class对象
                String declaredClass = serializedLambda.getImplClass().replace("/", ".");
                Class<?>aClass = Class.forName(declaredClass, false, ClassUtils.getDefaultClassLoader());
                return ReflectionUtils.findField(aClass, fieldName);
            }
            catch (ClassNotFoundException e) {
                throw new RuntimeException("get class field exception.", e);
            }
        }
    
        static String convertToFieldName(String getterMethodName) {
            // 获取方法名
            String prefix = null;
            if (getterMethodName.startsWith("get")) {
                prefix = "get";
            }
            else if (getterMethodName.startsWith("is")) {
                prefix = "is";
            }
    
            if (prefix == null) {
                throw new IllegalArgumentException("invalid getter method: " + getterMethodName);
            }
    
            // 截取get/is之后的字符串并转换首字母为小写
            return Introspector.decapitalize(getterMethodName.replace(prefix, ""));
        }
    
        static <T> SerializedLambda getSerializedLambda(SFunction<T> function) {
            try {
                Method method = function.getClass().getDeclaredMethod("writeReplace");
                method.setAccessible(Boolean.TRUE);
                return (SerializedLambda) method.invoke(function);
            }
            catch (Exception e) {
                throw new RuntimeException("get SerializedLambda exception.", e);
            }
        }
    }
    

测试

public class Test {
    public static void main(String[] args) {
        SFunction<User> user = User::getName;
        final String fieldName = BeanUtils.getFieldName(user);
        System.out.println(fieldName);
    }

    @Data
    static class User {
        private String name;

        private int age;
    }
}

执行测试 输出结果

通过Lambda函数的方式获取属性名称

原理剖析

为什么SFunction必须继承Serializable

首先简单了解一下java.io.Serializable接口,该接口很常见,我们在持久化一个对象或者在RPC框架之间通信使用JDK序列化时都会让传输的实体类实现该接口,该接口是一个标记接口没有定义任何方法,但是该接口文档中有这么一段描述:

通过Lambda函数的方式获取属性名称

概要意思就是说,如果想在序列化时改变序列化的对象,可以通过在实体类中定义任意访问权限的Object writeReplace()来改变默认序列化的对象。

代码中SFunction只是一个接口, 但是其在最后必定也是一个实现类的实例对象,而方法引用其实是在运行时动态创建的,当代码执行到方法引用时,如User::getName,最后会经过

java.lang.invoke.LambdaMetafactory
java.lang.invoke.InnerClassLambdaMetafactory

去动态的创建实现类。而在动态创建实现类时则会判断函数式接口是否实现了Serializable,如果实现了,则添加writeReplace方法

通过Lambda函数的方式获取属性名称

通过Lambda函数的方式获取属性名称

通过Lambda函数的方式获取属性名称

也就是说我们代码BeanUtils#getSerializedLambda方法中反射调用的writeReplace方法是在生成函数式接口实现类时添加进去的.

SFunction Class中的writeReplace方法

从上文中我们得知 当SFunction继承Serializable时, 底层在动态生成SFunction的实现类时添加了writeReplace方法, 那这个方法有什么用?

首先 我们将动态生成的类保存到磁盘上看一下

我们可以通过如下属性配置将 动态生成的Class保存到 磁盘上

java8中可以通过硬编码

 System.setProperty("jdk.internal.lambda.dumpProxyClasses", ".");

例如:

通过Lambda函数的方式获取属性名称

jdk11 中只能使用jvm参数指定,硬编码无效,原因是模块化导致的

-Djdk.internal.lambda.dumpProxyClasses=.

例如:

通过Lambda函数的方式获取属性名称

执行方法后输出文件如下:

通过Lambda函数的方式获取属性名称

其中实现类的类名是有具体含义的

通过Lambda函数的方式获取属性名称

其中Test$Lambda$15.class信息如下:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package test.java8.lambdaimpl;

import java.lang.invoke.SerializedLambda;
import java.lang.invoke.LambdaForm.Hidden;
import test.java8.lambdaimpl.Test.User;

// $FF: synthetic class
final class Test$$Lambda$15 implements SFunction {
    private Test$$Lambda$15() {
    }

    @Hidden
    public Object apply(Object var1) {
        return ((User)var1).getName();
    }

    private final Object writeReplace() {
        return new SerializedLambda(Test.class, "test/java8/lambdaimpl/SFunction", "apply", "(Ljava/lang/Object;)Ljava/lang/Object;", 5, "test/java8/lambdaimpl/Test$User", "getName", "()Ljava/lang/String;", "(Ltest/java8/lambdaimpl/Test$User;)Ljava/lang/Object;", new Object[0]);
    }
}

通过源码得知 调用writeReplace方法是为了获取到方法返回的SerializedLambda对象

SerializedLambda: 是Java8中提供,主要就是用于封装方法引用所对应的信息,主要的就是方法名、定义方法的类名、创建方法引用所在类。拿到这些信息后,便可以通过反射获取对应的Field。

值得注意的是,代码中多次编写的同一个方法引用,他们创建的是不同Function实现类,即他们的Function实例对象也并不是同一个。

通过Lambda函数的方式获取属性名称

一个方法引用创建一个实现类,他们是不同的对象,那么BeanUtils中将SFunction作为缓存key还有意义吗?

答案是肯定有意义的!!!因为同一方法中的定义的Function只会动态的创建一次实现类并只实例化一次,当该方法被多次调用时即可走缓存中查询该方法引用对应的Field。

通过内部类实现类的类名规则我们也能大致推断出来, 只要申明lambda的相对位置不变, 那么对应的Function实现类包括对象都不会变。

通过在刚才的示例代码中添加一行, 就能说明该问题, 之前15号对应的是getName, 而此时的15号class对应的是getAge这个函数引用

通过Lambda函数的方式获取属性名称

通过Lambda函数的方式获取属性名称

我们再通过代码验证一下 刚才的猜想

通过Lambda函数的方式获取属性名称

参考:

https://blog.csdn.net/u013202238/article/details/105779686

https://blog.csdn.net/qq_39809458/article/details/101423610文章来源地址https://www.toymoban.com/news/detail-711515.html

到了这里,关于通过Lambda函数的方式获取属性名称的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 设备通过thingsboard iot gateway 来获取属性和更新属性

    此部分中的配置是可选的。 为了向ThingsBoard服务器节点请求客户端或共享设备属性,网关允许发送属性请求。 属性 默认值 描述 retain false 如果设置为true,该消息将被设置为主题的“最后已知良好”/保留消息。 topicFilter v1/devices/me/attributes/request 属性请求的主题 deviceNameJsonE

    2024年02月12日
    浏览(31)
  • js对象获取属性的方法(.和[]方式)

    js对象获取属性有两种方法:1.通过.的方式  2. 通过[]方式 // 通过.方式获取属性值,key是静态的 var aa = {name: \\\"zhang\\\", age: 18}; console.log(aa.name);   // 通过[]获取属性值, key是动态的,可以是字符串,或者数字的形式 var bb = {\\\"apple\\\": 3, \\\"pear\\\": 2} var cc = {1: \\\"number1\\\", 2: \\\"number2\\\"} console.log(b

    2024年02月01日
    浏览(26)
  • Mybatis传递实体对象只能直接获取,不能使用对象.属性方式获取

    mybatis的自动识别参数功能很强大,pojo实体类可以直接写进mapper接口里面,不需要在mapper.xml文件中添加paramType,但是加了可以提高mybatis的效率

    2024年02月09日
    浏览(31)
  • 【Unity3D】Unity 脚本 ③ ( C# 脚本的执行入口函数 | 获取当前游戏物体及物体名称 | 获取游戏物体的 Transform 组件数据 | UnityEngine 命名空间简介 )

    在 C# 脚本中控制 游戏物体 GameObject 运动 , 要先获取该物体 , 然后 修改其 Transform 组件的属性 ; 在 游戏开始运行后 , 会自动执行 游戏物体 GameObject 上的 C# 组件代码 , 程序入口是 MonoBehaviour#Start() 函数 ; 在 C# 脚本中 , 主要的内容都在 Start() 函数 中实现 ; 在 C# 脚本中 , 游戏物体

    2023年04月12日
    浏览(58)
  • RTPEngine 通过 HTTP 获取指标的方式

    RTPEngine 是常用的媒体代理服务器,通常被集成到 SIP 代理服务器中以减小代理服务器媒体传输的压力,其架构如下图所示。这种使用方式相当于将 RTPEngine 隐藏在 SIP 代理服务器后面,我们虽然可以借助 SIP 代理服务器提供的相关接口有限操作 RTPEngine,但是对 RTPEngine 实例的相

    2024年02月10日
    浏览(30)
  • 通过java方式获取微信用户openId

    https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html 目的:使本地的服务能在微信客户端进行访问 1.购买隧道 https://natapp.cn/tunnel/buy 2.进行配置 配置域名,ip和端口。在这里配本地可以访问的服务的ip和端口。 3 下载客户端 3 打开命令行在客户端同级目录执

    2024年02月11日
    浏览(31)
  • dede中调用文章属性的名称(调用自定义属性名称)

    接下来我们给大家分享一下如何在文章页内调用相关的属性名称: 我们可以看到,这个标题后面就是我们需要的属性名字,通过代码分析我们可以看到调用的代码为 我们能够看出来,这个调用需要函数:IsCommendArchives(@me) 这个函数就是把文章内获取的flag的内容,转化对应的

    2024年02月02日
    浏览(27)
  • Springboot通过请求头获取当前用户信息的一种方式

    一、实现原理 1、token的存储 当用户登录时,将token, userInfo存入redis缓存中,以便鉴权与获取用户信息。 2、发送请求 每次发送请求时将token放入请求头中,令key为“Authorization”或其他值。 3、获取请求头部 4、用户请求头部携带的token在redis中获得userInfo 二、导入依赖 1、redi

    2024年02月05日
    浏览(28)
  • 【微信小程序】通过云函数获取用户openid

    1.pages同级目录下新建新文件夹,命名为cloudFunctions(其他名字也可以)。 2.project.config.json中添加以下内容,值为上一步创建的文件夹名字。编译一次后上一步创建的文件夹前图标就带“云”了。 3.app.js内的App中添加 1.右击cloudFunctions文件夹,点击【新建Node.js云函数】,命名为

    2024年02月10日
    浏览(37)
  • react通过ref获取函数子组件实例方法

    在react16之后带来了hooks之后,确实方便了很多组件开发,也加快了函数式编程的速度,但是当你通过useRef获取子组件的时候,又恰好子组件是一个函数组件,那么将会报一个错误:报这个错误的主要原因是函数组件没有实例对象,所以你没办法通过ref获取子组件实例  Warnin

    2024年02月11日
    浏览(27)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包