OSGi原理与最佳实践--查询字典B/S类型系统


   作者林昊    由lan16转载自InfoQ中文站    更新于2014-09-09 21:01    已被浏览3071次

   1.B/S
     我们首先来看一下,如何基于OSGi来开发B/S结构的应用。B/S结构应用程序的开发,可有两个选择:一个是在OSGi的框架
   中嵌入Http服务器,另外一个是在Servlet容器中嵌入OSGi框架。下面分别介绍这两种方式的实现。此外,本节还会给出
   Declarative Service的使用实例。首先来构想一下这个B/S应用的功能。我们提供一个字典服务,用户在浏览器中输入一个单
   词,点击提交,给出这个单词的解释。当然,这个例子并不会真正细致地实现这个功能,比如不会真的做一个字典来提供服务,
   仅仅支持很少的单词的查询。有了这个例子,如果你有兴趣,可以来完善它。在这个例子中,我们会有四个Bundle,分别是字
   典查询响应Bundle,字典查询接口Bundle,本地字典服务Bundle,远程字典服务Bundle。设计示意图如图1-18所示。
     
     下面分析介绍它们。
     ■ 字典查询响应Bundle
       提供输入要查询单词的页面,接受用户的查询请求,从BundleContext 中获取字典服务的Service,调用字典服务的
       查询接口得到查询结果,并返回结果到页面。
     ■ 字典查询接口Bundle
       对外提供字典查询的接口。
     ■ 本地字典服务Bundle
       提供字典查询服务,是从本地的字典中查询结果。
     ■ 远程字典服务Bundle
       提供字典查询服务,是从远程的字典中查询结果。好了,我知道大家已经迫不及待了。下面我们就来动手实现第一
       个B/S结构的应用。
   2.OSGi框架嵌入Http服务器
     这一节我们采用把Http服务器嵌入到OSGi框架中的方法来完成这个字典查询系统的开发。首先我们要准备一下环境。回忆
   一下,我们在前面讲到HelloWorld例子的时候,介绍过环境的准备。在HelloWorld的例子中,我们只需要一个系统的Bundle。
   现在我们的运行环境要比HelloWorld稍微复杂一些,我们需要更多的Bundle,下面先来准备一下我们的环境。
     我们在Run Configurations的对话框中创建一个新的OSGi Framework的运行配置,在这个配置的Bundles 中 选择下面几
   个Bundle:javax.servlet 、org.apache.commons.logging、org.eclipse.equinox.http.jetty 、org.eclipse.
   equinox.http.servlet 、org.eclipse.osgi 、org.eclipse.osgi.services 和org.mortbay.jetty,如图1-19所示。
     
     点击Run,可以看到有些启动的日志输出。如果看到osgi>而且没有错误信息,则表明环境已经配置成功;而如果看到类似
   Address already in use: JVM_Bind,则说明本机的80端口已被占用,由于Equinox的Http Service实现默认使用的是80端口,
   所以会报这个错,那么我们可以指定Http Service使用的端口。我们打开Run Configurations中运行配置里面的Arguments页
   签,在VM arguments中添加“-Dorg.osgi.service.http.port=8080”就可以设置使用8080作为Http Service的端口了,本例
   子中仍采用默认的80端口,在正常启动后,输入ss,回车,可以看到如图1-20所示的显示。
     
     说明我们的环境已经准备好了。我们可以打开浏览器,输入http://localhost/,提示见图1-21。
     
     说明我们的服务器已经正常启动。下面就可以开始Bundle的开发工作了。
     第一步,完成字典查询接口Bundle工程。
     首先创建名为DictQuery的Plug-in工程(见图1-22)。
     
     然后我们在这个工程的org.osgichina.demo.dictquery.query package中创建一个接口(见图1-23)。
     
     因为这个Bundle是为了导出接口,所以这个Bundle的BundleActivator不做任何的改动。
     双击这个Bundle工程中META-INF下的MANIFEST.MF文件,在新的窗口中会显示这个MANIFEST的信息(见图1-24)。
     
     可以看到这个窗口下有多个页签,其中Overview、Dependencies、Runtime、Build这四个页签是图形化的编辑页签,对
   应的修改会反映到MANIFEST.MF和build.properties文件中。MANIFEST.MF和build.properties这两个页签分别直接显示了
   MANIFEST.MF和build.properties文件的内容。
     下面来完成将接口的package从Bundle中导出,能够让其他的Bundle来使用这个接口的package。
     首先选择Runtime页签,然后点击Exported Packages中的Add,在弹出的窗口中选择所创建的QueryService所在的package:
   org.osgichina.demo.dictquery.query,点击保存就完成导出Package的操作了(见图1-25)。
     
     这个时候去看MANIFEST.MF文件,其中多了如下一行:
     Export-Package: org.osgichina.demo.dictquery.query
     到此,我们就完成了字典查询接口Bundle的开发了。接着,我们来完成本地字典查询Bundle的开发。
     第二步,完成本地字典查询Bundle。
     ■ 首先,创建一个名字为LocalDictQuery的插件工程。
     ■ 然后,导入字典查询接口Bundle,并且实现一个真正的字典查询的类。我们打开LocalDictQuery工程中META-INF下
       的MANIFEST.MF文件,选择Dependencies页签。点击右侧Imported Packages的Add按钮,在弹出的对话框中选择前
       面请求处理接口Bundle导出的那个Package:org.osgichina.demo.dictquery.query,保存后就完成了导入的操作。
       我们可以查看一下请求处理Bundle的MANIFEST.MF文件,在Import-Package项中,多了org.osgichina.demo.
       dictquery.query(见图1-26)。
     
     ■ 接着,我们来编写LocalDictQueryServiceImpl的代码,这个是实现了QueryService接口的一个类。
       LocalDictQueryServiceImpl中的queryWord方法,可从本地初始化的字典中查询结果;如果没有结果,返回一个
       “N/A”,代码如图1-27所示。
     
     ■ 最后,我们要编写Activator的代码(见图1-28),在Bundle启动的时候注册我们提供的字典查询服务。从开发角度
       来看,Service有点像虚拟的概念,因为在编写Service 时和编写普通的Java类(POJO)没有任何区别,这个工程中
       的LocalDictQueryServiceImp就是一个Service类。那么如何能够让其他的Bundle使用这个Service?在OSGi的框架
       中,我们要通过BundleContext来进行服务的注册,并且后面会看到在其他的Bundle中如何拿到这个Service实例来
       使用。
     
     可以看到,在自动生成的Activator中,我们改动了两处。一个是我们加入了一个类型是ServiceRegistration的成员
   变量sr,一个是我们在start和stop方法中分别加入了一行代码,下面我们来看这行代码的含义。
   sr = context.registerService(QueryService.class.getName(),new LocalDictQueryServiceImpl(), null);
     上面这行代码是是用QueryService的全类名作为注册的Service 的名称,把新创建的LocalDictQueryServiceImpl对象
   注册成为了一个Service。而这个Service对象将在下面演示如何被使用。
   sr.unregister();
     以上这行代码是取消注册的Service。到这里,我们就完成了请求处理Bundle的代码编写。
     第三步,完成实现远程字典查询Bundle。
     Bundle和LocalDictQuery Bundle非常地类似,只是工程名称为RemoteDictQuery,另外为了能够显示区别,这个Bundle
   中提供服务的类的代码有所变化。实现QueryService接口的类的代码如图1-29所示。
     
     最后,我们完成字典查询响应Bundle。
     第四步,完成字典查询响应Bundle。
     和传统的Web开发方式不同,由于OSGi 框架中并没有像web应用服务器那样的Bundle,就不能像web应用直接部署到web
   服务器那样简单了,要通过HttpService将Servlet及资源文件(像图片、css、html 等)进行注册,这样才可以访问。这里
   只是一个简单的Demo,提供一个字典查询的页面和对应的Servlet。我们在src目录下建立一个page的目录,在其中编写
   dictquery.htm,另外就是实现字典查询响应的Servlet,由于Servlet 要继承HttpServlet,要引用
   Servlet API。要引入javax.servlet 和javax.servlet.http 两个包,接着,除了要引入org.osgichina.demo.dictquery.
   query这个package外,还要一个org.osgi.service.http package,如图1-30所示。
     
     然后,我们可以编写Servlet的代码,和普通的Servlet的写法没有差别。要解释的是在doGet方法中的这么一段代码:
   ServiceReference serviceRef = context.getServiceReference(QueryService.class.getName());
   if(null != serviceRef){
     queryService = (QueryService) context.getService(serviceRef);
   }
     context是在创建Servlet的时候传入的BundleContext,要通过这个context来获取提供字典查询功能的服务。首先通
   过context获取Service的引用,返回的是一个ServiceReference对象。然后再通过ServiceReference获取Service实例。
   拿到Service实例后,就可以调用Service的方法来完成字典查询功能了。
     另外,在这个字典查询响应Bundle中还要做的一件事情就是在Bundle启动的时候,把Servlet注册到Http服务中去。
   这个代码是在BundleActivator中完成的。
     至此已经完成了字典查询系统的开发。下面来运行一下系统。启动之后我们在osgi>提示符下输入ss,回车,可以看到类
   似图1-31的显示。
     
     打开浏览器,然后输入http://localhost/demo/page/dictquery.htm,将会显示如图1-32所示的内容。
     
     下面,我们先执行stop 10(停掉RemoteDictQuery Bundle),然后输入test,点击查询。会有如下显示:
     Result is 测试
     并且我们可以在Eclipse的Console中看到如下的输出:
     osgi> LocalDictQueryServiceImpl.queryWord called!
     接着我们执行stop 11,回车,stop 10,回车。停止LocalDictQuery Bundle,并且启动RemoteDictQuery Bundle,然后
   在查询页面上输入sky,点击查询。会有如下显示:
     Result is 天空
     到这里,我们已经完成了字典查询系统的开发。
   3.应用部署
     我们已经完成了字典查询整个应用的开发、运行、调试和测试工作。不过这些都是在Eclipse这个开发工具中完成的,
   我们可不能给客户一个要客户在Eclipse上运行的应用,所以下面来看一下如何部署OSGi应用。
     第一步,创建独立的Equinox运行环境。
     在硬盘上创建一个osgi_demo目录,从Eclipse的plugins目录复制org.eclipse.osgi_3.4.3.R34x_ v20081215-1030.jar(在
   不同版本的Eclipse中,这个jar包的org.eclipse.osgi_后面的部分会有所不同)到这个osgi_demo目录。修改这个jar 包的名称
   为equinox.jar,然后在相同目录下编写一个run.bat,其内容如下:
     java -jar equinox.jar -console
     双击run.bat,如果出现osgi>的提示,则说明启动已经成功了。输入ss命令然后回车,这个时候会看到只有一个ACTIVE状态
   的system bundle。
     第二步,导出各个Bundle工程为jar。
     以最复杂的DictQueryWeb为例,首先打开MANIFEST.MF,选择Runtime页签,设置Classpath(见图1-34)。
     
     然后选择Build页签,选中其中Binary Build里面的lib目录(见图1-35)。
     
     接着,选中DictQueryWeb工程,点右键,选择Export,然后选中弹出对话框中的Deployable plug-ins and fragements
   (见图1-36)。
     
     在进入的Deployable plug-ins and fragments窗口中已经默认选择了DictQueryWeb这个bundle,然后选择Destination
   标签页,设置一个有效的Directory,然后点击Finish,在设置的目录中可以看到一个plugins目录,在plugins目录中就有
   DictQueryWeb_1.0.0.jar这个bundle了。
     按照相同的方法可以导出其他的几个bundle,也可以一次性地把几个bundle 都导出来(见图1-37)。
     
     第三步,安装各Bundle到Equinox中。
     首先在osgi_demo目录下创建一个bundles目录,然后将第二步生成的三个bundle复制到bundles目录下,此外,我们要从
   Eclipse的plugins目录中把我们需要的
   javax.servlet_2.4.0.v200806031604.jar
   org.eclipse.equinox.http.servlet_1.0.100.v20080427-0830.jar
   org.eclipse.equinox.http.jetty_1.1.0.v20080425.jar
   org.mortbay.jetty_5.1.14.v200806031611.jar
   org.apache.commons.logging_1.0.4.v20080605-1930.jar
   org.eclipse.osgi.services_3.1.200.v20071203.jar
   这几个Jar文件复制到Bundles目录中。
     将Bundle安装到Equinox中:
     运 行之前 编写 的run.bat,在osgi>中输入install reference:file:bundles/ DictQuery_1.0.0.jar,回车,这样就完
   成了DictQuery Bundle的安装。我们可以用同样的方法完成DictQueryWeb Bundle、LocalDictQueryBundle、RemoteDictQuery
   Bundle及其他系统Bundle的安装。
   然后在osgi>提示符下输入ss,回车,可以看到如图1-38所示的状态。
     
     可见,目前我们安装后的10个Bundle都已经是INSTALLED 的状态。下面让我们来启动这些Bundle。首 先来启动系统的
   Bundle,依次启 动javax.servlet、org.apache.commons.logging、org.eclipse.osgi.services、org.mortbay.jetty、
   org.eclipse.equinox.http.servlet 和org.eclipse.equinox.http. jetty,然后启动我们自己开发的Bundle。可以在osgi>
   提示符下输入ss,会有如图1-39所示的输出。
     
     这个时候,可以通过浏览器来测试我们的应用了。
     最后输入exit,就可以退出系统。以后只须双击run.bat就可以完成系统的启动。经过这样的步骤,就形成了单独运行的
   OSGi系统。

豫ICP备13022176号-2   鄂公网安备 42010602000416号   Copyright © 2013 - 2020 蓝石榴. All Rights Reserved  联系我 联系我 蓝石榴技术社区