那么我们现在就看看如果在osgi framework中,用多个bundle来实现的效果吧。
先看看mc的bundle实现代码片断:
package
com.wukong.test.family.control;
import
java.util.
*
;
import
com.wukong.test.family.control.impl.
*
;
public
interface
FamilyInfoDatabase
{
public static class FamilyFactory {
private static FamilyInfoDatabase database;
public static FamilyInfoDatabase getDatabaseInstance(){
if(database == null){
database = new FamilyDatabase();
}
return database;
}
}
public String[] getColumns();
public Object getValueAt(int row, int column);
public String[] getSortingFields();
public int getRowCount();
public void sort(String sortField) throws IllegalArgumentException;
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;
}
这个interface是用来描述一个数据库所具备的基本功能。注意,这个interface被放到了package com.wukong.test.family.control中。
另外,这个interface还提供了一个内嵌类,用来专门提供一个工厂方法来产生唯一的一个数据库实例。
package
com.wukong.test.family.model.impl;
public
class
FamilyInfoEntry
{
private String familyName;
private int population;
private int incomePerYear;
FamilyInfoEntry(String familyName,int population,int income){
this.familyName = familyName;
this.population = population;
this.incomePerYear = income;
}
String getFamilyName() {
return familyName;
}
int getIncomePerYear() {
return incomePerYear;
}
int getPopulation() {
return population;
}
}
package
com.wukong.test.family.control.impl;
import
com.wukong.test.family.control.FamilyInfoDatabase;
import
java.util.
*
;
public
class
FamilyDatabase
implements
FamilyInfoDatabase
{
private LinkedList familyEntryList = new LinkedList();
private Object[] sortedValues = null;
public FamilyDatabase() {
this.familyEntryList.add(new FamilyInfoEntry("Zhang",3,1200));
this.familyEntryList.add(new FamilyInfoEntry("Li",6,1800));
this.familyEntryList.add(new FamilyInfoEntry("Liu",4,1500));
this.sortedValues = this.familyEntryList.toArray();
}
public String[] getColumns() {
return new String[] { "Family Name", "Family Population", "Income" };
}
public Object getValueAt(int row, int column) {
FamilyInfoEntry entry = (FamilyInfoEntry)this.sortedValues[row];
switch (column) {
case 0:
return entry.getFamilyName();
case 1:
return new Integer(entry.getPopulation());
case 2:
return new Integer(entry.getIncomePerYear());
default:
throw new IllegalArgumentException("Invalid column index.");
}
}
public String[] getSortingFields() {
return new String[] { "FamilyName","Income" };
}
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(String sortField) throws IllegalArgumentException{
if(sortField.equals("FamilyName")){
this.sortedValues = this.familyEntryList.toArray();
Arrays.sort(this.sortedValues,new SortedByName());
}
if(sortField.equals("Income")){
this.sortedValues = this.familyEntryList.toArray();
Arrays.sort(this.sortedValues,new SortedByIncome());
}
throw new IllegalArgumentException("Sorting enties by field '" + sortField + "' is not supported.");
}
class SortedByName implements Comparator{
public int compare(Object entry1,Object entry2) {
if (entry1 == entry2) {
return 0;
}
FamilyInfoEntry en1 = (FamilyInfoEntry) entry1;
FamilyInfoEntry en2 = (FamilyInfoEntry) entry2;
return en1.getFamilyName().compareTo(en2.getFamilyName());
}
}
class SortedByIncome implements Comparator {
public int compare(Object entry1, Object entry2) {
if (entry1 == entry2) {
return 0;
}
FamilyInfoEntry en1 = (FamilyInfoEntry) entry1;
FamilyInfoEntry en2 = (FamilyInfoEntry) entry2;
return en1.getIncomePerYear() - en2.getIncomePerYear();
}
}
}
同样需要注意的是我们把这个实现放到了package com.wukong.test.family.control.impl中
package
com.wukong.test.family.gui;
import
org.osgi.framework.
*
;
import
javax.swing.
*
;
import
javax.swing.table.
*
;
import
java.awt.
*
;
import
java.awt.event.
*
;
import
com.wukong.test.family.control.
*
;
public
class
FamilyInfoGui
implements
BundleActivator, ActionListener,ItemListener
{
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");
FamilyInfoDatabase database = null;
public void start(BundleContext context) throws Exception {
Runnable r = new Runnable() {
public void run() {
contentPanel = new JPanel();
familiesTableScrollPane = new JScrollPane();
database = FamilyInfoDatabase.FamilyFactory
.getDatabaseInstance();
familiesTable = new JTable(new FamilyInfoTableModel(database));
familiesTableScrollPane.setViewportView(familiesTable);
String[] sortedFields = database.getSortingFields();
sortedByList = new JComboBox(sortedFields);
sortedByList.addItemListener(FamilyInfoGui.this);
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 {
this.mainFrame.dispose();
}
public void actionPerformed(ActionEvent event) {
}
public void itemStateChanged(ItemEvent event) {
if(event.getSource() == this.sortedByList){
String sortingField = (String)event.getItem();
this.database.sort(sortingField);
this.familiesTable.repaint();
}
}
class FamilyInfoTableModel extends AbstractTableModel {
private FamilyInfoDatabase database;
FamilyInfoTableModel(FamilyInfoDatabase database) {
this.database = database;
}
public String getColumnName(int col) {
return database.getColumns()[col];
}
public boolean isCellEditable(int row, int col) {
return false;
}
public int getColumnCount() {
return database.getColumns().length;
}
public int getRowCount() {
return database.getRowCount();
}
public Object getValueAt(int row, int col) {
return database.getValueAt(row, col);
}
}
}
v的实现基本是界面的构造,而关于数据库,它只能看到interface FamilyInfoDatabase,因为它只import com.wukong.test.family.control.*,然后通过FamilyInfoDatabase内嵌静态类的静态工厂方法获得数据库的实例,这样它完全不用关心数据库如何实现。
我们先给出mc bundle的manifest文件(familycontrol.mf):
Manifest-Version:
1.0
Bundle-SymbolicName: com.wukong.test.family.control
Bundle-Name: family control
Bundle-Version:
1.0
Bundle-Vendor: LiMing
Export-Package: com.wukong.test.family.control
非常简单,但请大家注意这行
Export-Package: com.wukong.test.family.control
它告诉framework,这个bundle提供com.wukong.test.family.control这个包。
所谓“bundle A提供某个包”意思,通俗简单的理解是如果bundle B在manifest文件中说明要使用这个包(通过Import-Package这个关键字,下面会在v bundle中有说明)那么bundle B运行时,当使用到这个包中的类时,framework将告诉B的class loader这个类的定义是来自bundle A,从而不会发生ClassNotFoundException。另外值得一提的是我们并没有把com.wukong.test.family.control.impl这个包加进来,这样做的目的也是为了隐藏具体实现。将来只要interface不变,我们可以使用不同的实现来替换现有实现,大大提高扩展性。
Manifest-Version:
1.0
Bundle-SymbolicName: com.wukong.test.family.gui
Bundle-Name: family gui
Bundle-Version:
1.0
Bundle-Vendor: LiMing
Bundle-Activator: com.wukong.test.family.gui.FamilyInfoGui
Import-Package: org.osgi.framework
;
version=1.3,com.wukong.test.family.control
正如上面曾经提到过的一样,我们在Import-Package:关键字中指定该bundle需要使用com.wukong.test.family.control包。在v bundle的源代码中,我们也看到这个bundle的确使用了com.wukong.test.family.control包中的FamilyInfoDatabase。更深入一点,我们可以简单的这样理解,当v bundle运行的时候,framework通过匹配bundle间的Import-Package和Export-Package,
然后告诉v bundle,FamilyInfoDatabase这个类型的定义和装载实际上是由mc bundle完成的。这样,v bundle就会使用mc bundle的class loader来加在这个类型。另外,按照osgi framework的spec规定,javax.swing;javax.swing.table;java.awt;java.awt.event这4个包都都不属于framework的必备包,所以应该在Import-Package中指定。为了不用在Import-Package中指定这4个包,这个例子要求运行在sun的j2se的jvm(比如version 1.4.2_04),这样4个包都作为java api由bootstrap classloader加载,每个bundle都可以直接使用了。
比如在windows下,你把两个manifest文件放在包的根目录,然后执行以下命令
jar -cvfm family.jar family.mf com/wukong/test/family/gui/*.class
启动这个framework后,它的shell提供了如下简单的framework管理功能命令:
也就是说,之前使用这些包的bundle不会因为这个bundle被更新或者卸载而发生变化。而执行这个命令后,受影响的bundle将被重新解析
OS: window
jvm: j2se 1.4.2_04
family.jar和familycontrol.jar这两个文件放在D:/framework/bundles目录下
输入以下两条命令:
Bundle Id Bundle Name Bundle State Start Level Bundle Location Bundle Symbolic Name
.
.(省略部分结果显示)
.
stt 16,17
Bundle Id Bundle Name Bundle State Start Level Bundle Location Bundle Symbolic Name
.
.(省略部分结果显示)
.
有了这样的结构,程序变得灵活些了。现在假设客户提出需要增加一个字段,即人均年收入,并且提供按人均年收入排列纪录。看看framework框架下,这样的变动是如何的简单!
首先,我们通过分析,发现只要更改mc bundle的代码,使得数据库的实现中增加这个字段以及相应的排序方法即可。更新后的代码如下:
package
com.wukong.test.family.control.impl;
import
com.wukong.test.family.control.FamilyInfoDatabase;
import
java.util.
*
;
public
class
FamilyDatabase
implements
FamilyInfoDatabase
{
private LinkedList familyEntryList = new LinkedList();
private Object[] sortedValues = null;
public FamilyDatabase() {
this.familyEntryList.add(new FamilyInfoEntry("Zhang",3,1260));
this.familyEntryList.add(new FamilyInfoEntry("Li",6,1800));
this.familyEntryList.add(new FamilyInfoEntry("Liu",4,1500));
this.sortedValues = this.familyEntryList.toArray();
}
public String[] getColumns() {
return new String[] { "Family Name", "Family Population", "Income" ,"IncomePerPerson"};//添加了“IncomePerPerson”字段
}
public Object getValueAt(int row, int column) {
FamilyInfoEntry entry = (FamilyInfoEntry)this.sortedValues[row];
switch (column) {
case 0:
return entry.getFamilyName();
case 1:
return new Integer(entry.getPopulation());
case 2:
return new Integer(entry.getIncomePerYear());
case 3:
return new Integer(entry.getIncomePerYear()/entry.getPopulation());//计算人均年收入
default:
throw new IllegalArgumentException("Invalid column index.");
}
}
public String[] getSortingFields() {
}
public int getRowCount() {
return this.familyEntryList.size();
}
public void addEntry(List columns, List values)
throws IllegalArgumentException {
// TODO Auto-generated method stub
}
public void deleteEntry(String familyName) {
// TODO Auto-generated method stub
}
public void update(String familyName, List columns, List values)
throws IllegalArgumentException {
// TODO Auto-generated method stub
}
public void sort(String sortField) throws IllegalArgumentException{
if(sortField.equals("FamilyName")){
this.sortedValues = this.familyEntryList.toArray();
Arrays.sort(this.sortedValues,new SortedByName());
}
if(sortField.equals("Income")){
this.sortedValues = this.familyEntryList.toArray();
Arrays.sort(this.sortedValues,new SortedByIncome());
}
if(sortField.equals("IncomePerPerson")){//对纪录按人均年收入进行排序
this.sortedValues = this.familyEntryList.toArray();
Arrays.sort(this.sortedValues,new SortedByIPP());
}
}
class SortedByName implements Comparator{
public int compare(Object entry1,Object entry2) {
if (entry1 == entry2) {
return 0;
}
FamilyInfoEntry en1 = (FamilyInfoEntry) entry1;
FamilyInfoEntry en2 = (FamilyInfoEntry) entry2;
return en1.getFamilyName().compareTo(en2.getFamilyName());
}
}
class SortedByIncome implements Comparator {
public int compare(Object entry1, Object entry2) {
if (entry1 == entry2) {
return 0;
}
FamilyInfoEntry en1 = (FamilyInfoEntry) entry1;
FamilyInfoEntry en2 = (FamilyInfoEntry) entry2;
return en1.getIncomePerYear() - en2.getIncomePerYear();
}
}
class SortedByIPP implements Comparator {//人均年收入的排序标准。
public int compare(Object entry1, Object entry2) {
if (entry1 == entry2) {
return 0;
}
FamilyInfoEntry en1 = (FamilyInfoEntry) entry1;
FamilyInfoEntry en2 = (FamilyInfoEntry) entry2;
return (en1.getIncomePerYear()/en1.getPopulation()) - (en2.getIncomePerYear()/en2.getPopulation());
}
}
}
编译完毕后,我们把mc bundle的manifest文件改为如下:
Manifest-Version:
1.0
Bundle-SymbolicName: com.wukong.test.family.control
Bundle-Name: family control
Bundle-Version:
2.0
//由1.0改为2
.0
Bundle-Vendor: LiMing
Export-Package: com.wukong.test.family.control
然后使用jar工具打包,并把产生的新的familycontrol.jar依旧放在D:/framework/bundles目录下。
这时,我们只要执行下面的命令,framework就完成对mc bundle的升级工作:
up 16
升级完毕后,你会发现v bundle的显示依旧没有任何变化。这时我们需要执行一个refresh package命令,来更新bundle的解析:
rfr
这时,你会发现原来的JFrame被关闭了,然后又一个JFrame显示出来,里面的纪录多了一列“IncomePerPerson”,而且在“Sorted By”里面也
多了“IncomePerPerson”,而且,排序的结果也很正确。
542



被折叠的 条评论
为什么被折叠?



