Lambda表达式提取字段名

文章目录

  • 前言
  • 例子
  • 原理
    • writeReplace
    • 反序列化对象
    • 缓存元数据
  • 写一个工具

前言

实体类:方法这种方式获取字段名,摒弃了字符串拼接方式,避免拼接出现的问题,提高框架维护性和可修改性。

例子

  1. 引入·Mybatis-Plus·

     <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.5.1</version>
    </dependency>
    
  2. 增加配置

    server:
      port: 8080
    
    spring:
      application:
        name: data-import
      datasource:
        url: jdbc:mysql://192.168.158.129:3306/data-import?useSSl=true&zeroDateTimeBehavior=convertToNull&characterEncoding=utf-8&serverTimezone=GMT%2B8
        username: root
        password: root
        driver-class-name: com.mysql.cj.jdbc.Driver
        hikari:
          minimum-idle: 5
          idle-timeout: 600000
          maximum-pool-size: 10
          auto-commit: true
          pool-name: datimportpool
          max-lifetime: 1800000
          connection-timeout: 30000
          connection-test-query: SELECT 1
    
  3. 添加扫描

    @MapperScan("com.liry.dataimport.repository.mapper")
    
  4. 写一个简单的查询逻辑在service

        @Override
        public Demo getByName(String name) {
            LambdaQueryWrapper<Demo> wrapper = Wrappers.lambdaQuery();
            wrapper.eq(Demo::getName, name);
            return demoMapper.selectOne(wrapper);
        }
    
  5. 测试

    @SpringBootTest
    @RunWith(SpringRunner.class)
    public class SpringTest {
    
        @Resource
        private DemoService demoService;
    
        @Test
        public void testGetName() {
            Demo demo = demoService.getByName("ali222");
            System.out.println(demo.getName());
        }
    }
    

好了,我们看第4步里的写法,它使用了Mybatis-PlusWrapper类构建SQL语句,然后我们可用使用Demo::getName获取到实体Demo的字段name的数据库属性名称,这个它底层的原理就是反序列化。

原理

以这个为人口我们深入看看

    public Demo getByName(String name) {
        LambdaQueryWrapper<Demo> wrapper = Wrappers.lambdaQuery();
        wrapper.eq(Demo::getName, name);
        return demoMapper.selectOne(wrapper);
    }

进入看 wrapper.eq(Demo::getName, name);,它会走到这个位置:

com.baomidou.mybatisplus.core.conditions.AbstractLambdaWrapper#columnToString(com.baomidou.mybatisplus.core.toolkit.support.SFunction<T,?>)

image-20241126181440541

注意看,这里column是一个lambda表达式,它并不是一个实际的字段。

我们可以放开焦点,看看当前这个类做了写什么AbstractLambdaWrapper

image-20241127134012333

可以看出,这个类是做了一个字段的缓存,其实就是我们的数据库实体的缓存信息。

再往下看,有这样的一段:

image-20241127135430840

image-20241127135448528

可以看到这里通过LambdaUtils.extract(column);就获取到了lambda的元数据,包含方法名、属性名、类名,然后通过方法名推断属性名。

image-20241127140030058

这里它有两种处理方式,反射方式是通过序列化中的方法writeReplace操作的,而序列化方式他是将字节系列转化为对象后,然后再反射读取,所以前者明显性能比后者高,但后者它保证了异常情况下正常执行的能力。

writeReplace

那什么是writeReplace

writeReplace是在序列化对象时写入的一个方法,这个方法会动态的创建并返回了一个SerializedLambda对象,该对象是Lambda表达式的序列化类,它记录了Lambda表达式创建时需要的必要信息,包括接口类型、方法签名、方法参数等,因此,我们可以通过这个对象获取到相关信息,但需要函数式接口实现Serializable接口,如下面,必须要有Seriablizable

@FunctionalInterface
public interface CusFunction<T, R> extends Function<T, R> , Serializable {
}

这里再提一嘴,我们可以自己实现writeReplace方法,这里举个例子:

public class TestA implements Serializable {

    String name = "aaaa";

