一、引言
可重用性是软件开发的目标之一,可重用性表现为可扩展性、可插入性和灵活性。本文通过对实例层层的分析,讨论基于Java多线程技术的可重用性软件框架的实现过程。
首先,假设目录(file1/in)下有很多文件,要求把以“A”开头的文件移动到目录(file1/ctlA)、以“B”开头的文件移动到目录(file1/ctlB)、其余的文件移动到目录(file1/out),同时,开头的字符串和移动的目录需要灵活配置。
在Java开发中,常利用XML对系统进行配置,使程序更加灵活,下面是config.xml配置文件:
<?xml version="1.0" encoding="gb2312"?>
<Config>
<welcome>welcome!</welcome>
<ThreadService">
<FilePath in_path="file1/in" out_path="file1/out">
<Ctl ctl_val="A" ctl_path="file1/ctlA" />
<Ctl ctl_val="B" ctl_path="file1/ctlB" />
</FilePath>
</ThreadService>
</Config>
这个XML配置文件中的in_path、out_path、ctl_val和ctl_path非常灵活地定义了各个变量,下面Java程序就从这个配置文件读取参数,实现需求。核心代码如下:
public class Service1 {
public static void main(String[] args) {
String configFile = "config.xml";
…
try {
factory = DocumentBuilderFactory.newInstance();
builder = factory.newDocumentBuilder();
xmlDoc = builder.parse(configFile);
} catch (Exception e) {
}
serviceList = xmlDoc.getElementsByTagName("ThreadService");
cfg = (Element) serviceList.item(0);
try {
Element filePathTag = (Element) cfg
.getElementsByTagName("FilePath").item(0);
inPath = filePathTag.getAttribute("in_path");
outPath = filePathTag.getAttribute("out_path");
NodeList list = filePathTag.getElementsByTagName("Ctl");
ctlNum = list.getLength();
ctlVal = new String[ctlNum];
ctlPath = new String[ctlNum];
for (int i = 0; i < ctlNum; i++) {
ctlVal[i] = ((Element) list.item(i)).getAttribute("ctl_val");
ctlPath[i] = ((Element) list.item(i)).getAttribute("ctl_path");
}
} catch (Exception e) {
}
…
while (true) {
File inPathFolder = new File(inPath);
msgList = inPathFolder.listFiles();
for (int i = 0; i < msgList.length; i++) {
String msgName = msgList[i].getName();
tmpPath = outPath;
for (int j = 0; j < ctlNum; j++) {
if (msgName.startsWith(ctlVal[j])) {
tmpPath = ctlPath[j];
}
}
objPath = addSep(tmpPath) + msgList[i].getName();
// addSep(tmpPath) 在tmpPath末尾添加separatorChar,“/”或“\”
pathFile = new File(objPath);
try {
pathFile.delete();
msgList[i].renameTo(pathFile);
System.out.println("File " + objPath + "\n");
} catch (Exception e) {
}
}
}
}
}
类Service1使用dom读XML配置文件获取参数,当需求中开始字符或移动目录改变时,只需要修改XML配置文件,而不需要修改Java程序。可见,这个程序已经具备了一定的灵活性。
二、线程的引入
Service1已经具备了一定的灵活性,但对于多个目录的文件按文件名的开头字符串进行分发,就比较麻烦。这种需求描述如下:目录(file1/in)下有很多文件,要求把以“A”开头的文件移动到目录(file1/ctlA)、以“B”开头的文件移动到目录(file1/ctlB)、其余的文件移动到目录(file1/out)。目录(file2/in)下有很多文件,要求把以“A”开头的文件移动到目录(file2/ctlA)、以“B”开头的文件移动到目录(file2/ctlB)、其余的文件移动到目录(file2/out)。
针对这样的需求,需要准备两个config.xml文件、并启用两个Service1进程。这样既不方便,又消耗大量资源。Java中的线程开销比进程小,而且线程个数的定义、线程的启动和停止也非常灵活。所以,把Java多线程技术引入框架,以提高程序的可扩展性。
把Service1分为两个部分,一部分用于读取config.xml配置信息,并管理线程,另外一部分用于定义线程。同时,config.xml文件也做简单修改,代码如下:
<?xml version="1.0" encoding="gb2312"?>
<Config>
<welcome>welcome!</welcome>
<ThreadService class="org.run.Service2">
<FilePath in_path="file1/in" out_path="file1/out">
<Ctl ctl_val="A" ctl_path="file1/ctlA" />
<Ctl ctl_val="B" ctl_path="file1/ctlB" />
</FilePath>
</ThreadService>
<ThreadService class="org.run.Service2">
<FilePath in_path="file2/in" out_path="file2/out">
<Ctl ctl_val="A" ctl_path="file2/ctlA" />
<Ctl ctl_val="B" ctl_path="file2/ctlB" />
</FilePath>
</ThreadService>
</Config>
修改后的线程Service2的核心代码如下:
public class Service2 extends Thread {
…
public void initial(Element cfgInfo) throws Exception {
this.cfg = cfgInfo;
try {
initParameter(); // 读取config.xml参数
} catch (Exception e) {
throw e;
}
}
public void run() {
try {
bizService();
} catch (Exception e) {
}
}
protected void bizService() throws Exception {
…
while (true) {
try {
File inPathFolder = new File(inPath);
msgList = inPathFolder.listFiles();
} catch (Exception e) {
continue;
}
for (int i = 0; i < msgList.length; i++) {
tmpPath = ctlFilePath(msgList[i].getName());
// ctlFilePath返回该文件待移动的目录
objPath = addSep(tmpPath) + msgList[i].getName();
pathFile = new File(objPath);
try {
pathFile.delete();
msgList[i].renameTo(pathFile);
System.out.println("ctl path File " + objPath + "\n");
} catch (Exception e) {
}
}
}
}
}
管理线程的类Control
public class Control {
public static void main(String[] args) {
String configFile = "config.xml";
…
try {
factory = DocumentBuilderFactory.newInstance();
builder = factory.newDocumentBuilder();
xmlDoc = builder.parse(configFile);
} catch (Exception e) {
}
serviceList = xmlDoc.getElementsByTagName("ThreadService");
for (int i = 0; i < serviceList.getLength(); i++) {
serviceCfg = (Element) serviceList.item(i);
try {
Service3 service = (Service3) Class.forName(
serviceCfg.getAttribute("class")).newInstance();
service.setName("service");
service.initial(serviceCfg);
service.start();
} catch (Exception e) {
}
}
}
}
框架的主要变化在于,将Service定义成线程形式,然后通过定义的Control类读取config.xml中的参数、管理线程。这样,这个框架既可以任意配置开头字符串和目录,又可以配置移动的并发数。可见,框架的扩展性和灵活性有了进一步提高。
三、线程的泛化
Service2比Service1更加灵活,但如果一个目录按照开头字符串移动,另一个目录按照结尾的字符串移动,那么就必须修改框架的管理类Control。为了使新的应用能够方便地插入这个框架,在Control和Service之间加入一个抽象层ServiceThread,Control只调用ServiceThread,而新插入的应用都继承自ServiceThread抽象类。
抽象类ServiceThread的定义如下:
public class ServiceThread extends Thread {
…
public void run() {
}
…
}
具体应用子类Service3继承自ServiceThread,实现的代码如下:
public class Service3 extends ServiceThread {
…
public void initial(Element cfgInfo) throws Exception {
this.cfg = cfgInfo;
try {
initParameter(); // 获取config.xml的参数
} catch (Exception e) {
}
}
public void run() {
try {
bizService();
} catch (Exception e) {
}
}
protected void bizService() throws Exception {
…
while (true) {
File inPathFolder = new File(inPath);
msgList = inPathFolder.listFiles();
for (int i = 0; i < msgList.length; i++) {
tmpPath = ctlFilePath(msgList[i].getName());
objPath = addSep(tmpPath) + msgList[i].getName();
pathFile = new File(objPath);
try {
pathFile.delete();
msgList[i].renameTo(pathFile);
System.out.println("ctl path File " + objPath + "\n");
} catch (Exception e) {
}
}
}
}
}
管理类Control不直接调用应用子类,而是通过抽象父类ServiceThread调用子类,实现的代码如下:
public class Control {
public static void main(String[] args) {
String configFile = "config.xml";
try {
factory = DocumentBuilderFactory.newInstance();
builder = factory.newDocumentBuilder();
xmlDoc = builder.parse(configFile);
} catch (Exception e) {
System.out.println("Get config.xml Error:" + e.toString());
return;
}
serviceList = xmlDoc.getElementsByTagName("ThreadService");
for (int i = 0; i < serviceList.getLength(); i++) {
serviceCfg = (Element) serviceList.item(i);
try {
ServiceThread service = (ServiceThread) Class.forName(
serviceCfg.getAttribute("class")).newInstance();
service.setName("service");
service.initial(serviceCfg);
service.start();
} catch (Exception e) {
System.out.println("Start Service Error:" + e.toString());
}
}
}
}
四、结语
应用程序开发中,软件的扩展性、可插入和灵活性非常重用。本文使用Java中的XML配置和多线程技术实现了一个重用性比较好的软件框架。当然,这个框架还需要进一步优化,特别是线程启动、监控和终止等的管理。
|