Felix iPOJO介绍
在OSGI框架中,Felix iPOJO意在简化面向服务编程。iPOJO的全称是inject POJO。Felix iPOJO提供了一种新的开发OSGI服务组件的方式,主要目标是简化服务组件的实现,使环境的动态变化对服务组件的实现透明。Felix iPOJO可以让开发者更好的分离功能代码(比如:POJO)和非功能代码(比如:依赖管理、服务的提供、配置等)。
Felix iPOJO服务组件简介
服务组件可以提供和(或者)依赖一个或者多个服务,服务(service)是一个实现了某个java接口的对象。另外Felix iPOJO提供了一个回调的机制,把各种状态变化通知给组件。
组件是Felix iPOJO的核心概念,一个组件描述了服务的依赖、提供哪些服务以及哪些回调功能;这些信息配置在组件描述中(metadata)。Felix iPOJO中的第二个重要的概念是组件的实例,一个组件实例是一个特殊版本的组件。通过合并组件描述和组件实例的配置,Felix iPOJO可以在运行时管理组件。比如:管理其生命周期、服务的依赖注入、开放服务、发现需要的服务。
Felix iPOJO Hello World(简单的示例)
下面的示例将阐述怎样使用Felix iPOJO的核心功能。示例中包含两个组件,一个提供Hello服务,一个需要任意数量的Hello服务。这些组件通过maven打包,被放在三个不同的bundle中。
- hello.service包含服务接口Hello的定义
- hello.impl包含一个组件,它实现了Hello服务接口,具有了提供Hello服务的能力
- hello.client包含了一个组件的消费者,它依赖一个或者多个Hello服务
这个示例的代码可以在这里下载
1. 准备工作
开始前,先下载并安装配置maven。
2. 第一个工程:服务接口(hello.service)
这是一个maven工程,工程仅有一个Hello接口类,类的路径为:src/main/java/ipojo/example/hello/Hello.java
|
public interface Hello { /** * Returns a message like: "Hello $user_name". * @param name the name * @return the hello message */ String sayHello(String name); } |
在工程目录中,pom.xml文件必须包含了maven-bundle-plugin插件,因为这个工程最终mvn install生成的jar包必须是符合osgi规范的bundle包,同时因为Hello这个类是接口类,需要提供给其他的bundle import,所以在pom.xml中需要export这个接口类所在的包ipojo.example.hello。如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
|
<project> <modelVersion>4.0.0</modelVersion> <packaging>bundle</packaging> <groupId>ipojo.example</groupId> <artifactId>hello.service</artifactId> <version>1.0.0</version> <name>Hello Service</name> <build> <plugins> <plugin> <groupId>org.apache.felix</groupId> <artifactId>maven-bundle-plugin</artifactId> <version>2.0.1</version> <extensions>true</extensions> <configuration> <instructions> <Bundle-SymbolicName> ${pom.artifactId} </Bundle-SymbolicName> <Export-Package> ipojo.example.hello </Export-Package> </instructions> </configuration> </plugin> </plugins> </build> </project> |
在这个工程中使用mvn clean install命令执行后,如果结果是SUCCESS,那么maven repository里面应该有这个jar包了,另外工程的target目录下应该也有打包好的jar包。这个包将在接下来的两个工程中被依赖上。
3. 第二个工程:服务提供者(hello.impl)
这个maven工程(hello.impl)里面有一个实现了Hello接口的类,这个工程依赖了第一个工程hello.service,这个类路径为:src/main/java/ipojo/example/hello/impl/HelloImpl.java
|
public class HelloImpl implements Hello { /** * Returns an 'Hello' message. * @param name : name * @return Hello message * @see ipojo.example.hello.Hello#sayHello(java.lang.String) */ public String sayHello(String name) { return "hello " + name; } } |
为了管理这个组件,iPOJO需要一些描述信息来描述这个组件提供了Hello这个接口的服务。iPOJO的描述文件(metadata.xml)在hello.impl工程的根目录下,这个描述文件的内容如下:
|
<?xml version="1.0" encoding="UTF-8"?> <ipojo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="org.apache.felix.ipojo http://felix.apache.org/ipojo/schemas/CURRENT/core.xsd" xmlns="org.apache.felix.ipojo"> <component classname="ipojo.example.hello.impl.HelloImpl" name="HelloProvider"> <provides /> </component> <instance component="HelloProvider" name="HelloService" /> </ipojo> |
‘component’元素的‘classname’属性是为了告诉iPOJO这个组件的实现类是哪个;‘name’属性是给这个组件取了一个名字,如果没有属性,那么它的值就是属性‘classname’的值;这个示例中‘component’元素下有‘provides’元素,这是为了告诉iPOJO需要管理这个组件怎样公布到osgi容器中,在上面的示例中,‘provides’元素没有属性,那么iPOJO会为这个组件实现的所有的服务接口都注册上这个组件,当然‘provides’元素可以指定一个或者多个服务接口。
‘instance’元素是告诉iPOJO,在这个bundle在osgi中启动的时候创建一个组件的实例。
这个maven的工程的pom.xml文件包含以下的内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
|
<project> <modelVersion>4.0.0</modelVersion> <packaging>bundle</packaging> <groupId>ipojo.example</groupId> <artifactId>hello.impl</artifactId> <version>1.0.0</version> <name>Hello Service Provider</name> <dependencies> <dependency> <!--Compilation (i.e. class) dependency on the service interface --> <groupId>ipojo.example</groupId> <artifactId>hello.service</artifactId> <version>1.0.0</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.felix</groupId> <artifactId>maven-bundle-plugin</artifactId> <version>2.0.1</version> <extensions>true</extensions> <configuration> <instructions> <Bundle-SymbolicName>${pom.artifactId}</Bundle-SymbolicName> <Private-Package>ipojo.example.hello.impl</Private-Package> </instructions> </configuration> </plugin> <plugin> <groupId>org.apache.felix</groupId> <artifactId>maven-ipojo-plugin</artifactId> <version>1.6.0</version> <executions> <execution> <goals> <goal>ipojo-bundle</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </project> |
其中<Private-Package>ipojo.example.hello.impl</Private-Package>是指HelloImpl类所在包不被export出去,其他bundle不能import到这个包下面的类。配置好后,使用mvn clean install命令打包。
4. 第三个工程:服务消费者(hello.client)
这个工程中有一个类HelloClient类,这个类中有一个Hello数组类型的field,这个field需要让iPOJO自动的注入,当有实现了Hello服务接口的组件实例被公布后,iPOJO会自动的把实例添加到field中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
|
package ipojo.example.hello.client; import ipojo.example.hello.Hello; public class HelloClient implements Runnable { /** * Delay between two invocations. */ private static final int DELAY = 10000; /** * Hello services. * Injected by the container. * */ private Hello[] m_hello; /** * End flag. * */ private boolean m_end; /** * m_name field. * */ private String m_name; /** * Run method. * @see java.lang.Runnable#run() */ public void run() { while (!m_end) { try { invokeHelloServices(); Thread.sleep(DELAY); } catch (InterruptedException ie) { /* will recheck end */ } } } /** * Invoke hello services. */ public void invokeHelloServices() { for (int i = 0; i < m_hello.length; i++) { System.out.println(m_hello[i]().sayHello(m_name)); } } /** * Starting. */ public void starting() { Thread thread = new Thread(this); m_end = false; thread.start(); } /** * Stopping. */ public void stopping() { m_end = true; } } |
在上面的代码中,服务消费者HelloClient创建了一个线程间歇地调用可用的Hello类型的服务实例,在至少一个Hello类型的服务实例出现时,这个线程会由iPOJO的回调机制启动。代码中组件的实现对象m_hello被直接使用,且m_hello是一个数组。在iPOJO中服务数组代表了一种依赖聚合或者多个合适的依赖,当然iPOJO也支持单一的简单的对象注入,只需要把代表数组的中括号去掉即可。在一个服务的组件类中声明了一个属性,那么这个组件类的其他部分代码就可以直接使用这个属性,因为这个属性会被自动初始化,比如:m_hello[i].sayHello(“world”)。
iPOJO也是同步管理服务的。服务的调用不需要使用同步语句块。这种同步机制是以线程为单位的,每个访问服务的方法都在线程上附带了一个给定的服务实例,这样线程就能看见相同服务实例,甚至是嵌套的方法调用。线程看不到不同的服务实例,除非它从开始进入的方法中完全退出。
组件提供了两个回调方法,用来激活和钝化服务实例,比如starting()和stopping()。当相关的某个组件的状态有变更时会通知组件来回调这些回调方法。在iPOJO中,组件状态要么是INVALID(比如:不是所有的组件约束都满足时),要么是VALID(比如:所有的组件约束都满足了)。在这个例子中,starting回调方法创建和启动了一个线程,stopping回调方法停止了这个线程。在这个组件的metadata(描述信息)会告诉iPOJO,当组件的状态变为VALID或者INVALID的时候,要回调starting或者stopping方法。
iPOJO描述文件命名为metadata.xml,包含如下内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
<?xml version="1.0" encoding="UTF-8"?> <ipojo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="org.apache.felix.ipojo http://felix.apache.org/ipojo/schemas/CURRENT/core.xsd" xmlns="org.apache.felix.ipojo"> <component classname="ipojo.example.hello.client.HelloClient"> <requires field="m_hello" /> <callback transition="validate" method="starting" /> <callback transition="invalidate" method="stopping" /> <properties> <property field="m_name" name="hello.name" /> </properties> </component> <instance component="ipojo.example.hello.client.HelloClient"> <property name="hello.name" value="world" /> </instance> </ipojo> |
上面的xml配置中,组件component元素有classname属性,用来标明这个组件的实现类是哪个;requires元素描述了这个组件的m_hello字段依赖了Hello服务;callback元素描述了当组件的状态改变时哪个方法会被回调;instance元素会让iPOJO创建一个组件的实例(注意:这里没有配置instance元素的name属性,iPOJO会自动给这个instance设置一个默认的name)。instance元素下面有子元素property,它配置了组件HelloClient的m_name字段的值为字符串“world”。
最后,pom.xml的配置如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
|
<project> <modelVersion>4.0.0</modelVersion> <packaging>bundle</packaging> <groupId>ipojo.example</groupId> <artifactId>hello.client</artifactId> <version>1.0.0</version> <name>Hello Client</name> <dependencies> <dependency> <!-- 编译时依赖了服务接口 --> <groupId>ipojo.example</groupId> <artifactId>hello.service</artifactId> <version>1.0.0</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.felix</groupId> <artifactId>maven-bundle-plugin</artifactId> <version>2.0.1</version> <extensions>true</extensions> <configuration> <instructions> <Bundle-SymbolicName>${pom.artifactId}</Bundle-SymbolicName> <Private-Package>ipojo.example.hello.client</Private-Package> </instructions> </configuration> </plugin> <plugin> <groupId>org.apache.felix</groupId> <artifactId>maven-ipojo-plugin</artifactId> <version>1.6.0</version> <executions> <execution> <goals> <goal>ipojo-bundle</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </project> |
在dependencies中有服务接口的依赖,因为HelloClient组件中依赖了这个接口Hello类。pom.xml配置完成后,即可执行下面的命令对服务消费者进行打包:
mvn clean install
如果打包成功,在target目录下回看一个jar包,这个jar包既可以当做普通的jar包,也可以当做bundle包,因为它与普通jar包唯一的不同就是MENIFEST.MF文件中多了一些OSGI的描述信息。
5. 运行实例
要运行这个实例,首先需要启动Felix。可以先下载Felix Framework Distribution,然后通过下面的命令来启动Felix:
java -jar bin/felix.jar
你可以在控制台中使用”ps”命令来检查已经安装的bundle:
-> ps
START LEVEL 1
ID State Level Name
[ 0] [Active ] [ 0] System Bundle (2.0.5)
[ 1] [Active ] [ 1] Apache Felix Bundle Repository (1.4.3)
[ 2] [Active ] [ 1] Apache Felix iPOJO (1.6.0)
[ 3] [Active ] [ 1] Apache Felix iPOJO Arch Command (1.6.0)
[ 4] [Active ] [ 1] Apache Felix Shell Service (1.4.2)
[ 5] [Active ] [ 1] Apache Felix Shell TUI (1.4.1)
接下来安装服务接口(hello.service)、服务提供者(hello.impl)、服务消费者(hello.client):
start file:../hello.service/target/hello.service-1.0.0.jar
start file:../hello.impl/target/hello.impl-1.0.0.jar
start file:../hello.client/target/hello.client-1.0.0.jar
以上的指令输入后,hello.service-1.0.0.jar的id为6,hello.impl-1.0.0.jar的id为7,hello.client-1.0.0.jar的id为8。在启动了服务提供者(hello.impl)后,服务消费者(hello.client)会被自动激活,由于服务消费者的instance实例在创建时就注入了m_name字段的值为“world”,因此控制台会循环输出“hello world”:
-> hello world
hello world
hello world
hello world
如果此时在控制台执行“stop 7”的指令,则服务消费者hello.client会自动钝化,并停止输出“hello world”,因为它依赖的服务实例对象(hello.impl的HelloService)变得不可用了。如果此时再重新“start 7“(或者重新安装部署实现了Hello接口的其他bundle),那么服务消费者又会变得可用。这两个步骤的效果如下:
-> stop 7
-> arch
Instance ArchCommand -> valid
Instance ipojo.example.hello.client.HelloClient-0 -> invalid
-> arch -instance ipojo.example.hello.client.HelloClient-0
instance name=”ipojo.example.hello.client.HelloClient-0″
component.type=”ipojo.example.hello.client.HelloClient”
state=”invalid” bundle=”8″
object name=”ipojo.example.hello.client.HelloClient@137c60d”
handler name=”org.apache.felix.ipojo.handlers.dependency.DependencyHandler” state=”invalid”
requires aggregate=”true” optional=”false” state=”resolved” specification=”ipojo.example.hello.Hello”
handler name=”org.apache.felix.ipojo.handlers.lifecycle.callback.LifecycleCallbackHandler” state=”valid”
handler name=”org.apache.felix.ipojo.handlers.architecture.ArchitectureHandler” state=”valid”
-> start 7
hello world
-> arch
Instance ArchCommand -> valid
Instance ipojo.example.hello.client.HelloClient-0 -> valid
Instance HelloService -> valid
-> arch -instance ipojo.example.hello.client.HelloClient-0
instance name=”ipojo.example.hello.client.HelloClient-0″
component.type=”ipojo.example.hello.client.HelloClient”
state=”valid” bundle=”8″
object name=”ipojo.example.hello.client.HelloClient@137c60d”
handler name=”org.apache.felix.ipojo.handlers.dependency.DependencyHandler” state=”valid”
requires aggregate=”true” optional=”false” state=”resolved” specification=”ipojo.example.hello.Hello”
uses service.id=”38″ instance.name=”HelloService”
handler name=”org.apache.felix.ipojo.handlers.lifecycle.callback.LifecycleCallbackHandler” state=”valid”
handler name=”org.apache.felix.ipojo.handlers.architecture.ArchitectureHandler” state=”valid”
总结
felix ipojo配置和spring的配置的目的差不多,都是为了解耦和简化依赖注入。但是felix ipojo比spring的配置要复杂一些,因为spring的bean都是创建后就不怎么变了,而且felix ipojo的instance可以随组件的任意时候的安装而产生,又随组件的任意时候卸载而悄悄的消失,所以ipojo的配置除了基本的spring功能外,主要是为了很好的解决组件中service来去匆匆的问题。
进入正题,felix ipojo配置主要是围绕着类、component和instance三个元素。以下是我对三者的理解:
- 类:平时写的普通的ipojo类(暂时先不考虑ipojo annotation),类有field,有构造函数,有method,但是没有描述这些field会被怎么注入,注入何种依赖,也没有描述这个类中的一些方法是如何按照ipojo生命周期的流程有机的结合起来。它只是一个类而已。
- component:一个类可以对应一至多个component(如果component是用annotation的方式来配置的话,那么一个类就只跟一个component对应了),component描述了一个类提供何种功能、其field将怎样被什么样的instance注入、其method何时被调用、在ipojo生命周期中调用顺序是怎样的。
- instance:每个instance都是根据component产生的一个具体的实例对象,它和spring中的bean的含义很接近。一个component可以有多个不同的instance(spring中一个类也可以配置多个不同id的bean),由于component描述了一个instance的整体结构(甚至可以给field设置默认实现或者默认值),但是instance自己还是可以给field设置符合一定条件的instance。
在使用felix ipojo之前首先需要在maven的pom.xml里面添加ipojo插件配置:maven-ipojo-plugin使用指南,然后在ipojo配置文件目录或者metadata.xml文件中添加component和instance配置。
Reference