Chasing an OSGi vision

OSGi技术的研究和讨论

2006年2月14日 #

OSGi介绍(六)OSGi的service

在给出采用service方式实现的“扶贫助手”之前,我们稍微回顾一下上一篇的成果。
在(五)中,我们看到程序被分成多个bundle后,程序的模块程度得到提高,而控制模块间的耦合度由Import-Package和Export-Package来控制,相对比较灵活。另一方面程序的更新和升级的粒度变小了。谁都知道只更新部分要比全部更新强,尤其当更新发生在一些需要建立昂贵的连接时,细粒度会节省不少花销。除了这些,我们看不到其他新鲜的东西。说白了,也就是挖空心思想一些design pattern来划分程序模块。
 
好了,马上就新鲜了。下面你会看到通过采用service方式来改造(五)中的程序,gui bundle在某些情况下不用重新启动,就能直接某些适应需求的变更!
先给出model bundle的代码,该bundle包含两个java package,分别是:
com.bajie.test.family.model
com.bajie.test.family.model.impl
在com.bajie.test.family.model这个package中包含如下的class和interface:
package com.bajie.test.family.model;
import java.util.List;
import javax.swing.table.AbstractTableModel;
public abstract class FamilyInfoDatabase extends AbstractTableModel{
   
    public abstract void sort(SortingFamilyInfoCriteria sortField) throws IllegalArgumentException;
   
    public abstract void addEntry(List columns, List values) throws IllegalArgumentException;
    public abstract void deleteEntry(String familyName);
    public abstract void update(String familyName,List columns, List values)throws IllegalArgumentException;
}

这是database的model,与(五)定义成interface不同,我们直接让它继承了AbstractTableModel,这是因为我们希望当数据或显示需求变化时,gui上的JTable能获得通知,并显示更新的结果。SortingFamilyInfoCriteria这个类型下文会给出说明。
 
package com.bajie.test.family.model;
public class FamilyInfoEntry {
    private String familyName;
    private int population;
    private int incomePerYear;
   
    public FamilyInfoEntry(String familyName,int population,int income){
        this.familyName = familyName;
        this.population = population;
        this.incomePerYear = income;
    }
   
    public String getFamilyName() {
        return familyName;
    }
    public int getIncomePerYear() {
        return incomePerYear;
    }
    public int getPopulation() {
        return population;
    }
}

这个类的结构和在(五)中完全一样,用来纪录一条家庭信息。唯一不同的是,在(五)中我们把它放入了实现(.impl)package中,在后面给出bundle的manifest文件时,我将解释为什么要这样改。
 
package com.bajie.test.family.model;
public interface FamilyInfoColumn {
    public Object getColumnValue(FamilyInfoEntry entry);
   
