
1. 斐波那契数列的数学魅力斐波那契数列这个数学概念最早出现在印度数学中后来由意大利数学家斐波那契引入西方。这个数列看似简单却蕴含着惊人的数学规律和美学价值。数列从0和1开始后续每个数字都是前两个数字之和形成0,1,1,2,3,5,8,13...这样无限延伸的序列。在实际编程中计算斐波那契数列第n个数是个经典问题它不仅能帮助我们理解递归和迭代的思想还能让我们深入掌握算法优化的技巧。对于初学者来说这是理解基础算法的最佳切入点对于有经验的开发者这又是检验算法优化能力的试金石。2. 递归解法最直观的实现方式2.1 基础递归实现最直观的解法就是直接按照斐波那契数列的定义来实现递归函数def fibonacci_recursive(n): if n 1: return n return fibonacci_recursive(n-1) fibonacci_recursive(n-2)这个实现简洁明了完美体现了数学定义。当n为0或1时直接返回n否则返回前两项之和。这种方法的优点是代码极其简单几乎就是数学定义的直接翻译。2.2 递归解法的性能问题然而这种朴素的递归实现存在严重的性能缺陷。计算fibonacci_recursive(5)时函数调用树会像这样展开fib(5) ├── fib(4) │ ├── fib(3) │ │ ├── fib(2) │ │ │ ├── fib(1) │ │ │ └── fib(0) │ │ └── fib(1) │ └── fib(2) │ ├── fib(1) │ └── fib(0) └── fib(3) ├── fib(2) │ ├── fib(1) │ └── fib(0) └── fib(1)可以看到fib(3)被计算了2次fib(2)被计算了3次存在大量重复计算。时间复杂度是指数级的O(2^n)空间复杂度是O(n)由于递归调用栈。注意在实际应用中这种朴素递归方法仅适用于n很小的情况n30。对于更大的n计算时间会变得不可接受。3. 优化方案一记忆化递归3.1 引入缓存机制为了优化递归解法我们可以引入记忆化技术将已经计算过的结果存储起来避免重复计算def fibonacci_memo(n, memo{}): if n 1: return n if n not in memo: memo[n] fibonacci_memo(n-1, memo) fibonacci_memo(n-2, memo) return memo[n]这种方法使用一个字典memo来缓存计算结果。每次计算前先检查是否已经缓存如果已经缓存就直接返回结果否则进行计算并缓存。3.2 性能分析记忆化技术将时间复杂度从O(2^n)降低到了O(n)因为每个fib(i)只需要计算一次。空间复杂度也是O(n)因为需要存储n个计算结果。虽然性能大幅提升但这种实现仍然使用递归当n很大时比如n1000可能会遇到递归深度限制的问题导致栈溢出。4. 优化方案二迭代解法4.1 基础迭代实现为了避免递归带来的问题我们可以改用迭代方式实现def fibonacci_iterative(n): if n 1: return n a, b 0, 1 for _ in range(2, n1): a, b b, a b return b这种方法从底部开始计算依次求出fib(2), fib(3), ..., fib(n)。它只需要常数级别的额外空间存储前两个值时间复杂度是O(n)空间复杂度是O(1)。4.2 迭代解法的优势迭代解法完全避免了递归带来的栈溢出风险可以处理非常大的n值# 1. 概述本文我们来分享 MyBatis 的脚本模块对应scripting包。如下图所示scripting包在 《MyBatis 文档 —— 动态 SQL》 中我们已经了解到MyBatis 强大的动态 SQL 功能可以根据不同的条件来拼接不同的 SQL 。而动态 SQL 的实现原理就是基于scripting模块根据我们编写的动态 SQL 的代码生成对应的可执行 SQL。下面我们就来看看具体的源码。因为scripting是 MyBatis 非常底层的一个模块所以我们会从最基础的脚本功能开始看起。2. LanguageDriverorg.apache.ibatis.scripting.LanguageDriver语言驱动接口。代码如下// LanguageDriver.java public interface LanguageDriver { /** * Creates a {link ParameterHandler} that passes the actual parameters to the the JDBC statement. * * 创建 ParameterHandler 对象。 * * param mappedStatement The mapped statement that is being executed * param parameterObject The input parameter object (can be null) * param boundSql The resulting SQL once the dynamic language has been executed. * return the parameter handler * see DefaultParameterHandler */ ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql); /** * Creates an {link SqlSource} that will hold the statement read from a mapper xml file. * It is called during startup, when the mapped statement is read from a class or an xml file. * * 创建 SqlSource 对象从 Mapper XML 配置的 select / 等标签中读取内容。 * * param configuration The MyBatis configuration * param script XNode parsed from a XML file * param parameterType input parameter type got from a mapper method or specified in the parameterType xml attribute. Can be null. * return the sql source */ SqlSource createSqlSource(Configuration configuration, XNode script, Class? parameterType); /** * Creates an {link SqlSource} that will hold the statement read from an annotation. * It is called during startup, when the mapped statement is read from a class or an xml file. * * 创建 SqlSource 对象从方法注解配置中读取内容。 * * param configuration The MyBatis configuration * param script The content of the annotation * param parameterType input parameter type got from a mapper method or specified in the parameterType xml attribute. Can be null. * return the sql source */ SqlSource createSqlSource(Configuration configuration, String script, Class? parameterType); }定义了创建 ParameterHandler 和 SqlSource 对象的方法。2.1 XMLLanguageDriverorg.apache.ibatis.scripting.xmltags.XMLLanguageDriver实现 LanguageDriver 接口XML 语言驱动实现类。2.1.1 createSqlSource#createSqlSource(Configuration configuration, XNode script, Class? parameterType)方法代码如下// XMLLanguageDriver.java Override public SqlSource createSqlSource(Configuration configuration, XNode script, Class? parameterType) { // 1 创建 XMLScriptBuilder 对象执行解析 XMLScriptBuilder builder new XMLScriptBuilder(configuration, script, parameterType); return builder.parseScriptNode(); }1处创建 XMLScriptBuilder 对象执行XMLScriptBuilder#parseScriptNode()方法执行解析。关于 XMLScriptBuilder 类详细解析见 「3.1 XMLScriptBuilder」 。#createSqlSource(Configuration configuration, String script, Class? parameterType)方法代码如下// XMLLanguageDriver.java Override public SqlSource createSqlSource(Configuration configuration, String script, Class? parameterType) { // 1 如果是 script 开头使用 XML 配置的方式使用动态 SQL if (script.startsWith(script)) { // 1.1 创建 XPathParser 对象解析出 script / 节点 XPathParser parser new XPathParser(script, false, configuration.getVariables(), new XMLMapperEntityResolver()); // 1.2 调用上面的 #createSqlSource(...) 方法创建 SqlSource 对象 return createSqlSource(configuration, parser.evalNode(/script), parameterType); // 2 } else { // 2.1 变量替换 script PropertyParser.parse(script, configuration.getVariables()); // 2.2 创建 TextSqlNode 对象 TextSqlNode textSqlNode new TextSqlNode(script); // 2.3 如果是动态 SQL 则创建 DynamicSqlSource 对象 if (textSqlNode.isDynamic()) { return new DynamicSqlSource(configuration, textSqlNode); // 2.4 如果非动态 SQL 则创建 RawSqlSource 对象 } else { return new RawSqlSource(configuration, script, parameterType); } } }1处如果是script开头使用 XML 配置的方式使用动态 SQL 。1.1处创建 XPathParser 对象解析出script /节点。1.2处调用#createSqlSource(Configuration configuration, XNode script, Class? parameterType)方法创建 SqlSource 对象。2处如果是非script开头使用注解的方式可能使用动态 SQL 。2.1处变量替换。2.2处创建 TextSqlNode 对象。2.3处如果是动态 SQL 则创建 DynamicSqlSource 对象。2.4处如果非动态 SQL 则创建 RawSqlSource 对象。2.1.2 createParameterHandler#createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql)方法代码如下// XMLLanguageDriver.java Override public ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) { // 创建 DefaultParameterHandler 对象 return new DefaultParameterHandler(mappedStatement, parameterObject, boundSql); }直接创建 DefaultParameterHandler 对象。2.2 RawLanguageDriverorg.apache.ibatis.scripting.defaults.RawLanguageDriver继承 XMLLanguageDriver 类Raw语言驱动实现类确保创建的 SqlSource 是RawSqlSource类。代码如下// RawLanguageDriver.java public class RawLanguageDriver extends XMLLanguageDriver { Override public SqlSource createSqlSource(Configuration configuration, XNode script, Class? parameterType) { // 调用父类创建 SqlSource 对象 SqlSource source super.createSqlSource(configuration, script, parameterType); // 校验创建的是 RawSqlSource 对象 checkIsNotDynamic(source); return source; } Override public SqlSource createSqlSource(Configuration configuration, String script, Class? parameterType) { // 调用父类创建 SqlSource 对象 SqlSource source super.createSqlSource(configuration, script, parameterType); // 校验创建的是 RawSqlSource 对象 checkIsNotDynamic(source); return source; } /** * 校验是 RawSqlSource 对象 * * param source 创建的 SqlSource 对象 */ private void checkIsNotDynamic(SqlSource source) { if (!RawSqlSource.class.equals(source.getClass())) { throw new BuilderException(Dynamic content is not allowed when using RAW language); } } }该类目前仅仅在单元测试中使用。例如// RawLanguageDriverTest.java Test public void testRawLanguageWithDynamicContent() { try { String sql SELECT * FROM table WHERE id #{value}; LanguageDriver langDriver new RawLanguageDriver(); langDriver.createSqlSource(configuration, sql, null); Assert.fail(Raw language driver should not be able to parse dynamic SQL); } catch (BuilderException ex) { Assert.assertTrue(ex.getMessage().contains(Dynamic content is not allowed when using RAW language)); } }2.3 LanguageDriverRegistryorg.apache.ibatis.scripting.LanguageDriverRegistryLanguageDriver 注册表。代码如下// LanguageDriverRegistry.java public class LanguageDriverRegistry { /** * LanguageDriver 映射 */ private final MapClass? extends LanguageDriver, LanguageDriver LANGUAGE_DRIVER_MAP new HashMap(); /** * 默认的 LanguageDriver 类 */ private Class? extends LanguageDriver defaultDriverClass; public void register(Class? extends LanguageDriver cls) { if (cls null) { throw new IllegalArgumentException(null is not a valid Language Driver); } // 创建 cls 对应的对象并添加到 LANGUAGE_DRIVER_MAP 中 if (!LANGUAGE_DRIVER_MAP.containsKey(cls)) { try { LANGUAGE_DRIVER_MAP.put(cls, cls.newInstance()); } catch (Exception ex) { throw new ScriptingException(Failed to load language driver for cls.getName(), ex); } } } public void register(LanguageDriver instance) { if (instance null) { throw new IllegalArgumentException(null is not a valid Language Driver); } // 添加到 LANGUAGE_DRIVER_MAP 中 Class? extends LanguageDriver cls instance.getClass(); if (!LANGUAGE_DRIVER_MAP.containsKey(cls)) { LANGUAGE_DRIVER_MAP.put(cls, instance); } } public LanguageDriver getDriver(Class? extends LanguageDriver cls) { return LANGUAGE_DRIVER_MAP.get(cls); } public LanguageDriver getDefaultDriver() { return getDriver(getDefaultDriverClass()); } public Class? extends LanguageDriver getDefaultDriverClass() { return defaultDriverClass; } /** * 设置 {link #defaultDriverClass} * * param defaultDriverClass 默认的 LanguageDriver 类 */ public void setDefaultDriverClass(Class? extends LanguageDriver defaultDriverClass) { // 注册到 LANGUAGE_DRIVER_MAP 中 register(defaultDriverClass); // 设置 defaultDriverClass this.defaultDriverClass defaultDriverClass; } }比较简单胖友自己看看。2.4 DefaultScriptingLanguageDriver在 Configuration 的构造方法中会进行初始化。代码如下// Configuration.java /** * LanguageDriverRegistry 对象 */ protected final LanguageDriverRegistry languageRegistry new LanguageDriverRegistry(); public Configuration() { // ... 省略其它代码 // 注册默认的 LanguageDriver 类 languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class); languageRegistry.register(RawLanguageDriver.class); }默认情况下使用 XMLLanguageDriver 类。3. XML 动态 SQL在scripting包中xmltags包负责处理 XML 动态 SQL 相关的内容。而动态 SQL 相关的内容在 《MyBatis 文档 —— 动态 SQL》 中已经有详细的介绍。所以本小节更多的是从代码的角度来看看动态 SQL 是如何实现的。3.1 XMLScriptBuilderorg.apache.ibatis.scripting.xmltags.XMLScriptBuilder继承 BaseBuilder 抽象类XML 动态语句( SQL )构建器负责将 SQL 解析成 SqlSource 对象。3.1.1 构造方法// XMLScriptBuilder.java /** * 当前 SQL 的 XNode 对象 */ private final XNode context; /** * 是否为动态 SQL */ private boolean isDynamic; /** * SQL 方法类型 */ private final Class? parameterType; /** * NodeNodeHandler 的映射 */ private final MapString, NodeHandler nodeHandlerMap new HashMap(); public XMLScriptBuilder(Configuration configuration, XNode context, Class? parameterType) { super(configuration); this.context context; this.parameterType parameterType; // 初始化 nodeHandlerMap 属性 initNodeHandlerMap(); }context属性当前 SQL 的 XNode 对象。例如select idgetUser resultTypeUserSELECT * FROM user WHERE id #{id}/select。isDynamic属性是否为动态 SQL 。parameterType属性SQL 方法类型。nodeHandlerMap属性NodeNodeHandler 的映射。在#initNodeHandlerMap()方法中我们可以看到如下代码// XMLScriptBuilder.java private void initNodeHandlerMap() { nodeHandlerMap.put(trim, new TrimHandler()); nodeHandlerMap.put(where, new WhereHandler()); nodeHandlerMap.put(set, new SetHandler()); nodeHandlerMap.put(foreach, new ForEachHandler()); nodeHandlerMap.put(if, new IfHandler()); nodeHandlerMap.put(choose, new ChooseHandler()); nodeHandlerMap.put(when, new IfHandler()); nodeHandlerMap.put(otherwise, new OtherwiseHandler()); nodeHandlerMap.put(bind, new BindHandler()); }我们可以看到nodeHandlerMap的 KEY 是熟悉的 MyBatis 动态 SQL 的节点。并且每个节点对应专属的一个 NodeHandler 实现类。关于 NodeHandler 类在 「3.2 NodeHandler」 中详细解析。3.1.2 parseScriptNode#parseScriptNode()方法负责解析 SQL 节点生成对应的 SqlSource 对象。代码如下// XMLScriptBuilder.java public SqlSource parseScriptNode() { // 1 解析 SQL MixedSqlNode rootSqlNode parseDynamicTags(context); // 2 创建 SqlSource 对象 SqlSource sqlSource; if (isDynamic) { sqlSource new DynamicSqlSource(configuration, rootSqlNode); } else { sqlSource new RawSqlSource(configuration, rootSqlNode, parameterType); } return sqlSource; }1处调用#parseDynamicTags(XNode node)方法解析 SQL 成 MixedSqlNode 对象。详细解析见 「3.1.3 parseDynamicTags」 。2处判断是否为动态 SQL 。如果是则创建 DynamicSqlSource 对象。如果否则创建 RawSqlSource 对象。3.1.3 parseDynamicTags#parseDynamicTags(XNode node)方法解析 SQL 成 MixedSqlNode 对象。代码如下// XMLScriptBuilder.java protected MixedSqlNode parseDynamicTags(XNode node) { // 1 创建 SqlNode 数组 ListSqlNode contents new ArrayList(); // 2 遍历 SQL 节点的所有子节点 NodeList children node.getNode().getChildNodes(); for (int i 0; i children.getLength(); i) { // 当前子节点 XNode child node.newXNode(children.item(i)); // 3 如果类型是 Node.CDATA_SECTION_NODE 或者 Node.TEXT_NODE 时 if (child.getNode().getNodeType() Node.CDATA_SECTION_NODE || child.getNode().getNodeType() Node.TEXT_NODE) { // 3.1 获得内容 String data child.getStringBody(); // 3.2 创建 TextSqlNode 对象 TextSqlNode textSqlNode new TextSqlNode(data); // 如果是动态的 TextSqlNode 对象 if (textSqlNode.isDynamic()) { // 添加到 contents 中 contents.add(textSqlNode); // 标记为动态 SQL isDynamic true; // 如果是非动态的 TextSqlNode 对象 } else { // 3.3 创建 StaticTextSqlNode 添加到 contents 中 contents.add(new StaticTextSqlNode(data)); } // 4 如果类型是 Node.ELEMENT_NODE } else if (child.getNode().getNodeType() Node.ELEMENT_NODE) { // issue #628 // 4.1 根据子节点的标签获得对应的 NodeHandler 对象 String nodeName child.getNode().getNodeName(); NodeHandler handler nodeHandlerMap.get(nodeName); if (handler null) { throw new BuilderException(Unknown element nodeName in SQL statement.); } // 4.2 执行 NodeHandler 处理 handler.handleNode(child, contents); // 4.3 标记为动态 SQL isDynamic true; } } // 5 创建 MixedSqlNode 对象 return new MixedSqlNode(contents); }1处创建 SqlNode 数组contents用于存储解析到的 SqlNode 节点们。2处遍历 SQL 节点的所有子节点。3处如果类型是Node.CDATA_SECTION_NODE或者Node.TEXT_NODE时3.1处获得节点的内容data。3.2处创建 TextSqlNode 对象textSqlNode。通过调用TextSqlNode#isDynamic()方法判断是否为动态 SQL 。如果是则添加到contents中并标记为动态 SQL 。否则3.3处创建 StaticTextSqlNode 添加到contents中。4处如果类型是Node.ELEMENT_NODE时4.1处根据子节点的标签获得对应的 NodeHandler 对象。4.2处执行 NodeHandler 处理。4.3处标记为动态 SQL 。5处所有解析到的 SqlNode 节点们创建成 MixedSqlNode 对象。3.2 NodeHandlerNodeHandler 在 XMLScriptBuilder 类中NodeHandler 是接口每个 XML 标签对应一个实现类。代码如下// XMLScriptBuilder.java private interface NodeHandler { /** * 处理 Node * * param nodeToHandle 要处理的 XNode 节点 * param targetContents 目标的 SqlNode 数组。实际上被处理的 XNode 节点会创建成对应的 SqlNode 对象添加到 targetContents 中 */ void handleNode(XNode nodeToHandle, ListSqlNode targetContents); }一共有 9 个实现类一一对应 9 个标签。如下图所示NodeHandler 实现类3.2.1 BindHandlerBindHandler 实现 NodeHandler 接口bind /标签的处理器。代码如下// XMLScriptBuilder.java private class BindHandler implements NodeHandler { public BindHandler() { // Prevent Synthetic Access } Override public void handleNode(XNode nodeToHandle, ListSqlNode targetContents) { // 解析 name、value 属性 final String name nodeToHandle.getStringAttribute(name); final String expression nodeToHandle.getStringAttribute(value); // 创建 VarDeclSqlNode 对象 final VarDeclSqlNode node new VarDeclSqlNode(name, expression); // 添加到 targetContents 中 targetContents.add(node); } }解析name、value属性并创建 VarDeclSqlNode 对象最后添加到targetContents中。3.2.2 TrimHandlerTrimHandler 实现 NodeHandler 接口trim /标签的处理器。代码如下// XMLScriptBuilder.java private class TrimHandler implements NodeHandler { public TrimHandler() { // Prevent Synthetic Access } Override public void handleNode(XNode nodeToHandle, ListSqlNode targetContents) { // 1 解析内部的 SQL 节点成 MixedSqlNode 对象 MixedSqlNode mixedSqlNode parseDynamicTags(nodeToHandle); // 2 获得 prefix、prefixOverrides、suffix、suffixOverrides 属性 String prefix nodeToHandle.getStringAttribute(prefix); String prefixOverrides nodeToHandle.getStringAttribute(prefixOverrides); String suffix nodeToHandle.getStringAttribute(suffix); String suffixOverrides nodeToHandle.getStringAttribute(suffixOverrides); // 3 创建 TrimSqlNode 对象 TrimSqlNode trim new TrimSqlNode(configuration, mixedSqlNode, prefix, prefixOverrides, suffix, suffixOverrides); // 4 添加到 targetContents 中 targetContents.add(trim); } }1处调用#parseDynamicTags(XNode node)方法解析内部的 SQL 节点成 MixedSqlNode 对象。2处获得prefix、prefixOverrides、suffix、suffixOverrides属性。3处创建 TrimSqlNode 对象。4处添加到targetContents中。3.2.3 WhereHandlerWhereHandler 实现 NodeHandler 接口where /标签的处理器。代码如下// XMLScriptBuilder.java private class WhereHandler implements NodeHandler { public WhereHandler() { // Prevent Synthetic Access } Override public void handleNode(XNode nodeToHandle, ListSqlNode targetContents) { // 解析内部的 SQL 节点成 MixedSqlNode 对象 MixedSqlNode mixedSqlNode parseDynamicTags(nodeToHandle); // 创建 WhereSqlNode 对象 WhereSqlNode where new WhereSqlNode(configuration, mixedSqlNode); // 添加到 targetContents 中 targetContents.add(where); } }从实现逻辑的思路上和 TrimHandler 是一个套路的。不过WhereHandler 创建的 WhereSqlNode 对象是 TrimSqlNode 的简化版两者可以对比下。3.2.4 SetHandlerSetHandler 实现 NodeHandler 接口set /标签的处理器。代码如下// XMLScriptBuilder.java private class SetHandler implements NodeHandler { public SetHandler() { // Prevent Synthetic Access } Override public void handleNode(XNode nodeToHandle, ListSqlNode targetContents) { // 解析内部的 SQL 节点成 MixedSqlNode 对象 MixedSqlNode mixedSqlNode parseDynamicTags(nodeToHandle); // 创建 SetSqlNode 对象 SetSqlNode set new SetSqlNode(configuration, mixedSqlNode); // 添加到 targetContents 中 targetContents.add(set); } }从实现逻辑的思路上和 TrimHandler 是一个套路的。不过SetHandler 创建的 SetSqlNode 对象是 TrimSqlNode 的简化版两者可以对比下。3.2.5 ForEachHandlerForEachHandler 实现 NodeHandler 接口foreach /标签的处理器。代码如下// XMLScriptBuilder.java private class ForEachHandler implements NodeHandler { public ForEachHandler() { // Prevent Synthetic Access } Override public void handleNode(XNode nodeToHandle, ListSqlNode targetContents) { // 解析内部的 SQL 节点成 MixedSqlNode 对象 MixedSqlNode mixedSqlNode parseDynamicTags(nodeToHandle); // 获得 collection、item、index、open、close、separator 属性 String collection nodeToHandle.getStringAttribute(collection); String item nodeToHandle.getStringAttribute(item); String index nodeToHandle.getStringAttribute(index); String open nodeToHandle.getStringAttribute(open); String close nodeToHandle.getStringAttribute(close); String separator nodeToHandle.getStringAttribute(separator); // 创建 ForEachSqlNode 对象 ForEachSqlNode forEachSqlNode new ForEachSqlNode(configuration, mixedSqlNode, collection, index, item, open, close, separator); // 添加到 targetContents 中 targetContents.add(forEachSqlNode); } }从实现逻辑的思路上和 TrimHandler 是一个套路的。当然也有差异点。例如ForEachHandler 创建的 ForEachSqlNode 对象。3.2.6 IfHandlerIfHandler 实现 NodeHandler 接口if /标签的处理器。代码如下// XMLScriptBuilder.java private class IfHandler implements NodeHandler { public IfHandler() { // Prevent Synthetic Access } Override public void handleNode(XNode nodeToHandle, ListSqlNode targetContents) { // 解析内部的 SQL 节点成 MixedSqlNode 对象 MixedSqlNode mixedSqlNode parseDynamicTags(nodeToHandle); // 获得 test 属性 String test nodeToHandle.getStringAttribute(test); // 创建 IfSqlNode 对象 IfSqlNode ifSqlNode new IfSqlNode(mixedSqlNode, test); // 添加到 targetContents 中 targetContents.add(ifSqlNode); } }从实现逻辑的思路上和 TrimHandler 是一个套路的。当然也有差异点。例如IfHandler 创建的 IfSqlNode 对象。3.2.7 OtherwiseHandlerOtherwiseHandler 实现 NodeHandler 接口otherwise /标签的处理器。代码如下// XMLScriptBuilder.java private class OtherwiseHandler implements NodeHandler { public OtherwiseHandler() { // Prevent Synthetic Access } Override public void handleNode(XNode nodeToHandle, ListSqlNode targetContents) { // 解析内部的 SQL 节点成 MixedSqlNode 对象 MixedSqlNode mixedSqlNode parseDynamicTags(nodeToHandle); // 添加到 targetContents 中 targetContents.add(mixedSqlNode); } }从实现逻辑的思路上和 TrimHandler 是一个套路的。不过OtherwiseHandler 是直接使用 MixedSqlNode 对象。3.2.8 ChooseHandlerChooseHandler 实现 NodeHandler 接口choose /标签的处理器。代码如下// XMLScriptBuilder.java private class ChooseHandler implements NodeHandler { public ChooseHandler() { // Prevent Synthetic Access } Override public void handleNode(XNode nodeToHandle, ListSqlNode targetContents) { // 1 获得 when / 和 otherwise / 的节点们 ListSqlNode whenSqlNodes new ArrayList(); ListSqlNode otherwiseSqlNodes new ArrayList(); handleWhenOtherwiseNodes(nodeToHandle, whenSqlNodes, otherwiseSqlNodes); // 2 获得 otherwise / 的节点 SqlNode defaultSqlNode getDefaultSqlNode(otherwiseSqlNodes); // 3 创建 ChooseSqlNode 对象 ChooseSqlNode chooseSqlNode new ChooseSqlNode(whenSqlNodes, defaultSqlNode); // 4 添加到 targetContents 中 targetContents.add(chooseSqlNode); } private void handleWhenOtherwiseNodes(XNode chooseSqlNode, ListSqlNode ifSqlNodes, ListSqlNode defaultSqlNodes) { ListXNode children chooseSqlNode.getChildren(); for (XNode child : children) { String nodeName child.getNode().getNodeName(); NodeHandler handler nodeHandlerMap.get(nodeName); if (handler instanceof IfHandler) { handler.handleNode(child, ifSqlNodes); } else if (handler instanceof OtherwiseHandler) { handler.handleNode(child, defaultSqlNodes); } } } private SqlNode getDefaultSqlNode(ListSqlNode defaultSqlNodes) { SqlNode defaultSqlNode null; if (defaultSqlNodes.size() 1) { defaultSqlNode defaultSqlNodes.get(0); } else if (defaultSqlNodes.size() 1) { throw new BuilderException(Too many default (otherwise) elements in choose statement.); } return defaultSqlNode; } }1处调用#handleWhenOtherwiseNodes(XNode chooseSqlNode, ListSqlNode ifSqlNodes, ListSqlNode defaultSqlNodes)方法获得when /和otherwise /的节点们。2处调用#getDefaultSqlNode(ListSqlNode defaultSqlNodes)方法获得otherwise /的节点。3处创建 ChooseSqlNode 对象。4处添加到targetContents中。3.3 SqlNodeorg.apache.ibatis.scripting.xmltags.SqlNodeSQL Node 接口每个 XML Node 会解析成对应的 SQL Node 对象。代码如下// SqlNode.java public interface SqlNode { /** * 应用当前 SQL Node 节点 * * param context 上下文 * return 当前 SQL Node 节点是否应用成功。 */ boolean apply(DynamicContext context); }关于 DynamicContext 类在 「4.1 DynamicContext」 中详细解析。3.3.1 VarDeclSqlNodeorg.apache.ibatis.scripting.xmltags.VarDeclSqlNode实现 SqlNode 接口bind /标签的 SqlNode 实现类。代码如下// VarDeclSqlNode.java public class VarDeclSqlNode implements SqlNode { /** * 名字 */ private final String name; /** * 表达式 */ private final String expression; public VarDeclSqlNode(String var, String exp) { name var; expression exp; } Override public boolean apply(DynamicContext context) { // 1 获得值 final Object value OgnlCache.getValue(expression, context.getBindings()); // 2 绑定到上下文 context.bind(name, value); return true; } }1