(四)中提到的直接型改造法實(shí)際上和一個(gè)傳統(tǒng)的java應(yīng)用程序沒(méi)有區(qū)別。因此客戶(hù)的需求發(fā)生變化,通常是牽一發(fā)而動(dòng)全身。
那么我們現(xiàn)在就看看如果在osgi framework中,用多個(gè)bundle來(lái)實(shí)現(xiàn)的效果吧。
我的想法是用兩個(gè)bundle來(lái)配合實(shí)現(xiàn)“扶貧助手”的功能。一個(gè)bundle專(zhuān)門(mén)負(fù)責(zé)錄入和顯示紀(jì)錄,一個(gè)bundle專(zhuān)門(mén)負(fù)責(zé)紀(jì)錄的數(shù)據(jù)結(jié)構(gòu)和對(duì)數(shù)據(jù)的處理,用時(shí)下時(shí)髦的說(shuō)法就是使用了mvc,只是我的m和c放到了一起。
先看看mc的bundle實(shí)現(xiàn)代碼片斷:
對(duì)于這個(gè)數(shù)據(jù)庫(kù)的實(shí)現(xiàn),代碼片斷如下(增加,刪除和修改的代碼都被省略了,只給出排序的代碼):
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();
}
}
}

同樣需要注意的是我們把這個(gè)實(shí)現(xiàn)放到了package com.wukong.test.family.control.impl中
下面看看v的bundle實(shí)現(xiàn)。
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的實(shí)現(xiàn)基本是界面的構(gòu)造,而關(guān)于數(shù)據(jù)庫(kù),它只能看到interface FamilyInfoDatabase,因?yàn)樗籭mport com.wukong.test.family.control.*,然后通過(guò)FamilyInfoDatabase內(nèi)嵌靜態(tài)類(lèi)的靜態(tài)工廠(chǎng)方法獲得數(shù)據(jù)庫(kù)的實(shí)例,這樣它完全不用關(guān)心數(shù)據(jù)庫(kù)如何實(shí)現(xiàn)。
源碼編譯后得到class文件后,下一步我們的工作就要來(lái)構(gòu)造這兩個(gè)bundle。最關(guān)鍵的步驟就是為每個(gè)bundle寫(xiě)它的manifest文件
我們先給出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