    public String getColumnName();
}
这个类用来描述table中的某个列。
package com.bajie.test.family.model;
import java.util.Comparator;
public interface SortingFamilyInfoCriteria extends Comparator{
    public String getSortFieldString();
}
这个类将用于对家庭纪录按某一列的值进行排序。
在com.bajie.test.family.model.impl这个package中包含上面抽象类和interface的实现:
package com.bajie.test.family.model.impl;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceEvent;
import org.osgi.framework.ServiceListener;
import org.osgi.framework.ServiceReference;
import com.bajie.test.family.model.FamilyInfoColumn;
import com.bajie.test.family.model.FamilyInfoDatabase;
import com.bajie.test.family.model.FamilyInfoEntry;
import com.bajie.test.family.model.SortingFamilyInfoCriteria;
public class FamilyDatabase extends FamilyInfoDatabase implements  BundleActivator,
        ServiceListener {
    private LinkedList familyEntryList = new LinkedList();
    private Object[] sortedValues = null;
    private LinkedList columns = new LinkedList();
    private BundleContext context;
    public int getColumnCount() {
        return this.columns.size();
    }
    public String getColumnName(int index) {
        return ((FamilyInfoColumn)columns.get(index)).getColumnName();
    }
   
    public Object getValueAt(int row, int column) {
        FamilyInfoEntry entry = (FamilyInfoEntry) this.sortedValues[row];
        if(column >= this.familyEntryList.size()){
            return null;
        }
        return ((FamilyInfoColumn) this.columns.get(column))
                .getColumnValue(entry);
    }
    public int getRowCount() {
        return this.familyEntryList.size();
    }
    public void addEntry(List columns, List values)
            throws IllegalArgumentException {
    }
    public void deleteEntry(String familyName) {
    }
    public void update(String familyName, List columns, List values)
            throws IllegalArgumentException {
    }
    public void sort(SortingFamilyInfoCriteria sortField) {
        Arrays.sort(this.sortedValues, sortField);
    }
    public void start(BundleContext context) throws Exception {
        this.context = context;
        this.familyEntryList.add(new FamilyInfoEntry("Zhang", 3, 1200));
        this.familyEntryList.add(new FamilyInfoEntry("Li", 6, 1800));
        this.familyEntryList.add(new FamilyInfoEntry("Liu", 5, 1500));
        this.familyEntryList.add(new FamilyInfoEntry("Wang", 4, 1300));
       
        this.sortedValues = this.familyEntryList.toArray();
 //向framework注册一个类型为FamilyInfoDatabase的服务
        context.registerService(FamilyInfoDatabase.class.getName(),this,null);
 //向framework注册三个服务,每个服务的类型既为FamilyInfoColumn,也是SortingFamilyInfoCriteria
        String[] clazzes = new String[] {FamilyInfoColumn.class.getName(),SortingFamilyInfoCriteria.class.getName()};
        context.registerService(clazzes,new FamilyNameColumn(),null);
        context.registerService(clazzes,new FamilyPopulationColumn(),null);
        context.registerService(clazzes,new FamilyIncomeColumn(),null);
       
 //向framework查找所有注册类型为FamilyInfoColumn的服务
 //先获得服务的引用
        ServiceReference[] columnRefs = context.getServiceReferences(
                FamilyInfoColumn.class.getName(), null);
        FamilyInfoColumn column = null;
        for (int i = 0; i < columnRefs.length; i++) {
            System.out.println(i + ":" + ((String[])(columnRefs[i].getProperty(Constants.OBJECTCLASS)))[0]);
     //通过引用获得具体的服务对象,每一个对象都将转化成gui中table的一列
            column = (FamilyInfoColumn) context.getService(columnRefs[i]);
            if (column != null) {
                this.columns.add(column);
            }else{
                System.out.println("null service object.");
            }
        }

 //注册服务侦听器,该侦听器专门侦听FamilyInfoColumn服务对象的动态(主要是增加和删除)
        context.addServiceListener(this,"(" + Constants.OBJECTCLASS + "="
                + FamilyInfoColumn.class.getName() + ")");
    }
    public void stop(BundleContext context) throws Exception {
    }
    public void serviceChanged(ServiceEvent event) {
        switch (event.getType()) {
        case ServiceEvent.MODIFIED:
            return;
        case ServiceEvent.REGISTERED://表明有新的列产生了。
            ServiceReference ref = event.getServiceReference();
            Object service = this.context.getService(ref);
            this.columns.add(service);
            this.fireTableStructureChanged();//通知gui,表结构发生变化
            return;
        case ServiceEvent.UNREGISTERING://表明有些列将被删除
            ref = event.getServiceReference();
            service = this.context.getService(ref);
            this.columns.remove(service);
            this.fireTableStructureChanged();//通知gui,表结构发生变化
            return;
        }
    }

    //这个类定义一个“Family Name”这个列,以及如何按这个列的值进行排序
    class FamilyNameColumn implements FamilyInfoColumn,SortingFamilyInfoCriteria {
        private static final String COLUMNNAME = "Family Name";
       
        public Object getColumnValue(FamilyInfoEntry entry) {
            return entry.getFamilyName();
        }
       
       
        public String getColumnName() {
            return FamilyNameColumn.COLUMNNAME;
        }
       
        public String getSortFieldString() {
            return FamilyNameColumn.COLUMNNAME;
        }
        public int compare(Object obj1, Object obj2) {
            if (obj1 == obj2) {
                return 0;
            }
            FamilyInfoEntry en1 = (FamilyInfoEntry)obj1;
            FamilyInfoEntry en2 = (FamilyInfoEntry)obj2;
           
            return en1.getFamilyName().compareTo(en2.getFamilyName());
        }
       
    }
    //这个类定义一个“Family Population”这个列,以及如何按这个列的值进行排序
    class FamilyPopulationColumn implements FamilyInfoColumn, SortingFamilyInfoCriteria {
        private static final String COLUMNNAME = "Family Population";
        public Object getColumnValue(FamilyInfoEntry entry) {
            return new Integer(entry.getPopulation());
        }
        public String getColumnName() {
            return FamilyPopulationColumn.COLUMNNAME;
        }
       
        public String getSortFieldString() {
            return FamilyPopulationColumn.COLUMNNAME;
        }
       
        public int compare(Object obj1, Object obj2) {
            if (obj1 == obj2) {
                return 0;
            }
            FamilyInfoEntry en1 = (FamilyInfoEntry)obj1;
            FamilyInfoEntry en2 = (FamilyInfoEntry)obj2;
           
            return en1.getPopulation() - en2.getPopulation();
        }
    }
   
    //这个类定义一个“Family Income”这个列,以及如何按这个列的值进行排序
    class FamilyIncomeColumn implements FamilyInfoColumn, SortingFamilyInfoCriteria {
        private static final String COLUMNNAME = "Family Income";
        public Object getColumnValue(FamilyInfoEntry entry) {
            return new Integer(entry.getIncomePerYear());
        }
        public String getColumnName() {
            return FamilyIncomeColumn.COLUMNNAME;
        }
       
       
        public String getSortFieldString() {
            return FamilyIncomeColumn.COLUMNNAME;
        }
        public int compare(Object obj1, Object obj2) {
            if (obj1 == obj2) {
                return 0;
            }
            FamilyInfoEntry en1 = (FamilyInfoEntry)obj1;
            FamilyInfoEntry en2 = (FamilyInfoEntry)obj2;
           
            return en1.getIncomePerYear() - en2.getIncomePerYear();
        }
       
    }
}
 
