一文看懂mybatis底层运行原理解析 电玩女神 2022-02-26 04:56 432阅读 0赞 友情提示 : 本文的重点是解析mybatis执行一次sql 的流程 ,过程中思维比较跳跃,觉得比较难得可以往下查看总结中源码流程 -------------------- 这篇文章通过一个insert语句来解析mybatis执行一次sql的运行流程。背景介绍,源码来自于 mybatis-3.0.5.jar, mybatis-config.xml: <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <properties resource="jdbc.properties" /> <!-- 配置别名 --> <typeAliases> <typeAlias type="dao.DataDao" alias="DataDao" /> </typeAliases> <!-- 配置环境变量 --> <environments default="development"> <environment id="development"> <transactionManager type="JDBC" /> <dataSource type="POOLED"> <property name="driver" value="${driverClass}" /> <property name="url" value="${jdbcUrl}" /> <property name="username" value="${DBusername}" /> <property name="password" value="${DBpassword}" /> </dataSource> </environment> </environments> <!-- 配置mappers --> <mappers> <mapper resource="dao/DataDao.xml" /> </mappers> </configuration> mapper对应的接口: public interface DataDao { public int mysqlInsert(Map<String, Object> map) throws Exception; } 接口对应的 DataDao.xml: <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="dao.DataDao"> <insert id="mysqlInsert" parameterType="map" > insert into ${tableName} (${columns}) values (${colValues}) </insert> </mapper> 首先我们会从mybatis的入口加载的地方来进行解读,先看一段mybatis的插入代码: /** * 传入map参数,执行插入 * @param map * @return */ public static int mysqlInsert(Map<String, Object> map) throws Exception { SqlSession session = null; int index=0; try { session = SessionFactory.getSession(); DataDao dataDao = session.getMapper(DataDao.class); index=dataDao.mysqlInsert(map); if(index>0){ logger.info("insert success: "+index); } session.commit(true); } catch (Exception e) { logger.error("插入异常:", e); session.rollback(true); throw e; } finally { session.close(); } return index; } public class SessionFactory { private static Logger logger = LoggerFactory.getLogger(SessionFactory.class); private static SqlSessionFactoryBuilder sqlSessionFactoryBuilder; private static SqlSessionFactory sqlSessionFactory; //初始化mybatis static { String resource = "mybatis-config.xml"; Reader reader = null; try { reader = Resources.getResourceAsReader(resource); sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder(); sqlSessionFactory = sqlSessionFactoryBuilder.build(reader); } catch (IOException e) { logger.error("加载配置文件错误:",e); e.printStackTrace(); }catch (Exception e){ logger.error("加载配置文件错误:",e); } logger.info("init mybatis"); } public static synchronized SqlSession getSession() throws IOException { SqlSession sqlSession =null; if(null == sqlSession) { sqlSession = (null != sqlSessionFactory)?sqlSessionFactory.openSession(): null; } return sqlSession; } } 代码中可以看到首先会通过 SessionFactory 去获取 SqlSession,里面会有一段static代码,在SessionFactory类加载时会执行mybatis-config.xml 的初始化过程。现在我们直接看重点: ![20190521160711500.png][] 通过SqlSession 的 getMapper获取DataDao数据接口的时候发现有2个实现,追溯到SessionFactory的初始化中会看到下面一段: ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy_size_16_color_FFFFFF_t_70][] 所以我们直接进入 DefaultSqlSession getMapper方法中,可以找到 MapperRegistry 中:![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy_size_16_color_FFFFFF_t_70 1][] 看到这里熟悉的动态代理的朋友知道这是一个获取java的动态代理类的写法(ps: 不熟悉动态代理的,可以看这里我的上一篇博客 [动态代理白话解析][Link 1]),我们继续往下看: ![20190521171500929.png][] 继续走到下一步:dataDao.mysqlInsert(map); 执行这个方法时会调用 MapperProxy 的 invoke方法: ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy_size_16_color_FFFFFF_t_70 2][] 开源看到除了Object 中的方法不能代理,其他的都会走到 mapperMethod.execute(args): ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy_size_16_color_FFFFFF_t_70 3][] type 字段会在mapperMethod初始化时赋值,这里我们执行的insert,继续往下走到DefaultSqlSession 的update方法 : ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy_size_16_color_FFFFFF_t_70 4][] 到这里后,接下来我们开始看 Configuration的初始化,它在SqlSessionFactory初始化时构造的![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy_size_16_color_FFFFFF_t_70 5][] ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy_size_16_color_FFFFFF_t_70 6][] 上面2段代码可以看到 Configuration 是通过加载mybatis-config.xml时进行初始化的。 现在我们回到 this.configuration.getMappedStatement(statement); 这段代码: ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy_size_16_color_FFFFFF_t_70 7][] 进去看下我们发现他其实是通过 id 去获取对应的MappedStatement ,通过上文我们可以知道id是MapperMethod的execute方法传进来的commandName,它在MapperMethod 的构造函数中的 setupFields 方法中初始化: ![20190604144742623.png][] 在本文中的值为: DataDao.mysqlInsert。类似这种,也就是接口类.方法名。 现在我们回到主流程中,查看 mappedStatements 的初始化过程,是在上文中初始化Configuration 的this.mapperElement(root.evalNode("mappers")); 中 : ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy_size_16_color_FFFFFF_t_70 8][] ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy_size_16_color_FFFFFF_t_70 9][] ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy_size_16_color_FFFFFF_t_70 10][] ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy_size_16_color_FFFFFF_t_70 11][] ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy_size_16_color_FFFFFF_t_70 12][] 这里会解析mapper.xml中的sql节点,本文中的示例是 这一段: ![20190604153718833.png][] 我们继续查看mappedStatements的初始化 : ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy_size_16_color_FFFFFF_t_70 13][] ![20190604152932286.png][] 这里的id我们可以看到就是节点中的namespace.id的值拼接,也就是 DataDao.mysqlInsert 现在我们继续回到主流程,看这段代码: ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy_size_16_color_FFFFFF_t_70 14][] 代码中通过executor执行update方法,现在我们看下 executor 的初始化过程,是在SqlSession初始化的过程中: ![2019060415531590.png][] ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy_size_16_color_FFFFFF_t_70 15][] ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy_size_16_color_FFFFFF_t_70 16][] ![20190604155332329.png][] 在Configuration的构造函数中可以看到这里的 executorType 默认为SIMPLE,cacheEnabled 默认为true. 现在我们继续回到主流程,进到 CachingExecutor 的 update 方法中: ![20190604160124530.png][] 这里会进入到 BaseExecutor 的 update方法中 ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy_size_16_color_FFFFFF_t_70 17][] 这里会进入到 SimpleExecutor 的doUpdate中: ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy_size_16_color_FFFFFF_t_70 18][] ![20190604161827607.png][] ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy_size_16_color_FFFFFF_t_70 19][] 这里首先初始化了一个 RoutingStatementHandler ,由上文可知 statementType 默认为 PREPARED,所以 其 delegate 属性为 PreparedStatementHandler。我们回到主流程,继续看 this.prepareStatement(handler) : ![2019060416270362.png][] 会走到 BaseStatementHandler 的 prepare 方法 : ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy_size_16_color_FFFFFF_t_70 20][] 这里会进入到 PreparedStatementHandler 的 instantiateStatement 方法: ![20190604180125706.png][] 这里的 boundSql 是在 上文中的 mappedStatement 初始化的时候赋值的: ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy_size_16_color_FFFFFF_t_70 21][] ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy_size_16_color_FFFFFF_t_70 22][] ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy_size_16_color_FFFFFF_t_70 23][] 首先是在 parseStatementNode 中初始化 DynamicSqlSource,然后获取 BoundSql,接下来我们看下XMLStatementBuilder的parseDynamicTags方法: ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy_size_16_color_FFFFFF_t_70 24][] ![20190606151505720.png][] 代码中我们可以看到若节点类型既不是文本节点也不是CDATA节点就会调用不同的处理类去处理,我们比较常用的就是查询时使用的 <if> 和 <where> 节点 ,这里我们执行的是 insert , 所以直接是一个文本节点,下面就是初始化了一个MixedSqlNode 和 configuration 构造了 DynamicSqlSource,然后通过getBoundSql 方法来构造BoundSql: ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy_size_16_color_FFFFFF_t_70 25][] ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy_size_16_color_FFFFFF_t_70 26][] 由上文知:rootSqlNode是MixedSqlNode,这里会执行 MixedSqlNode 的 apply 方法,在本例中会执行 TextSqlNode 的 apply 方法: ![20190606163521804.png][] ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy_size_16_color_FFFFFF_t_70 27][] 这一段呢主要是用来处理sql中的$\{\}占位符参数,本文中的 insert into $\{tableName\} ($\{columns\}) values ($\{colValues\}) 经过parse 转换后 insert into table (id,name) values (1,'test') , 这一段的代码就不分析了,比较繁杂,主要是通过将对应的占位符替换为对应的值。 我们回到 getBoundSql 方法 ,继续往下走,到 sqlSourceParser.parse(context.getSql(), parameterType) 这一行代码: ![20190606174142891.png][] 又看到了熟悉的代码,这一段是将\#\{\} 占位符替换为对应的value. 我们继续回到上文主流程中的 PreparedStatementHandler 的 instantiateStatement 方法,往下走: ![20190604180125706.png][] 这里的 Jdbc3KeyGenerator 是如果你设置了useGeneratedKeys=true 用来 自动生成主键的,resultSetType 本例中也没有涉及为空,所以这里会返回一个 PreparedStatement, 到这里我们的 SimpleExecutor 的 doUpdate 的 this.prepareStatement(handler) 这段代码就分析完了。 我们回到主流程继续往下走: ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy_size_16_color_FFFFFF_t_70 28][] ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy_size_16_color_FFFFFF_t_70 29][] 这里进入到 jdbc 的 PreparedStatement 的执行过程,执行insert插入的操作。后面2段代码我就不分析了,主要是用来获取主键的。 到这里,mybatis执行一次insert sql的运行机制就解析完成了。 总结: 在源码分析的过程中,可能会比较跳跃,因为在源码分析过程中会有一些配置类或执行类的初始化过程。这里分享一下快速阅读源码的方式,在查看源码的过程中,可能会有许多扩展,校验,初始化的一些功能代码,比如: 1. Configuration 类的初始化 2. [Interceptor 拦截器底层执行原理][Interceptor](实际采用的是责任链设计模式和动态代理设计模式来实现的拦截) 3. cache缓存的实现方式 可以暂时先跳过,先把主要流程的代码过一遍,比如mybatis 执行一次sql的流程源码可以按照如下方式查看: ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy_size_16_color_FFFFFF_t_70 30][] 当我们将整体流程实现查看清楚后,如果想查看更多的细节,可以使用其他时间回头来查看扩展功能的实现方式 [20190521160711500.png]: /images/20220226/a004256bf3a64e26afeb7660a61b543a.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy_size_16_color_FFFFFF_t_70]: /images/20220226/9c331cb0b16541efb28648695bde4165.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy_size_16_color_FFFFFF_t_70 1]: /images/20220226/e0d93a7548db40b6866832693bdf036d.png [Link 1]: https://blog.csdn.net/Royal_lr/article/details/88883876 [20190521171500929.png]: /images/20220226/51d8bf30dfad43d2833e809f5fe844f6.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy_size_16_color_FFFFFF_t_70 2]: /images/20220226/4e8e22373a6d4daa9e7dd2cd3b82d06f.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy_size_16_color_FFFFFF_t_70 3]: /images/20220226/f04abfaaef074e4f9bf696ad65e8e9ba.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy_size_16_color_FFFFFF_t_70 4]: /images/20220226/abf53d45354841a0919d3a8a1a2d7d1e.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy_size_16_color_FFFFFF_t_70 5]: /images/20220226/cd6e4895febe420ea022ef4397e04096.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy_size_16_color_FFFFFF_t_70 6]: /images/20220226/7471efeb8418494f9de275abf94e2762.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy_size_16_color_FFFFFF_t_70 7]: /images/20220226/602f3de96b53489c818dfe706bf20a14.png [20190604144742623.png]: /images/20220226/369493bc3c064013a21dd0f9176756ff.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy_size_16_color_FFFFFF_t_70 8]: /images/20220226/9190141ea6f94ed981aabb4e0d71de1f.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy_size_16_color_FFFFFF_t_70 9]: /images/20220226/72d17db1832c4364990416b6d7f603bb.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy_size_16_color_FFFFFF_t_70 10]: /images/20220226/18fce3d7baf34f4c83cd57ee957f6bbf.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy_size_16_color_FFFFFF_t_70 11]: /images/20220226/853605f7edce4d64ac2c93e3c5f219c4.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy_size_16_color_FFFFFF_t_70 12]: /images/20220226/b160a5a85d1c4ad181946654173a7e96.png [20190604153718833.png]: /images/20220226/a19a2f8f0f1c4948862333e8f89c5b2d.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy_size_16_color_FFFFFF_t_70 13]: /images/20220226/609db877472040afa5e191f14cf0fd3a.png [20190604152932286.png]: /images/20220226/7d051120145e48f4a569904a770daee7.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy_size_16_color_FFFFFF_t_70 14]: /images/20220226/575deb84808e414ab06e5f540779b262.png [2019060415531590.png]: /images/20220226/95db8d6bc8d64d7ab520032eab0f23e5.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy_size_16_color_FFFFFF_t_70 15]: /images/20220226/3889cb4104114b7f95e8b10bba79d6b9.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy_size_16_color_FFFFFF_t_70 16]: /images/20220226/6900c6968a5c4918ad5939ad255f8f54.png [20190604155332329.png]: /images/20220226/b104d1db4174468981b5ece399206a54.png [20190604160124530.png]: /images/20220226/e529c1653f58401a9b4c05dfef00a641.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy_size_16_color_FFFFFF_t_70 17]: /images/20220226/7b39a5f502a64310ad3147cc97176bd2.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy_size_16_color_FFFFFF_t_70 18]: /images/20220226/279aa5a2bac24c829158d2a54b9a3fe3.png [20190604161827607.png]: /images/20220226/123d375af3f74f79a55d874ef143424d.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy_size_16_color_FFFFFF_t_70 19]: /images/20220226/e6ec137a46ab41c5a67696c7cb6d936b.png [2019060416270362.png]: /images/20220226/24306444ff40465ba08c066b413ad6be.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy_size_16_color_FFFFFF_t_70 20]: /images/20220226/52529f0f168d438cab12779fa9c7f93a.png [20190604180125706.png]: /images/20220226/25607bee18f248e3be7f91a8ed3a1593.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy_size_16_color_FFFFFF_t_70 21]: /images/20220226/e19e5fc1455e4a2680fd76debb9c676e.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy_size_16_color_FFFFFF_t_70 22]: /images/20220226/e0ca31b2c71f4134842bd0c4a1e46db1.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy_size_16_color_FFFFFF_t_70 23]: /images/20220226/7f2cd6066b994cd5968a9c7c90b6072f.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy_size_16_color_FFFFFF_t_70 24]: /images/20220226/144f5167e80c42d4b10d42ae773da991.png [20190606151505720.png]: /images/20220226/42d54cee44e64c818967582adf0d3e75.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy_size_16_color_FFFFFF_t_70 25]: /images/20220226/63975257dd8943dd884530c046d7e9d6.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy_size_16_color_FFFFFF_t_70 26]: /images/20220226/629e7c35152e4743be6a9e604276373f.png [20190606163521804.png]: /images/20220226/041d0fffa31d4f9da9577d5bed581b75.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy_size_16_color_FFFFFF_t_70 27]: /images/20220226/264f2a5f99c34f6baecff2c9c5968d8e.png [20190606174142891.png]: /images/20220226/c2eee661a9b04d0885bbe2b1a6b497a8.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy_size_16_color_FFFFFF_t_70 28]: /images/20220226/4949ce7d26474357b785692e6b63ce07.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy_size_16_color_FFFFFF_t_70 29]: /images/20220226/351fd919772042f9a1fe3d6bed1caad8.png [Interceptor]: https://blog.csdn.net/Royal_lr/article/details/110642945 [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy_size_16_color_FFFFFF_t_70 30]: /images/20220226/3fe1c75f7c3d4492bacfcf2f5a47de71.png
相关 HashMap底层实现原理解析 ![6225b9e118544330a17ef8314bc9e2d0.png][] 一、 HashMap中的put()和get()的实现原理: 1、map.put(k 阳光穿透心脏的1/2处/ 2023年10月13日 11:03/ 0 赞/ 85 阅读
相关 Spring底层核心原理解析 class userServiceProxy extends UserService(){ //生成代理类去继承UserService UserSer 偏执的太偏执、/ 2023年10月03日 22:04/ 0 赞/ 29 阅读
相关 SpringBoot:SpringBoot 的底层运行原理解析 声明原文出处:狂神说 文章目录 1. pom.xml 1 . 父依赖 旧城等待,/ 2023年09月24日 23:35/ 0 赞/ 85 阅读
相关 Mybatis原理解析 > mabatis启动流程: 1. 伴随tomcat,在tomcat启动时候,cat扫描web.xml文件,找到mabatis配置文件路径,mabatis配置文件包括但不限 ╰+攻爆jí腚メ/ 2023年02月10日 05:23/ 0 赞/ 27 阅读
相关 CopyOnWriteArrayList底层原理解析 ArrayList并不是一个线程安全的容器,对于高并发下可以用Vector,或者使用Collections的静态方法将ArrayList包装成一个线程安全的类,但是这些方式都是 不念不忘少年蓝@/ 2022年12月28日 08:22/ 0 赞/ 193 阅读
相关 终于找到了一篇一看就懂的 OKHttp 原理解析 一、概述 最近在群里听到各种讨论okhttp的话题,可见okhttp的口碑相当好了。再加上Google貌似在6.0版本里面删除了HttpClient相关API,对于这个行 今天药忘吃喽~/ 2022年09月30日 10:55/ 0 赞/ 217 阅读
相关 JavaScript运行原理解析 写在前面的话: 发现使用了那么长时间的Javascript,但是对其运行原理还是不清晰,今天特意总结一下,把大神们的理论和自己的总结都记录到下面; 1. 什么是JavaSc àì夳堔傛蜴生んèń/ 2022年08月19日 08:56/ 0 赞/ 214 阅读
相关 一文看懂mybatis底层运行原理解析 友情提示 : 本文的重点是解析mybatis执行一次sql 的流程 ,过程中思维比较跳跃,觉得比较难得可以往下查看总结中源码流程 -------------------- 电玩女神/ 2022年02月26日 04:56/ 0 赞/ 433 阅读
相关 一文看懂HashMap底层原理 文章目录 一、概念 二、继承关系 三、基本属性 四、数据存储结构 五、JDK 1.8,HashMap采 港控/mmm°/ 2022年02月25日 05:42/ 0 赞/ 559 阅读
还没有评论,来说两句吧...