Chasing an mobile web vision

闯荡在移动互联网的世界中

#

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 勤劳的蜜蜂 阅读(5921) | 评论 (13)编辑 收藏

OSGi介绍(五)两个bundle

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

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

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 勤劳的蜜蜂 阅读(5503) | 评论 (4)编辑 收藏

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");
  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());
  }

  
 }

}


 

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

osgi介绍(二)一个假想的实例

如何分析OSGi service platform的机制?给出几个硬生生的例子,然后分析一下
代码?那还不如你自己看书看规范好了。因此,我觉得还是结合一个应用实例来分析会更
容易理解,当然,是一个假想的应用实例。用怎样一个实例呢?嗯......

几个月前,一个中学同学打电话给我说他们要在PDA上开发一个简单的应用程序来临时纪
录工作的结果,并向我咨询,这种开发的难度和周期。这事启发我了,就以PDA的应用为
背景,让我们来假想一个场景,从而来比较传统的应用模型与采用OSGi的应用模型有怎样
的区别。

我这样想象:
小李是一个软件工程师,在一家专门为PDA开发应用程序和解决方案的公司工作。最近,
他刚为公司的一个客户开发完成了一套运行在PDA的JAVA应用程序,我们不要关心PDA是
什么硬件配置,只要知道它配备了JVM(cvm) + CDC以及PP和文件系统(呵呵设备还是比较
强劲的)。而这个客户是一个慈善机构,该机构人员携带PDA进入偏远山区收集生活困难
家庭的信息,以准备进行资助。而这套程序将会暂时把家庭信息保存在PDA中,并随时供
用户查询修改。用户使用一个月后,反馈非常好,但是,他们有新需求了,说原来只是想纪录
信息就成了,现在希望能给出一些排序功能,比如按家庭年收入对纪录进行排序.

接到这个需求,小李一看,这个简单,只要增加一个排序方法就可以了,让我们假设他使用了如下
数据结构来纪录家庭信息:

Class FamilyInfo {

 
private String familyName;//家庭名称

 
private int population; //人口数量

 
private int incomePerYear; //年收入

 .(省略Getter和Setter方法)
}


 

为了满足这个需求,小李决定添加一个静态的排序方法:

public static FamilyInfo[] sortByIncomePerYear(FamilyInfo[] familyInfos){
 
//根据incomePerYear的值进行冒泡排序。
}



把相关连部分修改完毕后,小李重新制作了安装包和启动脚本,发送给客户,不管客户如何操作
总之,原来的PDA程序必须卸载,新程序必须拷贝到PDA上再次执行安装,重新启动运行。

又过了一阵,客户说,要求提供按人均年收入进行排序,然后同样的事情又发生了......

几个轮次下来,小李发现,客户的需求还在增加,他们可能要求增加一个字段,记录目前该
家庭得到的资助额,还可能添加按收入范围查询纪录等等,事情还远没有结束。

如何改进这个情况呢?当然,改进涉及多方面,比如从软件本身出发,可以使用合适的design
pattern重新设计程序的体系结构,使得程序更易于扩展,关于这一点,有太多的讨论了,我就不
掺和了。还有从部署方面说,配置,安装和卸载程序,对最终用户往往是一项mission impossible,
能否让应用程序自己升级,而用户只要点击一个"升级"来触发这个过程而已......

我想你当然知道我给的答案:OSGi,OSGi,OSGi!!!!

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

osgi介绍(一)什么是osgi

过于的一年多,在和很多it届的同学及朋友见面时,他们总会问我最近在做什么。“OSGi!”,我不加思索的回答。到目前为止,对这个单词得到的反应都没有超出“这是什么?”,“我没有听说过”,“噢,能具体点吗?”等等。而我的回答更让他们糊涂,最后,大家干脆放弃这个话题,转到买房,运动等等更能体现聚会实质的问题上。不过最近,我一直在思考这个问题,下次再遇到这种情况时,该如何去表达才能让也是it届的哥们姐们能迅速的理解这个领域的范围呢?要知道,技术人员往往不善于表达,我们已经习惯了和业内人士用行话交流。

关于这个问题,我访问了OSGi Alliance的网站,在里面的faqs中,找到了我想要的东西。实际上,正如faqs中所解答的,OSGi涵盖了太多的范围,简单的两三句话是无法说清楚的。而我这里指的OSGi从技术的角度,应该说是“OSGi service platform ”,faqs中这样解释OSGi service platform(http://www.osgi.org/about/faqs.asp?section=1#q19) :
The OSGi service platform delivers an open, common architecture for service providers, developers, software vendors, gateway operators and equipment vendors to develop, deploy and manage services in a coordinated fashion. .......(以下省略上千英文单词)

好长!不过第一句话就已经能总结陈词了,“OSGi service platform是一个开放并且提供统一接口标准的体系框架,基于这个体系框架,服务提供商,程序开发人员,软件提供商,服务网管运营商,设备提供商能够协调地联合起来开发,部署以及管理向用户提供的各种服务。”还需要提到的是OSGi service platform是一个基于Java的platform。

OSGi的提出和诞生之初,其目的主要是能够灵活方便并远程管理互联的网络嵌入设备(听说是1997年左右提出,与Jini有深厚渊源)。随着硬件设备的能力不断提高,java技术的日益普及,尤其J2ME的壮大,现实应用的需求也不断扩大和推进,一个统一的标准变得非常的必要。OSGi Alliance就在这样的背景下成立了。从1999年成立以来,OSGi Alliance已经针对这个service platform发布了4版规范,其中r4是2005年10月份刚刚发布。

目前有不少公司对OSGi service platform推出了自己的实现,象ibm的smf(Service Management Framework,嗯,多好的名字,在那么多的platform实现中,我个人最喜欢这个名字,言简意赅)。

德国的ProSyst公司(http://www.prosyst.com)是OSGi Alliance中非常活跃的推动者,看看他们的产品列表吧http://www.prosyst.com/products/osgi.html(他们甚至提供了kvm + cldc的OSGi framework)

开源的Oscar(http://oscar.objectweb.org/),Knopflerfish(http://www.knopflerfish.org/)

对于OSGi的成功应用,最有名的应该是eclipse了,它就是基于OSGi service platform的产品。还有Apache,据说OSGi将被应用于其新一代的build工具中。这些都是j2se和j2ee的应用,而基于j2me的,手机(对应OSGi Alliance的MEG)和车载设备(对应OSGi Alliance的VEG)是OSGi的主要领域,OSGi Alliance已经有相应的规范,这些领域的应用相信会更加精彩,让我们拭目以待吧。

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

仅列出标题
共2页: 上一页 1 2