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