    // writeReplace执行序列化替换对象
    private final Object writeReplace() {
        return new TestB();
    }

}
public class TestB implements Serializable {

    String name = "bbbb";
}

public class TestSeri {

    public static void main(String[] args) {
        // 序列化  TestA
        try (ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("./osa"))) {
            os.writeObject(new TestA());
            os.flush();
            os.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        // 反序列化 TestA
        try (ObjectInputStream is = new ObjectInputStream(new FileInputStream("./osa"))) {
            Object obj = is.readObject();
            is.close();
            System.out.println("类名:"+ obj.getClass().getName());
            System.out.println("属性值:" + ((TestB)obj).name);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }
}

结果如下:

image-20241128172111375

反序列化对象

我们再看另一种情况,它其实就是还是从字节序列中构建SerializedLambda对象

image-20241128173207880

位置:com.baomidou.mybatisplus.core.toolkit.support.SerializedLambda#extract

image-20241128173240188

这段代码较为简单,就是将Lambda表达式中通过反序列化的方式解析相关信息。

缓存元数据

在解析得到Lambda表达式后,反射获取到类名,属性字段信息,然后缓存,之后通过key读取。

image-20241128173650120

    protected ColumnCache getColumnCache(SFunction<T, ?> column) {
        // 解析得到lambda的表达式信息
        LambdaMeta meta = LambdaUtils.extract(column);
        // 根据方法名解析字段名称,如方法名为 getName,则属性应该是 name
        String fieldName = PropertyNamer.methodToProperty(meta.getImplMethodName());
        // 获取到类
        Class<?> instantiatedClass = meta.getInstantiatedClass();
        // 初始化当前类的映射信息
        tryInitCache(instantiatedClass);
        // 根据字段名返回字段记录信息
        return getColumnCache(fieldName, instantiatedClass);
    }


/*
* 初始化映射信息
*/
 private void tryInitCache(Class<?> lambdaClass) {
     // 是否初始化过映射信息
        if (!initColumnMap) {
            // 反射获取类型
            final Class<T> entityClass = getEntityClass();
            if (entityClass != null) {
                lambdaClass = entityClass;
            }
            // 反射获取字段信息
            // ** 并缓存 **
            columnMap = LambdaUtils.getColumnMap(lambdaClass);
            Assert.notNull(columnMap, "can not find lambda cache for this entity [%s]", lambdaClass.getName());
            initColumnMap = true;
        }
    }

image-20241128175727595

image-20241128175747356

COLUMN_CACHE_MAP是类名为外面的key,然后字段名为里面的key。

写一个工具

上面我们弄清楚了实体类:方法这样的写法是怎么实现的,现在我们可以根据它的自己弄一个较为通用的,一个工具。

  1. 定义一个函数式接口
@FunctionalInterface
public interface CusFunction<T, R> extends Function<T, R>, Serializable {
}

  1. Lambda元数据对象

    public class LambdaMetaCache {
    
        private Class<?> clazz;
    
        private String methodName;
    
        public LambdaMetaCache(Class<?> clazz, String methodName) {
            this.clazz = clazz;
            this.methodName = methodName;
        }
    
        public Class<?> getClazz() {
            return clazz;
        }
    
        public void setClazz(Class<?> clazz) {
            this.clazz = clazz;
        }
    
        public String getMethodName() {
            return methodName;
        }
    
        public void setMethodName(String methodName) {
            this.methodName = methodName;
        }
    }
    
    
  2. 写一个Lambda解析工具

public final class LambdaUtils {

    /**
     * lambda表达式反序列化
     *
     * @param func lambda表达式
     */
    private static <T> SerializedLambda resolve(CusFunction<T, ?> func) {
        try {
            Method method = func.getClass().getDeclaredMethod("writeReplace");
            method.setAccessible(true);
            // 安全访问
            return (SerializedLambda) AccessController.doPrivileged(new CusPrivilegedAction<>(method)).invoke(func);
        } catch (Exception e) {
            // 反序列化解析
            return extractLambda(func);
        }
    }

