| 一、引言 可重用性是软件开发的目标之一,可重用性表现为可扩展性、可插入性和灵活性。本文通过对实例层层的分析,讨论基于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配置和多线程技术实现了一个重用性比较好的软件框架。当然,这个框架还需要进一步优化,特别是线程启动、监控和终止等的管理。       |