与(五)相比,最大的不同就是表结构的“列”是通过查找所有类型为FamilyInfoColumn的服务对象而组成的。而通过framework提供的服务侦听机制(即实现ServiceListener接口并注册到framework中),bundle能够获得该类服务对象的动态事件通知,如果该事件是新服务注册,则添加一个显示列,如果是服务被注销,则删除对应的显示列。
 
下面是bundle的manifest文件
Manifest-Version: 1.0
Bundle-SymbolicName: com.bajie.test.family.model
Bundle-Name: family model
Bundle-Version: 1.0
Bundle-Vendor: LiMing
Bundle-Activator: com.bajie.test.family.model.impl.FamilyDatabase
Import-Package: org.osgi.framework;version=1.3,com.bajie.test.family.model
Export-Package: com.bajie.test.family.model;version=1.0
从中我们看到com.bajie.test.family.model这个package被export出来,这样其他bundle就能够import这个package,并根据FamilyInfoEntry所提供的基本内容提供一些额外的处理结果,从而产生新列(FamilyInfoColumn)以及排序方法(SortingFamilyInfoCriteria),比如家庭人均年收入。

下面来看看gui bundle,它只包含一个package
package com.bajie.test.family.gui;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.Hashtable;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceEvent;
import org.osgi.framework.ServiceListener;
import org.osgi.framework.ServiceReference;
import com.bajie.test.family.model.FamilyInfoDatabase;
import com.bajie.test.family.model.SortingFamilyInfoCriteria;
public class FamilyInfoGui implements BundleActivator, ActionListener,
        ItemListener, ServiceListener {
    private JFrame mainFrame;
    private JPanel contentPanel;
    private JTable familiesTable;
    private JScrollPane familiesTableScrollPane;
    private JPanel sortedByPanel = new JPanel(new GridLayout(1, 2));
    private JLabel sortedByLabel = new JLabel("Sorted By: ");
    private JComboBox sortedByList = null;
    private JPanel commandPanel = new JPanel(new GridLayout(1, 3));
    private JButton addEntry = new JButton("Add");
    private JButton deleteEntry = new JButton("Delete");
    private JButton updateEntry = new JButton("Update");
    private Hashtable sortingFields = new Hashtable();
    private BundleContext context;
    FamilyInfoDatabase database = null;
    public void start(BundleContext context) throws Exception {
        this.context = context;
        //查找所有注册类型为FamilyInfoDatabase的服务对象。在我们这个例子,它是由上面给出的model bundle注册的
        ServiceReference databaseServiceRef = context
                .getServiceReference(FamilyInfoDatabase.class.getName());
        if (databaseServiceRef == null) {
            System.out.println("No database service is registered.");
            return;
        }
 //这个服务对象将成为JTable的数据model
        this.database = (FamilyInfoDatabase) context
                .getService(databaseServiceRef);
        if (this.database == null) {
            System.out.println("Can not get database object");
            return;
        }
        //查找所有注册类型为SortingFamilyInfoCriteria的服务对象。
        ServiceReference[] sortingCriteria = context.getServiceReferences(
                SortingFamilyInfoCriteria.class.getName(), null);
        sortedByList = new JComboBox();
        SortingFamilyInfoCriteria criterion = null;
        if (sortingCriteria != null) {
            for (int i = 0; i < sortingCriteria.length; i++) {
                criterion = (SortingFamilyInfoCriteria) context
                        .getService(sortingCriteria[i]);
                if (criterion != null) {
      //每个服务对象将对应一种排序方法,并加入到下拉列表中
                    sortedByList.addItem(criterion.getSortFieldString());
                    this.sortingFields.put(criterion.getSortFieldString(),
                            criterion);
                }
            }
        }
 //注册服务侦听器,该侦听器专门侦听SortingFamilyInfoCriteria服务对象的动态(主要是增加和删除)
        context.addServiceListener(this, "(" + Constants.OBJECTCLASS + "="
                + SortingFamilyInfoCriteria.class.getName() + ")");
        sortedByList.addItemListener(FamilyInfoGui.this);
        //construct gui
        Runnable r = new Runnable() {
            public void run() {
                contentPanel = new JPanel();
                familiesTableScrollPane = new JScrollPane();
  //获得的FamilyInfoDatabase对象成为gui中JTable的model
                familiesTable = new JTable(database);
                familiesTableScrollPane.setViewportView(familiesTable);
                sortedByPanel.add(sortedByLabel);
                sortedByPanel.add(sortedByList);
                commandPanel.add(addEntry);
                commandPanel.add(deleteEntry);
                commandPanel.add(updateEntry);
                contentPanel.add(sortedByPanel, BorderLayout.NORTH);
                contentPanel.add(familiesTableScrollPane, BorderLayout.CENTER);
                contentPanel.add(commandPanel, BorderLayout.SOUTH);
                mainFrame = new JFrame();
                mainFrame.setContentPane(contentPanel);
                mainFrame.setSize(new Dimension(500, 600));
                mainFrame.show();
            }
        };
        Thread t = new Thread(r);
        t.start();
    }
    public void stop(BundleContext context) throws Exception {
        if (this.mainFrame != null)
            this.mainFrame.dispose();
    }
    public void actionPerformed(ActionEvent event) {
    }
    public void itemStateChanged(ItemEvent event) {
        if (event.getSource() == this.sortedByList) {
            SortingFamilyInfoCriteria criterion = (SortingFamilyInfoCriteria) this.sortingFields
                    .get(event.getItem());
            if (criterion == null)
                return;
            this.database.sort(criterion);
            this.familiesTable.repaint();
        }
    }
    public void serviceChanged(ServiceEvent event) {
        switch (event.getType()) {
        case ServiceEvent.MODIFIED:
            return;
        case ServiceEvent.REGISTERED://有新的排序方法注册到framework当中
            ServiceReference ref = event.getServiceReference();
            SortingFamilyInfoCriteria criterion = (SortingFamilyInfoCriteria) this.context
                    .getService(ref);
            if (criterion != null) {
  //把新的排序方法加入到下拉列表中
                sortedByList.addItem(criterion.getSortFieldString());
                this.sortingFields.put(criterion.getSortFieldString(),
                        criterion);
            }
            return;
        case ServiceEvent.UNREGISTERING://一个现有的排序方法将被从framework被取消
            ref = event.getServiceReference();
            criterion = (SortingFamilyInfoCriteria) this.context
                    .getService(ref);
            if (criterion != null) {
  //把该排序方法从下拉列表中删除
                sortedByList.removeItem(criterion.getSortFieldString());
                this.sortingFields.remove(criterion);
            }
            return;
        }
    }
}
 
