-
Notifications
You must be signed in to change notification settings - Fork 0
Felix_Tutorial_4
示例四 —— 可靠的辞典客户端程序包
在示例三中,我们为我们的辞典服务创建了一个简单的客户端程序包。但那个客户端的有一个问题,就是它不会监控辞典服务的动态可用性,如果辞典服务在客户端正在使用的时候消失则会发生错误。在本例中我们会创建一个监控辞典服务动态可用性的辞典服务客户端,这个客户端使用起来会更可靠。
新辞典客户端的功能基本上和旧的客户端一样,它会从标准输入读取单词并在辞典服务中检查它们是否存在。我们的程序包使用其运行时的上下文环境将其自身注册为服务事件监听器,它会监听服务器事件并且让程序包监控辞典服务的可用性。我们的客户端会使用其找到的第一个辞典服务,其源代码在如下所示的名为 Activator.java的文件中:
/*
- Apache Felix OSGi 教程。 **/
package tutorial.example4;
import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.IOException;
import org.osgi.framework.BundleActivator; import org.osgi.framework.BundleContext; import org.osgi.framework.InvalidSyntaxException; import org.osgi.framework.ServiceReference; import org.osgi.framework.ServiceListener; import org.osgi.framework.ServiceEvent;
import tutorial.example2.service.DictionaryService;
/**
-
此类实现了一个用辞典服务检查一个单词在辞典中是否存在的方法判断此单词是否拼写正确的程序包。
-
因其会监视辞典服务的动态可用性,这个程序包比示例三中的程序包复杂得多。也就是说,
-
如果它正在使用的服务消失了,它就不会再去使用那个服务。或者如果它需要一个服务并且一个同类型的服务加入进来了,它就会去使用。
-
在以前的例子里,程序包使用其找到的第一个服务,并且在start()方法运行时中从标准输入读取单词。
-
你可以以输入一个空行的方法停止单词检查过程,但如果你想重新开始单词检查过程,你必须将此软件包停止并且重新启动。 **/ public class Activator implements BundleActivator, ServiceListener { // 软件包运行时的上下文环境。 private BundleContext m_context = null; // 被使用服务的引用。 private ServiceReference m_ref = null; // 被使用服务的实例。 private DictionaryService m_dictionary = null;
/**
-
BundleActivator.start()方法的实现。 在这里将其自身添加为服务事件监听器,然後查询可用的辞典服务。
-
如果找到辞典则获取所找到的第一本可用辞典的引用,然後开始�´单词检查循环¡。
-
如果没有找到辞典,会进入´单词检查循环¡,但在有辞典服务加入之前程序不会检查任何单词,
-
当没有任何一个辞典服务可用时新加入的辞典服务会被自动选用,程序会从标准输入读取单词并使用
-
选用的辞典查看其是否存在。
-
(注意: 用默认方法运行这种很长的业务过程并不是一种好的做法,这里只是为了本教程的需要)
-
@param context 软件包运行时在框架中所处的上下文环境。 **/ public void start(BundleContext context) throws Exception { m_context = context;
// 我们在注册服务监听器和执行首次辞典查找时将过程同步(synchronize)起来, // 因为当辞典服务已经存在时我们不想在查找辞典服务时收到服务事件。 synchronized (this) { // 监听和辞典服务相关的事件。 m_context.addServiceListener(this, "(&(objectClass=" + DictionaryService.class.getName() + ")" + "(Language=*))");
// 查询面向任何语言的任何服务。 ServiceReference[] refs = m_context.getServiceReferences( DictionaryService.class.getName(), "(Language=*)"); // 如果我们找到了辞典服务,为了使用它我们要获取第一个服务的引用。 if (refs != null) { m_ref = refs[0]; m_dictionary = (DictionaryService) m_context.getService(m_ref); }}
try { System.out.println("输入空行则退出。"); String word = ""; BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
// 永远的循环。 while (true) { // 提示用户输入一个单词。 System.out.print("输入单词: "); word = in.readLine(); // 如果用户输入了一个空行,则结束循环。 if (word.length() == 0) { break; } // 如果没有辞典服务,则提示。 else if (m_dictionary == null) { System.out.println("没有可用的辞典服务。"); } // 否则打印出此单词是否正确 else if (m_dictionary.checkWord(word)) { System.out.println("正确。"); } else { System.out.println("不正确。"); } }} catch (Exception ex) { } }
/**
- 实现BundleActivator.stop()方法。 因为框架会自动放回所有正在使用的服务,所以在这里不做任何事情。
- @param context 程序包运行时在框架中所处的上下文环境。 **/ public void stop(BundleContext context) { // 注意: 服务会被自动释放。 }
/**
-
实现ServiceListener.serviceChanged()方法。检查我们正在使用的服务是否正在脱离或者
-
在我们需要时尝试获取一个服务。
-
@param event 引发的服务事件 **/ public synchronized void serviceChanged(ServiceEvent event) { String[] objectClass = (String[]) event.getServiceReference().getProperty("objectClass");
// 如果注册了一个辞典服务,查看是否我们需要,如果需要则获取一个引用。 if (event.getType() == ServiceEvent.REGISTERED) { if (m_ref == null) { // 获取一个服务实例的引用。 m_ref = event.getServiceReference(); m_dictionary = (DictionaryService) m_context.getService(m_ref); } } // 如果一个辞典服务被注销,则查看是否是我们正在使用的那个, // 如果是的话,放回服务并尝试获取另外一个。 else if (event.getType() == ServiceEvent.UNREGISTERING) { if (event.getServiceReference() == m_ref) { // 放回服务并将现有引用设置为null。 m_context.ungetService(m_ref); m_ref = null; m_dictionary = null;
// 通过查询尝试是否可以获取另外一个服务。 ServiceReference[] refs = null; try { refs = m_context.getServiceReferences( DictionaryService.class.getName(), "(Language=*)"); } catch (InvalidSyntaxException ex) { // 不会发生这种情况 } if (refs != null) { // 获取第一个服务实例的引用。 m_ref = refs[0]; m_dictionary = (DictionaryService) m_context.getService(m_ref); } }} } }
-
客户端会监听辞典服务加入或消失的服务事件。如果一个新的辞典服务加入,当而且仅当程序包没有使用任何辞典服务时,它会开始使用此服务。如果一个现有的辞典服务消失,程序包会检查消失的服务是否是正在使用的那个,如果是的话它会停止继续使用并查找另外一个辞典服务,否则它会忽略此事件。
同样,我们必须要创建包含程序包导引信息的manifest.mf文件:
Bundle-Name: Dynamic dictionary client Bundle-Description: A bundle that uses the dictionary service whenever it becomes available Bundle-Vendor: Apache Felix Bundle-Version: 1.0.0 Bundle-Activator: tutorial.example4.Activator Import-Package: org.osgi.framework, tutorial.example2.service
我们通过Bundle-Activator属性指定程序包接口,并且通过Import-Package指明我们的类引入了OSGi框架的核心包和辞典服务接口包,OSGi框架会自动处理导入包的细节问题。(提示:确信你的manifest文件的尾部以回车结束,否则最后一行会被忽略)
为了编译这个源文件,我们需要在classpath中加入felix.jar文件(可以在felix套件的bin目录下找到),我们用如下命令编译源文件:
javac -d c:\classes *.java
此命令会编译所有的源文件并且把生成的类文件输出到c:\classes目录中一个子目录下,这个子目录就是tutorial\example4,按照我们在源文件中制定的包名命名。为了让以上命令能够正常执行,目录c:\classes必须存在。编译后,我们必须创建一个JAR文件以包装生成的包目录,而且要将包含软件包导引信息的描述文件一并添加到JAR包中。我们使用如下命令创建这个JAR文件:
jar cfm example4.jar manifest.mf -C c:\classes tutorial\example4
此命令利用我们创建的描述文件创建了一个JAR包而且包含了在c:\classes目录下的tutorial\example4目录中所有的类文件。当此JAR文件成功创建后,我们便可以准备安装并且启动此程序包。
我们按照usage.html中描述的步骤启动Felix,当我们启动Felix后,程序会询问一个档案名,我们可以将我们所有的程序包置入一个名为 "tutorial"的档案中。启动Felix後,我们要确信教程中描述的所有程序包除了示例二中的英语辞典服务程序包外都是停止的。我们可以使用 Felix的 shell命令 ´ps¡获取所有程序包的列表,以及它们的状态和标记值。如果示例一中描述的程序包不是有效(activa)的,我们必须使用start命令和´ps¡命令打印出的那个标记值将其启动并且使用stop命令停掉所有其他在本教程中不需要的软件包。(注意: Felix使用一些程序包来提供命令行交互接口,所以不要把这些软件包停掉)现在我们可以安装并且启辞典服务程序包。假如我们的把我们的程序包创建在c: \tutorial。我们可以在Felix shell中使用如下方法将其安装并且启用:
start file:/c:/tutorial/example4.jar
上述命令可以在一个步骤里安装并启动这个软件包,同时也可以使用install和start两个命令分两步安装并启动此软件包。当我们启动软件包後,它会使用它会使用shell提示我们输入单词。这时如果想检查一个单词则将其输入如果想退出则可以直接输入一个空行。如果想要重新启动这个软件包,我们必须使用Felix shell中的ps命令获取此软件包的标识值,然後首先使用stop命令停掉这个软件包,然後使用start命令将其重新启动。想要测试这个辞典服务,可以输入辞典中的单词(例如´welcome¡、´to¡、´the¡、´OSGi¡、´tutorial¡),或者辞典中没有的单词。
因为此客户端会监视辞典服务的动态可用性,在辞典服务消失时会表现出很强的´健壮性¡。在此之后,当辞典服务加入时,如果需要此服务的话它会自动获取然后继续运行。在解释这些功能的时候有一点困难,因为我们在这里使用单线程的方法处理问题,但在多线程或者面向用户界面的程序中,´健壮性¡就显得十分重要。