本文共 10403 字,大约阅读时间需要 34 分钟。
mybaits源码分析(二) 配置解析
概述:
从上篇我们知道,mybaits在构建SqlsessionFacotry,实际是调用一系列的BaseBuilder的子类(内有configuration属性),来完成各种xml配置的解析,而实际上解析工作,是由XPathParser和包装dom节点node的Xnode完成解析工作。下面是XMLConfigBuilder的部分代码。
private XPathParser parser; // xpath解析器 public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) { this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props); } private XMLConfigBuilder(XPathParser parser, String environment, Properties props) { super(new Configuration()); ErrorContext.instance().resource("SQL Mapper Configuration"); this.configuration.setVariables(props); this.parsed = false; this.environment = environment; this.parser = parser; }
配置解析源码过程大致过程不在复述,详见前篇
mybaits源码分析(一) 核心执行流程 :
一、XPathParser和Xnode的详解
1、XPathParser的详解
1) XPathParser的成员变量和构造函数
private Document document; // 顶级document节点 private boolean validation; private EntityResolver entityResolver; private Properties variables; // 额外的props private XPath xpath; // 原生Xpath解析 private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) { this.validation = validation; this.entityResolver = entityResolver; this.variables = variables; XPathFactory factory = XPathFactory.newInstance(); this.xpath = factory.newXPath(); } public MyXPathParser(String xml) { // 注入属性 commonConstructor(false, null, null); // dom解析出document this.document = createDocument(new InputSource(new StringReader(xml))); }
2) XPathParser解析节点
不管是解析节点元素、还是列表元素,最终XPathParser调用的是一个evaluate的函数。 它有三个参数分别为:表达式、查询的父节点、节点的类型
public ListevalNodes(String expression) { return evalNodes(document, expression); } public List evalNodes(Object root, String expression) { List xnodes = new ArrayList (); NodeList nodes = (NodeList) evaluate(expression, root, XPathConstants.NODESET); for (int i = 0; i < nodes.getLength(); i++) { xnodes.add(new MyXNode(this, nodes.item(i), variables)); } return xnodes; } public MyXNode evalNode(String expression) { return evalNode(document, expression); } public MyXNode evalNode(Object root, String expression) { Node node = (Node) evaluate(expression, root, XPathConstants.NODE); if (node == null) { return null; } return new MyXNode(this, node, variables); } private Object evaluate(String expression, Object root, QName returnType) { try { return xpath.evaluate(expression, root, returnType); } catch (Exception e) { throw new BuilderException("Error evaluating XPath. Cause: " + e, e); } }
2、XNode详解
1) 成员变量和构造函数
public class MyXNode { private Node node; // 原生dom节点 private String name; // node名称 private String body; // 从node预解析出来的dom的text private Properties attributes; // 从node预解析出来属性 private Properties variables; // 扩展props private MyXPathParser xpathParser; // xpathParser引用 (一个Builder实例共享一个XpathParser,这样Xnode节点一直可根据顶级节点扩展解析额外的信息) public MyXNode(MyXPathParser xpathParser, Node node, Properties variables) { this.xpathParser = xpathParser; this.node = node; this.name = node.getNodeName(); this.variables = variables; this.attributes = parseAttributes(node); this.body = parseBody(node); }
2) Xnode预解析
在构造函数中,我们对Xnode的body和属性预先进行了解析,这样后面需要的时候,就不需要多次进行操作。
private Properties parseAttributes(Node n) { Properties attributes = new Properties(); NamedNodeMap attributeNodes = n.getAttributes(); // 属性map if (attributeNodes != null) { for (int i = 0; i < attributeNodes.getLength(); i++) { Node attribute = attributeNodes.item(i); // 对属性的值占位符做了处理,比如:${username} String value = PropertyParser.parse(attribute.getNodeValue(), variables); attributes.put(attribute.getNodeName(), value); // 解析后添加到props } } return attributes; } private String parseBody(Node node) { String data = getBodyData(node); if (data == null) { NodeList children = node.getChildNodes(); // 子节点 for (int i = 0; i < children.getLength(); i++) { Node child = children.item(i); data = getBodyData(child); // text节点处理,并且也做了占位符处理 if (data != null) break; } } return data; }
上面重点看: String value = PropertyParser.parse(attribute.getNodeValue(), variables) 这个代码。这个是处理${}属性占位符用的。
另外说下Properties variables这个属性,我们在Xpathparser中是由Configure中初始构建时传入,并且可以设置到Xpathparser中,而XpathParser创建Xnode节点时,一定会传入variables属性,这个Properties的作用就是处理参数占位用途的,比如我们用了${username},而这个username如果在我们的variables中有,那么就会被解析成username在variables中的值。
3、PropertyParser详解
PropertyParser.parse方法,实际上是调用一个TokenParser的类,这个类可以传入占位前缀、占位后缀,以及用来处理内容的Hanlder接口。
class PropertyParsers { public static String parse(String string, Properties variables) { MTokenHandler handler = new MVariableTokenHandler(variables); MTokenParser parser = new MTokenParser("${", "}", handler); return parser.parse(string); }}class MTokenParser { private String beforeToken; // token前缀 private String endToken; // token后缀 private MTokenHandler mTokenHandler; // handler public MTokenParser(String beforeToken, String endToken, MTokenHandler mTokenHandler) { super(); this.beforeToken = beforeToken; this.endToken = endToken; this.mTokenHandler = mTokenHandler; }
解析逻辑也很简单,就是不停查询前缀,找到前缀就找后缀,内容丢给hanlder处理,如果只有前缀没有后缀此属性占位符就是无效的。
public String parse(String content) { StringBuilder builder = new StringBuilder(); if (content != null && content.length() > 0) { char[] src = content.toCharArray(); int offset = 0; int start = content.indexOf(beforeToken, offset); while (start > -1) { if (start > 0 && src[start - 1] == '\\') { builder.append(src, offset, start - offset - 1); offset = start + beforeToken.length(); } else { int end = content.indexOf(endToken, start); if (end == -1) { // 没有直接添加全部 builder.append(src, offset, src.length - offset); offset = src.length; } else { // 常规遍历 builder.append(src, offset, start - offset); offset = start + beforeToken.length(); String str = new String(src, offset, end - offset); builder.append(mTokenHandler.handler(str)); offset = end + endToken.length(); } } start = content.indexOf(beforeToken, offset); } if (offset < src.length) { builder.append(src, offset, src.length - offset); } } return builder.toString(); }
MTokenHandler接口,就是对输入的string处理返回值string,而我们需要从variables中查询,就是variables作为构造函数参数,handler方法到variables找到就返回,没找到返回null。
class MVariableTokenHandler implements MTokenHandler { private Properties variables; public MVariableTokenHandler(Properties properties) { super(); this.variables = properties; } @Override public String handler(String content) { if (variables != null && variables.containsKey(content)) { return variables.getProperty(content); } return null; }}
二、解析测试
第一部分对xpathparser和xnode的功能做了分析,下面我们模拟XMLConfigBuilder的解析过程,截取部分解析工作,用XpathParse进行模拟测试解析流程。
public class XPathTest { private static MyXPathParser myXPathParser; static { try { InputStream in = Resources.getResourceAsStream("custom/sqlMapConfig2.xml"); myXPathParser = new MyXPathParser(in); } catch (IOException e) { e.printStackTrace(); } } /** * 1、解析properties的node */ @Test public void test1() throws IOException { MyXNode root = myXPathParser.evalNode("/configuration"); MyXNode propNode = root.evalNode("properties"); Properties defaults = propNode.getChildrenAsProperties(); String resource = propNode.getStringAttribute("resource"); defaults.putAll(Resources.getResourceAsProperties(resource)); System.out.println(defaults); } /** * 2、解析环境配置*/ @Test public void test2() throws IOException, InstantiationException, IllegalAccessException { MyXNode root = myXPathParser.evalNode("/configuration"); MyXNode propNode = root.evalNode("properties"); Properties defaults = propNode.getChildrenAsProperties(); String resource = propNode.getStringAttribute("resource"); defaults.putAll(Resources.getResourceAsProperties(resource)); // 将解析的属性对设置到XpathParser中,这样此节点内部的解析会把${}参数替换成props的对应值。 myXPathParser.setVariables(defaults); // 别名隐射- 下面要用 Map > TYPE_ALIASES = new HashMap >(); TYPE_ALIASES.put("POOLED", PooledDataSourceFactory.class); MyXNode environments = root.evalNode("environments"); String systemEnviroment = ""; // 假设系统环境,如果没有设置,就是取environments的default属性 systemEnviroment = environments.getStringAttribute("default"); for (MyXNode child : environments.getChildren()) { String id = child.getStringAttribute("id"); if (systemEnviroment.equals(id)) { // 系统配置的等于此子环境配置 //DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource")); // 开始解析datasource节点 MyXNode datasourceNode = child.evalNode("dataSource"); String type = datasourceNode.getStringAttribute("type"); DataSourceFactory factory = (DataSourceFactory) TYPE_ALIASES.get(type).newInstance(); Properties properties = datasourceNode.getChildrenAsProperties(); factory.setProperties(properties); // System.out.println(properties); DataSource dataSource = factory.getDataSource(); } } } }
另附:
mybaits在解析配置成ResultMapping等对象的时候,以及其他很多复杂对象的构建,都用到了构建模式(内部类的构建模式),下面一个范例进行演示。
/** * mybaits框架ResultMapping等类的构建模式的演示 */public class BuilderDemo { public static void main(String[] args) { BuilderObj.Builder builder = new BuilderObj.Builder("id", "name"); builder.setVar1("var1"); builder.setVar2("var2"); BuilderObj obj = builder.build(); System.out.println(obj); } public static class BuilderObj { private String name; private String id; private String var1; private String var2; private Listitems; @Override public String toString() { return "BuilderObj [name=" + name + ", id=" + id + ", var1=" + var1 + ", var2=" + var2 + ", items=" + items + "]"; } public static class Builder { private BuilderObj builderObj = new BuilderObj(); public Builder(String id) { builderObj.id = id; } public Builder(String id, String name) { this(id); builderObj.name = name; } public void setVar1(String var) { builderObj.var1 = var; } public void setVar2(String var) { builderObj.var2 = var; } public void setItems(String[] arr) { if (arr != null) { List list = Arrays.asList(arr); builderObj.items = list; } } public BuilderObj build() { return builderObj; } } }}
end !
转载地址:http://druni.baihongyu.com/