初步
本日小伴侣X在开拓进程中碰着了一个bug,并给mybatis提了一个ISSUE:throw ReflectionException when using #{array.length}
大抵说明下该问题,在mapper.xml中,利用#{array.length}来获取数组的长度时,会报出ReflectionException。 代码:
public List<QuestionnaireSent> selectByIds(Integer[] ids) { return commonSession.selectList("QuestionnaireSentMapper.selectByIds", ImmutableMap.of("ids", ids)); }
对应的xml:
<select id="selectByIds"> SELECT * FROM t_questionnaire <if test="ids.length > 0"> WHERE id in <foreach collection="ids" open="(" separator="," close=")" item="id">#{id} </foreach> </if> LIMIT #{ids.length} </select>
下面团结源码对该问题举办阐明
阐明
xml中有两处利用了length,那么这个报错毕竟是哪个引起的呢?
实验把test条件去掉,limit保存后,依然报错。那么可定位出报错是#{ids.length}导致的。
由此引出了两个问题:
带着这两个问题,我们进入源码
在类org.apache.ibatis.scripting.xmltags.XMLScriptBuilder中
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()); } protected MixedSqlNode parseDynamicTags(XNode node) { List<SqlNode> contents = new ArrayList<SqlNode>(); NodeList children = node.getNode().getChildNodes(); for (int i = 0; i < children.getLength(); i++) { XNode child = node.newXNode(children.item(i)); if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) { String data = child.getStringBody(""); TextSqlNode textSqlNode = new TextSqlNode(data); if (textSqlNode.isDynamic()) { contents.add(textSqlNode); isDynamic = true; } else { contents.add(new StaticTextSqlNode(data)); } } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628 String nodeName = child.getNode().getNodeName(); NodeHandler handler = nodeHandlerMap.get(nodeName); if (handler == null) { throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement."); } handler.handleNode(child, contents); isDynamic = true; } } return new MixedSqlNode(contents); }
在每个对应的Handler中,有相应的处理惩罚逻辑。
以IfHandler为例:
private class IfHandler implements NodeHandler { public IfHandler() { // Prevent Synthetic Access } @Override public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) { MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle); String test = nodeToHandle.getStringAttribute("test"); IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test); targetContents.add(ifSqlNode); } }
在这里主要生成了IfSqlNode,理会在相应的类中
public class IfSqlNode implements SqlNode { private final ExpressionEvaluator evaluator; private final String test; private final SqlNode contents; public IfSqlNode(SqlNode contents, String test) { this.test = test; this.contents = contents; this.evaluator = new ExpressionEvaluator(); } @Override public boolean apply(DynamicContext context) { // OGNL执行test语句 if (evaluator.evaluateBoolean(test, context.getBindings())) { contents.apply(context); return true; } return false; } }
ExpressionEvaluator利用的是OGNL表达式来运算的。
再举一个高级的例子:ForEachSqlNode,个中包罗对数组和Collection以及Map的理会,焦点是通过OGNL获取对应的迭代器:
final Iterable<?> iterable = evaluator.evaluateIterable(collectionExpression, bindings);
public Iterable<?> evaluateIterable(String expression, Object parameterObject) { Object value = OgnlCache.getValue(expression, parameterObject); if (value == null) { throw new BuilderException("The expression '" + expression + "' evaluated to a null value."); } if (value instanceof Iterable) { return (Iterable<?>) value; } if (value.getClass().isArray()) { // the array may be primitive, so Arrays.asList() may throw // a ClassCastException (issue 209). Do the work manually // Curse primitives! :) (JGB) int size = Array.getLength(value); List<Object> answer = new ArrayList<Object>(); // 数组为何要这样处理惩罚?参考跋文1 for (int i = 0; i < size; i++) { Object o = Array.get(value, i); answer.add(o); } return answer; } if (value instanceof Map) { return ((Map) value).entrySet(); } throw new BuilderException("Error evaluating expression '" + expression + "'. Return value (" + value + ") was not iterable."); }
中间有个有意思的注释,参考跋文1.
首先需要明晰:
对付xml标签,个中的表达式也是利用的${}的理会方法,利用OGNL表达式来理会。
对付参数标志符理会,mybatis利用的是本身设计的理会器,利用反射机制获取各类属性。
以#{bean.property}为例,利用反射取到bean的属性property值。他的理会进程如下: