当前位置:网站首页>[转]: OSGI规范 深入浅出

[转]: OSGI规范 深入浅出

2022-07-05 05:09:00 morpheusWB

OSGI规范 深入浅出

一、OSGI简介

1、OSGI简介

OSGI(Open Service Gateway Initiative),即开放服务网关协议,是面向Java的动态模型系统。
OSGI是指由OSGI Alliance组织制定的Java模块化规范,OSGI规范的核心部分是一个框架,其中定义了应用程序的生命周期模式和服务注册。基于OSGI框架定义了大量的OSGI服务:日志、配置管理,HTTP服务(运行Servlet)、XML解析、设备访问、软件包管理、许可管理、用户管理、IO连接、Jini和UPnP等。
OSGI中文社区:http://www.osgi.com.cn/ 
OSGI官方网站:https://www.osgi.org/
OSGI框架实现了一个优雅、完整和动态的组件模型,组件(bundle)无需重新引导可以被远程安装、启动、升级和卸载。
OSGI服务平台提供在多种网络设备上无需重启的动态改变构造的功能。
为了最小化耦合度和促使耦合度可管理,OSGI技术提供了一种面向服务的架构,使组件动态地发现对方。
OSGI联盟已经开发了如HTTP服务器、配置、日志、安全、用户管理、XML等很多公共功能标准组件接口。标准组件的兼容性插件实现可以从不同计算机服务提供商得到。
OSGi的主要职责就是为了让开发者能够创建动态化、模块化的Java系统。

2、OSGI规范的组成

OSGI规范包括以下子规范:
A、Framework规范(OSGI核心,提供一个安全的可管理的Java Framework来部署可扩展的Java服务)
B、Package Admin Service规范(管理不同的Bundle之间的引用关系。当Bundle更新或者卸载时判断是否有其它的服务正在使用当前的Bundle)
C、Start Level规范(定义了启动和停止一个OSGi Service Platform时,不同的Bundles的启动或者停止的先后顺序)
D、Permission Admin Service规范(Bundle是否许可执行另外的Bundle的代码)
E、URL Handlers Service规范(怎样注册URL Schema,如何将java.io.InputStream对象转换为特定的Java对象)
F、Log Service规范
G、Configuration Admin Service规范
H、Device Access Specification
I、User Admin Service Specification
J、IO Connector Service Specification
K、Http Service Specification
L、Preference Service Specification
M、Wire Admin Service Specification
N、XML Parser Service Specification
O、Metatype Specification
P、Service Tracker Specification
Q、Measurment and State Specification
R、Position Specification
S、Execution Environment Specfication

3、OSGI的优点

OSGI的优势主要表现在以下几个方面:

A、热插拔的插件体系结构

基于OSGI的应用程序可动态更改运行状态和行为。在OSGI框架中,每一个组件都是可热插拔的,因此,对某一特定的组件的修改并不会影响到容器中的所有组件,运行中的大部分组件依旧能照常工作,部署一个新的Bundle时也不需要重新启动服务器。

B、可复用性

OSGI框架本身可复用性极强,易于构建真正面向接口的程序架构,每一个组件(Bundle)都是一个独立可复用的单元。基于OSGI独特的特性,进行企业开发的时候,对于新的开发,可以从企业的组件库中精简出可复用的模块,量身定做新的组件,最大限度的利用了已有的资源,降低开发成本,增强企业的竞争力。

C、高效性,稳定性

OSGI是一个稳定而高效的系统。OSGI作为一个微核的系统,其核心只有为数不多的几个JAR包。基于OSGI框架的系统的低耦合性,其结构的优势性保证具体的某一个组件不至于影响到全局,更不会因为局部的错误导致全局系统的崩溃。

4、OSGI的缺点

A、每个组件(Bundle)都由单独的类加载器加载,与一些Java EE项目中使用比较多的框架整合比较困难,如Spring MVC、Struts2等。
B、目前OSGI框架提供的管理端不够强大,现在的管理端中仅提供了基本的组件状态管理、日志查看等功能,像动态修改系统级别的配置(config.ini)、动态修改组件的配置(Manifest.mf)、启动级别等功能都尚未提供。
C、采用OSGI作为规范的模块开发、部署方式自然给现有开发人员提出了新的要求,需要学习新的基于OSGI的开发方式。

二、OSGI框架原理

1、OSGI框架简介

OSGI框架从概念上可以分为三层:模块层、生命周期层和服务层。
Module Layer:模块层主要涉及包及共享的代码;
Lifecycle Layer:生命周期层主要涉及组件的运行时生命周期管理;
Service Layer:服务层主要涉及模块之间的交互和通信。