与(五)相比不同的地方是,这个gui的table model以及排序的方法,都是通过查询service对象获得。
 
manifest文件如下:
Manifest-Version: 1.0
Bundle-SymbolicName: com.bajie.test.family.gui
Bundle-Name: family gui
Bundle-Version: 1.0
Bundle-Vendor: LiMing
Bundle-Activator: com.bajie.test.family.gui.FamilyInfoGui
Import-Package: org.osgi.framework;version=1.3,com.bajie.test.family.model
 
然后我们生成bundle的jar文件。分别为familymodel.jar和familygui.jar,之后我们用“in”命令把两个bundle装入framework。
接着我们先启动model bundle,然后再启动gui bundle,我们会看到JTable中有3列,而排序方法列表中也有3个选项,完全和程序的逻辑符合。
 
接下来,我们假设客户需要添加显示每个家庭的人均年收入并按其排列纪录。要满足这个需求,我们可以参考在(五)中做法,就是在model bundle里面再添加一个同时实现了FamilyInfoColumn和SortingFamilyInfoCriteria的类,并在bundle的启动中作为服务注册到framework中?不过这样就得更新model bundle然后调用rfr命令来刷新。为什么不再装一个补丁bundle,在这个bundle中包含了同时实现FamilyInfoColumn和SortingFamilyInfoCriteria的类,并在这个新bunle启动时注册产生该类的新对象作为服务注册到framework中,这样gui和model bundle都能侦听到该新服务的到来(他们都实现了服务侦听接口ServiceListener),gui上马上就能有所体现。

