[转载]没有eclipse截图的OSGi入门教程 – chenjianjx – 博客园.
网上能找到的OSGi教程,都喜欢在开篇时就教你如何在eclipse里做一个OSGi的东西,就算你跟着做了(如果有足够的耐心,还是不知道OSGi是怎么一回事);更可气的是,这些教程还会配上大段大段的eclipse截图,以达到浪费篇幅的目的。
鉴于这种情况,我只好找本英文OSGi书看了看。这本书上没有eclipse截图,我看时觉得很满意,所以顺便把入门部分微缩一下,给大家做个简短的教程。
Why OSGi?
OSGi用于在同一个JVM内部实现“模块化” (“组件化”)。
首先,它用来实现模块化,提供模块化的一些常见特性:
1. 模块向外只暴露特定的接口,内部实现对外不可见
2. 模块可独立部署
3. 像服务治理一样管理自己模块暴露的接口,包括服务发布、寻找和版本管理
其次,和SOA那种分布式模块化方案不同,OSGi的模块并不会分布在多台机器上,而是部署在同一个jvm进程里的:
1. 每个模块打包成一个JAR
2. 整个应用只需要一个进程,开发、部署会比SOA更高效
OSGi的具体功能有哪些?
官方文档把OSGi的功能分成三大块:
1. Module: 将程序组装成一个一个的“模块”,模块里除了业务逻辑,还有各种元数据
2. Lifecycle:管理模块的生命周期,比如启动和停止模块,并在运行时让模块跟OSGi框架发生关系(比如“热部署”)
3. Service层:把模块暴露的接口当作SOA服务来管理,比如服务的发布和寻找。
实际使用时,你并不需要用上所有的功能。如果你只对模块组织感兴趣,“热部署”、“服务治理”等等你都可以不用。
顺便提一下OSGi的规范与实现。”OSGi Alliance”组织只提供了规范(specification),具体的java实现则由第三方提供。目前主流的实现有:
1. Apache Felix
2. Eclipse Euinox — Eclipse组织提供的实现。Eclipse还为它提供了相应的IDE Plugin,以快速搭建OSGi应用。估计这也是为什么很多OSGi教程喜欢从Eclipse截图开始的原因。
3. Knopflerfish
OSGi应用举例
闲话少叙,接下来我们直接看代码,看看基于OSGi的模块化是怎么实现的。在下面的例子里,我们会涉及到OSGi模块的服务化、打包和生命周期管理。
Service/Client
在模块化的语境中,一个模块总是在充当服务提供者(Service)或者服务消费者(Client)。所以我们可以先把Service/Client建好:
Service:
<span class = "kwd" > public </span><span class = "pln" > </span><span class = "kwd" > interface </span><span class = "pln" > </span><span class = "typ" >HelloService</span><span class = "pln" > </span><span class = "pun" >{</span><span class = "pln" > </span><span class = "kwd" > public </span><span class = "pln" > </span><span class = "kwd" > void </span><span class = "pln" > sayHelloTo</span><span class = "pun" >(</span><span class = "typ" >String</span><span class = "pln" > whom</span><span class = "pun" >);</span><span class = "pln" > </span><span class = "pun" >}</span><span class = "pln" > </span><span class = "kwd" > public </span><span class = "pln" > </span><span class = "kwd" > class </span><span class = "pln" > </span><span class = "typ" >HelloServiceImpl</span><span class = "pln" > </span><span class = "kwd" > implements </span><span class = "pln" > </span><span class = "typ" >HelloService</span><span class = "pln" > </span><span class = "pun" >{</span><span class = "pln" > </span><span class = "lit" > @Override </span><span class = "pln" > </span><span class = "kwd" > public </span><span class = "pln" > </span><span class = "kwd" > void </span><span class = "pln" > sayHelloTo</span><span class = "pun" >(</span><span class = "typ" >String</span><span class = "pln" > whom</span><span class = "pun" >)</span><span class = "pln" > </span><span class = "pun" >{</span><span class = "pln" > </span><span class = "typ" >System</span><span class = "pun" >.</span><span class = "kwd" >out</span><span class = "pun" >.</span><span class = "pln" >println</span><span class = "pun" >(</span><span class = "str" > "Saying hello to " </span><span class = "pln" > </span><span class = "pun" >+</span><span class = "pln" > whom</span><span class = "pun" >);</span><span class = "pln" > </span><span class = "pun" >}</span><span class = "pln" > </span><span class = "pun" >}</span> |
Client:
<span class = "kwd" > public </span><span class = "pln" > </span><span class = "kwd" > class </span><span class = "pln" > </span><span class = "typ" >HelloClient</span><span class = "pln" > </span><span class = "pun" >{</span><span class = "pln" > </span><span class = "kwd" > private </span><span class = "pln" > </span><span class = "kwd" > static </span><span class = "pln" > </span><span class = "typ" >HelloService</span><span class = "pln" > helloService</span><span class = "pun" >;</span><span class = "pln" > </span><span class = "kwd" > public </span><span class = "pln" > </span><span class = "kwd" > void </span><span class = "pln" > callHelloService</span><span class = "pun" >()</span><span class = "pln" > </span><span class = "pun" >{</span><span class = "pln" > helloService</span><span class = "pun" >.</span><span class = "pln" >sayHelloTo</span><span class = "pun" >(</span><span class = "str" > "beckham" </span><span class = "pun" >);</span><span class = "pln" > </span><span class = "com" > //向Beckham问好</span><span class="pln"> </span><span class = "pun" >}</span><span class = "pln" > </span><span class = "pun" >…</span><span class = "pln" > </span><span class = "pun" >}</span> |
将Service/Client都打包成模块
接下来我们把Service和Client分别打成jar包,形成OSGi模块。当然,OSGi模块不仅仅是一个jar包,它还要包含一些必要的元数据和辅助类,以被OSGi框架识别和管理。
我们先从Service模块开始。
Service:
1. 先写一个“服务激活者”,用于指定本模块的服务的实现者并发布服务。这个类会在模块启动时被执行
<span class = "kwd" > public </span><span class = "pln" > </span><span class = "kwd" > class </span><span class = "pln" > </span><span class = "typ" >ServiceActivator</span><span class = "pln" > </span><span class = "kwd" > implements </span><span class = "pln" > org</span><span class = "pun" >.</span><span class = "pln" >osgi</span><span class = "pun" >.</span><span class = "pln" >framework</span><span class = "pun" >.</span><span class = "typ" >BundleActivator</span><span class = "pun" >{</span><span class = "pln" > </span><span class = "lit" > @Override </span><span class = "pln" > </span><span class = "kwd" > public </span><span class = "pln" > </span><span class = "kwd" > void </span><span class = "pln" > start</span><span class = "pun" >(</span><span class = "typ" >BundleContext</span><span class = "pln" > context</span><span class = "pun" >)</span><span class = "pln" > </span><span class = "kwd" > throws </span><span class = "pln" > </span><span class = "typ" >Exception</span><span class = "pln" > </span><span class = "pun" >{</span><span class = "pln" > context</span><span class = "pun" >.</span><span class = "pln" >registerService</span><span class = "pun" >(</span><span class = "typ" >HelloService</span><span class = "pun" >.</span><span class = "kwd" > class </span><span class = "pun" >.</span><span class = "pln" >getName</span><span class = "pun" >(),</span><span class = "pln" > </span><span class = "kwd" > new </span><span class = "pln" > </span><span class = "typ" >HelloServiceImpl</span><span class = "pun" >(),</span><span class = "pln" > </span><span class = "kwd" > null </span><span class = "pun" >);</span><span class = "pln" > </span><span class = "typ" >System</span><span class = "pun" >.</span><span class = "kwd" >out</span><span class = "pun" >.</span><span class = "pln" >println</span><span class = "pun" >(</span><span class = "typ" >HelloService</span><span class = "pun" >.</span><span class = "kwd" > class </span><span class = "pun" >.</span><span class = "pln" >getName</span><span class = "pun" >()</span><span class = "pln" > </span><span class = "pun" >+</span><span class = "pln" > </span><span class = "str" > " has been registred as a service" </span><span class = "pun" >);</span><span class = "pln" > </span><span class = "pun" >}</span><span class = "pln" > </span><span class = "pun" >…….</span><span class = "pln" > </span><span class = "pun" >}</span> |
你可能注意到这里出现了”Bundle”字样。这是因为在OSGi术语中,模块就是Bundle.
2. 在即将打出的jar包的MANIFET.MF中,标识一下本模块(Bundle),指定模块中的哪些类允许被外界使用,并指定本模块的“激活者”:
<span class = "typ" >Bundle</span><span class = "pun" >-</span><span class = "typ" >SymbolicName</span><span class = "pun" >:</span><span class = "pln" > player</span><span class = "pun" >.</span><span class = "pln" >kent</span><span class = "pun" >.</span><span class = "pln" >chen</span><span class = "pun" >.</span><span class = "pln" >osgi</span><span class = "pun" >.</span><span class = "pln" >service </span><span class = "typ" >Bundle</span><span class = "pun" >-</span><span class = "typ" >Version</span><span class = "pun" >:</span><span class = "pln" > </span><span class = "lit" > 1.0 </span><span class = "pun" >.</span><span class = "lit" > 0 </span><span class = "pln" > </span><span class = "typ" >Export</span><span class = "pun" >-</span><span class = "typ" >Package</span><span class = "pun" >:</span><span class = "pln" > player</span><span class = "pun" >.</span><span class = "pln" >kent</span><span class = "pun" >.</span><span class = "pln" >chen</span><span class = "pun" >.</span><span class = "pln" >osgi</span><span class = "pun" >.</span><span class = "pln" >service</span><span class = "pun" >.</span><span class = "pln" >interfaces </span><span class = "typ" >Bundle</span><span class = "pun" >-</span><span class = "typ" >Activator</span><span class = "pun" >:</span><span class = "pln" > player</span><span class = "pun" >.</span><span class = "pln" >kent</span><span class = "pun" >.</span><span class = "pln" >chen</span><span class = "pun" >.</span><span class = "pln" >osgi</span><span class = "pun" >.</span><span class = "pln" >service</span><span class = "pun" >.</span><span class = "pln" >impl</span><span class = "pun" >.</span><span class = "typ" >ServiceActivator</span> |
还有一个条目:
<span class = "typ" >Import</span><span class = "pun" >-</span><span class = "typ" >Package</span><span class = "pun" >:</span><span class = "pln" > org</span><span class = "pun" >.</span><span class = "pln" >osgi</span><span class = "pun" >.</span><span class = "pln" >framework</span> |
这指明了本模块可以依赖哪些外部java package;由于“激活者”在运行时依赖了org.osgi.framework,如果没有上面这一句,激活者的运行就会失败。
3. 打包成jar
把Service的接口、实现、激活者及MANIFEST.MF打成jar包,并命名为play-osgi-service.jar.bundle
Client:
1. 先写一个“激活者”,从OSGi Context中寻找Service
<span class = "kwd" > public </span><span class = "pln" > </span><span class = "kwd" > class </span><span class = "pln" > </span><span class = "typ" >ClientActivator</span><span class = "pln" > </span><span class = "kwd" > implements </span><span class = "pln" > </span><span class = "typ" >BundleActivator</span><span class = "pun" >{</span><span class = "pln" > </span><span class = "kwd" > public </span><span class = "pln" > </span><span class = "kwd" > static </span><span class = "pln" > </span><span class = "typ" >HelloService</span><span class = "pln" > helloService</span><span class = "pun" >;</span><span class = "pln" > </span><span class = "lit" > @Override </span><span class = "pln" > </span><span class = "kwd" > public </span><span class = "pln" > </span><span class = "kwd" > void </span><span class = "pln" > start</span><span class = "pun" >(</span><span class = "typ" >BundleContext</span><span class = "pln" > context</span><span class = "pun" >)</span><span class = "pln" > </span><span class = "kwd" > throws </span><span class = "pln" > </span><span class = "typ" >Exception</span><span class = "pln" > </span><span class = "pun" >{</span><span class = "pln" > </span><span class = "typ" >ServiceReference</span><span class = "pln" > </span><span class = "kwd" >ref</span><span class = "pln" > </span><span class = "pun" >=</span><span class = "pln" > context</span><span class = "pun" >.</span><span class = "pln" >getServiceReference</span><span class = "pun" >(</span><span class = "typ" >HelloService</span><span class = "pun" >.</span><span class = "kwd" > class </span><span class = "pun" >.</span><span class = "pln" >getName</span><span class = "pun" >());</span><span class = "pln" > helloService </span><span class = "pun" >=</span><span class = "pln" > </span><span class = "pun" >(</span><span class = "typ" >HelloService</span><span class = "pun" >)</span><span class = "pln" > context</span><span class = "pun" >.</span><span class = "pln" >getService</span><span class = "pun" >(</span><span class = "kwd" >ref</span><span class = "pun" >);</span><span class = "pln" > </span><span class = "typ" >HelloClient</span><span class = "pun" >.</span><span class = "pln" >setHelloService</span><span class = "pun" >(</span><span class = "pln" >helloService</span><span class = "pun" >);</span><span class = "pln" > </span><span class = "pun" >}</span><span class = "pln" > </span><span class = "pun" >……</span><span class = "pln" > </span><span class = "pun" >}</span> |
2. 在MANIFET.MF中标识一下本模块,指定欲依赖的类,并指定激活者
<span class = "typ" >Bundle</span><span class = "pun" >-</span><span class = "typ" >SymbolicName</span><span class = "pun" >:</span><span class = "pln" > player</span><span class = "pun" >.</span><span class = "pln" >kent</span><span class = "pun" >.</span><span class = "pln" >chen</span><span class = "pun" >.</span><span class = "pln" >osgi</span><span class = "pun" >.</span><span class = "pln" >client </span><span class = "typ" >Bundle</span><span class = "pun" >-</span><span class = "typ" >Version</span><span class = "pun" >:</span><span class = "pln" > </span><span class = "lit" > 1.0 </span><span class = "pun" >.</span><span class = "lit" > 0 </span><span class = "pln" > </span><span class = "typ" >Import</span><span class = "pun" >-</span><span class = "typ" >Package</span><span class = "pun" >:</span><span class = "pln" > player</span><span class = "pun" >.</span><span class = "pln" >kent</span><span class = "pun" >.</span><span class = "pln" >chen</span><span class = "pun" >.</span><span class = "pln" >osgi</span><span class = "pun" >.</span><span class = "pln" >service</span><span class = "pun" >.</span><span class = "pln" >interfaces</span><span class = "pun" >,</span><span class = "pln" > org</span><span class = "pun" >.</span><span class = "pln" >osgi</span><span class = "pun" >.</span><span class = "pln" >framework </span><span class = "typ" >Bundle</span><span class = "pun" >-</span><span class = "typ" >Activator</span><span class = "pun" >:</span><span class = "pln" > player</span><span class = "pun" >.</span><span class = "pln" >kent</span><span class = "pun" >.</span><span class = "pln" >chen</span><span class = "pun" >.</span><span class = "pln" >osgi</span><span class = "pun" >.</span><span class = "pln" >client</span><span class = "pun" >.</span><span class = "typ" >ClientActivator</span> |
3. 打包成jar
把Client类、激活者及MANIFEST.MF打成jar包,并命名为play-osgi-client.jar.bundle
创建launch程序
Service和Client模块都构建好了,最后写一个Main程序把它们集成在一起跑起来(launch it)。
<span class = "kwd" > public </span><span class = "pln" > </span><span class = "kwd" > class </span><span class = "pln" > </span><span class = "typ" >Main</span><span class = "pln" > </span><span class = "pun" >{</span><span class = "pln" > </span><span class = "kwd" > public </span><span class = "pln" > </span><span class = "kwd" > static </span><span class = "pln" > </span><span class = "kwd" > void </span><span class = "pln" > main</span><span class = "pun" >(</span><span class = "typ" >String</span><span class = "pun" >[]</span><span class = "pln" > args</span><span class = "pun" >)</span><span class = "pln" > </span><span class = "kwd" > throws </span><span class = "pln" > </span><span class = "typ" >Exception</span><span class = "pln" > </span><span class = "pun" >{</span><span class = "pln" > </span><span class = "com" > // 创建launch framework,它是OSGi launch的入口. (getFramework()方法的实现请参见附件里的代码文件)</span><span class="pln"> org</span><span class = "pun" >.</span><span class = "pln" >osgi</span><span class = "pun" >.</span><span class = "pln" >framework</span><span class = "pun" >.</span><span class = "pln" >launch</span><span class = "pun" >.</span><span class = "typ" >Framework</span><span class = "pln" > framework </span><span class = "pun" >=</span><span class = "pln" > getFramework</span><span class = "pun" >();</span><span class = "pln" > </span><span class = "com" > // 安装service bundle</span><span class="pln"> </span><span class = "typ" >Bundle</span><span class = "pln" > serviceBundle </span><span class = "pun" >=</span><span class = "pln" > framework</span><span class = "pun" >.</span><span class = "pln" >getBundleContext</span><span class = "pun" >().</span><span class = "pln" >installBundle</span><span class = "pun" >(</span><span class = "pln" >fileToUri</span><span class = "pun" >(</span><span class = "str" > "../play-osgi-service-and-client/service/dist/play-osgi-service.jar.bundle" </span><span class = "pun" >));</span><span class = "pln" > </span><span class = "com" > // 安装client bundle</span><span class="pln"> </span><span class = "typ" >Bundle</span><span class = "pln" > clientBundle </span><span class = "pun" >=</span><span class = "pln" > framework</span><span class = "pun" >.</span><span class = "pln" >getBundleContext</span><span class = "pun" >().</span><span class = "pln" >installBundle</span><span class = "pun" >(</span><span class = "pln" >fileToUri</span><span class = "pun" >(</span><span class = "str" > "../play-osgi-service-and-client/client/dist/play-osgi-client.jar.bundle" </span><span class = "pun" >));</span><span class = "pln" > </span><span class = "com" > // 启动framework和bundles</span><span class="pln"> framework</span><span class = "pun" >.</span><span class = "pln" >start</span><span class = "pun" >();</span><span class = "pln" > serviceBundle</span><span class = "pun" >.</span><span class = "pln" >start</span><span class = "pun" >();</span><span class = "pln" > clientBundle</span><span class = "pun" >.</span><span class = "pln" >start</span><span class = "pun" >();</span><span class = "pln" > </span><span class = "com" > // 启动client</span><span class="pln"> </span><span class = "typ" >Class</span><span class = "pun" ><?></span><span class = "pln" > clientClass </span><span class = "pun" >=</span><span class = "pln" > clientBundle</span><span class = "pun" >.</span><span class = "pln" >loadClass</span><span class = "pun" >(</span><span class = "str" > "player.kent.chen.osgi.client.HelloClient" </span><span class = "pun" >);</span><span class = "pln" > </span><span class = "typ" >Method</span><span class = "pln" > method </span><span class = "pun" >=</span><span class = "pln" > clientClass</span><span class = "pun" >.</span><span class = "pln" >getMethod</span><span class = "pun" >(</span><span class = "str" > "callHelloService" </span><span class = "pun" >,</span><span class = "pln" > </span><span class = "kwd" > new </span><span class = "pln" > </span><span class = "typ" >Class</span><span class = "pun" ><?>[]{});</span><span class = "pln" > method</span><span class = "pun" >.</span><span class = "pln" >invoke</span><span class = "pun" >(</span><span class = "pln" >clientClass</span><span class = "pun" >.</span><span class = "pln" >newInstance</span><span class = "pun" >());</span><span class = "pln" > </span><span class = "com" > // 最后停止framework</span><span class="pln"> framework</span><span class = "pun" >.</span><span class = "pln" >stop</span><span class = "pun" >();</span><span class = "pln" > </span><span class = "pun" >}</span><span class = "pln" > </span><span class = "pun" >…</span><span class = "pln" > </span><span class = "pun" >}</span> |
运行一下这个Main程序,控制台将打印” Saying hello to Beckham”,表明HelloService调用成功。
你的第一个OSGi应用写好了!它组建了模块(Bundle),指定了依赖关系;但这个例子本身还不能充分地展示“模块化”,我们要再看一个例子:
体验一个反例:越过Service的接口直接调用内部实现
OSGi的模块中未显式暴露的类对其他模块来说是不可见的,我们来测试一下。
把client的激活者的代码改一下,让它直接依赖HelloServiceImpl对象,看看会怎么样。
<span class = "kwd" > public </span><span class = "pln" > </span><span class = "kwd" > class </span><span class = "pln" > </span><span class = "typ" >ClientActivator</span><span class = "pln" > </span><span class = "kwd" > implements </span><span class = "pln" > </span><span class = "typ" >BundleActivator</span><span class = "pun" >{</span><span class = "pln" > </span><span class = "kwd" > public </span><span class = "pln" > </span><span class = "kwd" > static </span><span class = "pln" > </span><span class = "typ" >HelloServiceImpl</span><span class = "pln" > helloServiceImpl</span><span class = "pun" >;</span><span class = "pln" > </span><span class = "lit" > @Override </span><span class = "pln" > </span><span class = "kwd" > public </span><span class = "pln" > </span><span class = "kwd" > void </span><span class = "pln" > start</span><span class = "pun" >(</span><span class = "typ" >BundleContext</span><span class = "pln" > context</span><span class = "pun" >)</span><span class = "pln" > </span><span class = "kwd" > throws </span><span class = "pln" > </span><span class = "typ" >Exception</span><span class = "pln" > </span><span class = "pun" >{</span><span class = "pln" > </span><span class = "typ" >ServiceReference</span><span class = "pln" > </span><span class = "kwd" >ref</span><span class = "pln" > </span><span class = "pun" >=</span><span class = "pln" > context</span><span class = "pun" >.</span><span class = "pln" >getServiceReference</span><span class = "pun" >(</span><span class = "typ" >HelloService</span><span class = "pun" >.</span><span class = "kwd" > class </span><span class = "pun" >.</span><span class = "pln" >getName</span><span class = "pun" >());</span><span class = "pln" > helloServiceImpl </span><span class = "pun" >=</span><span class = "pln" > </span><span class = "pun" >(</span><span class = "typ" >HelloServiceImpl</span><span class = "pun" >)</span><span class = "pln" > context</span><span class = "pun" >.</span><span class = "pln" >getService</span><span class = "pun" >(</span><span class = "kwd" >ref</span><span class = "pun" >);</span><span class = "pln" > </span><span class = "typ" >HelloClient</span><span class = "pun" >.</span><span class = "pln" >setHelloService</span><span class = "pun" >(</span><span class = "pln" >helloServiceImpl</span><span class = "pun" >);</span><span class = "pln" > </span><span class = "pun" >}</span><span class = "pln" > </span><span class = "pun" >…</span><span class = "pln" > </span><span class = "pun" >}</span> |
运行程序,结果是: java.lang.NoClassDefFoundError: player/kent/chen/osgi/service/impl/HelloServiceImpl . 也就是说, Client在执行时,相关的类装载器装载不到HelloServiceImpl,因为HelloServiceImpl不属于client模块的Import-Package (player.kent.chen.osgi.service.interfaces)
那如果client模块引入了HelloServiceImpl呢? 在MANIFEST.MF中加入一个Import-Package试试:
Import-Package: player.kent.chen.osgi.service.interfaces, org.osgi.framework, player.kent.chen.osgi.service.impl
运行结果是:
Unresolved constraint in bundle player.kent.chen.osgi.client: missing requirement package; (package=player.kent.chen.osgi.service.impl)
也就是说,虽然client模块中引入了对player.kent.chen.osgi.service.impl的依赖,但并没有任何模块提供了这个package,client的调用依然以异常告终。
试想,如果没有OSGi,仅把client和service两个jar文件放在一起,除非自写Class Loader,否则你无法阻止client里的代码直接调用service的内部实现,继而导致弱封装、强耦合了。这下你可以看出OSGi的模块化的功效了吧?
总结
通过上面的介绍,估计你对OSGi的基本作用已经有了一定的了解,对它如何使用也有了感性的认识。你可以下载附件所含的代码示例,自己体验一下。(这些代码使用Apache Felix作为OSGi的实现,由于Felix对bundle的装载使用了缓存,在每次运行之前,你最好都清除一下前一次运行时产生的felix-cache文件夹)
另外,本文内容比较少,只是“入门的入门”,它只能帮你消除对OSGi的神秘感。要想“完整入门”、或者想全面学习OSGi的强大功能,建议阅读专门的书籍。个人推荐的书是: ‘OSGi in Action’