深入淺出Mybatis原始碼系列(三)---配置詳解之properties與environments(mybatis原始碼篇)
上篇文章《深入淺出Mybatis原始碼系列(二)---配置簡介(mybatis原始碼篇)》我們通過對mybatis原始碼的簡單分析,可看出,在mybatis配置檔案中,在configuration根節點下面,可配置properties、typeAliases、plugins、objectFactory、objectWrapperFactory、settings、environments、databaseIdProvider、typeHandlers、mappers這些節點。那麼本次,就會先介紹properties節點和environments節點。
為了讓大家能夠更好地閱讀mybatis原始碼,我先簡單的給大家示例一下properties的使用方法。
<configuration> <!-- 方法一: 從外部指定properties配置檔案, 除了使用resource屬性指定外,還可通過url屬性指定url <properties resource="dbConfig.properties"></properties> --> <!-- 方法二: 直接配置為xml --> <properties> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/test1"/> <property name="username" value="root"/> <property name="password" value="root"/> </properties>
那麼,我要是 兩種方法都同時用了,那麼哪種方法優先?
當以上兩種方法都xml配置優先, 外部指定properties配置其次。至於為什麼,接下來的原始碼分析會提到,請留意一下。
再看一下envirements元素節點的使用方法吧:
<environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <!-- 如果上面沒有指定資料庫配置的properties檔案,那麼此處可以這樣直接配置 <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/test1"/> <property name="username" value="root"/> <property name="password" value="root"/> --> <!-- 上面指定了資料庫配置檔案, 配置檔案裡面也是對應的這四個屬性 --> <property name="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </dataSource> </environment> <!-- 我再指定一個environment --> <environment id="test"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <!-- 與上面的url不一樣 --> <property name="url" value="jdbc:mysql://localhost:3306/demo"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource> </environment> </environments>
environments元素節點可以配置多個environment子節點, 怎麼理解呢?
假如我們系統的開發環境和正式環境所用的資料庫不一樣(這是肯定的), 那麼可以設定兩個environment, 兩個id分別對應開發環境(dev)和正式環境(final),那麼通過配置environments的default屬性就能選擇對應的environment了, 例如,我將environments的deault屬性的值配置為dev, 那麼就會選擇dev的environment。 至於這個是怎麼實現的, 下面原始碼就會講。
好啦,上面簡單給大家介紹了一下properties 和 environments 的配置, 接下來就正式開始看原始碼了:
上次我們說過mybatis 是通過XMLConfigBuilder這個類在解析mybatis配置檔案的,那麼本次就接著看看XMLConfigBuilder對於properties和environments的解析:
XMLConfigBuilder:
public class XMLConfigBuilder extends BaseBuilder {
private boolean parsed;
//xml解析器
private XPathParser parser;
private String environment;
//上次說到這個方法是在解析mybatis配置檔案中能配置的元素節點
//今天首先要看的就是properties節點和environments節點
private void parseConfiguration(XNode root) {
try {
//解析properties元素
propertiesElement(root.evalNode("properties")); //issue #117 read properties first
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
settingsElement(root.evalNode("settings"));
//解析environments元素
environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
//下面就看看解析properties的具體方法
private void propertiesElement(XNode context) throws Exception {
if (context != null) {
//將子節點的 name 以及value屬性set進properties物件
//這兒可以注意一下順序,xml配置優先, 外部指定properties配置其次
Properties defaults = context.getChildrenAsProperties();
//獲取properties節點上 resource屬性的值
String resource = context.getStringAttribute("resource");
//獲取properties節點上 url屬性的值, resource和url不能同時配置
String url = context.getStringAttribute("url");
if (resource != null && url != null) {
throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other.");
}
//把解析出的properties檔案set進Properties物件
if (resource != null) {
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
defaults.putAll(Resources.getUrlAsProperties(url));
}
//將configuration物件中已配置的Properties屬性與剛剛解析的融合
//configuration這個物件會裝載所解析mybatis配置檔案的所有節點元素,以後也會頻頻提到這個物件
//既然configuration物件用有一系列的get/set方法, 那是否就標誌著我們可以使用java程式碼直接配置?
//答案是肯定的, 不過使用配置檔案進行配置,優勢不言而喻
Properties vars = configuration.getVariables();
if (vars != null) {
defaults.putAll(vars);
}
//把裝有解析配置propertis物件set進解析器, 因為後面可能會用到
parser.setVariables(defaults);
//set進configuration物件
configuration.setVariables(defaults);
}
}
//下面再看看解析enviroments元素節點的方法
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
if (environment == null) {
//解析environments節點的default屬性的值
//例如: <environments default="development">
environment = context.getStringAttribute("default");
}
//遞迴解析environments子節點
for (XNode child : context.getChildren()) {
//<environment id="development">, 只有enviroment節點有id屬性,那麼這個屬性有何作用?
//environments 節點下可以擁有多個 environment子節點
//類似於這樣: <environments default="development"><environment id="development">...</environment><environment id="test">...</environments>
//意思就是我們可以對應多個環境,比如開發環境,測試環境等, 由environments的default屬性去選擇對應的enviroment
String id = child.getStringAttribute("id");
//isSpecial就是根據由environments的default屬性去選擇對應的enviroment
if (isSpecifiedEnvironment(id)) {
//事務, mybatis有兩種:JDBC 和 MANAGED, 配置為JDBC則直接使用JDBC的事務,配置為MANAGED則是將事務託管給容器,
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
//enviroment節點下面就是dataSource節點了,解析dataSource節點(下面會貼出解析dataSource的具體方法)
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
DataSource dataSource = dsFactory.getDataSource();
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
//老規矩,會將dataSource設定進configuration物件
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
//下面看看dataSource的解析方法
private DataSourceFactory dataSourceElement(XNode context) throws Exception {
if (context != null) {
//dataSource的連線池
String type = context.getStringAttribute("type");
//子節點 name, value屬性set進一個properties物件
Properties props = context.getChildrenAsProperties();
//建立dataSourceFactory
DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();
factory.setProperties(props);
return factory;
}
throw new BuilderException("Environment declaration requires a DataSourceFactory.");
}
}
通過以上對mybatis原始碼的解讀,相信大家對mybatis的配置又有了一個深入的認識。
還有一個問題, 上面我們看到,在配置dataSource的時候使用了 ${driver} 這種表示式, 這種形式是怎麼解析的?其實,是通過PropertyParser這個類解析:
PropertyParser:
/**
* 這個類解析${}這種形式的表示式
*/
public class PropertyParser {
public static String parse(String string, Properties variables) {
VariableTokenHandler handler = new VariableTokenHandler(variables);
GenericTokenParser parser = new GenericTokenParser("${", "}", handler);
return parser.parse(string);
}
private static class VariableTokenHandler implements TokenHandler {
private Properties variables;
public VariableTokenHandler(Properties variables) {
this.variables = variables;
}
public String handleToken(String content) {
if (variables != null && variables.containsKey(content)) {
return variables.getProperty(content);
}
return "${" + content + "}";
}
}
}
好啦,以上就是對於properties 和 environments元素節點的分析,比較重要的都在對於原始碼的註釋中標出。本次文章到此結束,接下來的文章會繼續分析其他節點的配置。