傳統(tǒng)的Swing工具欄的按鈕從生成到響應(yīng)總是需要一堆相似的代碼來完成的,如下:
生成工具欄按鈕的代碼示例:
.....
響應(yīng)上面這個(gè)按鈕的點(diǎn)擊事件的代碼如下:
如果只是這點(diǎn)代碼,程序整體確實(shí)沒有什么問題,但數(shù)量多了就不得了,試想想看,按照MVC的放置原則,生成的代碼和響應(yīng)點(diǎn)擊的代碼應(yīng)該放在兩個(gè)類中,如果需要修改就是兩個(gè)修改點(diǎn);而工具欄按鈕一般是較多的,假設(shè)有十個(gè)按鈕那么就意味著十對修改點(diǎn),如果數(shù)量再多呢?這對軟件的可維護(hù)性可謂一個(gè)災(zāi)難,也為系統(tǒng)的隱患埋下了伏筆。有人說這就要考驗(yàn)維護(hù)者對系統(tǒng)的理解和對代碼的責(zé)任心了,對這種轉(zhuǎn)移問題和把維護(hù)人員等同于機(jī)器的觀點(diǎn)作者不敢茍同,框架的設(shè)計(jì)者不能把自己應(yīng)該盡到的責(zé)任推卸給維護(hù)者。
如果有效利用XML和反射等手段,我們可以做到工具欄菜單項(xiàng)的可配置化。具體來說就是,將菜單項(xiàng)的文字,圖片和點(diǎn)擊后的響應(yīng)函數(shù)都在XML配置文件中配置好,程序啟動(dòng)時(shí)去讀取文件生成菜單,點(diǎn)擊菜單項(xiàng)后會(huì)動(dòng)態(tài)的通過反射找到具體需要處理的函數(shù)。這樣做以后,修改一個(gè)按鈕對應(yīng)的功能定位代碼,或是增刪一個(gè)按鈕及其功能就很容易了。下面來看具體的做法:
1.工具欄菜單的配置文件
以上子項(xiàng)里,icon是工具欄按鈕的對應(yīng)圖片,function是點(diǎn)擊按鈕后響應(yīng)的具體函數(shù)名,description是用于ToolTipText的文字,這些信息都會(huì)被程序讀取出來。
2.讀取配置文件中的信息
3.根據(jù)讀出的信息生成按鈕項(xiàng)并添加到工具欄。
下面就是添加完成的工具欄:

ToolbarItemLoader.getItems()會(huì)得到上面第二段代碼中的items的引用,然后遍歷得到子項(xiàng)后就可以生成按鈕了。但注意一下,生成的按鈕是自定義的ToolbarButton類實(shí)例而不是JButton的子類實(shí)例,ToolbarButton繼承自JButton但多了一個(gè)function屬性,這個(gè)屬性對于事件響應(yīng)是必不可少的,請看下面的代碼:
4.添加工具欄菜單按鈕的事件響應(yīng):
上面代碼就顯示了ToolbarButton的function屬性,它作為一個(gè)函數(shù)的參數(shù)傳了出去,這個(gè)函數(shù)用反射來具體定位函數(shù),具體來說就是,function就是要真正調(diào)用的函數(shù)名。
5.用反射調(diào)用具體的函數(shù)executeFunction.
通過以上的反射處理后,如果點(diǎn)擊的是
這段信息對應(yīng)的按鈕,那么本類的format函數(shù)就會(huì)得到調(diào)用。
上面需要注意的有幾點(diǎn):
一是被反射調(diào)用的函數(shù)的訪問權(quán)限需要公有,否則會(huì)發(fā)生IllegalAccessException異常;二是函數(shù)名一定要和配置文件中的function節(jié)點(diǎn)對應(yīng)好,否則會(huì)拋出NoSuchMethodException異常,三是函數(shù)有自己的異常拋出后,需要用e.getTargetException()取得其真正異常。
通過以上五步,按鈕的生成和響應(yīng)就可以被一個(gè)配置文件所控制,這種方式改善了程序的可維護(hù)性,反射是實(shí)現(xiàn)改進(jìn)的五個(gè)步驟的核心環(huán)節(jié)。另外反射也是諸多框架和通用性組件的常用技術(shù)之一,值得每個(gè)程序員好好掌握。
就是這些,再見吧!
生成工具欄按鈕的代碼示例:
.....
reopenBtn=new JButton(ResourceUtil.ToolbarMain_Reopen_ImageIcon);
reopenBtn.setToolTipText("刷新數(shù)據(jù)庫內(nèi)容");
toolbar.add(reopenBtn);// toolbar是工具欄,JToolBar的示例
reopenBtn.setToolTipText("刷新數(shù)據(jù)庫內(nèi)容");
toolbar.add(reopenBtn);// toolbar是工具欄,JToolBar的示例
響應(yīng)上面這個(gè)按鈕的點(diǎn)擊事件的代碼如下:
view.getToolbarPanel().getReopenBtn().addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
.. // 具體處理略
}
});
public void actionPerformed(ActionEvent e) {

}
});
如果只是這點(diǎn)代碼,程序整體確實(shí)沒有什么問題,但數(shù)量多了就不得了,試想想看,按照MVC的放置原則,生成的代碼和響應(yīng)點(diǎn)擊的代碼應(yīng)該放在兩個(gè)類中,如果需要修改就是兩個(gè)修改點(diǎn);而工具欄按鈕一般是較多的,假設(shè)有十個(gè)按鈕那么就意味著十對修改點(diǎn),如果數(shù)量再多呢?這對軟件的可維護(hù)性可謂一個(gè)災(zāi)難,也為系統(tǒng)的隱患埋下了伏筆。有人說這就要考驗(yàn)維護(hù)者對系統(tǒng)的理解和對代碼的責(zé)任心了,對這種轉(zhuǎn)移問題和把維護(hù)人員等同于機(jī)器的觀點(diǎn)作者不敢茍同,框架的設(shè)計(jì)者不能把自己應(yīng)該盡到的責(zé)任推卸給維護(hù)者。
如果有效利用XML和反射等手段,我們可以做到工具欄菜單項(xiàng)的可配置化。具體來說就是,將菜單項(xiàng)的文字,圖片和點(diǎn)擊后的響應(yīng)函數(shù)都在XML配置文件中配置好,程序啟動(dòng)時(shí)去讀取文件生成菜單,點(diǎn)擊菜單項(xiàng)后會(huì)動(dòng)態(tài)的通過反射找到具體需要處理的函數(shù)。這樣做以后,修改一個(gè)按鈕對應(yīng)的功能定位代碼,或是增刪一個(gè)按鈕及其功能就很容易了。下面來看具體的做法:
1.工具欄菜單的配置文件
<items>


<item>
<icon>tollbar_sqlwindow/run.gif</icon>
<function>run</function>
<description>執(zhí)行所選擇的Sql語句</description>
</item>
<item>
<icon>tollbar_sqlwindow/batchRun.gif</icon>
<function>batchRun</function>
<description>批量執(zhí)行所選擇的Sql語句,以分號為分割單位</description>
</item>
<item>
<icon>tollbar_sqlwindow/format.gif</icon>
<function>format</function>
<description>格式化所選擇的Sql語句</description>
</item>


</items>


<item>
<icon>tollbar_sqlwindow/run.gif</icon>
<function>run</function>
<description>執(zhí)行所選擇的Sql語句</description>
</item>
<item>
<icon>tollbar_sqlwindow/batchRun.gif</icon>
<function>batchRun</function>
<description>批量執(zhí)行所選擇的Sql語句,以分號為分割單位</description>
</item>
<item>
<icon>tollbar_sqlwindow/format.gif</icon>
<function>format</function>
<description>格式化所選擇的Sql語句</description>
</item>