架构设计——OSGI规范

OSGI Framework是OSGI Service Platform规范的核心组成部分,提供了一个通用的、安全可管理的Java framework。通过OSGI Framework可以支持一种叫做组件的Service application的部署和扩展。
OSGI兼容设备可以下载并且安装OSGI组件,也可一旦不再需要的时候删除。组件安装后会注册一定数量的Services,并被由同一个Framework下的其它组件使用。
在一个动态扩展的的OSGI环境中,Framework管理组件的安装和更新,同时也管理组件和Services之间的依赖关系。
Framework提供给组件开发者必须的资源来在Java平台上开发,为开发的组件提供了代码动态加载的功能, 也使得开发者开发、部署一个大规模的Services变的很容易。
其次,Framework为Java组件开发者提供了简明一致的编程模型,简化了开发部署的复杂性。编程模型允许开发者将自己的接口规范绑定到OSGI环境中的Service。
一个一致的编程模型帮助开发者可以应付一些可估计的危急错误。Framework将会运行在不同的硬件环境上,但一致的接口确保组件可以运行在一致的服务接口上。

2、模块层

模块层是OSGi框架中最基础的部分。
OSGi的模块化,是通过为Jar包添加metadata 来定义哪些类该暴露,哪些类该隐藏,其控制单元叫做组件Bundle(jar包)。 
Bundle是以jar包形式存在的一个模块化物理单元,包含代码、资源文件和元数据(metadata),并且jar包的物理边界也是运行时逻辑模块的封装边界。
Bundle是OSGi中的基本组件,其表现形式仍然为Java概念中传统的Jar包。
通过META-INF目录下的MANIFEST.MF文件对其予以进一步的定义。
通常一个MANIFEST.MF文件的内容如下:

Manifest-Version: 1.0Bundle-ManifestVersion: 2Bundle-Name: UtilBundle-SymbolicName: com.ibm.director.la.utilBundle-Version: 1.0.0Bundle-RequiredExecutionEnvironment: J2SE-1.5Import-Package: org.osgi.framework;version="1.3.0"Export-Package: com.ibm.director.la.util;uses:="org.osgi.framework"Bundle-ClassPath: lib/junit.jar,

MANIFEST.MF文件存储的实际上是Bundle的元数据。
元数据的内容可以精确的定义Bundle的各种特征,同时能更好的对Bundle进行标识同时帮助用户对Bundle进行理解。

3、生命周期层

生命周期层在OSGi框架中属于模块层上面的一层,生命周期层的运作是建立在模块层的功能之上的。
生命周期层的主要功能是控制动态安装、开启、关闭、更新和卸载组件。
生命周期层能够从外部管理应用或者建立能够自我管理的应用(或者两者的结合),并且给了应用本身很大的动态性。 
Bundle的使用需要生命周期层的API与OSGi框架的生命周期层进行交互。

4、服务层

OSGi服务是注册到OSGi框架中的一个Java对象。注册的时候可以设置Service的属性。而在获取Service的时候可以根据属性进行过滤。
Bundle可以通过Bundle的上下文去注册Service或去查询Service。

三、模块层

1、模块化简介

模块化是将一个大型系统分解为多个较小的互相协作的逻辑单元,通过强制设定模块之间的逻辑边界来改善系统的维护性和封装性。

架构设计——OSGI规范

模块(module)定义了一个逻辑边界,模块本身精确地控制哪些类是完全被封装起来的,而哪些类需要暴露出来作为外部使用。开开发者可以轻松地将实现屏蔽在模块的内部,将公共API暴露在外部。

2、OSGI模块化与面向对象

面向对象编程中不会把所有功能都塞到同一个类。面向对象编程从问题域中发现多个事物,每个事物负责不同的功能,尽量做到高内聚和低耦合。面向对象的模块化粒度在类级别上。 
OSGi的模块化是通过为JAR包添加metadata来定义哪些类应该暴露哪些类又隐藏在包中,其控制可见性的粒度是在组件(JAR包)层面上的。 
OSGI模块化和面向对象思想所带来的能力都是通过控制可见性和可用性来保证高内聚和低耦合的,但粒度不同,一个是组件层面上,一个是对象层面上。

3、JAVA在模块化方面的局限

A、底层代码的可见性控制

Java提供了private、public、protected和package private(无修饰符)四种访问控制级别,提供了底层的OO数据封装特性。Packege具有分割代码的作用,但如果包中的代码要对包外可见,必须设置为public(protected,使用继承)。 
org.serc.helloworld.Hello.java:定义了一个接口

package org.serc.helloworld;  public interface Hello {      void sayHello();  }


org.serc.helloworld.impl.HelloImpl.java:实现了Hello接口

