在OSGI框架中,Felix iPOJO意在简化面向服务编程。iPOJO的全称是inject POJO。Felix iPOJO提供了一种新的开发OSGI服务组件的方式,主要目标是简化服务组件的实现,使环境的动态变化对服务组件的实现透明。Felix iPOJO可以让开发者更好的分离功能代码(比如:POJO)和非功能代码(比如:依赖管理、服务的提供、配置等)。
服务组件可以提供和(或者)依赖一个或者多个服务,服务(service)是一个实现了某个java接口的对象。另外Felix iPOJO提供了一个回调的机制,把各种状态变化通知给组件。
组件是Felix iPOJO的核心概念,一个组件描述了服务的依赖、提供哪些服务以及哪些回调功能;这些信息配置在组件描述中(metadata)。Felix iPOJO中的第二个重要的概念是组件的实例,一个组件实例是一个特殊版本的组件。通过合并组件描述和组件实例的配置,Felix iPOJO可以在运行时管理组件。比如:管理其生命周期、服务的依赖注入、开放服务、发现需要的服务。
下面的示例将阐述怎样使用Felix iPOJO的核心功能。示例中包含两个组件,一个提供Hello服务,一个需要任意数量的Hello服务。这些组件通过maven打包,被放在三个不同的bundle中。
这个示例的代码可以在这里下载
开始前,先下载并安装配置maven。
这是一个maven工程,工程仅有一个Hello接口类,类的路径为:src/main/java/ipojo/example/hello/Hello.java
1
2
3
4
5
6
7
8
9
|
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包。这个包将在接下来的两个工程中被依赖上。
这个maven工程(hello.impl)里面有一个实现了Hello接口的类,这个工程依赖了第一个工程hello.service,这个类路径为:src/main/java/ipojo/example/hello/impl/HelloImpl.java
1
2
3
4
5
6
7
8
9
10
|
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工程的根目录下,这个描述文件的内容如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
<?
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命令打包。
这个工程中有一个类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的描述信息。
要运行这个实例,首先需要启动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),那么服务消费者又会变得可用。这两个步骤的效果如下:
felix ipojo配置和spring的配置的目的差不多,都是为了解耦和简化依赖注入。但是felix ipojo比spring的配置要复杂一些,因为spring的bean都是创建后就不怎么变了,而且felix ipojo的instance可以随组件的任意时候的安装而产生,又随组件的任意时候卸载而悄悄的消失,所以ipojo的配置除了基本的spring功能外,主要是为了很好的解决组件中service来去匆匆的问题。
进入正题,felix ipojo配置主要是围绕着类、component和instance三个元素。以下是我对三者的理解:
在使用felix ipojo之前首先需要在maven的pom.xml里面添加ipojo插件配置:maven-ipojo-plugin使用指南,然后在ipojo配置文件目录或者metadata.xml文件中添加component和instance配置。