    public static <T> LambdaMetaCache extractLambdaMeta(CusFunction<T, ?> func) {
        SerializedLambda resolve = resolve(func);
        return new LambdaMetaCache(getClass(resolve), getMethodName(resolve));

    }

    /**
     * 读取Lambda的方法名
     *
     * @param lambda lambda表达式序列化对象
     * @return 方法名
     */
    private static String getMethodName(SerializedLambda lambda) {
        return lambda.getImplMethodName();
    }

    /**
     * 读取Lambda的类型信息
     *
     * @param lambda lambda表达式序列化对象
     * @return 类型信息
     */
    private static Class<?> getClass(SerializedLambda lambda) {
        String instantiatedMethodType = lambda.getImplClass();
        String instantiatedType = instantiatedMethodType.replace("/", ".");
        try {
            // 加载类
            return loadClass(instantiatedType, getClassLoaders(null));
        } catch (ClassNotFoundException e) {
            throw new RuntimeException("找不到指定的class!请仅在明确确定会有 class 的时候,调用该方法", e);
        }
    }

    /**
     * 反序列化
     *
     * @param serializable 可序列化对象
     */
    private static SerializedLambda extractLambda(Serializable serializable) {
        try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
             ObjectOutputStream oos = new ObjectOutputStream(baos)) {
            oos.writeObject(serializable);
            oos.flush();
            try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray())) {
                @Override
                protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
                    return super.resolveClass(desc);
                }
            }) {
                return (SerializedLambda) ois.readObject();
            }
        } catch (IOException | ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 类加载器列表
     */
    private static ClassLoader[] getClassLoaders(ClassLoader classLoader) {
        return new ClassLoader[] {
            classLoader,
            Thread.currentThread().getContextClassLoader(),
            LambdaUtils.class.getClassLoader()};
    }

    /**
     * 加载类
     *
     * @param className    类名
     * @param classLoaders 类加载器
     * @return 类型
     */
    private static Class<?> loadClass(String className, ClassLoader[] classLoaders) throws ClassNotFoundException {
        for (ClassLoader classLoader : classLoaders) {
            if (classLoader != null) {
                try {
                    return Class.forName(className, true, classLoader);
                } catch (ClassNotFoundException e) {
                    // ignore
                }
            }
        }
        throw new ClassNotFoundException("Cannot find class: " + className);
    }

    /**
     * 为安全访问创建的对象信息
     *
     * @param <T>
     */
    static class CusPrivilegedAction<T extends AccessibleObject> implements PrivilegedAction<T> {
        private final T obj;

        public CusPrivilegedAction(T obj) {
            this.obj = obj;
        }

        @Override
        public T run() {
            obj.setAccessible(true);
            return obj;
        }
    }
}
  1. 然后把Mybatis-Plus里的PropertyNamer复制过来
public final class PropertyNamer {
    private PropertyNamer() {
    }

    /**
     * 方法名转成属性名
     */
    public static String methodToProperty(String name) {
        if (name.startsWith("is")) {
            name = name.substring(2);
        } else {
            if (!name.startsWith("get") && !name.startsWith("set")) {
                throw new RuntimeException("Error parsing property name '" + name + "'.  Didn't start with 'is', 'get' or 'set'.");
            }

            name = name.substring(3);
        }

        if (name.length() == 1 || name.length() > 1 && !Character.isUpperCase(name.charAt(1))) {
            name = name.substring(0, 1).toLowerCase(Locale.ENGLISH) + name.substring(1);
        }

        return name;
    }

    public static boolean isProperty(String name) {
        return isGetter(name) || isSetter(name);
    }

    public static boolean isGetter(String name) {
        return name.startsWith("get") && name.length() > 3 || name.startsWith("is") && name.length() > 2;
    }

    public static boolean isSetter(String name) {
        return name.startsWith("set") && name.length() > 3;
    }
}

测试一下:

    @Test
    public void testLambda() {
        LambdaMetaCache lambdaMetaCache = LambdaUtils.extractLambdaMeta(Job::getName);
        System.out.println(lambdaMetaCache.getClazz().getName());
        System.out.println(lambdaMetaCache.getMethodName());
    }

image-20241204182800261

以上作为基础工具,基于不同的框架可以添加实体类工具以获取到对应的字段名。

比如Neo4j所对应的实体是由注解@Node、@Property、@Id、@Relationship等这些标记,而Mybatis是由@TableName、@TableField、@TableId这些标记,所以,针对不同框架我们可以写对应的反射工具获取对应实体的字段,然后做映射处理。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/928678.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Dataset用load_dataset读图片和对应的caption的一个坑

代码&#xff1a; data_files {} if args.train_data_dir is not None:data_files["train"] os.path.join(args.train_data_dir, "**")dataset load_dataset("imagefolder",data_filesdata_files,cache_dirargs.cache_dir,) 数据&#xff1…

git查看本地库对应的远端库的地址

git查看本地库对应的远端库的地址 git remote -v 如果想要查看特定的远端库的url地址&#xff0c;可以使用如下命令&#xff0c;其中origin是默认的远端库的名称&#xff0c;可以使用其他远端库的名称 get remote get-url origin

传统PID和模糊控制在matlab仿真效果的对比

通过学习汇总和复现&#xff0c;利用matlab和simulink进行对传统PID和添加了模糊控制器的仿真效果进行对比&#xff1a; 上图中红色信号为传统PID仿真信号&#xff0c;比直接作用到对象的信号拟合度好很多PID的积分和比例的作用&#xff0c;直接作用到对象相当于只通过了二阶函…

网络编程(JavaEE)

前言&#xff1a; 熟悉了网络的基本概念之后&#xff0c;接下来就需要针对网络进行一系列的编程&#xff0c;其中可能涉及到新的一些编程操作&#xff0c;需要我们进一步探索&#xff01; 网络编程套接字&#xff1a; 套接字其实是socket的翻译。 操作系统给应用程序(传输层给…

算法第一弹-----双指针

目录 1.移动零 2.复写零 3.快乐数 4.盛水最多的容器 5.有效三角形的个数 6.查找总价值为目标值的两个商品 7.三数之和 8.四数之和 双指针通常是指在解决问题时&#xff0c;同时使用两个指针&#xff08;变量&#xff0c;常用来指向数组、链表等数据结构中的元素位置&am…

Linux-虚拟环境

文章目录 一. 虚拟机二. 虚拟化软件三. VMware WorkStation四. 安装CentOS操作系统五. 在VMware中导入CentOS虚拟机六. 远程连接Linux系统1. Finalshell安装2. 虚拟机网络配置3. 连接到Linux系统 七. 虚拟机快照 一. 虚拟机 借助虚拟化技术&#xff0c;我们可以在系统中&#…

分而治之—利用决策树和规则进行分类

当在几个具有不同薪资和福利水平的工作机会之间做出选择时&#xff0c;很多人会从列出利弊开始&#xff0c;并基于简单的规则来排除选项。比如&#xff0c;“如果我上下班的时间超过1小时&#xff0c;那么我会不高兴”。通过这种方式&#xff0c;通过这种方式&#xff0c;预测一…

【spring mvc】全局处理请求体和响应体

目录 说明实现效果逻辑图 实现步骤创建公共处理的请求和响应的类api接口测试前端请求响应结果 扩展Response响应格式实体ResponseCode 响应状态码RSA工具类 RequestBodyAdvice 介绍使用场景 ResponseBodyAdvice 介绍使用场景 说明 由于项目中需要进行加密传输数据提高项目安全…

Python酷库之旅-第三方库Pandas(255)

目录 一、用法精讲 1206、pandas.tseries.offsets.SemiMonthEnd.is_on_offset方法 1206-1、语法 1206-2、参数 1206-3、功能 1206-4、返回值 1206-5、说明 1206-6、用法 1206-6-1、数据准备 1206-6-2、代码示例 1206-6-3、结果输出 1207、pandas.tseries.offsets.S…

matlab conv函数和vivado fir ip对应输出什么时候相等