package org.serc.helloworld.impl;import org.serc.helloworld.Hello;public class HelloImpl implements Hello{    final String helloString;    public HelloImpl(String helloString){        this.helloString = helloString;    }    public void sayHello(){        System.out.println(this.helloString);    }}


org.serc.helloworld.main.Main.java文件:

package org.serc.helloworld.main;import org.serc.helloworld.Hello;import org.serc.helloworld.HelloImpl;public class Main{    final String helloString;    public static void main(String[] args){        Hello hello = new HelloImpl(“Hello,SERC!”);        hello.sayHello();    }}


三个文件分别在不同的包中。HelloImpl实现细节不应该暴露给其它包,但从Main.java的main方法中可以看出,为了创建Hello的实例,必须引入HelloImpl类,但是HelloImpl作为接口的实现细节,不应该暴露给使用者,违反了封装的原则。 
如果不想让HelloImpl暴露出来,需要做额外的工作保证既隐藏了实现细节,又能简单的创建一个实现了Hello接口的实例。可以通过多种方法(比如工厂模式)来实现,但增加了与应用本身功能无关的多余工作,是Java的局限。

B、classpath的局限

在classpath中加入jar包的时候,只给出文件路径,而jar包的版本、一致性、依赖性,无法在classpath中明确的设置或是从classpath中看出相应属性。 
classpath中的jar包是按序加载的,例如: 
classpath=/servlet2.2/servlet.jar;/servlet2.3/servlet.jar, 在实际应用的过程中,Java使用servlet2.2,而不是servlet2.3。如果在大型系统中团队分开开发时,各用各的servlet包,并且版本号不一样,在最后将开发结果合并的时候,用的是哪个版本的servlet包就很难搞清楚。 
即使classpath能注意到版本的问题,也没法精确指出依赖。开发者需要根据提示信息增加依赖包,直到虚拟机不运行到缺包异常为止。 

C、OSGI对JAVA局限的改善

OSGi中很好解决了JAVA的局限性:
包的可见性:OSGi通过引入包的可见性机制,能够完全控制一个包中的代码对哪些模块可见,而不仅仅局限于无差别的可见性,从而完善了Java的代码访问控制机制。
包的版本:OSGi通过为包增加版本信息,可以精确控制代码的依赖,保证代码的版本一致性,弥补了classpath的缺点。

4、组件Bundle简介

组件(bundle)是以jar包形式存在的一个模块化物理单元,包含了代码、资源文件和元数据(metadata),并且jar包的物理边界也是运行时逻辑模块的封装边界。 

架构设计——OSGI规范

在标准的jar包的manifest文件中添加一些组件(bundle)的模块化特征(metadata)后,jar包就变成了一个bundle。 
bundle和普通jar包最大的区别就在于元数据。
Bundle元数据的目的在于准确描述模块化相关的bundle特征,让OSGi框架对bundle进行各种处理工作(比如依赖解析,强制封装等),元数据主要有三部分: 

A、可读信息(可选)

OSGi标准定义了几个元数据条目帮助更好地理解和使用bundle,但所有的条目都不是必须的,并且不对模块化特性产生任何的影响,OSGi框架会完全无视可读信息。

Bundle-Name: SERC HelloworldBundle-Vendor: GR, SERCBundle-DocURL: http://elevenframework.orgBundle-Category: exampleBundle-Copyright: SERC  

B、bundle标识符(必须)

bundle标识符用于唯一的标识一个bundle。
早期的OSGi标准中并没有提供标识一个已知bundle的方法,直到OSGi R4标准,“唯一bundle标识符”被提出来。为了向后兼容,Bundle-Name不能用来作为标识符,否则就会增加维护向后兼容的工作,所以使用新的manifest属性Bundle-SymbolicName。

Bundle-SymbolicName: org.serc.helloworld

Bundle-Name是给用户读的,而Bundle-SymbolicName是给OSGi框架读的,让OSGi框架能够唯一标识一个bundle。 
只用一个Bundle-SymbolicName肯定是可以唯一标识一个bundle,但是随着时间的推移,bundle可能会有新版本,加入版本属性会让bundle的信息更加准确。

Bundle-Name: SERC HelloworldBundle-Vendor: GR, SERCBundle-DocURL: http://elevenframework.orgBundle-Category: exampleBundle-Copyright: SERC  

C、代码可见性(必须)

代码可见性用于定义内部与外部代码。在JavaSE中的jar包如果放在classpath里,那么jar包对classpath下的所有程序都是可见的,并且可见性不能改变。而OSGi标准定义了如下的属性用于描述代码的可见性:
Bundle-ClassPath:定义了形成bundle的所有代码所在的位置,Java 中的classpath是定义的jar包的位置,而Bundle-ClassPath属性描述的是bundle内部类在bundle中的路径。例如:

Bundle-ClassPath:.,other-classes/,embedded.jar

Export-Package:显式暴露需要和其它bundle共享的代码,每个包之间用逗号分隔,每个包可用修饰词来修饰包的其它特征。

Export-Package: org.serc.hellworld; vendor=”SERC”,org.serc.hellworld.impl; vendor=”Gou Rui” 

Import-Package:定义bundle所依赖的外部代码,其格式和Export-Package相同,并且也可以使用修饰词来修饰包。修饰词是用来限制所依赖包的范围的,类似过滤器,而不像Export-Package中用来声明包的特征。例如: 

Import-Package: org.serc.helloworld; vendor=”SERC”

四、生命周期层

1、生命周期层简介

生命周期层在OSGi框架中属于模块层上面的一层,其运作建立在模块层的功能之上的。生命周期层主要功能是让开发者能够从外部管理应用或者建立能够自我管理的应用(或者两者的结合),并且给应用本身很大的动态性。

2、生命周期管理简介

一般来说,程序(或者程序的一部分)都一定服从某种生命周期。软件的生命周期有4个典型的阶段,如下:

架构设计——OSGI规范

如果正在创建一个应用,首先得安装(install)应用;当应用的所有依赖都满足,可以执行应用;如果应用不再需要,可以停止(stop);一段时间后,可能需要更新(update)应用的版本;最终,可能会移除(remove)应用。 
通过在外部或者内部对应用进行操作,完成对应用的生命周期管理过程。对于非模块化应用,操作是以整个应用为对象的;对于模块化应用,可以有更细粒度(针对应用中的某个模块)的生命周期管理。

3、OSGi bundle生命周期

要想使用组件(bundle),就得使用生命周期层的API与OSGi框架的生命周期层进行交互。
OSGi框架的核心并没有强制使用任何特定的API交互机制(比如命令行,GUI,或者XML配置文件等),只是单纯的Java API而已,开发者可以任意创造出自己想要的交互模式,保证了框架的灵活性。 
在标准的Java编程中,会通过将jar包放到classpath中来使用jar包,而bundle不同。Bundle只有在被安装(install)到一个OSGi框架的运行实例中才能用起来,并且OSGi框架支持对bundle完整的生命周期管理,并且支持这些管理操作在应用执行完成,其动态性可见一斑。 
Bundle生命周期的状态转移图如下:

架构设计——OSGI规范

可以通过Bundle的getState方法来获得bundle的当前状态。
Starting和Stopping状态是暂态,在持续一会儿后就会自动转移到下一个状态,不需要转移条件。

4、生命周期层API

生命周期层的API主要由BundleActivator、BundleContext、Bundle三个核心接口组成。

A、BundleActivator

BundleActivator让开发者能够捕捉bundle的start和stop事件,并对作出自定义的反应。 
BundleActivator的接口定义如下:

public interface BundleActivator {    public void start(BundleContext context) throws Exception;    public void stop(Bundlecontext context) throws Exception;}

如果一个类实现了BundleActivator接口,那么类就成为一个Activator。但有实现是不够的,要让OSGi框架知道Activator的存在。所以还需要在MANIFEST文件中添加如下一项属性: 

Bundle-Activator:org.demo.Activator

当bundle启动(start)的时候,OSGi框架就会调用Activator的start方法,同样的也适用与stop方法。 
并不是每个bundle都需要一个activator,有时候bundle只是为了和其它bundle分享代码,而并不需要在启动和停止的时候做出多余的动作。是否使用BundleActivator借口,需要具体问题具体分析。 

B、BundleContext

BundleContext是bundle在框架中的执行时上下文,提供了与框架进行交互的方法。 
BundleContext接口中的方法主要分为两类,一类与部署和生命周期管理相关,另一类是关于利用服务层进行bundle间交互的方法。

public interface BundleContext {    String getProperty(String key);    Bundle getBundle();    Bundle installBundle(String location, InputStream input) throws BundleException;    Bundle installBundle(String location) throws BundleException;    Bundle getBundle(long id);    Bundle[] getBundles();    void addBundleListener(BundleListener listener);    void removeBundleListener(BundleListener listener);    void addFrameworkListener(FrameworkListener listener);    void removeFrameworkListener(FrameworkListener listener);}

