初步
本日小伴侣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值。他的理会进程如下: