一年一度的Ig Nobel prize典礼都都会带来一些非常新颖的观点、发现,这些内容甚至超过了Ig Nobel prizes本身。每位获奖者在做完七个字的总结后,还有机会利用24秒的时间对自己的新观点、新发现进行阐述。
这是一个极其绝妙的主意,这对每位获奖者都是一个需要完成的挑战。
OSGi
是近来业界经常提到的事物,随着Equinox成为Eclipse的顶级项目,Felix被用于Sling和Glassfish V3的容器,以及Spring-Modules的发布。但是,很多人都不熟悉OSGi…而且一直不去了解它,也不在意他人正在循序渐进的了解OSGi。
至于我,我不是OSGi的鼓吹者,仅仅是喜欢花些时间试图向那些还未了解OSGi的朋友进行讲解。显然,做点少量的范例代码能帮助大家尽快上手。再次强调,我不是OSGi的鼓吹者(也不会站在任何OSGi的立场上。)我仅仅是个普通人,看着OSGi的确不错,并且它的文档也不多才写作本文。当然,如果你很熟悉OSGi,那么不需要阅读本文。(译注:这人太罗嗦了,和唐僧有一拼!)
首先,我给大家做七字总结,然后是24秒的阐述,接着我将用范例来解释这一切。
OSGi
的七字总结和24秒阐述
OSGi
是一个为Java而设计的组件框架。
24
秒的阐述:OSGi是一个Java框架,该框架能装载以bundle为单位的资源。Bundle能提供服务或响应处理请求,而他们之间的依赖都是被管理起来的,正如一个bundle能从容器中获得它所需要的管理。每个bundle都可以有它自己的内部类路径,所以它可以作为独立的服务单元。所有的这些符合OSGi规范的bundle理论上都可以安装在任何符合OSGi规范的容器中。
闲聊一下,不管我念得如何得快都要花掉24秒,貌似我就只能念这么快了。还缺少什么?喔,这则阐述缺少对为何需要这样一个模块化系统的解释。
为什么需要模块化系统?
模块化系统为分布式bundle提供了翻译支持(这里的“bundle”超过了“OSGi bundle”的范畴—我习惯使用这一术语来做比喻。)当然,模块话系统的依赖关系是个话题,生命周期也是个话题… …有趣。
所有这些都很重要;所谓翻译并不是使用web service,EJB翻译依然被强迫通过JNDI方式,jar之间的依赖由并行jar部署来管理(除了JCA和WAR,当然他们也有不同的方式进行依赖管理)。
Java EE
是一套解决方案,尽管这些都不是必要的:WAR和JCA可以包含jar文件,EJB jar通过配置他们的manifest能参考其他jar文件,尽管应用服务器能提供高级的类资源库;一旦你在相同web应用程序或web service中使用不同的版本,JNDI将提供版本检测机制。生命周期是为web应用程序(加载并启动 servlets、上下文监听器)和JCA而存在的,但是EJB3.1可能会有自己的生命周期机制—还不确定。
现在我们知道了,Java EE就是个棒槌可以搞定一切事情。
OSGi
和JSR-277试图把Java的模块化部署标准化起来,而不是强行往Java EE概念上靠拢,这样也能避免Java EE关于依赖和版本检测以及生命周期方面的弱点。既然本文以OSGi而非模型为主题,那就集中在OSGi上吧…
开始OSGi
简单的讲,运行OSGi是非常简单的,基本上没什么乐趣:寻找一个OSGi容器的实现方案(Equinox、Felix、Knopflerfish、ProSyst),并运行这些容器的启动命令,有点像在运行Java EE的服务器。类似Java EE,每个容器都有不同的启动环境和细小的性能差异;请检查你选择容器的具体信息和选项。为了更加清晰点,我将在本文中采用Equinox。
Equinox
是一个OSGi容器,你可以从http://download.eclipse.org/eclipse/equinox/下载它。下载的文件是ZIP格式,解压缩到“eclipse”目录,不必惊讶:Equinox是Eclipse内置的OSGi容器。(我将把包含所有Equinox发布的顶级目录“/eclipse”作为$EQUINOX变量。)文档在Equinox快速入门中可以找到,访问$EQUINOX/plugins将显示出很多jar文件:
/opt/tools/eclipse/plugins> ls
javax.servlet.jsp_2
.0.0
.v200706191603.jar org.eclipse.equinox.jsp.jasper_1
.0.1
.R33x_v20070816.jar
javax.servlet_2
.4.0
.v200706111738.jar org.eclipse.equinox.launcher_1
.0.1
.R33x_v20070828.jar
org.apache.commons.el_1
.0.0
.v200706111724.jar org.eclipse.equinox.launcher_1
.0.1
.R33x_v20080118.jar
org.apache.commons.logging_1
.0.4
.v200706111724.jar org.eclipse.equinox.log_1
.0.100
.v20070226.jar
org.apache.jasper_5
.5.17
.v200706111724.jar org.eclipse.equinox.metatype_1
.0.0
.v20070226.jar
org.eclipse.equinox.app_1
.0.1
.R33x_v20070828.jar org.eclipse.equinox.preferences_3
.2.100
.v20070522.jar
org.eclipse.equinox.common_3
.3.0
.v20070426.jar org.eclipse.equinox.preferences_3
.2.101
.R33x_v20080117.jar
org.eclipse.equinox.device_1
.0.0
.v20070226.jar org.eclipse.equinox.registry_3
.3.1
.R33x_v20070802.jar
org.eclipse.equinox.event_1
.0.100
.v20070516.jar org.eclipse.equinox.servletbridge_1
.0.1
.R33x_v20070816.jar
org.eclipse.equinox.http.jetty_1
.0.1
.R33x_v20070816.jar org.eclipse.equinox.source_3
.3.1
.R33x_r20070918-7n7LECgEKVsLIM1aGBO4b00
org.eclipse.equinox.http.registry_1
.0.0
.v20070608.jar org.eclipse.equinox.source_3
.3.1
.R33x_r20070918-7n7LEClEIdwb-bbP_z--EYAO
org.eclipse.equinox.http.registry_1
.0.1
.R33x_v20071231.jar org.eclipse.equinox.useradmin_1
.0.0
.v20070226.jar
org.eclipse.equinox.http.servlet_1
.0.1
.R33x_v20070816.jar org.eclipse.osgi.services_3
.1.200
.v20070605.jar
org.eclipse.equinox.http.servletbridge_1
.0.0
.v20070523.jar org.eclipse.osgi.util_3
.1.200
.v20070605.jar
org.eclipse.equinox.http_1
.0.100
.v20070423.jar org.eclipse.osgi_3
.3.1
.R33x_v20070828.jar
org.eclipse.equinox.http_1
.0.101
.R33x_v20071016.jar org.eclipse.osgi_3
.3.2
.R33x_v20080105.jar
org.eclipse.equinox.jsp.jasper.registry_1
.0.0
.v20070607.jar org.mortbay.jetty_5
.1.11
.v200706111724.jar
启动Equinox很简单:在这个目录中,执行java -jar org.eclipse.osgi_3.3.2.R33x_v20080105.jar –console(在控制台窗口),然后你将看到一个命令提示符:
osgi>
这就是Equinox的OSGi控制台。从这里开始,你可以安装新的bundle并启动、停止、卸载他们,检查他们的依赖、注册服务,或者其他东西。你尝试到的第一个命令将是“ss”,是“short status”的缩写。如果当前是全新安装,这样的交互看起来是这样的:
osgi> ss
Framework is launched.
id State Bundle
0
ACTIVE org.eclipse.osgi_3
.3.2
.R33x_v20080105
osgi>
这样的显示证明容器是运行着的,并安装了一个bundle。这个bundle的id为“0”,这个id有很大的作用,因为你可以使用它来控制bundle的生命周期。
那么,bundle有什么优点呢?bundle提供了生命周期和服务暴露,就像我们在24秒的总结中提到一样。
将一个简单的资料库放入Bundle
让我们创建一个bundle。我想利用资料库存储信息。我的最初接口看起来是这样的:
package
repository;
public
interface
RepositoryService {
Node put(String path, Node content);
Node get(String path);
}
不是太多,这是一个开始:我可以利用它来存放或提取信息。我的节点类是一个简单的POJO,看起来是这样的:
package
repository;
import
java.util.ArrayList;
import
java.util.Date;
import
java.util.List;
public
class
Node {
Date created
=
new
Date();
String source
=
"
unknown
"
;
String author
=
"
unknown
"
;
List contents
=
new
ArrayList
<
String
>
();
String path;
@Override
public
String toString() {
String s
=
"
node: path=
"
+
getPath()
+
"
, author=
"
+
getAuthor()
+
"
, created=
"
+
getCreated()
+
"
, source=
"
+
getSource()
+
"
, data=[
"
;
String separator
=
""
;
for
(String d : getContents()) {
s
+=
separator
+
d;
separator
=
"
,
"
;
}
s
+=
"
]
"
;
return
s;
}
public
Node() {
}
public
Node(String content) {
getContents().add(content);
}
public
Node(String content, String context) {
this
(content);
setSource(context);
}
//
.. accessors and mutators go here.
}
这是一个服务实现,它可以独立于OSGi进行实现和测试;到此为止我们还没有涉及到OSGi。要向OSGi迈进就应该有个服务的实现。我最初的代码是以Map为基础:
import
java.util.HashMap;
import
java.util.Map;
import
repository.Node;
import
repository.RepositoryService;
public
class
MapRepositoryService
implements
RepositoryService {
Map
<
String, Node
>
data
=
new
HashMap
<
String, Node
>
();
public
Node put(String path, Node content) {
return
data.put(path, content);
}
public
Node get(String path) {
return
data.get(path);
}
}
然而,一个Map不能很好的实现查询功能,而且很有可能遇到有层次结构的信息。我想用DOM来代替它。所以我现在将引入XOM,并重新实现资源库服务:
package
repository.impl;
import
java.util.Date;
import
nu.xom.Attribute;
import
nu.xom.Document;
import
nu.xom.Element;
import
nu.xom.Elements;
import
nu.xom.Nodes;
import
nu.xom.ParentNode;
import
repository.Node;
import
repository.RepositoryService;
public
class
XMLRepositoryService
implements
RepositoryService {
Document document;
Element data;
public
XMLRepositoryService() {
data
=
new
Element(
"
data
"
);
document
=
new
Document(data);
}
Node toNode(nu.xom.Element node) {
if
(node.getAttribute(
"
source
"
)
==
null
) {
return
null
;
}
Node n
=
new
Node();
n.setAuthor(node.getAttributeValue(
"
author
"
));
n.setCreated(
new
Date(node.getAttributeValue(
"
created
"
)));
n.setSource(node.getAttributeValue(
"
source
"
));
Elements e
=
node.getChildElements(
"
contents
"
);
n.setPath(node.getAttributeValue(
"
path
"
));
for
(
int
i
=
0
; i
<
e.size(); i
++
) {
Element elt
=
e.get(i);
for
(
int
i1
=
0
; i1
<
elt.getChildCount(); i1
++
) {
n.getContents().add(elt.getChild(i1).getValue());
}
}
return
n;
}
nu.xom.Element getElement(String path) {
while
(
!
path.startsWith(
"
//
"
)) {
path
=
"
/
"
+
path;
}
while
(path.endsWith(
"
/
"
)) {
path
=
path.substring(
0
, path.length()
-
1
);
}
Nodes nodes
=
document.query(path);
if
(nodes.size()
>
0
) {
return
(Element) nodes.get(
0
);
}
return
null
;
}
public
Node get(String path) {
Element e
=
getElement(path);
if
(e
!=
null
) {
return
toNode(e);
}
return
null
;
}
public
Node put(String path, Node content) {
Element oldElt
=
getElement(path);
if
(oldElt
!=
null
) {
//
need to remove this node!
ParentNode p
=
oldElt.getParent();
p.removeChild(oldElt);
}
StringBuilder pathBuilder
=
new
StringBuilder(
"
/
"
);
String[] tree
=
path.split(
"
/
"
);
Element node
=
data;
for
(String t : tree) {
if
(t.length()
!=
0
) {
Element child
=
node.getFirstChildElement(t);
if
(child
==
null
) {
//
System.err.println("creating new "+t);
child
=
new
Element(t);
node.appendChild(child);
}
pathBuilder.append(
'
/
'
);
pathBuilder.append(t);
node
=
child;
}
}
node.addAttribute(
new
Attribute(
"
created
"
, content.getCreated()
.toString()));
node.addAttribute(
new
Attribute(
"
source
"
, content.getSource()));
node.addAttribute(
new
Attribute(
"
author
"
, content.getAuthor()));
content.setPath(pathBuilder.toString());
node.addAttribute(
new
Attribute(
"
path
"
, content.getPath()));
Element contents
=
new
Element(
"
contents
"
);
node.appendChild(contents);
for
(String c : content.getContents()) {
Element e
=
new
Element(
"
content
"
);
e.appendChild(c);
contents.appendChild(e);
}
//
System.out.println(data.toXML());
return
null
;
}
public
static
void
main(String[] args) {
XMLRepositoryService s
=
new
XMLRepositoryService();
s.put(
"
/foo/bar/baz
"
,
new
Node(
"
stuff
"
));
Node n
=
new
Node(
"
bletch
"
);
n.setAuthor(
"
jottinger
"
);
s.put(
"
/foo/bar/bletch
"
, n);
System.out.println(s.get(
"
foo/bar/baz/
"
));
System.out.println(s.get(
"
//foo/bar/baz
"
));
System.out.println(s.get(
"
//foo/bar/bletch
"
));
System.out.println(s.get(
"
foo/bar/
"
));
System.out.println(s.get(
"
//*[@author='jottinger']
"
));
}
}
这离完美还有很长的距离,不过总算是个好的开始。然而,我们还是没涉及到任何关于OSGi方面的东西—我们只有一个无关紧要的资源库类。然后我们来看看OSGi模块是怎样被构建的。
转移目标:根据依赖关系构造OSGi bundle
一个OSGi模块是一个.jar文件,在这里,它应该遵循标准的.jar文件规范,除此之外还有一部分信息应该放进manifest文件中。
让我们创建一个简单的模块,首先,最简单的它应该能在启动和结束的时候显示消息,作为内部依赖将引入一个.jar文件。这个jar文件对其他bundle是不可见的-它只是作为我们的范例bundle的资源,在鄙文中这是一个很简单和普通的需求。(是的,不怎么样的例子-我在寻找更简单的,但没找到。)依赖将放在jar中,baselib.jar,这里面只有一个类:baselib.BaseService。
package
baselib;
import
java.util.logging.Logger;
public
class
BaseService {
Logger log
=
Logger.getLogger(
this
.getClass().getName());
public
void
sayHello() {
log.info(
"
Hello, world!
"
);
}
}
接下来要做的是把编译好的文件放进.jar中,这就是baselib.jar。
现在,一个OSGi bundle需要一个“activator”,是一个管理bundle生命周期的类。一个activator要实现org.osgi.framework.BundleActivator接口,该接口有两个方法:start(BundleContext)以及stop(BundleContext)。这些生命周期方法能让bundle注册服务或启动服务,但是在这里它还很简单:
package
tutorial;
import
baselib.BaseService;
import
org.osgi.framework.BundleActivator;
import
org.osgi.framework.BundleContext;
import
java.util.logging.Logger;
public
class
TutorialActivator
implements
BundleActivator {
Logger log
=
Logger.getLogger(
this
.getClass().getName());
public
void
start(BundleContext bc) {
log.info(
"
started
"
);
new
BaseService().sayHello();
}
public
void
stop(BundleContext bc) {
log.info(
"
stopped.
"
);
}
}
我们不只是把这些东西放进一个.jar文件,然后就能工作了,真不巧(Spring-OSGi可以派上用场,但是这超出了本文的范围。)我们还需要以一种特定的文件集和特定的结构来创建tutorialbundle.jar。首先,baselib.jar应该放在我们新的jar文件的根目录。我们还需要一个MANIFEST.MF文件,它包含了一些OSGi配置和启动信息:
Manifest-Version:
1.0
Bundle-ManifestVersion:
2
Bundle-SymbolicName: com.theserverside.tutorial.osgi.TutorialBundle
Bundle-Version:
1
Bundle-Activator: tutorial.TutorialActivator
Import-Package: org.osgi.framework
;
version="1.3.0"
Bundle-ClassPath: .
,
baselib.jar
这个manifest文件假设相应的jar像这样:
$ jar tvf tutorialbundle.jar
0
Thu Apr
17
11
:
57
:
14
EDT
2008
META-INF/
391
Thu Apr
17
11
:
57
:
12
EDT
2008
META-INF/MANIFEST.MF
0
Thu Apr
17
11
:
29
:
56
EDT
2008
tutorial/
714
Thu Apr
17
11
:
51
:
02
EDT
2008
tutorial/TutorialActivator.class
902
Thu Apr
17
11
:
15
:
28
EDT
2008
baselib.jar
这里的要点是我们的manifest文件引入了包(只是一些OSGi框架需要的包)、activator类文件名以及bundle类路径-以逗号隔开的在jar中的资源集。如果我们能再次重复实现这个结构,说明我们已经准备好在Equinox中安全和运行bundle了。
$ java -jar org.eclipse.osgi_3
.3.2
.R33x_v20080105.jar -console
osgi> ss
Framework is launched.
id State Bundle
0
ACTIVE org.eclipse.osgi_3
.3.2
.R33x_v20080105
osgi> install file:///workspaces/osgi/tutorial/tutorialbundle.jar
Bundle id is
4
osgi> start
4
Apr
17
,
2008
11
:
57
:
29
AM tutorial.TutorialActivator start
INFO: started
Apr
17
,
2008
11
:
57
:
29
AM baselib.BaseService sayHello
INFO: Hello
,
world!
osgi> stop
4
Apr
17
,
2008
1
:
29
:
25
PM tutorial.TutorialActivator stop
INFO: stopped.
osgi>
现在我们知道如何构建bundle了,并知道如何进行相关依赖jar的部署(PS:
后面的资源库类将依赖XOM。)
构建我们的资源库Bundle
现在我们可以搞定自己的资源库bundle了,需要在Activator中添加些功能:把资源库注册成服务并发布它,所以其他bundle也可以使用我们的资源库进行查询。
package
repository;
import
java.util.Hashtable;
import
org.osgi.framework.BundleActivator;
import
org.osgi.framework.BundleContext;
import
org.osgi.util.tracker.ServiceTracker;
import
repository.impl.XMLRepositoryService;
public
class
Activator
implements
BundleActivator {
/**
*
@see
org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
*/
public
void
start(BundleContext context)
throws
Exception {
//
register the service
context.registerService(
RepositoryService.
class
.getName(),
new
XMLRepositoryService(),
new
Hashtable
<
Object,Object
>
());
//
create a tracker and track the log service
ServiceTracker repositoryServiceTracker
=
new
ServiceTracker(context, RepositoryService.
class
.getName(),
null
);
repositoryServiceTracker.open();
//
grab the service
RepositoryService repositoryService
=
(RepositoryService) repositoryServiceTracker.getService();
System.err.println(
"
RepositoryService Activated
"
);
}
/*
* (non-Javadoc)
* @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
*/
public
void
stop(BundleContext context)
throws
Exception {
//
close the service tracker
System.err.println(
"
RepositoryService Deactivated
"
);
}
}
注意,上面的代码把XMLRepositoryService硬编码进去了,有点不爽。我们可以用Spring、或者Service Provider Interface、或者环境变量、或者—甚至是OSGi青睐的方式,不过这些都超出了本文的范围。让我们开始服务部署,然后我们将揭示如何在其他bundle中调用它。
根据我们的第一个范例bundle,我们的资源库bundle的相关信息将组成manifest文件和形成目录结构。下面是目录结构:
$ jar tvf repositorybundle.jar
0
Thu Apr
17
14
:
08
:
46
EDT
2008
META-INF/
553
Thu Apr
17
14
:
08
:
44
EDT
2008
META-INF/MANIFEST.MF
0
Thu Apr
17
13
:
57
:
12
EDT
2008
repository/
0
Thu Apr
17
13
:
57
:
12
EDT
2008
repository/impl/
1383
Thu Apr
17
13
:
57
:
12
EDT
2008
repository/Activator.class
2095
Thu Apr
17
13
:
57
:
12
EDT
2008
repository/Node.class
205
Thu Apr
17
13
:
57
:
12
EDT
2008
repository/RepositoryService.class
694
Thu Apr
17
13
:
57
:
12
EDT
2008
repository/impl/MapRepositoryService.class
3823
Thu Apr
17
13
:
57
:
12
EDT
2008
repository/impl/XMLRepositoryService.class
895924
Thu Apr
17
14
:
03
:
40
EDT
2008
xerces-
2.4.0
.jar
109318
Thu Apr
17
14
:
05
:
08
EDT
2008
xml-apis-
1.0
.b2.jar
431568
Thu Apr
17
13
:
54
:
06
EDT
2008
xom-
1.1
.jar
And the manifest file:
Manifest-Version:
1.0
Bundle-ManifestVersion:
2
Bundle-Name: Repository Plug-in
Bundle-SymbolicName: repository
Bundle-Version:
1.0.0
Bundle-Activator: repository.Activator
Bundle-Vendor: theserverside.com
Import-Package: org.osgi.framework
;
version="1.3.0",
org.osgi.util.tracker
;
version="1.3.1"
Export-Package: repository
;
uses:="org.osgi.framework"
Bundle-ClassPath: .
,
xom-
1.1
.jar
,
xerces-
2.4.0
.jar
,
xml-apis-
1.0
.b2.jar
我们在干什么呢?我们在创建一个jar,使用我们之前写好的Activator实现类,以及数个依赖包:Xerces的实现包XOM,以及ServiceTracker API。
我们也干了一件有趣的事情:我们把资源库bundle暴露出来。这意味着在OSGi容器中的其他的bundle能引入那些bundle,也可以通过详细的名称来查询服务(这样的话,资源库被注册为“RepositoryService“接口,或者“repository.RepositoryService“。)接下来安装bundle并启动它:
osgi> install file:///workspaces/osgi/tutorial/repositorybundle.jar
Bundle id is
11
osgi> start
11
RepositoryService Activated
osgi>
目前为止一点都不令人兴奋,但是我们已经在利用OSGi基础部件工作了。需要注意的是,我们把接口和实现都放进去了。理想情况下,RepositoryService可以放在自己的jar中,所以我们能分离接口和实现。这并不困难,甚至从bundle的观点看;在activator 中调用bundle接口,你不需要做任何事情,而在bundle实现中,你应该从其他地方导入接口bundle。我们在这里并没有这样干,因为这样做会走很多弯路,相应的也会减缓开发速度。
在其他Bundle中调用我们的OSGi Bundle
最后步骤是构建例外一个bundle—但它将会找到资源库服务并使用之。
让我们先来看看Bundle Activator。的确很简单,基本上没什么功能:当bundle启动时,它先查找RepositoryService,并在往这个服务中存入数据。它使用stop()机制来实际查找资源库里面的数据并显示在控制台上。这不是一个严谨的测试,但这足以证明流程的行为:
package
testrepouser;
import
org.osgi.framework.BundleActivator;
import
org.osgi.framework.BundleContext;
import
org.osgi.framework.ServiceReference;
import
java.util.logging.Logger;
import
repository.Node;
import
repository.RepositoryService;
public
class
SampleActivator
implements
BundleActivator {
Logger log
=
Logger.getLogger(
this
.getClass().getName());
/*
* (non-Javadoc)
* @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
*/
public
void
start(BundleContext context)
throws
Exception {
ServiceReference ref
=
context.getServiceReference(
RepositoryService.
class
.getName());
RepositoryService lookup
=
(RepositoryService) context.getService(ref);
Node testNode
=
new
Node(
"
this is some content
"
);
lookup.put(
"
/foo/bar/baz
"
, testNode);
log.info(
"
/foo/bar/baz stored.
"
);
}
/*
* (non-Javadoc)
* @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
*/
public
void
stop(BundleContext context)
throws
Exception {
ServiceReference ref
=
context.getServiceReference(
RepositoryService.
class
.getName());
RepositoryService lookup
=
(RepositoryService) context.getService(ref);
log.info(lookup.get(
"
//*/baz
"
));
}
}
The MANIFEST.MF file looks like
this
:
Manifest
-
Version:
1.0
Bundle
-
ManifestVersion:
2
Bundle
-
Name: Repository Sample Plug
-
in
Bundle
-
SymbolicName: samplerepouser
Bundle
-
Version:
1.0
.
0
Bundle
-
Activator: sample.SampleActivator
Bundle
-
Vendor: theserverside.com
Import
-
Package: org.osgi.framework;version
=
"
1.3.0
"
Require
-
Bundle: repository
在上面最后一行中。如果你检查RepositoryService的manifest,字符名字是“repository.”,在这里我们说bundle应该能访问任何被参考的bundle暴露的类,换句话说,一旦我们的资源库bundle暴露了“repository”包,我们的SampleActivator就能够直接导入这个资源库类而不是自己再用包来组织。
我们构建了sample.jar bundle,结构是这样的:
$ jar tvf ../samplebundle.jar
0
Thu Apr
17
14
:
47
:
48
EDT
2008
META-INF/
421
Thu Apr
17
14
:
47
:
46
EDT
2008
META-INF/MANIFEST.MF
0
Thu Apr
17
14
:
47
:
48
EDT
2008
sample/
1270
Thu Apr
17
14
:
47
:
48
EDT
2008
sample/SampleActivator.class
注意一个简单的地方:这个bunlde除了activator没什么其他内容了。没有服务实现,甚至接口。
我们安装并开启这个bundle,然后再停止,是否和预期的效果一样:
osgi> install file:///workspaces/osgi/tutorial/samplebundle.jar
Bundle id is
17
osgi> start
17
Apr
17
,
2008
2
:
49
:
07
PM sample.SampleActivator start
INFO: /foo/bar/baz stored.
osgi> stop
17
Apr
17
,
2008
2
:
49
:
09
PM sample.SampleActivator stop
INFO: node: path
=
//foo/bar/baz
,
author
=
unknown
,
created
=
Thu Apr
17
14
:
49
:
07
EDT
2008
,
source
=
unknown
,
data
=
[
this is some content
]
osgi>
在另一个OSGi容器中运行我们的Bundle
OSGi
的强大力量之一是容器的“平台无关”,就像Java EE模块能部署到任何兼容的容器中一样。现在我们花点时间来展示我们之前写好的bundle部署到Felix上—Apache的OSGi容器,再看看在其他容器上bundle看起来是什么样子的。Felix首先需要你对当前配置命名,如果你用一样的名字的话它可以重新载入,在这里的范例,我们将把它叫做“tutorial01”。
$ java -jar bin/felix.jar
Welcome to Felix.
=================
Enter profile name: tutorial01
DEBUG: WIRE:
1.0
-> org.ungoverned.osgi.service.shell ->
1.0
DEBUG: WIRE:
1.0
-> org.osgi.service.startlevel ->
0
DEBUG: WIRE:
1.0
-> org.apache.felix.shell ->
1.0
DEBUG: WIRE:
1.0
-> org.osgi.framework ->
0
DEBUG: WIRE:
1.0
-> org.osgi.service.packageadmin ->
0
DEBUG: WIRE:
2.0
-> org.apache.felix.shell ->
1.0
DEBUG: WIRE:
2.0
-> org.osgi.framework ->
0
DEBUG: WIRE:
3.0
-> org.osgi.framework ->
0
DEBUG: WIRE:
3.0
-> org.osgi.service.obr ->
3.0
DEBUG: WIRE:
3.0
-> org.apache.felix.shell ->
1.0
-> install file:///workspaces/osgi/tutorial/tutorialbundle.jar
Bundle ID:
7
-> start
7
DEBUG: WIRE:
7.0
-> org.osgi.framework ->
0
Apr
18
,
2008
10
:
46
:
37
AM tutorial.TutorialActivator start
INFO: started
Apr
18
,
2008
10
:
46
:
37
AM baselib.BaseService sayHello
INFO: Hello
,
world!
-> install file:///workspaces/osgi/tutorial/repositorybundle.jar
Bundle ID:
8
-> start
8
DEBUG: WIRE:
8.0
-> org.osgi.util.tracker ->
0
DEBUG: WIRE:
8.0
-> org.osgi.framework ->
0
RepositoryService Activated
-> install file:///workspaces/osgi/tutorial/samplebundle.jar
Bundle ID:
9
-> start
9
DEBUG: WIRE:
9.0
-> org.osgi.framework ->
0
DEBUG: WIRE:
9.0
-> module
;
bundle-symbolic-name="repository";bundle-version="1.0.0" -> 8.0
Apr
18
,
2008
10
:
47
:
08
AM sample.SampleActivator start
INFO: /foo/bar/baz stored.
-> stop
9
Apr
18
,
2008
10
:
47
:
09
AM sample.SampleActivator stop
INFO: node: path
=
//foo/bar/baz
,
author
=
unknown
,
created
=
Fri Apr
18
10
:
47
:
07
EDT
2008
,
source
=
unknown
,
data
=
[
this is some content
]
-> shutdown
-> RepositoryService Deactivated
Apr
18
,
2008
10
:
47
:
12
AM tutorial.TutorialActivator stop
INFO: stopped.
一个实际的应用程序,类似IRC Bot
非常清晰的看到资源库范例是如何运行的—但是测试是没什么乐趣的。让我们再把这个测试更进一步,引入一个IRC bot。我们的IRC bot将使用PircBot,因为学习它的API没什么难度,IRC bot将加入某个IRC网络(irc.freenode.net的"#pircbot"频道)的频道,将响应两个外部命令:~set和~get。~set将获取一个路径和一些文字,并把文字加入到路径中;而~get将从路径中获取信息。同时,这个例子极其简单并且也不能达到infobot的水平;那就把这个例子留下来给读者练习,并赋予它更多功能吧。
首先要做的事情是建立一个查询服务的通用方式。的确还没有最好的办法实现!有多种不同模式可选;现在有个简单而不是最好的方式来快速展开。我们先创建ServiceLookup接口,接着为OSGi实现ServiceLookup接口。
package
service;
public
interface
ServiceLookup {
Object getService(String name);
}
package
service.osgi;
import
org.osgi.framework.BundleContext;
import
org.osgi.framework.ServiceReference;
import
service.ServiceLookup;
public
class
OSGIServiceLookupImpl
implements
ServiceLookup {
BundleContext ctx;
public
OSGIServiceLookupImpl(BundleContext ctx) {
this
.ctx
=
ctx;
}
public
Object getService(String name) {
ServiceReference ref
=
ctx.getServiceReference(name);
return
ctx.getService(ref);
}
}
创建OSGIServiceLookupImpl是很简单的,只是它的构造函数传入了Activator的 BundleContext:
package
ircbot;
import
org.jibble.pircbot.IrcException;
import
org.jibble.pircbot.NickAlreadyInUseException;
import
org.osgi.framework.BundleActivator;
import
org.osgi.framework.BundleContext;
import
service.osgi.OSGIServiceLookupImpl;
import
java.io.IOException;
public
class
BotActivator
implements
BundleActivator {
IRCBot bot
=
null
;
public
void
start(
final
BundleContext context)
throws
Exception {
try
{
bot
=
new
IRCBot(
new
OSGIServiceLookupImpl(context));
bot.setVerbose(
true
);
bot.connect(
"
irc.freenode.net
"
);
bot.joinChannel(
"
#pircbot
"
);
}
catch
(NickAlreadyInUseException e) {
e.printStackTrace();
}
catch
(IOException e) {
e.printStackTrace();
}
catch
(IrcException e) {
e.printStackTrace();
}
}
public
void
stop(BundleContext context)
throws
Exception {
bot.disconnect();
bot.dispose();
}
}
下面的代码全是在创建IRCBot实例,要用到ServiceLookup实现:
package
ircbot;
import
org.jibble.pircbot.PircBot;
import
service.ServiceLookup;
import
repository.RepositoryService;
import
repository.Node;
public
class
IRCBot
extends
PircBot {
ServiceLookup service;
public
IRCBot(ServiceLookup service) {
super
();
this
.service
=
service;
setName(
"
OSGIBot
"
);
}
@Override
protected
void
onMessage(String channel, String sender, String login, String hostname, String message) {
String[] command
=
message.split(
"
"
);
if
(command.length
>
1
&&
(
"
~set
"
.equals(command[
0
])
||
"
~get
"
.equals(command[
0
]))) {
String path
=
command[
1
];
//
we should use a tracker for this!
RepositoryService repository
=
(RepositoryService) service.getService(RepositoryService.
class
.getName());
if
(
"
~set
"
.equals(command[
0
])) {
StringBuilder content
=
new
StringBuilder();
for
(
int
i
=
2
;i
<
command.length;i
++
) {
content.append(
"
"
);
content.append(command[i]);
}
Node node
=
repository.get(path);
if
(node
==
null
) {
node
=
new
Node();
node.setAuthor(sender);
node.setSource(
"
irc
"
);
}
node.getContents().add(content.toString().trim());
repository.put(path, node);
}
if
(
"
~get
"
.equals(command[
0
])) {
Node node
=
repository.get(path);
if
(node
!=
null
) {
int
count
=
0
;
//
will only do two at most, to be polite
for
(String content:node.getContents()) {
if
(count
++>
2
) {
break
;
}
sendMessage(channel, sender
+
"
:
"
+
content);
}
}
}
}
}
}
最后,我们的MANIFEST.MF文件指明了类路径(包含在pircbot.jar中),以及activator名字(“ircbot.BotActivator”),以及对“repository”的外部依赖:
Manifest-Version:
1.0
Bundle-ManifestVersion:
2
Bundle-Name: IRCBot Plug-in
Bundle-SymbolicName: ircbot
Bundle-Version:
1.0.0
Bundle-Activator: ircbot.BotActivator
Bundle-Vendor: theserverside.com
Import-Package: org.osgi.framework
;
version="1.3.0"
Require-Bundle: repository
Bundle-ClassPath: pircbot.jar
,
.
安装并开启这个bundle(废话多),它将连接到Freenode并加入#pircbot。注意这里没有对昵称冲突进行处理;你可以自己写点代码搞定,或者修改默认的昵称。这不是一些好代码,人人都可以放任何东西进去,经不住测试…
这里并不是说只有IRCBot才能使用资源库。理论上,一个jabber客户端也可以用相同的资源库(可以用相似的代码来处理。)事实上,这就是OSGi的亮点: IRCBot中处理命令的代码能在一个bundle内部独立运行,并且如果需要的话,IRCBot也能很容易的调用适当的bundle管理命令,一旦要求访问资源库,它们能立刻查找资源库服务。
结论
希望,本文能启发读者去探求OSGi的潜能,并能让读者知道如何开始使用它。
请注意!引用、转贴本文应注明原译者:RosenJiang 以及出处:
http://www.blogjava.net/rosen