felix ipojo入门教程

felix ipojo入门教程


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

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包。这个包将在接下来的两个工程中被依赖上。

3. 第二个工程:服务提供者(hello.impl)

这个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命令打包。

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

  • iPOJO in 10 minutes
  • iPOJO Hello World with Maven

你可能感兴趣的:(osgi,felix,ipojo)