1&#xff09;下变频中&#xff0c;“matlab conv函数抽取”“vivado fir ip”。 2&#xff09;matlab conv函数的输入数据和输出数据的对应关系。 3&#xff09;vivado fir ip的输入数据和输出数据的对应关系。 与matlab conv函数一致&#xff0c;如上图。 不同的是&#xff…

大数据新视界 -- Hive 数据湖集成与数据治理(下)(26 / 30)

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到 青云交的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…

Linux获取文件属性

目录 stat函数 获取文件属性 获取文件权限 实现“head -n 文件名”命令的功能 编程实现“ls -l 文件名”功能 stat/fstat/lstat的区别&#xff1f; stat函数 int stat(const char *path, struct stat *buf); 功能&#xff1a;获取文件属性 参数&#xff1a; path&…

容器运行应用及Docker命令

文章目录 一、使用容器运行Nginx应用1_使用docker run命令运行Nginx应用1 观察下载容器镜像过程2 观察容器运行情况 2_访问容器中运行的Nginx服务1 确认容器IP地址2 容器网络说明3 使用curl命令访问 二、Docker命令1_Docker命令获取帮助方法2_Docker官网提供的命令说明3_docker…

网络(TCP)

目录 TCP socket API 详解 套接字有哪些类型&#xff1f;socket有哪些类型&#xff1f; 图解TCP四次握手断开连接 图解TCP数据报结构以及三次握手&#xff08;非常详细&#xff09; socket缓冲区以及阻塞模式详解 再谈UDP和TCP bind(): 我们的程序中对myaddr参数是这样…

如何将快捷指令添加到启动台

如何将快捷指令添加到启动台/Finder/访达&#xff08;Mac&#xff09; 1. 打开快捷指令创建快捷指令 示例创建了一个文件操作测试的快捷指令。 2. 右键选择添加到程序坞 鼠标放在待添加的快捷指令上。 3. 右键添加到访达 鼠标放在待添加的快捷指令上。 之后就可以在启…

4.5 TCP 报文段的首部格式

欢迎大家订阅【计算机网络】学习专栏&#xff0c;开启你的计算机网络学习之旅&#xff01; 文章目录 前言1 TCP 报文段的基本结构2 固定部分2.1 源端口与目的端口2.2 序号2.3 确认号2.4 数据偏移2.5 保留字段2.6 控制位2.7 窗口2.8 检验和2.9 紧急指针 3 可变部分3.1 选项3.2 填…

计算机视觉——相机标定(Camera Calibration)

文章目录 1. 简介2. 原理3. 相机模型3.1 四大坐标系3.2 坐标系间的转换关系3.2.1 世界坐标系到相机坐标系3.2.2 相机坐标系到图像坐标系3.2.3 像素坐标系转换为图像坐标系3.2.4 世界坐标转换为像素坐标 3.3 畸变3.3.1 畸变类型3.3.1.1 径向畸变&#xff08;Radial Distortion&a…

线程条件变量 生产者消费者模型 Linux环境 C语言实现

只能用来解决同步问题&#xff0c;且不能独立使用&#xff0c;必须配合互斥锁一起用 头文件&#xff1a;#include <pthread.h> 类型&#xff1a;pthread_cond_t PTHREAD_COND_INITIALIZER 初始化 初始化&#xff1a;int pthread_cond_init(pthread_cond_t * cond, NULL);…

Springboot美食分享平台

私信我获取源码和万字论文&#xff0c;制作不易&#xff0c;感谢点赞支持。 Springboot美食分享平台 一、 绪论 1.1 研究意义 当今社会作为一个飞速的发展社会&#xff0c;网络已经完全渗入人们的生活&#xff0c; 网络信息已成为传播的第一大媒介&#xff0c; 可以毫不夸张…

爬虫(JAVA笔记第四十期)

p.s.这是萌新自己自学总结的笔记&#xff0c;如果想学习得更透彻的话还是请去看大佬的讲解 目录 正则表达式爬虫 正则表达式 正则表达式可以用来校验字符串是否满足一定的规则&#xff0c;并用来校验数据格式的合法性&#xff1b;也可以在一段文本中查找满足要求的内容 单字符…