BundleContext接口对于与其相关的bundle来说都是唯一的执行上下文,并且只有在bundle是属于active状态的时候执行时上下文才是有意义的,即在start方法被调用和stop方法被调用的两个时间点之间。如果一个bundle没有处于active时间段,但组件的bundlecontext对象被使用,框架会抛出异常。 
框架使用这个上下文对象还有一个目的就是为了bundle的安全和资源分配,所以BundleContext对象应该被当做私有对象,不应该被随意在bundle之间传递。

C、Bundle

Bundle在逻辑上表示一个bundle,OSGi环境中的一个物理bundle对应一个bundle对象。bundle对象中包含了bundle的基本信息和bundle声明周期的控制接口。 
在BundleContext接口中,getBundle方法可以得到Bundle对象。 
对于每个被安装到框架中的bundle,框架都创建一个Bundle对象在逻辑上表达之。Bundle接口中定义了bundle生命周期管理的方法:

public interface Bundle {    BundleContext getBundleContext();    long getBundleId();    Dictionary getHeaders();    Dictionary getHeaders(String locale);    String getLocation();    int getState();    String getSymbolicName();    Version getVersion();    void start(int options) throws BundleException;    void start() throws BundleException;    void stop(int options) throws BundleException;    void stop() throws BundleException;    void update(InputStream input) throws BundleException;    void update() throws BundleException;    void uninstall() throws BundleException;}

大部分OSGi框架的实现都是将locatioin解释为指向OSGi bundle的一个URL,在需要的时候就会通过URL将bundle下载到框架中来安装使用。但OSGi标准没有规定location的形式必须是URL,而且URL也并不是非要不可的,还可以通过输入流(Input Stream)来安装bundle。 
bundle不能自己改变自己的状态,比如说一个active的bundle不能stop自己,stop自己就会抛出异常。

五、服务层

1、服务简介

服务是服务提供者和服务使用者之间的一个契约。使用者一般不关心服务实现的细节,只关心是否满足契约(服务应该提供什么功能,满足什么格式)。使用服务的过程包含了发现服务和达成协议的形式,需要通过服务的标志性特征来找到对应的服务。

2、OSGI服务层

服务层是OSGi框架中最上面的一层,服务层带来了更多的动态性,并且使用了面向服务编程模型。当一个bundle发现开始使用OSGi中的一个服务后,服务可能在任何的时候改变或者是消失。 
OSGi框架有一个中心化的注册表,注册表遵从publish-find-bind模型: 

架构设计——OSGI规范

一个提供服务的bundle可以发布POJO作为服务的实体;一个使用服务的bundle可以通过注册表找到和绑定服务。 
可以通过BundleContext接口来完成服务的发布、发现、绑定,如下:

public interface BundleContext {       void addServiceListener(ServiceListener listener, String filter) throws InvalidSyntaxException;      void addServiceListener(ServiceListener listener);      void removeServiceListener(ServiceListener listener);      ServiceRegistration registerService(String[] clazzes, Object service, Dictionary properties);       ServiceRegistration registerService(String clazz, Object service, Dictionary properties);      ServiceRegistration[] getServiceReferences(String clazz, String filter) throws InvalidSyntaxException;      ServiceRegistration[] getAllServiceReferences(String clazz, String filter) throws InvalidSyntaxException;      ServiceReference getServiceReference(String clazz);      Object getService(ServiceReference reference);      boolean ungetService(ServiceReference reference);  }

3、发布服务

为了让其它bundle能发现服务,必须在发布服务前对其进行特征描述。服务特征包括接口的名字(可以是名字的数组),接口的实现和一个可选的java.util.Dictionary类型的元数据信息。示例如下:

String[] interfaces =  new String[]{StockListing.class.getName(), StockChart.class.getname()};  Dictionary metadata =  new Properties();  metadata.setProperty("name", "LSE");  metadata.setProperty("currency", Currency.getInstance("GBP"));  metadata.setProperty("country", "GB");  ServiceRegistration registration = bundleContext.registerService(interfaces, new LSE(), metadata);

上述代码中,通过ServiceRegistration对象可以更新服务的元数据: 

registration.setProperties(newMetadata);

可以直接将服务注销: 

registration.unregister();

ServiceRegistration对象不能和其它Bundles共享,因为ServiceRegistration对象和发布服务的bundle的生命周期相互依存。如果bundle已经不在框架执行环境中存在,那么ServiceRegistration对象也不应该存在。 
代码中的参数new LSE()是一个POJO,不需要实现任何OSGi类型或者使用标注,只要满足服务约定就可以。 
如果在删除发布的服务前bundle以及停止,框架会帮助删除服务。

4、发现服务

可以根据服务约定从注册表中找到正确的服务。发现服务并获得其引用的接口如下:

ServiceReference reference = bundleContext.getServiceReference(StockListing.class.getName());  