</items>
以上子項(xiàng)里,icon是工具欄按鈕的對應(yīng)圖片,function是點(diǎn)擊按鈕后響應(yīng)的具體函數(shù)名,description是用于ToolTipText的文字,這些信息都會(huì)被程序讀取出來。
2.讀取配置文件中的信息
List<ToolbarItem> items=new ArrayList<ToolbarItem>();
// 下面開始讀取sqlWndToolbar.xml得到菜單項(xiàng)
try {
SAXReader reader = new SAXReader();
InputStream is=TreeMenuPanel.class.getResourceAsStream(“sqlWndToolbar.xml”);// toolbar.xml是具體文件名
Document document = reader.read(is);
Element rootElm = document.getRootElement();
List<Element> elms = rootElm.elements("item");
for (Element elm : elms) {
String icon=elm.elementText("icon");
String function=elm.elementText("function");
String description=elm.elementText("description");
ToolbarItem btn=new ToolbarItem(icon,function,description);
items.add(btn);
}
} catch (Exception ex) {
DlgUtil.popupErrorDialog("無法讀取文件"+ResourceUtil.SqlWndToolbar_XMLFile);
ex.printStackTrace();
}
上述代碼中,ToolbarItem是包括icon,function,description 三個(gè)字符串子項(xiàng)的JavaBean。// 下面開始讀取sqlWndToolbar.xml得到菜單項(xiàng)
try {
SAXReader reader = new SAXReader();
InputStream is=TreeMenuPanel.class.getResourceAsStream(“sqlWndToolbar.xml”);// toolbar.xml是具體文件名
Document document = reader.read(is);
Element rootElm = document.getRootElement();
List<Element> elms = rootElm.elements("item");
for (Element elm : elms) {
String icon=elm.elementText("icon");
String function=elm.elementText("function");
String description=elm.elementText("description");
ToolbarItem btn=new ToolbarItem(icon,function,description);
items.add(btn);
}
} catch (Exception ex) {
DlgUtil.popupErrorDialog("無法讀取文件"+ResourceUtil.SqlWndToolbar_XMLFile);
ex.printStackTrace();
}
3.根據(jù)讀出的信息生成按鈕項(xiàng)并添加到工具欄。
for (ToolbarItem item : ToolbarItemLoader.getItems()) {
toolbar.add(new ToolbarButton(item.getIcon(), item.getFunction(),
item.getDescription()));
}
toolbar.add(new ToolbarButton(item.getIcon(), item.getFunction(),
item.getDescription()));
}
下面就是添加完成的工具欄:

ToolbarItemLoader.getItems()會(huì)得到上面第二段代碼中的items的引用,然后遍歷得到子項(xiàng)后就可以生成按鈕了。但注意一下,生成的按鈕是自定義的ToolbarButton類實(shí)例而不是JButton的子類實(shí)例,ToolbarButton繼承自JButton但多了一個(gè)function屬性,這個(gè)屬性對于事件響應(yīng)是必不可少的,請看下面的代碼:
4.添加工具欄菜單按鈕的事件響應(yīng):
for (Component c : toolbar.getComponents()) {
if (c instanceof ToolbarButton) {
final ToolbarButton btn = (ToolbarButton) c;
btn.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
// 要執(zhí)行的函數(shù)
String function = btn.getFunction();
// 所選擇的文本
String selectedText = inputTxt.getSelectedText();
// 執(zhí)行函數(shù)
executeFunction(function, selectedText);
}
});
}
}
if (c instanceof ToolbarButton) {
final ToolbarButton btn = (ToolbarButton) c;
btn.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
// 要執(zhí)行的函數(shù)
String function = btn.getFunction();
// 所選擇的文本
String selectedText = inputTxt.getSelectedText();
// 執(zhí)行函數(shù)
executeFunction(function, selectedText);
}
});
}
}
上面代碼就顯示了ToolbarButton的function屬性,它作為一個(gè)函數(shù)的參數(shù)傳了出去,這個(gè)函數(shù)用反射來具體定位函數(shù),具體來說就是,function就是要真正調(diào)用的函數(shù)名。
5.用反射調(diào)用具體的函數(shù)executeFunction.
private void executeFunction(String fucntionName, String selectedText) {
// 利用反射去找對應(yīng)函數(shù)
try {
// 得到實(shí)例對應(yīng)的類
Class<?> cls = this.getClass();
// 通過反射得到方法
Method method = cls.getMethod(fucntionName,
new Class[] { String.class });
// 通過反射調(diào)用對象的方法
method.invoke(this, new Object[] { selectedText });
} catch (NoSuchMethodException e) {
// 找不到方法時(shí)
DlgUtil.popupErrorDialog("找不到方法" + fucntionName + ".");
} catch (IllegalAccessException e) {
// 當(dāng)訪問權(quán)限不夠時(shí)
DlgUtil.popupErrorDialog("方法" + fucntionName + "的訪問權(quán)限不夠.");
} catch (InvocationTargetException e) {
// 當(dāng)調(diào)用的函數(shù)拋出異常時(shí)
Exception tragetException = (Exception) e.getTargetException();
StringBuilder sb = new StringBuilder();
sb.append("調(diào)用函數(shù)" + fucntionName + "出現(xiàn)異常,");
sb.append("具體的異常信息為" + tragetException.getMessage() + ".");
SqlTextDialog dlg = new SqlTextDialog("調(diào)用函數(shù)" + fucntionName
+ "出現(xiàn)異常", sb.toString(), 400, 300);
dlg.setVisible(true);
}
}
// 利用反射去找對應(yīng)函數(shù)
try {
// 得到實(shí)例對應(yīng)的類
Class<?> cls = this.getClass();
// 通過反射得到方法
Method method = cls.getMethod(fucntionName,
new Class[] { String.class });
// 通過反射調(diào)用對象的方法
method.invoke(this, new Object[] { selectedText });
} catch (NoSuchMethodException e) {
// 找不到方法時(shí)
DlgUtil.popupErrorDialog("找不到方法" + fucntionName + ".");
} catch (IllegalAccessException e) {
// 當(dāng)訪問權(quán)限不夠時(shí)
DlgUtil.popupErrorDialog("方法" + fucntionName + "的訪問權(quán)限不夠.");
} catch (InvocationTargetException e) {
// 當(dāng)調(diào)用的函數(shù)拋出異常時(shí)
Exception tragetException = (Exception) e.getTargetException();
StringBuilder sb = new StringBuilder();
sb.append("調(diào)用函數(shù)" + fucntionName + "出現(xiàn)異常,");
sb.append("具體的異常信息為" + tragetException.getMessage() + ".");
SqlTextDialog dlg = new SqlTextDialog("調(diào)用函數(shù)" + fucntionName
+ "出現(xiàn)異常", sb.toString(), 400, 300);
dlg.setVisible(true);
}
}
通過以上的反射處理后,如果點(diǎn)擊的是
<item>
<icon>tollbar_sqlwindow/format.gif</icon>
<function>format</function>
<description>格式化所選擇的Sql語句</description>
</item>
<icon>tollbar_sqlwindow/format.gif</icon>
<function>format</function>
<description>格式化所選擇的Sql語句</description>
</item>
這段信息對應(yīng)的按鈕,那么本類的format函數(shù)就會(huì)得到調(diào)用。
public void format(String selectedText) throws Exception {


}


}
上面需要注意的有幾點(diǎn):
一是被反射調(diào)用的函數(shù)的訪問權(quán)限需要公有,否則會(huì)發(fā)生IllegalAccessException異常;二是函數(shù)名一定要和配置文件中的function節(jié)點(diǎn)對應(yīng)好,否則會(huì)拋出NoSuchMethodException異常,三是函數(shù)有自己的異常拋出后,需要用e.getTargetException()取得其真正異常。
通過以上五步,按鈕的生成和響應(yīng)就可以被一個(gè)配置文件所控制,這種方式改善了程序的可維護(hù)性,反射是實(shí)現(xiàn)改進(jìn)的五個(gè)步驟的核心環(huán)節(jié)。另外反射也是諸多框架和通用性組件的常用技術(shù)之一,值得每個(gè)程序員好好掌握。
就是這些,再見吧!