这个新bundle的代码如下:
package com.bajie.test.family.model.impladd;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import com.bajie.test.family.model.FamilyInfoColumn;
import com.bajie.test.family.model.FamilyInfoEntry;
import com.bajie.test.family.model.SortingFamilyInfoCriteria;
public class FamilyIncomePerPerson implements BundleActivator {
    public void start(BundleContext context) throws Exception {
 //注册一个新的服务,服务的类型既为FamilyInfoColumn,也是SortingFamilyInfoCriteria
        String[] clazzes = new String[] {FamilyInfoColumn.class.getName(),SortingFamilyInfoCriteria.class.getName()};
        context.registerService(clazzes,new FamilyIncomePerPersonColumn(),null);
       
    }
    public void stop(BundleContext context) throws Exception {
    }
    //这个类实现了“Income Per Person”这个列以及按该列排序的方法。
    class FamilyIncomePerPersonColumn implements FamilyInfoColumn,SortingFamilyInfoCriteria {
        private static final String COLUMNNAME = "Income Per Person";
       
        public Object getColumnValue(FamilyInfoEntry entry) {
            return new Integer(entry.getIncomePerYear()/entry.getPopulation());
        }
       
       
        public String getColumnName() {
            return FamilyIncomePerPersonColumn.COLUMNNAME;
        }
       
        public String getSortFieldString() {
            return FamilyIncomePerPersonColumn.COLUMNNAME;
        }
        public int compare(Object obj1, Object obj2) {
            if (obj1 == obj2) {
                return 0;
            }
            FamilyInfoEntry en1 = (FamilyInfoEntry)obj1;
            FamilyInfoEntry en2 = (FamilyInfoEntry)obj2;
           
            return en1.getIncomePerYear()/en1.getPopulation() - en2.getIncomePerYear()/en2.getPopulation();
        }
       
    }
}
 
manifest文件如下:
Manifest-Version: 1.0
Bundle-SymbolicName: com.bajie.test.family.modeladd
Bundle-Name: family model add
Bundle-Version: 1.0
Bundle-Vendor: LiMing
Bundle-Activator: com.bajie.test.family.model.impladd.FamilyIncomePerPerson
Import-Package: org.osgi.framework;version=1.3,com.bajie.test.family.model
 
打包安装到framework后,启动该bundle,我们就会在gui上看到新的列已经被添加,而且排序列表中增加了一个新的排序选项。
这个结果,完全符合需求的意图。
如果我们用stp命令停止这个bundle,我们在gui上就会发现,新列消失,而且排序列表中对应选项也没有了。这就是service带来的动态效果。不过,如果我们的model发生了一些实质的变化,比如FamilyInfoEntry需要添加一个“地址”列,那么model bundle就要更新,进而gui bundle以及使用到这个类型的bundle都需要通过rfr命令刷新。
 
好了,对扶贫助手的分析就此打住,我们总结一下,通过程序可以看到注册服务一点都不复杂。最简单的情况我们只需要提供一个java类型名称,以及实现这个类型的一个java对象就可以了,
不需要提供复杂的类型描述,比如xml描述文件。而使用服务的bundle通过类型名称就轻而易举的查找到相关的服务对象。
 
到此,osig介绍系列就要结束了,只希望这个系列能够把你引入到osgi的门口,其后面的精彩世界就看你的兴趣了。
就我个人的关注和理解,今年是osgi很重要的一年。JSR249今年应该投票,如果osgi入选,那么osgi将成为高端手机中java体系结构的重要组成部分。
在汽车领域,siemensVDO已经推出了基于osgi的解决方案,听说已经配备在BMW serials 5里面了。应该还会有更多的应用......
 