reference是服务对象的间接引用,不直接使用服务对象是为了将服务的使用和服务的实现进行解耦,将服务注册表作为两者的中间人,达到跟踪和控制服务的目的,同时还可以在服务消失后通知使用者。 
ServiceReference可以在bundle之间互享,与使用服务的bundle的生命周期无关。 
在getServiceReference方法中,选择service的默认优先级是先选择service.rank最高的,在rank相等的情况下选择最早在框架中注册的。除了默认的规则,还可以在getServiceReferences中通过添加过滤参数(作为调用该方法的第二个参数)来做一些筛选。

ServiceReference[] references = bundleContext.getServiceReferences(StockListing.class.getName(),                                 "(&(currency=GBP)(objectClass=org.example.StockChart))");

匹配参数是一个字符串,字符串的格式属于LDAP查询格式,在RFC1960标准中有完整的描述。 
字符串中等号左边的内容是元数据(Dictionary)中的左值,通过左值对应的右值与服务的元数据进行匹配。匹配示例如下: 
属性匹配:

(name=John Smith) (age>=20) (age<=65) 

模糊匹配: 

(name~=johnsmith)

通配符匹配: 

(name=Jo*n*Smith*)

判断某个属性是否存在: 

(name=)

条件与: 

(&(name=John Smith)(occupation=doctor))

条件或: 

(|(name~=John Smith)(name~=Smith John))

*条件非: ** 

(!(name=John Smith))

5、绑定和使用服务

发现服务后,使用服务之前,必须从注册表中绑定实现的服务。

StockListing listing = (StockListing) bundleContext.getService(reference);

返回的POJO实例和在注册表中注册的实例是同一个。 
每次使用getService方法的时候,注册表会将对应服务的使用次数加1,同时会记录谁在使用该服务。如果不想使用服务的,注销服务。

bundleContext.ungetService(reference); listing = null; 

六、OSGI的实现

1、OSGI的具体实现

OSGI是OSGi Alliance组织制定的Java模块化规范,但OSGI联盟并没有给出OSGI容器的实现,具体实现由第三方厂商完成,目前使用较多的OSGI容器有Apache Felix、Equinox、Spring DM。

2、OSGI的JAVA实现

A、Apache Felix

Apache Felix是Apache旗下的一个OSGi框架,Felix是一个OSGi版本4规范的Apache实现。Apache Felix提供的服务几乎涵盖了全部的OSGi 4.2的标准,除此之外还提供了一些非标准的功能,例如iPOJO。Apache Felix框架本身非常紧凑,只需要3个包加一个shell就可以运行,无论是开发还是Debug都非常简便。

B、Equinox

Equinox是Eclipse旗下的OSGi框架,本身也被Eclipse采用,是Eclipse的PDE开发环境的底层。Equinox本身也是相当的全面的框架,提供的功能不比Felix少多少。Equinox被当做开发Eclipse Plugin的应用较多,如果要开发一个Web程序,就会感到功能和文档不够全面。Equinox最大的优势在于和Eclipse结合紧密,只要安装了PDE,就已经有了Equinox,可以方便的在Eclipse里设置开发的Bundle,启动、部署等操作也异常简单,而且有专门的Debug界面。

C、Spring DM

Spring DM是Spring旗下的OSGi框架,Spring DM的最大特点是结合了Spring框架。

D、Knopflerfish

Knopflerfish是OSGi的先行者,是一个相当标准OSGi框架,提供了绝大多数标准功能。

3、OSGI的C++实现

A、CTK Plugin Framework

CTK是基于Qt开发的支持生物医学影像计算的开源项目。
CTK中的CTK Plugin Framework模块借鉴了OSGI的思想,并实现了几乎完整的OSGI框架API。

B、C++ Micro Services

C++ Micro Services是基于OSGI思想的用于创建和管理模块化软件系统的C++库。

4、OSGI的C实现

Apache Celix是基于C/C++的OSGI规范实现,提供了一个使用组件和面向服务编程(SOP)开发模块化应用的框架。
Apache Celix主要使用C语言开发,为了支持C++,以库的形式增加了抽象。
官方网站:http://celix.apache.org/

七、OSGI应用实例

我们需要解决一下几问题:

1.如何正确的理解和认识OSGI技术?

我们来回到我们以前的某些开发场景中去,假设我们使用SSH(struts+spring+hibernate)框架来开发我们的Web项目,我们做产品设计和开发的时候都是分模块的,我们分模块的目的就是实现模块之间的“解耦”,更进一步的目的是方便对一个项目的控制和管理。
我们对一个项目进行模块化分解之后,我们就可以把不同模块交给不同的开发人员来完成开发,然后项目经理把大家完成的模块集中在一起,然后拼装成一个最终的产品。一般我们开发都是这样的基本情况。

