2021SC@SDUSC
在上篇文章中,我们从Admin类询问阶段processInstall方法入手,分析到了ConnectionPropertiesPatcher类的patch方法、patch方法中的getConnectionProperties方法、getConnectionProperties方法中的Arr类;从Admin类跨越到了同包下的ConnectionPropertiesPatcher类,不过也都处在我的分析任务内,所以直接就一并对他们进行详细分析了。
在这篇文章中,我们将继续对上篇文章中未分析完的方法进行详细分析。
getConnectionProperties
// ConnectionPropertiesPatcher.java
public static ConnectionProperties getConnectionProperties(File conf) throws Exception {
// 新建一个ConnectionProperties对象
ConnectionProperties props = new ConnectionProperties();
// 通过传入的File实例获取Document实例
Document doc = getDocument(conf);
Attr attr = getConnectionProperties(doc);
String[] tokens = attr.getValue().split(",");
loadProperties(tokens, props);
return props;
}
在上篇文章中,我们知道Attr attr = getConnectionProperties(doc)方法会根据获得的Document实例从当前元素中获取到了节点的value属性,赋给Attr的实例对象attr。
接下来我们分析loadProperties(tokens, props);这段代码
// ConnectionPropertiesPatcher.java
private static void loadProperties(String[] tokens, ConnectionProperties connectionProperties) {
//ConnectionProperties来自openmeetings-util
String prop;
//遍历每个token,token即为获取到的节点属性
for (int i = 0; i < tokens.length; ++i) {
prop = getPropFromPersistence(tokens, i, DRIVER_PREFIX);
if (prop != null) {
connectionProperties.setDriver(prop);
}
prop = getPropFromPersistence(tokens, i, USER_PREFIX);
if (prop != null) {
connectionProperties.setLogin(prop);
}
prop = getPropFromPersistence(tokens, i, PASS_PREFIX);
if (prop != null) {
connectionProperties.setPassword(prop);
}
prop = getPropFromPersistence(tokens, i, URL_PREFIX);
if (prop != null) {
try {
//will try to "guess" dbType
String[] parts = prop.split(":");
connectionProperties.setDbType("sqlserver".equals(parts[1]) ? DbType.mssql : DbType.valueOf(parts[1]));
} catch (Exception e) {
//ignore
}
connectionProperties.setURL(prop);
}
}
}
看到prop = getPropFromPersistence(tokens, i, DRIVER_PREFIX);这行代码,我们来分析一下getPropFromPersistence方法
//ConnectionPropertiesPatcher.java
protected static String getPropFromPersistence(String[] tokens, int idx, String name){
String prop = tokens[idx].trim();
//startsWith() 方法用于检测字符串是否以指定的前缀开始。
if (prop.startsWith(name)) {
// From "Username=root" getting only "root"
// substring() 方法返回字符串的子字符串。
// indexOf(int ch): 返回指定字符在字符串中第一次出现处的索引,如果此字符串中没有这样的字符,则返回 -1。
// 在这里,返回“=”第一次出现的索引,并+1,作为返回子字符串的开始索引
return prop.substring(prop.indexOf('=') + 1);
}
return null;
}
所以loadProperties方法的作用就是加载属性。遍历属性列表,依次取出一些目标属性对应的值,赋给prop。prop若不为空,则将对应的值设置为connectionProperties的驱动。
这样getConnectionProperties就结束了。可以回顾一下这个方法做了什么:首先新建了ConnectionProperties对象,然后通过传入的File实例获取Document实例,接着根据该Document实例获取属性数组,对属性数组进行解析,分别加载进对应的connectionProperties驱动。
Patch
// ConnectionPropertiesPatcher.java
public static ConnectionProperties patch(String dbType, String host, String port, String db, String user, String pass) throws Exception {
ConnectionProperties props = getConnectionProperties(OmFileHelper.getPersistence(dbType));
props.setLogin(user);
props.setPassword(pass);
ConnectionPropertiesPatcher patcher = getPatcher(props);
props.setURL(patcher.getUrl(props.getURL(), host, port, db));
patch(props);
return props;
}
我们从getConnectionProperties方法返回后,接下来通过setLogin、setPassword等方法设置该props的用户名和密码等属性,然后我们看一下patch(props)这个方法
// ConnectionPropertiesPatcher.java
public static void patch(ConnectionProperties props) throws Exception {
ConnectionPropertiesPatcher patcher = getPatcher(props);
Document doc = getDocument(OmFileHelper.getPersistence(props.getDbType()));
Attr attr = getConnectionProperties(doc);
String[] tokens = attr.getValue().split(",");
patcher.patchAttribute(tokens);
attr.setValue(StringUtils.join(tokens, ","));
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer();
DOMSource source = new DOMSource(doc);
transformer.transform(source, new StreamResult(OmFileHelper.getPersistence().getCanonicalPath())); //this constructor is used to avoid transforming path to URI
}
前面几行代码和我们在getConnectionProperties方法(本篇文章第一部分)中分析的作用是类似的,我们主要关注下面Transformer的部分
TransformerFactory实例可用于创建Transformer和Templates对象。
Transformer这个抽象类的实例可以将源树转换为结果树。可以使用该类实例处理来自各种来源的XML,并将转换输出写入各种接收器。该类的对象不能用于并发运行的多个线程中。不同的Transformer可以由不同的线程同时使用。一个Transformer可以被多次使用。在转换中保留参数和输出属性。
transform方法接收2个参数,一个是Source类实例,一个是Result类实例。该方法将XML源转换为结果。特定的转换行为由实例化Transformer时的TransformerFactory的设置以及对Transformer实例的任何修改决定。一个空的Source表示为一个由DocumentBuilder.newDocument()构造的空文档。转换空源的结果取决于转换行为;它并不总是一个空的结果。
在我们的代码中传入的第一个实参是DOMSource对象,DOMSource是文档对象模型(DOM)树形式的转换源树的持有者。第二个实参是StreamResult对象,StreamResult是转换结果的持有者,转换结果可以是XML、纯文本、HTML或其他形式的标记。在我们的代码中,我们使用的是StreamResult(String systemId)构造函数,用于从url中构造一个StreamResult对象。
我们分析一下OmFileHelper.getPersistence().getCanonicalPath():
OmFileHelper来自openmeeting-util;getPersistence方法最后得到的是路径为"classes/META-INF/persistence.xml"(默认路径)的File类文件;getCanonicalPath方法会返回抽象路径名的规范路径名字符串。 规范路径名是绝对路径名,并且是惟一的。规范路径名的准确定义与系统有关。如有必要,此方法首先将路径名转换成绝对路径名,这与调用 getAbsolutePath() 方法的效果一样,然后用与系统相关的方式将它映射到其惟一路径名。这通常涉及到从路径名中移除多余的名称(比如 “.” 和 “…”)、分析符号连接(对于 UNIX 平台),以及将驱动器名转换成标准大小写形式(对于 Microsoft Windows 平台)。
所以OmFileHelper.getPersistence().getCanonicalPath()这段代码最终得到的是一个规范路径名字符串,作为参数传入StreamResult构造器中,StreamResult构造器根据该url构造一个StreamResult对象,作为参数传入transform方法中。tranform方法负责将源转换为结果,保存在StreamResult中。
小小总结一下patch方法,首先获取props,设置其属性,获取该props对应的patcher,执行patch方法(另一个);patch方法从DOM中获取属性数组,解析属性并设置,接着通过transform方法将XML源转换为结果。最后返回一个已附带上属性的connectionProperties实例。
总结
本篇文章对上篇文章中未分析完的代码进行补充分析,分析了getConnectionProperties和patch两个方法。接下来的文章,就可以回到processInstall方法体内再对其他代码进行分析了。