如果你是osgi的粉丝,欢迎你来信jerrylee.li@gmail.com拍砖交流。

posted @ 2006-02-14 16:08 勤劳的蜜蜂 阅读(2105) | 评论 (13)编辑 收藏

OSGi介绍(五)两个bundle

     摘要: (四)中提到的直接型改造法实际上和一个传统的java应用程序没有区别。因此客户的需求发生变化,通常是牵一发而动全身。那么我们现在就看看如果在osgi framework中,用多个bundle来实现的效果吧。 我的想法是用两个bundle来配合实现“扶贫助手”的功能。一个bundle专门负责录入和显示纪录,一个bundle专门负责纪录的数据结构和对数据的处理,用时下时髦的说法就是使用了mvc,只是...  阅读全文

posted @ 2006-02-14 16:02 勤劳的蜜蜂 阅读(2264) | 评论 (1)编辑 收藏

OSGi介绍(四)第一个bundle

先给出“扶贫助手”的第一种改造,我称之为“直接型”,请看:

package aa.bb.cc;
//需要import osgi的核心package
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
//实现了BundleActivator
public class FamilyInfo implements BundleActivator {
 
private String familyName;
 
private int population;
 
private int incomePerYear;
 省略了getter和setter方法 
 
public String toString() {
  
  
return "Family: " + this.familyName + ", population: " + this.population + ", income: " + this.incomePerYear;
 }

 
 
public int getIncomePerMember(){
  
return (int)(this.incomePerYear/this.population);
 }

 
public static void sortByIncomePerYear(FamilyInfo[] families){
  FamilyInfo temp 
= null;
  
for(int i = 0; i < families.length -1; i ++){
   
for(int j = i + 1; j < families.length; j ++){
    
    
if(families[i].getIncomePerYear() > families[j].getIncomePerYear()){
     temp 
= families[i];
     families[i] 
= families[j];
     families[j] 
= temp;
    }

   }

  }

  
 }

 
public static void sortByIncomePerMember(FamilyInfo[] families){
  FamilyInfo temp 
= null;
  
for(int i = 0; i < families.length -1; i ++){
   
for(int j = i + 1; j < families.length; j ++){
    
    
if(families[i].getIncomePerMember() > families[j].getIncomePerMember()){
     temp 
= families[i];
     families[i] 
= families[j];
     families[j] 
= temp;
    }

   }

  }

  
  
 }

 
//在framework每次启动该bundle的时候该方法会被framework调用执行。
 public void start(BundleContext context) throws Exception {
  FamilyInfo[] families 
= new FamilyInfo[3];
  families[
0= new FamilyInfo();
  families[
0].setFamilyName("Zhang");
  families[
0].setPopulation(3);
  families[
0].setIncomePerYear(1200);
  families[
1= new FamilyInfo();
  families[
1].setFamilyName("Li");
  families[
1].setPopulation(6);
  families[
1].setIncomePerYear(1800);
  families[
2= new FamilyInfo();
  families[
2].setFamilyName("Liu");
  families[
2].setPopulation(4);
  families[
2].setIncomePerYear(1500);
  FamilyInfo.sortByIncomePerYear(families);
  
for(int i = 0; i < families.length; i ++){
   System.out.println(families[i].toString());
  }

  FamilyInfo.sortByIncomePerMember(families);
  
for(int i = 0; i < families.length; i ++){
   System.out.println(families[i].toString());
  }

 }

 
//在framework停止该bundle时,该方法将被framework调用
 public void stop(BundleContext context) throws Exception {
 }

}


看到代码的区别了吗?我在不同之处都标注了注释。其实,从说白了,就是实现了org.osgi.framework.BundleActivator这个接口。
当然,细心的话,你会发现这个bundle没有public static void main(String[] args)方法了。那么它怎么被启动呢?这个就是bundle的奥秘所在。不过,如果你了解java的class loading机制以及reflection技术,你立马会明白这个bundle的运行机制。这两项技术广泛应用于j2ee(对吧?我得承认,j2ee的经验不多,呵呵)以及java的plugin机制。
简单说来,java.lang.Class这个类有一个方法:
public Object newInstance()throws InstantiationException,IllegalAccessException
针对上面的“扶贫助手”bundle而言,framework只要通过ClassLoader找到aa.bb.cc.FamilyInfo.class并加载后,就可以通过newInstance()方法创建一个BundleActivator的实例,然后调用public void start(BundleContext context)方法,就完成了启动bundle的动作了。之后,调用public
void stop(BundleContext context)方法来停止bundle
如果你接着问,framework怎么知道这个bundle里面的BundleActivator是哪个类呢?嗯,问到点子上了。这就涉及到下面我们要讲的bundle的部署了。在上一篇给出的bundle定义中指出,Jar文件是bundle的唯一格式,也就是说,我们要运行bundle,必须把代码打成jar文件。而jar文件可以带有manifest文件,这个文件对bundle是不可缺少的。OSGi规范里面,通过定义一系列适用于bundle的manifest关键字(bundle manifest header)来扩展manifest文件。
比如,开发人员在manifest中添加下面一行:
Bundle-Activator: aa.bb.cc.FamilyInfo
这样,在bundle被部署到framework后,framework就可以通过读取manifest的关键字来获得BundleActivator的具体实现类名,并通过reflection机制产生BundleActivator的实例。
这里就给出扶贫助手的manifest的一个例子:

Manifest-Version: 1.0  
Bundle-SymbolicName: aa.bb.cc.family //osgi specification 4强制要求的关键字,每个bundle都必须有唯一的symbolic name
Bundle-Name: Family Info Manager        //bundle的名称
Bundle-Version: 
1.0   //bundle的版本号
Bundle-Activator: aa.bb.cc.FamilyInfo   //指明BundleActivator的实现类名
Import-Package: org.osgi.framework
;version=1.3   //列出该bundle需要从其他bundle所引入的
                                                                     //package(s)(提供该package的bundle必须在其
                                                                     //manifest中有Export-Package: 
                                                                     //org.osgi.framework
;version=1.3)

然后我们用jdk自带的jar工具,来生成bundle jar文件。这样,第一个bundle就完成了,您可以下载一个开源的framework安装这个bundle试一试。在framework上尝试对该bundle的启动和停止,输出的结果应该和原先的java application是一样的,然后您还可以在那个start(context)的方法中,再增加一条记录,重新打包,然后通过framework的update功能,就能够在不重新启动framework的情况下升级该bundle,我就暂时偷懒不针对具体framework来给出操作的方法了,先给您自己先摸索了(当然您也可以偷懒,因为后面我会结合具体framework深入讲述的)。
好了,说完代码的改造,再看看改造所带来的程序设计结构变化:那~~~就~~~~是~~~~没变化!因此我把这种原封不动的改造方法称为“直接型”,用这种直接法,我们可以轻易的把一个java应用程序改造成bundle。而这种改造目前能看到的好处就是bundle的“热”升级。那怎样能更漂亮些呢?在下一篇中,我会进一步改造这个扶贫助手成为两个bundle,看看bundle的合作将会带来怎样的精彩效果

posted @ 2006-02-14 15:46 勤劳的蜜蜂 阅读(1953) | 评论 (2)编辑 收藏

OSGi介绍(三)OSGi service platform的体系结构

先让我们来看看OSGi service platform的体系结构。另外要说明的是,我在后面的文章中,将采用framework来代替OSGi service platfrom,这样比较简便。
下面这张图来自OSGi Alliance的主页(http://www.osgi.org/
 
OSGi Service Platform Architecture

层次很分明吧。放到我们假想的案例中,OS&Hardware可以对应为PDA的硬件和操作系统,您可以想象它是Intel xscacle + Microsoft window mobile,或者是Arm + embedded Linux
而Execution Environment当然是我们上次提到的CVM + CDC + FP + PP,有这个jvm的配置运行framework就绰绰有余了。而再往上,就是我们要重点学习和分析的OSGi framework了。而Modules, Life Cycle, Service Registry, Services和Security是从不同的5个角度来划分framework所具备的功能,后面我将会从除了Security外的四个方面分别结合我们的假设场景来分析。而体系结构的最上层是符合OSGi framework接口标准的应用程序,也就是OSGi世界中有名的“bundle”。

下面来看看OSGi规范是如何定义一个bundle的。在r4规范的第27页中大致这样描述到:Framework定义了模块(modularization)的单元,叫作bundle。Bundle实际就是一个具有jar(Java ARchive)格式的文件,其中包含了java的class文件和其他资源文件(比如图标,配置文件等等)。Bundle可以在自己的manifest文件中说明自己能够提供哪些java包,其他bundle如果在自己的manifest文件中指定了它需要这个包,那他们之间就可能产生java包的依赖关系,这样多个bundle之间就可以共享java包。值得注意的是,bundle是能够在framework中部署的唯一格式。下面给出原文的描述:
A bundle is a JAR file that:
? Contains the resources necessary to provide some functionality. These resources may be class files for the Java programming language, as well as other data such as HTML files, help files, icons, and so on. A bundle JAR file can also embed additional JAR files that are available as resources and classes. This is however not recursive.
? Contains a manifest file describing the contents of the JAR file and providing information about the bundle. This file uses headers to specify information that the Framework needs to install correctly and activate a bundle. For example, it states dependencies on other resources, such as Java packages, that must be available to the bundle before it can run.
? Can contain optional documentation in the OSGI-OPT directory of the JAR file or one of its sub-directories. Any information in this directory is optional. For example, the OSGI-OPT directory is useful to store the source code of a bundle. Management systems may remove this information to save storage space in the OSGi Service Platform.

framework的modules这一方面功能将主要负责bundle的安装部署,更新和卸载,以及bundle在设备的物理存储(如果有的话)。在这个层次,每个bundle都是独立的,它的安装,升级和卸载完全不依赖任何其他bundle,这点framework提供了强大的隔离性。Life Cycle专门负责对bundle的解析(比如关联两个有相互依赖关系的bundle),启动(相当于运行应用程序)和停止(相当于停止应用程序)。这个层次中,bundle间的逻辑关系被创建起来,这些关系能否成功的创建,将会直接影响bundle的成功解析和启动。Service Registry可以认为是一个数据库,bundle启动后,可以向这个数据库注册它动态提供的服务。只要bundle不被停止,且bundle不主动撤销注册的服务,这个服务将一直保存在这个数据库中供其它bundle来查询和使用。而Services就是由bundle运行时提供的具体服务对象,这些服务对象的存在,使得framework具有极其动态的特征,并为framework运行时提供灵活强大的功能。
另外,根据OSGi Alliance的说法,OSGi的运行平台包括了j2me(kvm + cldc + midp, cvm + cdc+fp), j2se, j2ee。不过,我个人还是觉得目前的midp规范还太弱,OSGi要想运行在上面,很多功能实现起来都比较不爽。

好,有了对framework结构层次的皮毛认识,下面我们就开始着手改造那个“扶贫助手”的程序,使其变成OSGi的bundle(s),然后从上面提到的4个方面来分析framework的机制。
这里,我先给出“扶贫助手”的java application模拟程序代码:

package aa.bb.cc;

public class FamilyInfo {
 
private String familyName; //家庭名称
 private int population; //人口数量
 private int incomePerYear; //家庭年收入

  …..
//省略了getter和setter方法

//获得家庭人均年收入
 public int getIncomePerMember(){
  
return (int)(this.incomePerYear/this.population);
 }


 
public String toString() {
  
  
return "Family: " + this.familyName + ", population: " + this.population + ", income: " + this.incomePerYear;
 }

 
//按家庭年收入又低到高排序
 public static void sortByIncomePerYear(FamilyInfo[] families){
  FamilyInfo temp 
= null;
  
for(int i = 0; i < families.length -1; i ++){
   
for(int j = i + 1; j < families.length; j ++){
    
    
if(families[i].getIncomePerYear() > families[j].getIncomePerYear()){
     temp 
= families[i];
     families[i] 
= families[j];
     families[j] 
= temp;
    }

   }

  }

  
 }


 
//按家庭人均年收入由低到高排序
 public static void sortByIncomePerMember(FamilyInfo[] families){
  FamilyInfo temp 
= null;
  
for(int i = 0; i < families.length -1; i ++){
   
for(int j = i + 1; j < families.length; j ++){
    
    
if(families[i].getIncomePerMember() > families[j].getIncomePerMember()){
     temp 
= families[i];
     families[i] 
= families[j];
     families[j] 
= temp;
    }

   }

  }

  
 }


 
public static void main(String[] args){
  FamilyInfo[] families 
= new FamilyInfo[3];
  families[
0= new FamilyInfo();
  families[
0].setFamilyName("Zhang");
  families[
0].setPopulation(3);
  families[
0].setIncomePerYear(1200);
  families[
1= new FamilyInfo();
  families[
1].setFamilyName("Li