那么我们开发的时候预计的是系统的功能,根据系统的功能来进行模块的划分,也就是说,这个产品的功能或客户的需求是划分的重要依据。

但是我们在开发过程中,我们模块之间还要彼此保持联系,比如A模块要从B模块拿到一些数据,而B模块可能要调用C模块中的一些方法(除了公共底层的工具类之外)。所以这些模块只是一种逻辑意义上的划分。

最重要的一点是,我们把最终的项目要去部署到tomcat或者jBoss的服务器中去部署。那么我们启动服务器的时候,能不能关闭项目的某个模块或功能呢?很明显是做不到的,一旦服务器启动,所有模块就要一起启动,都要占用服务器资源,所以关闭不了模块,假设能强制拿掉,就会影响其它的功能。

以上就是我们传统模块式开发的一些局限性。

我们做软件开发一直在追求一个境界,就是模块之间的真正“解耦”、“分离”,这样我们在软件的管理和开发上面就会更加的灵活,甚至包括给客户部署项目的时候都可以做到更加的灵活可控。但是我们以前使用SSH框架等架构模式进行产品开发的时候我们是达不到这种要求的。

所以我们“架构师”或顶尖的技术高手都在为模块化开发努力的摸索和尝试,然后我们的OSGI的技术规范就应运而生。

现在我们的OSGI技术就可以满足我们之前所说的境界:在不同的模块中做到彻底的分离,而不是逻辑意义上的分离,是物理上的分离,也就是说在运行部署之后都可以在不停止服务器的时候直接把某些模块拿下来,其他模块的功能也不受影响。

由此,OSGI技术将来会变得非常的重要,因为它在实现模块化解耦的路上,走得比现在大家经常所用的SSH框架走的更远。这个技术在未来大规模、高访问、高并发的Java模块化开发领域,或者是项目规范化管理中,会大大超过SSH等框架的地位。

现在主流的一些应用服务器,Oracle的weblogic服务器,IBM的WebSphere,JBoss,还有Sun公司的glassfish服务器,都对OSGI提供了强大的支持,都是在OSGI的技术基础上实现的。有那么多的大型厂商支持OSGI这门技术,我们既可以看到OSGI技术的重要性。所以将来OSGI是将来非常重要的技术。

但是OSGI仍然脱离不了框架的支持,因为OSGI本身也使用了很多spring等框架的基本控件(因为要实现AOP依赖注入等功能),但是哪个项目又不去依赖第三方jar呢?

2.OSGI技术对我们项目的开发有什么帮助?

(1)项目展示

接下来我们同过项目代码来展示一下OSGI的魅力:
我们先不要去急着理解如何使用OSGI,我们通过一个项目先来看一下OSGI的效果。
(以下工程代码是网上教学视频中的样例,源码我这里是没有的)
(提前说一下:我们要学习的重点就是我们这个购物网站如何结合OSGI技术,使得项目更加的灵活可控,而购物网站本身并不是重点。)
首先在Eclipse中先打开我们的单服务器版本的项目:

https://img-blog.csdn.net/20160310173347989?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center

启动成功:

https://img-blog.csdn.net/20160310173401567?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center

这是一个Web项目,我们打开浏览器看一下效果:

https://img-blog.csdn.net/20160310173429161?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center

可以看出是一个网上购物的项目。

我们来看一下我们基于OSGI技术的项目和我们一般的项目有什么区别。
首先介绍一下这个项目的模块:

1.大类展示

https://img-blog.csdn.net/20160310173537333?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center
2.小类展示(大类的子产品)

https://img-blog.csdn.net/20160310173552836?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center

点进去之后就是产品的具体信息

https://img-blog.csdn.net/20160310173601523?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center

3.购物车
没买东西是空的:

https://img-blog.csdn.net/20160310173611224?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center

买完之后:

https://img-blog.csdn.net/20160310173619274?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center

4.商品管理(上架、下架)

可以看到,这个项目和我们平常开发的项目没有什么不同(我知道界面很简陋= =),重点是它的启动和加载过程。

https://img-blog.csdn.net/20160310173629587?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center

(2)关于服务器

我们是通过动态加载,也就是“热部署”来启动我们的项目的。就是说,我们这个项目把它放在Web容器中之后,我们可以将某些功能给它拿下来,而且拿下来的时候不会对其他模块造成影响。

我们以前运行tomcat的时候,启动一下服务器,将Web项目一次性装载完毕,控制台会出现类似这种信息:

https://img-blog.csdn.net/20160310173643287?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center
但是我们启动这个项目的时候并不是这样:

https://img-blog.csdn.net/20160310173703556?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center
那么我们没有用tomcat和jBoss,那是如何部署和启动Web项目的呢?不可能没有Web服务器中间件的啊?这里告诉大家,OSGI技术里面也是内嵌了一个Web服务器的,就是jetty。

 

我们打开这个项目的Run Configuration配置窗口,看一下运行这个项目所需要的插件包:

https://img-blog.csdn.net/20160310173803120?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center

可以看到,除了一些Web项目需要的jar包,还是有jetty的存在的。所以用到的服务器是jetty,不再是tomcat。


大家可能还是比较熟悉tomcat,对于jetty不是太熟悉,那么我们简单介绍一下jetty:
jetty也是一个比较优秀的Web容器,在某些性能方面要比tomcat强大的多(如高并发,长连接)。而且它的整个结构比tomcat轻巧很多(tomcat更臃肿),具体区别大家可以去网上自己看一下。


(3)运行模式和插件

我们接下来正式看一下此项目在OSGI下的运行模式:
我们在启动的时候,加载了四个模块,分别是:

https://img-blog.csdn.net/20160310173838100?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center

按照模块化的思想他们就是四个对应的功能模块。
他们对应的四个功能模块的工程代码我们可以在Eclipse中看到:

我们看一下我们的启动配置(依然打开是Run Configuration配置窗口):

https://img-blog.csdn.net/20160310173851028?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center

https://img-blog.csdn.net/20160310173908287?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center

配置分为“WorkSpace”和“Target Platform”,分别是我们工作空间(我们自己写的项目模块和工具类)的插件和运行平台(一些依赖jar的配置)的插件,两者结合启动我们的项目就会正常运行。

我们启动项目之后,在控制台输入指令“ss”,就会出现我们所有加载的插件的运行情况:

https://img-blog.csdn.net/20160310173922491?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center
一启动的时候,它会首先加载Eclipse的OSGI插件(Eclipse本身也是一种OSGI的容器):

我们打开我们的Eclipse安装目录,然后找到plugins文件夹,可以看到Eclipse所有的插件:

https://img-blog.csdn.net/20160310173950670?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center

可以看到有文件夹形式的,有jar形式的插件。

我们怎么去理解插件呢?
插件其实就是被开发工具或OSGI容器管理和配置起来的jar包。  

我们随便打开一个文件夹类型的插件,可以看到:

https://img-blog.csdn.net/20160310174004498?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center

可以看到里面除了lib之外还有其它东西,然后有一个“OSGI-INF”文件夹。且不管它是什么,这都足以说明我们的Eclipse就是一个OSGI容器。

(4)热部署和热启动

我们接下来回到重点,在我们启动的过程中,我们不停止运行,然后去停掉其中的一个模块:

假如我们要停掉“管理”模块:

https://img-blog.csdn.net/20160310174017304?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center

也就是停掉id为22的插件

结果:

https://img-blog.csdn.net/20160310174029225?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center
然后刷新我们的网站主页面:

https://img-blog.csdn.net/20160310174125101?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center

发现我们的“管理”模块消失了!

这个模块的消失并不是javascript的技术,而是一种服务器技术,我们是通过服务器内部把它动态卸载掉的。

我们的管理模块去掉之后,网站的其它功能不受任何影响。至此我们的服务器没有进行任何的暂停或关闭。

我们再停掉“购物车”模块:

https://img-blog.csdn.net/20160310174849760?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center

效果:

https://img-blog.csdn.net/20160310174743931?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center

其它模块依旧不受影响。

我们关闭了两个模块,现在输入ss看一下所有插件和模块的运行情况:

https://img-blog.csdn.net/20160310174908917?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center

可以看到我们的两个模块处于RESOLVED状态,也就是待解决状态。

https://img-blog.csdn.net/20160310174936589?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center

当然我们也可以将我们的模块在服务器开启状态下部署上去:
如我们启动购物车模块:

https://img-blog.csdn.net/20160310174936589?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center
发现购物车回来了:

https://img-blog.csdn.net/20160310174946605?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center
这就是所谓的热部署,即是这个项目把它放在Web容器中之后,我们可以将某些功能给它拿下来,而且拿下来的时候不会对其他模块造成影响。


通过购物网站这个项目让大家真实的感受一下OSGI这个技术在项目开发和管理的一些强大的功能。
————————————————
版权声明:本文为CSDN博主「荷叶清泉」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/heyeqingquan/article/details/86491707

 

原网站

版权声明
本文为[morpheusWB]所创,转载请带上原文链接,感谢
https://my.oschina.net/morpheusWB/blog/5548339