Mybaits source code analysis (two) configuration analysis

Mybaits source code analysis (two) configuration analysis

   Overview:

   From the previous article, we know that mybaits is building SqlsessionFacotry, actually calling a series of BaseBuilder subclasses (with configuration attributes) to complete the parsing of various xml configurations, but in fact the parsing work is done by XPathParser and packaging dom nodes Xnode of node completes the analysis work. Below is part of the code of 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;
  }

The general process of configuring and parsing the source code is not repeated, see the previous part for details

Mybaits source code analysis (1) Core execution process: https://blog.csdn.net/shuixiou1/article/details/113531162

1. Detailed explanation of XPathParser and Xnode

1. Detailed explanation of XPathParser

1) Member variables and constructors of 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 parsing node

 Whether it is parsing node elements or list elements, XPathParser ultimately calls an evaluate function. It has three parameters: expression, query parent node, node type

public List<MyXNode> evalNodes(String expression) {
		return evalNodes(document, expression);
	}

	public List<MyXNode> evalNodes(Object root, String expression) {
		List<MyXNode> xnodes = new ArrayList<MyXNode>();
		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. Detailed XNode

1) Member variables and constructors

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 pre-parse

In the constructor, we parse the body and properties of Xnode in advance, so that when needed later, there is no need to perform multiple operations.

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;
  }

Focus on the above: String value = PropertyParser.parse(attribute.getNodeValue(), variables) This code. This is used to process the ${} attribute placeholder.

In addition, let’s talk about the properties of Properties variables. In Xpathparser, it is passed in during the initial construction of Configure and can be set to Xpathparser. When XpathParser creates an Xnode node, the variables properties must be passed in. The role of this Properties is to process parameters. For placeholder purposes, for example, we use ${username}, and if this username is in our variables, it will be parsed into the value of username in the variables.

3. Detailed explanation of PropertyParser

The PropertyParser.parse method actually calls a TokenParser class. This class can pass in placeholder prefixes, placeholder suffixes, and the Hanlder interface used to process content.

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;
	}

The parsing logic is also very simple, that is, constantly query the prefix, find the prefix and look for the suffix, and the content is thrown to hanlder for processing. If there is only a prefix without a suffix, this attribute placeholder is invalid.

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();
	}

The MTokenHandler interface is to process the returned value string on the input string, and we need to query from the variable, that is, the variable is used as the constructor parameter, and the handler method returns when the variable is found, and returns null if it is not found.

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;
	}
}

Two, analysis test

The first part analyzes the functions of xpathparser and xnode. Next, we simulate the parsing process of XMLConfigBuilder, intercept part of the parsing work, and use XpathParse to simulate the parsing process.

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、解析环境配置
	  <environments default="development">
		<environment id="development">
			<transactionManager type="JDBC"></transactionManager>
			<dataSource type="POOLED">
				<property name="driver" value="${db.driver}" />
				<property name="url" value="${db.url}" />
				<property name="username" value="${db.username}" />
				<property name="password" value="${db.password}" />
			</dataSource>
		</environment>
		</environments>
	 */
	@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<String, Class<?>> TYPE_ALIASES = new HashMap<String, Class<?>>();
		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();
			}
		}
	}
	
	
}

Attached:

Mybaits uses the construction mode (the construction mode of internal classes) when parsing and configuring objects such as ResultMapping and the construction of many other complex objects. The following example demonstrates.

/**
 * 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 List<String> items;

		@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<String> list = Arrays.asList(arr);
					builderObj.items = list;
				}
			}

			public BuilderObj build() {
				return builderObj;
			}
		}
	}
}

 

end !

 

 

 

 

Guess you like

Origin blog.csdn.net/shuixiou1/article/details/113573036