非常簡(jiǎn)單,但請(qǐng)大家注意這行
Export-Package: com.wukong.test.family.control
它告訴framework,這個(gè)bundle提供com.wukong.test.family.control這個(gè)包。
所謂“bundle A提供某個(gè)包”意思,通俗簡(jiǎn)單的理解是如果bundle B在manifest文件中說(shuō)明要使用這個(gè)包(通過(guò)Import-Package這個(gè)關(guān)鍵字,下面會(huì)在v bundle中有說(shuō)明)那么bundle B運(yùn)行時(shí),當(dāng)使用到這個(gè)包中的類(lèi)時(shí),framework將告訴B的class loader這個(gè)類(lèi)的定義是來(lái)自bundle A,從而不會(huì)發(fā)生ClassNotFoundException。另外值得一提的是我們并沒(méi)有把com.wukong.test.family.control.impl這個(gè)包加進(jìn)來(lái),這樣做的目的也是為了隱藏具體實(shí)現(xiàn)。將來(lái)只要interface不變,我們可以使用不同的實(shí)現(xiàn)來(lái)替換現(xiàn)有實(shí)現(xiàn),大大提高擴(kuò)展性。
還有一點(diǎn),細(xì)心的話(huà),你會(huì)發(fā)現(xiàn)這個(gè)bundle沒(méi)有activator。這是允許的,這種bundle就像動(dòng)態(tài)連接庫(kù),只提供接口和方法以及實(shí)現(xiàn),等待其他實(shí)體的調(diào)用,而本身不能運(yùn)行。
有了manifest文件,我們最后就是打jar包了。
比如在windows下,你把兩個(gè)manifest文件放在包的根目錄,然后執(zhí)行以下命令
jar -cvfm family.jar family.mf com\wukong\test\family\gui\*.class
jar -cvfm familycontrol.jar familycontrol.mf com\wukong\test\family\control
這樣,我們就可以認(rèn)為family.jar是我們的v bundle而familycontrol.jar是我們的mc bundle。
下面,我們就來(lái)安裝運(yùn)行這兩個(gè)bundle吧。
我將首先簡(jiǎn)單介紹一個(gè)我自己編寫(xiě)的osgi framework,然后結(jié)合這個(gè)demo framework來(lái)部署和運(yùn)行新的“扶貧助手”。
本人編寫(xiě)的osgi framework基本上完成了spec r4的核心規(guī)范,但是與安全相關(guān)的功能都沒(méi)有實(shí)現(xiàn)。
啟動(dòng)這個(gè)framework后,它的shell提供了如下簡(jiǎn)單的framework管理功能命令:
bl 即bundle list,查看所有已經(jīng)安裝的bundle
dl 即detailed bundle list,用來(lái)查看某個(gè)bundle的詳細(xì)信息
sl 即service list,用來(lái)查看所有已經(jīng)注冊(cè)的service
ls 即list setting,用來(lái)查看所有設(shè)置的系統(tǒng)屬性(System.getProperties)和framework當(dāng)前的active start level
in 即install bundle,通過(guò)指定bundle文件的url來(lái)安裝該bundle
up 即update bundle,通過(guò)指定bundle id來(lái)升級(jí)指定的bundle
un 即uninstall bundle,通過(guò)指定bundle id來(lái)卸載指定的bundle
stt 即start bundle,通過(guò)指定bundle id來(lái)啟動(dòng)指定的bundle
stp 即stop bundle,通過(guò)指定bundle id來(lái)停止指定bundle的運(yùn)行
rst 即restart bundle,通過(guò)指定bundle id來(lái)重新啟動(dòng)指定的bundle
rfr 即refresh package,當(dāng)某些bundle被更新或卸載后,如果不執(zhí)行這個(gè)命令,那么被更新或卸載的bundle的Export-Package(如果有的話(huà))將繼續(xù)服役,
也就是說(shuō),之前使用這些包的bundle不會(huì)因?yàn)檫@個(gè)bundle被更新或者卸載而發(fā)生變化。而執(zhí)行這個(gè)命令后,受影響的bundle將被重新解析
rest 即restart,重新啟動(dòng)framework,屬于“熱起”
shut 即shutdown,停止整個(gè)framework的運(yùn)行
set 用來(lái)設(shè)置framework的active startlevel或者某個(gè)bundle的start level
log 用來(lái)查看系統(tǒng)的日志信息
現(xiàn)在我們來(lái)假設(shè)運(yùn)行環(huán)境如下:
OS: window
jvm: j2se 1.4.2_04
family.jar和familycontrol.jar這兩個(gè)文件放在D:/framework/bundles目錄下
第一步:安裝bundle
輸入以下兩條命令:
in file:D:/framework/bundles/familycontrol.jar
in file:D:/framework/bundles/family.jar
然后用bl命令查看結(jié)果,其中得到如下顯示結(jié)果:
Bundle Id Bundle Name Bundle State Start Level Bundle Location Bundle Symbolic Name
0 OSGiFramework Active 0 System Bundle system.bundle
.
.(省略部分結(jié)果顯示)
.
16 family control Installed 1 file:D:/framework/bundles/familycontrol.jar com.wukong.test.family.control
17 family gui Installed 1 file:D:/framework/bundles/family.jar com.wukong.test.family.gui
說(shuō)明兩個(gè)bundle安裝成功,其中mc bundle的id是16, v bundle的id是17
第二步:?jiǎn)?dòng)bundle
可以輸入以下命令
stt 16,17
然后我們就可以看到一個(gè)JFrame顯示出來(lái),里面顯示了3條記錄,沒(méi)有任何順序(其順序?qū)嶋H上是在程序中添加到LinkedList的順序)。我們可以在Sorted By的列表中看到有兩個(gè)選擇“FamilyName”和“Income”,當(dāng)選擇“Income”時(shí),你將發(fā)現(xiàn)3條記錄按年收入由小到大排列起來(lái)。程序的運(yùn)行完全符合我們的意圖。
這時(shí)如果我們?cè)儆胋l命令查看,將得到如下結(jié)果:
Bundle Id Bundle Name Bundle State Start Level Bundle Location Bundle Symbolic Name
0 OSGiFramework Active 0 System Bundle system.bundle
.
.(省略部分結(jié)果顯示)
.
16 family control Resolved 1 file:D:/framework/bundles/familycontrol.jar com.wukong.test.family.control
17 family gui Active 1 file:D:/framework/bundles/family.jar com.wukong.test.family.gui
注意:由于mc bundle沒(méi)有Activator,所以啟動(dòng)這個(gè)bundle只會(huì)使其進(jìn)入Resolved狀態(tài)。
有了這樣的結(jié)構(gòu),程序變得靈活些了。現(xiàn)在假設(shè)客戶(hù)提出需要增加一個(gè)字段,即人均年收入,并且提供按人均年收入排列紀(jì)錄。看看framework框架下,這樣的變動(dòng)是如何的簡(jiǎn)單!
首先,我們通過(guò)分析,發(fā)現(xiàn)只要更改mc bundle的代碼,使得數(shù)據(jù)庫(kù)的實(shí)現(xiàn)中增加這個(gè)字段以及相應(yīng)的排序方法即可。更新后的代碼如下:
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());//計(jì)算人均年收入
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"))
{//對(duì)紀(jì)錄按人均年收入進(jìn)行排序
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
{//人均年收入的排序標(biāo)準(zhǔn)。

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工具打包,并把產(chǎn)生的新的familycontrol.jar依舊放在D:/framework/bundles目錄下。
這時(shí),我們只要執(zhí)行下面的命令,framework就完成對(duì)mc bundle的升級(jí)工作:
up 16
升級(jí)完畢后,你會(huì)發(fā)現(xiàn)v bundle的顯示依舊沒(méi)有任何變化。這時(shí)我們需要執(zhí)行一個(gè)refresh package命令,來(lái)更新bundle的解析:
rfr
這時(shí),你會(huì)發(fā)現(xiàn)原來(lái)的JFrame被關(guān)閉了,然后又一個(gè)JFrame顯示出來(lái),里面的紀(jì)錄多了一列“IncomePerPerson”,而且在“Sorted By”里面也
多了“IncomePerPerson”,而且,排序的結(jié)果也很正確。
瞧,framework為應(yīng)用程序提供了強(qiáng)大的動(dòng)態(tài)更新功能,非常方便。甚至都不用重新啟動(dòng)framework,更不用說(shuō)JVM了。您也許會(huì)說(shuō),最終用戶(hù)根本不可能幫你輸入這些什么in,up晦澀的命令。還有,讓用戶(hù)拷貝新版本到本地似乎也不是什么好主意。嗯,這個(gè)問(wèn)題說(shuō)得很對(duì)。其實(shí)我是偷懶了,這些操作framework都留有接口,我們只要再編寫(xiě)一個(gè)專(zhuān)門(mén)管理這兩個(gè)bundle升級(jí)的第三個(gè)bundle就可以了,這個(gè)bundle將提供友好的界面來(lái)告訴最終用戶(hù)如何升級(jí)。這樣就能夠提供比較好的解決方案了。至于第二個(gè)問(wèn)題,別忘了bundle的location是一個(gè)url,只要jvm注冊(cè)了相應(yīng)的url handler完全可以支持http,ftp等豐富的url,例如,升級(jí)的時(shí)候,framework自動(dòng)連接到供應(yīng)商的http下載服務(wù)器獲取最新版本進(jìn)行更新,當(dāng)然,我們也可以把更強(qiáng)大更靈活的升級(jí)功能及選項(xiàng)加入到上面提到的第三個(gè)bundle中,從而極大提高了程序的易用性,而且極大降低了軟件提供商的維護(hù)成本。
到此為止,我已經(jīng)想不出再多好話(huà)了,但是,還可以更好!下一節(jié),將采用service的方式來(lái)實(shí)現(xiàn),嘿嘿,情況又會(huì)不一樣了,到時(shí)候我會(huì)對(duì)比一下這兩種實(shí)現(xiàn)方式的優(yōu)劣。
題外話(huà):抱歉沒(méi)有拿某個(gè)著名的產(chǎn)品或者open source的產(chǎn)品來(lái)舉例,我打算把自己的實(shí)現(xiàn)也opensource,有興趣參與嗎?不管怎樣,過(guò)兩天就把實(shí)現(xiàn)上傳上來(lái),歡迎拍磚。