• 【Mybatis源码分析】动态代理的使用(Javassist、CGLIB、JDK动态代理)


    先说说这篇博客说得啥?

    本是不想写这篇博客的,因为关于 Mybatis 对 Mapper 的动态代理实现也很简单,就是使用 JDK 动态代理,调用其接口中的方法转到调用到 sqlSession 的方法上去,然后和上一篇的Mybatis查询流程 源码分析串起来就可以了,顶多需要注意点 Mybatis 是如何处理参数的就是。

    但是我发现 Mybatis 还引入了 CGLIB 动态代理库,why?这我有以下几个疑问?

    • 为什么代理 Mapper 不使用 CGLIB 动态代理?
    • 为什么处理映射对象动态代理实现懒加载不使用CGLIB动态代理异或是JDK提供的动态代理,而是引入 javassist 代理库呢?

    这篇博客除了源码分析带大家知道 Mybatis 代理 Mapper 是如何处理参数的,还会给大伙解释清楚那俩个问题。

    Mybatis 俩处使用了动态代理,一处是动态代理 Mapper 对象,供外界面向抽象操纵数据库.另一处是针对返回值对象进行动态映射实现懒加载。

    Mybatis 处理参数源码分析

    1. public Object getNamedParams(Object[] args) {
    2. final int paramCount = names.size();
    3. if (args == null || paramCount == 0) {
    4. return null;
    5. }
    6. if (!hasParamAnnotation && paramCount == 1) {
    7. Object value = args[names.firstKey()];
    8. return wrapToMapIfCollection(value, useActualParamName ? names.get(names.firstKey()) : null);
    9. } else {
    10. // 针对没有使用 @Param 注解和含多个参数的
    11. final Map param = new ParamMap<>();
    12. int i = 0;
    13. for (Map.Entry entry : names.entrySet()) {
    14. // key 对应的是参数名,或者说@Param中的字符串,Value是对应args中的值
    15. param.put(entry.getValue(), args[entry.getKey()]);
    16. // add generic param names (param1, param2, ...)
    17. // 通用的参数名称,ORM映射需要的名称,需要和填充的相对于
    18. final String genericParamName = GENERIC_NAME_PREFIX + (i + 1);
    19. // ensure not to overwrite parameter named with @Param
    20. if (!names.containsValue(genericParamName)) {
    21. param.put(genericParamName, args[entry.getKey()]);
    22. }
    23. i++;
    24. }
    25. return param;
    26. }
    27. }

    它内部封装了个 names,会指示各个参数对应的名字,如果用了  @Param 注解,其对应参数名字就会使用 @Param 中的 Value 属性对应的值。

    如果有多个参数或者说用了 @Param 注解的话,它会将 名称-》对应参数值 封装成一个 map 对象然后返回。为了避免没有使用 @Param 注解的参数,它会添加通用的名称对应的参数值,即类似param1、param2.....

    然后这边知道怎么把方法中的参数转换成对应后,就可以将前面说的串起来了。

    也是 ${} 和 #{} 区别?

    在使用执行器准备执行对应 SQL 时——

    会调用 MappedStatement.getBoundSql(param) 也就是调用 SqlSource.getBoundSql(param)——

    我们所说的${}在解析动态SQL的时候其实它对应的就是 TextSqlNode,在getBoundSql执行中就会把 ${} 替换成对应的参数值,#{} 会用 ? 去替换,这个时候就得到真正的sql片段——

    此时的sql片段预编译后就可以获得到PreparedStatement操纵对象——得到它就可以进行上一篇说的执行流程了

    为什么实现懒加载用的是Javassist动态代理

    首先得分析一下它使用该动态代理的时机。

    是在执行查询时,然后判断是否配置了 fetchType=lazy 且存在子查询,如果是的话就使用动态代理去处理返回值对象。查询是随着外界调用而执行的操作,是运行时的,而不是项目加载就会去执行,可以理解为是执行了 Mapper 代理,然后内部执行了对应的查询。

    抓住这个时机呢,就知道使用该动态代理是在运行时实现的。

    然后 Mybatis 实现的该代理过程是先生成一个代理类,然后该代理类是会去继承那个实际的类,并且去实现对应的接口,实际的代理对象就是这个生成的代理类的对象。也就是说在运行时得生成这个代理类的字节码,那使用 Javassist 相比其他动态代理库来说是很好的选择。

    举个例子,假设有一个 User 对象,在启用懒加载的情况下,MyBatis 会动态生成一个 UserProxy 类作为 User 对象的代理。UserProxy 类继承自 User 类,并实现了 User 接口(如果有)。在 UserProxy 类中,会覆写访问延迟加载属性的方法,包括 getter 方法。

    为什么Mapper代理用的是JDK自带的动态代理

    这个其实很简单,因为咱定义的Mapper是接口,然而JDK自带的解决动态代理的相关api就是处理接口的,那肯定是最直接的方案。至于引入CGLIB依赖,是为了满足用户需求,如果用户想用CGLIB去实现Mapper的动态代理可以进行配置。如下:

    1. <configuration>
    2. <settings>
    3. <setting name="proxyFactory" value="org.apache.ibatis.executor.CglibProxyFactory" />
    4. settings>
    5. ...
    6. configuration>

    所以说引入 CGLIB 依赖是为了让你自由切换咯,注意是生成Mapper代理对象这个代理过程的切换方案。至于懒加载那里就是使用Javassist,最佳~

  • 相关阅读:
    Java中类的内部成员之五: 内部类
    group by 分组【mysql数据库】
    HTML5+CSS3+移动web 前端开发入门笔记(二)HTML标签详解
    Java:Stream流
    realsense d455 semantic_slam实现语义八叉树建图
    springboot集成Redis
    SecureFX[po破] for Mac FTP/SSH传输工具[解] 安装教程
    ChatGPT:解释Java中 ‘HttpResponse‘ 使用 ‘try-with-resources‘ 的警告和处理 ‘Throwable‘ 打印警告
    基于非侵入式负荷检测与分解的电力数据挖掘
    Metabase学习教程:权限-1
  • 原文地址:https://blog.csdn.net/qq_63691275/article/details/132734294