
2008年10月4日
摘要: * 此框架采用前后臺分開,前后臺都可以單獨部署,前端采用輕量級的扁平化設計(html+javascript+Bootstrap), 會自動針對不同的屏幕尺寸調整頁面,使其在各個尺寸的屏幕上表現良好。
* 后端采用Spring boot,它使我們更容易去創建基于Spring的獨立和產品級的可以即時運行的應用和服務。直接嵌入Tomcat 或Jetty服務器,不需要部署WAR 文件,可直接運行jar文件。
* 系統權限框架采用Shiro,實現前后臺權限校驗 * 持久層采用JPA ,并實現類ibatis的查詢功能;數據響應該采用json格式。
* 服務采用REST形式,能夠方便的與手機app進行對接,集成swagger能夠在線查看RESTAPI 文檔和在線測試服務接口
* 支持單點登錄,可以多系統進行菜單集成,形成一個portal
* 支持高并發和水平擴展,支持Session的統一存儲
* 項目采用gradle構建,能夠方便的將各項目進行按需組裝
閱讀全文
posted @
2016-06-13 10:00 Loy Fu 閱讀(4037) |
評論 (0) |
編輯 收藏
摘要: java nio的全稱是java new I/O,即一個全新的I/O控制系統,它的API的包名為java.nio,是在jdk1.4后引入的。
nio之所以為為新,在于它并沒在原來I/O的基礎上進行開發,而是提供了全新的類和接口,除了原來的基本功能之外,它還提供了以下新的特征:
? 多路選擇的非封鎖式...
閱讀全文
posted @
2008-10-21 17:44 Loy Fu 閱讀(835) |
評論 (0) |
編輯 收藏
作者:羅代均 ldj_work#126.com,轉載請保持完整性
環境說明
Apache :apache_2.0.55 1 個
Tomcat: apache-tomcat-5.5.17 (zip版) 2個
mod_jk:: mod_jk-apache-2.0.55.so 1個
第一部分:負載均衡
負載均衡,就是apache將客戶請求均衡的分給tomcat1,tomcat2....去處理
1.安裝apche,tomcat
http://httpd.apache.org/ 下載Apache 2.0.55
http://tomcat.apache.org/download-55.cgi 下載tomcat5.5 zip版本(解壓即可,綠色版)
http://apache.justdn.org/tomcat/tomcat-connectors/jk/binaries/win32/jk-1.2.15/ 下載mod_jk,注意和 apache版本匹配
按照jdk,我的路徑為:E:\ide\apache\Apache2
解壓兩份Tomcat, 路徑分別為 E:\ide\tomcat1,E:\ide\tomcat2
下載mod_jk
2.修改Apache配置文件http.conf
在apache安裝目錄下conf目錄中找到http.conf
在文件最后加上下面一句話就可以了
include "E:\ide\apache\Apache2\conf\mod_jk.conf"
2. http.conf 同目錄下新建mod_jk.conf文件,內容如下
#加載mod_jk Module
LoadModule jk_module modules/mod_jk-apache-2.0.55.so
#指定 workers.properties文件路徑
JkWorkersFile conf/workers.properties
#指定那些請求交給tomcat處理,"controller"為在workers.propertise里指定的負載分配控制器
JkMount /*.jsp controller
3.在http.conf同目錄下新建 workers.properties文件,內容如下
worker.list = controller,tomcat1,tomcat2 #server 列表
#========tomcat1========
worker.tomcat1.port=8009 #ajp13 端口號,在tomcat下server.xml配置,默認8009
worker.tomcat1.host=localhost #tomcat的主機地址,如不為本機,請填寫ip地址
worker.tomcat1.type=ajp13
worker.tomcat1.lbfactor = 1 #server的加權比重,值越高,分得的請求越多
#========tomcat2========
worker.tomcat2.port=9009 #ajp13 端口號,在tomcat下server.xml配置,默認8009
worker.tomcat2.host=localhost #tomcat的主機地址,如不為本機,請填寫ip地址
worker.tomcat2.type=ajp13
worker.tomcat2.lbfactor = 1 #server的加權比重,值越高,分得的請求越多
#========controller,負載均衡控制器========
worker.controller.type=lb
worker.controller.balanced_workers=tomcat1,tomcat2 #指定分擔請求的tomcat
worker.controller.sticky_session=1
4.修改tomcat配置文件server.xml
如果你在不同電腦上安裝tomcat,tomcat的安裝數量為一個,可以不必修改tomcat配置文件
我這里是在同一臺電腦上安裝兩個tomcat,所以需要更改其中一個的設置
打開tomcat2/conf/server.xml文件

5.編寫一個測試jsp
建立一個目錄test.里面新建一個test.jsp,內容為
<%
System.out.println("===========================");
%>
把test放到tomcat1,tomcat2的webapps下
6.啟動apache,tomcat1,tomcat2,進行測試
再刷新一次,tomcat2也打印了一條,再刷新,可以看到請求會被tomcat1,tomcat2輪流處理,實現了負載均衡
第二部分,配置集群
只配置負載均衡還不行,還要session復制,也就是說其中任何一個tomcat的添加的session,是要同步復制到其它tomcat, 集群內的tomcat都有相同的session
1. 修改tomcat1, tomcat2的server.xml,將集群部分配置的在注釋符刪掉,并將tomcat2的4001端口改為4002,以避免與tomcat沖突,當然,如果是兩臺電腦,是不用改端口的,去掉注釋符即可

2,修改測試項目test
修改test.jsp,內容如下
<%@ page contentType="text/html; charset=GBK" %>
<%@ page import="java.util.*" %>
<html><head><title>Cluster App Test</title></head>
<body>
Server Info:
<%
out.println(request.getLocalAddr() + " : " + request.getLocalPort()+"<br>");%>
<%
out.println("<br> ID " + session.getId()+"<br>");
// 如果有新的 Session 屬性設置
String dataName = request.getParameter("dataName");
if (dataName != null && dataName.length() > 0) {
String dataValue = request.getParameter("dataValue");
session.setAttribute(dataName, dataValue);
}
out.print("<b>Session 列表</b>");
Enumeration e = session.getAttributeNames();
while (e.hasMoreElements()) {
String name = (String)e.nextElement();
String value = session.getAttribute(name).toString();
out.println( name + " = " + value+"<br>");
System.out.println( name + " = " + value);
}
%>
<form action="index.jsp" method="POST">
名稱:<input type=text size=20 name="dataName">
<br>
值:<input type=text size=20 name="dataValue">
<br>
<input type=submit>
</form>
</body>
</html>
然后在test 新建WEB-INF目錄,WEB-INF下新建web.xml,內容如下
注意:在你的應用的web.xml加入 <distributable/> 即可
ok,講test復制到tomcat1,tomcat2的webapps下,重啟apache,tomcat1,tomcat2,
新建一個 名稱為 xiaoluo ,值為 cdut 的session,提交查詢,新開一個ie窗口,再提交查詢,如圖,可以看到,兩個tomcat 是負載均衡,并且session同步的
posted @
2008-10-20 08:49 Loy Fu 閱讀(527) |
評論 (0) |
編輯 收藏
在實際的網頁開發中,大部分時間都要涉及到Form表單的處理。在Ext框架中也提供了很多這方面的控件,而且還有一個專門的FormPanel布 局,該布局默認為放在面板上面的所有控件都是換行放置,而在實際應用中為了美觀,有些需要橫排,特別是Radio控件,這個時候就需要我們重新定制這些控 件的布局了,該例子中使用CSS來實現這些功能,先貼出一張效果圖。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Ext中FormPanel面板及Form控件橫排測試(CSS)</title>
<link rel="stylesheet" type="text/css" media="all" href="../ext/resources/css/ext-all.css" />
<style type="text/css" media="all">
.allow-float {clear:none!important;} /* 允許該元素浮動 */
.stop-float {clear:both!important;} /* 阻止該元素浮動 */
.sex-male {float:left;}
.sex-female {float:left;padding:0 0 0 20px;}
.age-field {float:left;padding:0 0 0 58px;*padding:0 0 0 50px!important;*padding:0 0 0 50px;}
</style>
</head>
<body>
<script type="text/javascript" src="../ext/adapter/ext/ext-base.js"></script>
<script type="text/javascript" src="../ext/ext-all.js"></script>
<script type="text/javascript" src="../ext/build/locale/ext-lang-zh_CN.js"></script>
<script type="text/javascript">Ext.BLANK_IMAGE_URL = '../ext/resources/images/default/s.gif';</script>
<script type="text/javascript">
Ext.onReady(function() {
//創建Form面板
var fp = new Ext.form.FormPanel({
buttonAlign:'center',
labelAlign:'right',
labelWidth:40,
frame:true,
bodyStyle:'padding:8px 0 0 0;',
items:[{
xtype:'textfield',
fieldLabel:'姓名',
name:'n_username',
id:'i_username',
width:320
},{
xtype:'radio',
fieldLabel:'性別',
boxLabel:'男',
name:'sex',
id:'male',
itemCls:'sex-male', //向左邊浮動,處理控件橫排
clearCls:'allow-float', //允許兩邊浮動,在實際生成的HTML結構中有專門的DIV阻斷浮動
checked:true
},{
xtype:'radio',
boxLabel:'女',
name:'sex',
id:'female',
itemCls:'sex-female', //向左浮動,處理控件橫排
clearCls:'allow-float', //允許兩邊浮動
hideLabel:true //不顯示前面"性別"的標簽
},{
xtype:'textfield',
fieldLabel:'年齡',
name:'n_age',
id:'i_age',
itemCls:'age-field', //向左浮動,處理控件橫排
width:133
},{
xtype:'textfield',
fieldLabel:'住址',
name:'n_address',
id:'i_address',
itemCls:'stop-float', //不允許浮動,結束控件橫排
width:320
}],
buttons:[{
text:'確定',
handler:onOK //實際應用一般是處理fp.getForm.submit()事件
}, {
text:'重置',
handler:function(){ fp.getForm().reset(); }
}],
keys:[{ //處理鍵盤回車事件
key:Ext.EventObject.ENTER,
fn:onOK,
scope:this
}]
});
//確定按鈕事件,這里只是簡單獲取各控件值,實際應用一般和后臺腳本結合
function onOK() {
var strMsg;
strMsg = ‘姓名:’ + fp.getComponent(’i_username’).getValue() + ‘,性別:’;
if (fp.getComponent(’male’).checked) strMsg += ‘男’;
if (fp.getComponent(’female’).checked) strMsg += ‘女’;
strMsg += ‘,年齡:’ + fp.getComponent(’i_age’).getValue();
strMsg += ‘,住址:’ + fp.getComponent(’i_address’).getValue();
alert(strMsg);
}
//創建主窗口
new Ext.Window({
title:’Ext中FormPanel面板及Form控件橫排測試(CSS)’,
width:400,
closable:false,
collapsible:true,
draggable:false,
resizable:false,
modal:true,
border:false,
items:[fp],
buttons:[]
}).show();
});
</script>
</body>
</html>
posted @
2008-10-15 13:03 Loy Fu 閱讀(733) |
評論 (0) |
編輯 收藏
Java下的框架編程之cglib的應用 |
|
|
Proxy可以看作是微型的AOP,明白提供了在繼承和委托之外的第三個代碼封裝途徑,只要有足夠的想象力,可以做得非常好玩,Spring的源碼里用Proxy就用得很隨便,看得我非常眼紅。可惜Proxy必須基于接口。因此Spring的做法,基于接口的用proxy,否則就用cglib。AOP么,一般小事非compoent一級的就不麻煩AspectJ出手了。
cglib的Enhancer說起來神奇,用起來一頁紙不到就講完了。
它的原理就是用Enhancer生成一個原有類的子類,并且設置好callback到proxy, 則原有類的每個方法調用都會轉為調用實現了MethodInterceptor接口的proxy的intercept() 函數:
public Object intercept(Object o,Method method,Object[] args,MethodProxy proxy)
|
在intercept()函數里,你可以在執行Object result=proxy.invokeSuper(o,args);來執行原有函數,在執行前后加入自己的東西,改變它的參數值,也可以瞞天過海,完全干別的。說白了,就是AOP中的around advice。
AOP沒有出現以前,該領域經典的設計模式是Decorator,像Java IO Stream的設計就是如此。不過,如果為每個DAO, 每個方法的寫Decorator函數會寫死人的,所以用上cglib的好處是一次過攔截所有方法。
另外,cglib除了Enhancer之外,還有BulkBean和Transform,都是Hibernate持久化的基礎,但文檔貧乏,一時還沒去看怎么用。
1.AOP里講了一百遍啊一百遍的log aspect在cglib是這樣做的:
public class LogDAOProxy implements MethodInterceptor
{
private Logger log=Logger.getLogger(LogDAOProxy.class);
private Enhancer enhancer=new Enhancer();
//返回DAO的子類
public Object getDAO(Class clz)
{
enhancer.setSuperclass(clz);
enhancer.setCallback(this);
return enhancer.create();
}
//默認的攔截方法
public Object intercept(Object o,Method method,Object[] args,
MethodProxy proxy) throws Throwable
{
log.info("調用日志方法"+method.getName());
Object result=proxy.invokeSuper(o,args);
return result;
}
}
|
應用的代碼:
LogDAOProxy proxy = new LogDAOProxy();
GoodsDAO dao = (GoodsDAO)proxy.getDAO(GoodsDAO.class);
dao.insert(goods);
|
2.而在Spring的管理下應該略加修改的高級Decorator
上面的例子用return enhancer.create();創建子類實例,但在Spring管理下,一些Bean的實例必須由Spring來創建和管理,而不由enhancer來創建的。所以我對上述用法略加修改,使它真正當一個Proxy的角色,請對比黑體字的部分。
public class LogDAOProxy implements MethodInterceptor
{
private Logger log=Logger.getLogger(LogDAOProxy.class);
private Object dao=null;
private Enhancer enhancer=new Enhancer();
//返回DAO的子類
public Object getDAO(Class clz,Object dao)
{
this.dao = dao;
enhancer.setSuperclass(clz);
enhancer.setCallback(this);
return enhancer.create();
}
//默認的攔截方法
public Object intercept(Object o,Method method,Object[] args,
MethodProxy proxy) throws Throwable
{
log.info("調用日志方法"+method.getName());
Object result=proxy.invoke(dao, args);
return result;
}
}
|
可見,原來模式里在getDao()時由enhancer創建dao,而 調用intercept時則將enhancer創建的dao以Object o參數傳回。
而新模式里,dao在getDao()時從外面傳入,enhancer.create()返回的是一個proxy. 而調用intercept時,實際會用之前傳入的dao進行操作,而忽略Object o參數傳入的proxy。
有點遺憾, intercept函數里MethodProxy的Signature是固定的,即客戶如果調用foo(String),你不可以用proxy.invoke偷換成foo(String,String);
|
posted @
2008-10-08 10:38 Loy Fu 閱讀(403) |
評論 (0) |
編輯 收藏
前臺:
Store:
var resource = new Ext.data.Store({
fields: ['imgpath','typeImage','title', 'type'],
url: 'teaching/resource/resourceAction.evi?method=getResourceList',
reader: new Ext.data.XmlReader(
{
record: "Item",
totalRecords: "TotalCount"
},
[{name:'title',mapping: 'title'}, {name:'type',mapping: 'type'},{name:'imgpath',mapping: 'imgpath'},{name:'typeImage',mapping: 'typeImage'} ]
)
});
resource.addListener('load', function(st, rds, opts) {
// st 是當前的store, rds是讀到的Record[], opts是store的配置
for( var c=0; c<rds.length; c++ ) {
rds[c].set('typeImage', "<img src='./images/33.gif' width='12' height='12' />");
//待定類別,先定死類別圖片
}
});
resource.load({params:{start:0,limit:10}});
var resourceType = new Ext.data.Store({
,
reader: new Ext.data.XmlReader({
record: "Item"
}, [
{name: 'resourceTypeId', mapping: 'resourceTypeId'},
{name: 'resourceType', mapping: 'resourceType'}
])
});
resourceType.load();
var languageType = new Ext.data.Store({
,
reader: new Ext.data.XmlReader({
record: "Item"
}, [
{name: 'languageTypeId', mapping: 'languageTypeId'},
{name: 'languageType', mapping: 'languageType'}
])
});
languageType.load();
列表:
resourcePanel = new Ext.grid.GridPanel({
id: 'resources',
frame: true,
header: false,
width: 288,
autoWidth: true,
autoHeight: true,
loadMask:{msg:'正在加載數據,請稍侯……'},
iconCls:'icon-grid',
viewConfig: { forceFit: true },
columns:[
{header: " ",dataIndex: 'typeImage' , width:20},
{header: "資源標題", width: 190, sortable: true, dataIndex: 'title'},
{header: "類別", width: 80, sortable: true, dataIndex: 'type'}
],
store: resource,
selModel: new Ext.grid.RowSelectionModel({singleSelect:false}),
bbar: new Ext.PagingToolbar({
pageSize: 10,
store: resource,
displayInfo: false,
//displayMsg: '顯示第 {0} 條到 {1} 條記錄,一共 {2} 條',
emptyMsg: "沒有記錄"
}),
listeners: {
rowclick:function(e) {
try {
window.parent.parent.Ext.ux.MyTips.msg("提示", "雙擊該行可預覽該資源");
} catch(e) {}
},
rowdblclick:function(g, rIdx, e) {
var rd = g.getStore().getAt(rIdx);
var html = "<img src='./images/" + rd.get('imgpath') + "' />";
window.parent.showWin({
layout: 'fit',
maximizable: true,
title: rd.get('title'),
width: 400,
height: 400,
//modal: true,
//closeAction: 'hide',
plain: true,
items: [ {html: html} ]
});
}
}
});
FormPanel:
var rform = new Ext.form.FormPanel({
id:'rform',
header: false,
frame: true,
hideBorders: false,
items: [
new Ext.form.TextField({
fieldLabel: '關鍵字',
name:'keyword'
}),
new Ext.form.ComboBox({
fieldLabel: '資源類別',
mode: 'local',
triggerAction: 'all',
store: resourceType,
typeAhead: true,
hiddenName:'resourceTypeId',
displayField: 'resourceType',
valueField: 'resourceTypeId',
readOnly: true,
selectOnFocus: true
}),
new Ext.form.ComboBox({
fieldLabel: '語言',
mode: 'local',
triggerAction: 'all',
typeAhead: true,
hiddenName:'languageTypeId',
displayField:'languageType',
valueField:'languageTypeId',
readOnly: true,
selectOnFocus: true,
store:languageType
}),
new Ext.Panel({
layout: 'table',
buttonAlign: 'center',
layoutConfig: { colspan: 3 },
buttons:[{text: '搜 尋',
handler: function() {
var keyword = Ext.get('keyword').dom.value;
var resourceTypeId = Ext.get('resourceTypeId').dom.value;
var languageTypeId = Ext.get('languageTypeId').dom.value;
resource.reload({params:{start:0,limit:3,keyword:keyword,resourceTypeId:resourceTypeId,languageTypeId:languageTypeId}});
//這里不用再寫ajax,Ext已經封裝了ajax,只要把參數傳進去就行了
}},
{
text: '重 置',
handler: function() {
Ext.getCmp('rform').form.reset();
}
}
]
})
]
})
后臺:
public ActionForward getResourceList(ActionMapping mapping,
ActionForm form, HttpServletRequest request,
HttpServletResponse response) throws IOException {
Document document = DocumentHelper.createDocument();
String start = request.getParameter("start");
String limit = request.getParameter("limit");
String keyword = request.getParameter("keyword");
String resourceTypeId = request.getParameter("resourceTypeId");
String languageTypeId = request.getParameter("languageTypeId");
List<HqlCondition> hqlFilter = new LinkedList<HqlCondition>();
if(keyword != null && keyword.length()>0){
hqlFilter.add( new HqlCondition("and", "rs.title", "like", "%" + keyword + "%", HqlCondition.String) );
hqlFilter.add( new HqlCondition("or", "rs.remarks", "like", "%" + keyword + "%", HqlCondition.String) );
}
if(resourceTypeId != null && resourceTypeId.length()>0){
hqlFilter.add( new HqlCondition("and", "rs.resourceType.resourceTypeId", "=", new Long(resourceTypeId), HqlCondition.Long) );
}
if(languageTypeId != null && languageTypeId.length()>0){
hqlFilter.add( new HqlCondition("and", "rs.languageType.languageTypeId", "=", new Integer(languageTypeId), HqlCondition.Integer) );
}
int pageno =1;
int pagesize = 10;
if(limit != null && limit.length()>0){
pagesize = Integer.parseInt(limit);
}
if(!start.equalsIgnoreCase("0") && start != null && start.length()>0){
int bpos = Integer.parseInt(start);
pageno = (bpos + pagesize)/pagesize;
}
int total = this.rse.getResourceTotalCount(hqlFilter);
Collection<BaseVO> coll = this.rse.getResourceList(hqlFilter,pageno,pagesize);
Iterator<BaseVO> it = coll.iterator();
while(it != null && it.hasNext()){
BaseVO bv = it.next();
ResourceType rt = this.rts.getResourceType(((ResourceType)bv.get("resourceType")).getResourceTypeId());
bv.set("type", rt.getResourceType());
}
document.addElement("type");
new OutputVOXml().writeXML(total,new LinkedList<BaseVO>(coll), response);
return null;
}
posted @
2008-10-04 22:43 Loy Fu 閱讀(1376) |
評論 (0) |
編輯 收藏
Apache
提供的一個插件包,可以把Action
中的數據以JSON
做個封裝然后返回。
它會將整個action中的變量轉化為JSON數據(根對象在JSON中數據添加一個”root”標識)。如果要使用它,Action必須遵循以下幾點:
1. 返回的頁面類型中”content-type”必須是”application/json”.(這個已經Internet Community采用).
2. JSON內容必須是符合格式要求的.
3. Action中field必須有public的set方法.(是不是沒有set方法就不會將field添加到JSON數據中,有待驗證).
4. 它支持的類型有: 基本類型(int,long...String), Date, List, Map, Primitive Arrays, 其它class, 對象數組.
5. 在JSON中任何的Object會被封裝在list或map中,數據會被封裝程Long,如果是含有的數據則會被封裝程Double,數組會被封裝程List.
下面給出JSON的數據格式:
{
"doubleValue": 10.10,
"nestedBean": {
"name": "Mr Bean"
},
"list": ["A", 10, 20.20, {
"firstName": "El Zorro"
}],
"array": [10, 20]
}
說明:
a. 這個插件支持以下幾個注釋:
注釋名
|
簡介
|
默認值
|
序列化
|
反序列化
|
name
|
配置JSON中name
|
empty
|
yes
|
no
|
serialize
|
在serialization中
|
true
|
yes
|
no
|
deserialize
|
在deserialization中
|
true
|
no
|
yes
|
format
|
格式化Date字段
|
"yyyy-MM-dd'T'HH:mm:ss"
|
yes
|
yes
|
可以通過配置來顯示指出要放在JSON中field,其中有個自己的驗證規則需要研究.
<!-- Result fragment -->
<result type="json">
<param name="excludeProperties">
login.password,
studentList.*".sin
</param>
</result>
<!-- Interceptor fragment -->
<interceptor-ref name="json">
<param name="enableSMD">true</param>
<param name="excludeProperties">
login.password,
studentList.*".sin
</param>
</interceptor-ref>
b. 根對象
<result type="json">
<param name="root">
person.job
</param>
</result>
也可以使用攔截器配置操作父對象
<interceptor-ref name="json">
<param name="root">bean1.bean2</param>
</interceptor-ref>
c. 將JSON數據用注釋封裝
如果wrapWithComments設置為true(默認值為false),則生成的JSON數據會變成這樣:
/* {
"doubleVal": 10.10,
"nestedBean": {
"name": "Mr Bean"
},
"list": ["A", 10, 20.20, {
"firstName": "El Zorro"
}],
"array": [10, 20]
} */
這樣做可以避免js中一些潛在的風險,使用時需要:
Var responseObject = eval("("+data.substring(data.indexOf(""/"*")+2, data.lastIndexOf(""*"/"))+")");
d. 父類
“root”對象中父類的field不會默認存放到JSON數據中,如果不想這樣做,需要在配置時指定ignoreHierarchy為false:
<result type="json">
<param name="ignoreHierarchy">false</param>
</result>
e. 枚舉類型
默認處理枚舉類型時,會被處理成JSON數據中name等于枚舉中value而value等于枚舉中name.
public enum AnEnum {
ValueA,
ValueB
}
JSON: "myEnum":"ValueA"
如果在處理枚舉類型時,在xml中配置了enumAsBean,則會被當作一個Bean處理,在JSON數據中會有一個特別的屬性”_name”值為name().這個枚舉中的所有屬性都會被處理.
public enum AnEnum {
ValueA("A"),
ValueB("B");
private String val;
public AnEnum(val) {
this.val = val;
}
public getVal() {
return val;
}
}
JSON: myEnum: { "_name": "ValueA", "val": "A" }
Xml中配置:
<result type="json">
<param name="enumAsBean">true</param>
</result>
f. 例子
a) Action
import java.util.HashMap;
import java.util.Map;
import com.opensymphony.xwork2.Action;
public class JSONExample {
private String field1 = "str";
private int[] ints = {10, 20};
private Map map = new HashMap();
private String customName = "custom";
//'transient' fields are not serialized
private transient String field2;
//fields without getter method are not serialized
private String field3;
public String execute() {
map.put("John", "Galt");
return Action.SUCCESS;
}
public String getField1() {
return field1;
}
public void setField1(String field1) {
this.field1 = field1;
}
public int[] getInts() {
return ints;
}
public void setInts(int[] ints) {
this.ints = ints;
}
public Map getMap() {
return map;
}
public void setMap(Map map) {
this.map = map;
}
@JSON(name="newName")
public String getCustomName() {
return this.customName;
}
}
b) Xml配置
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<package name="example" extends="json-default">
<action name="JSONExample" class="example.JSONExample">
<result type="json"/>
</action>
</package>
</struts>
這里有兩個地方需要注意:
1) 需要繼承json-default包
2) <result>簽的定義
c) JSON數據
{
"field1" : "str",
"ints": [10, 20],
"map": {
"John":"Galt"
},
"newName": "custom"
}
d) JSON RPC
JSON插件可以在js中調用action方法,返回執行結果。這個已經在dojo中有了實現,可以用Simple Method Definition調用遠程服務。來一起看看下面的例子:
首先寫一個Action:
package smd;
import com.googlecode.jsonplugin.annotations.SMDMethod;
import com.opensymphony.xwork2.Action;
public class SMDAction {
public String smd() {
return Action.SUCCESS;
}
@SMDMethod
public Bean doSomething(Bean bean, int quantity) {
bean.setPrice(quantity * 10);
return bean;
}
}
e) 方法必須用SMDMethod加上注解,這樣才能被遠程調用,為了安全因素。這個方法會產生一個bean對象,實現修改價格的功能。Action被添加上SMD注解會生成一個SMD,同時參數也會被加上SMDMethodParameter注解。像你所看到的,Action中定義了一個空方法:smd。這個方法是作為Simple Method Definition (定義class中提供的服務),在struts.xml配置<result>時使用type屬性值為”json”。
下面是bean的定義:
package smd;
public class Bean {
private String type;
private int price;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
}
Xml文件:
<package name="RPC" namespace="/nodecorate" extends="json-default">
<action name="SMDAction" class="smd.SMDAction" method="smd">
<interceptor-ref name="json">
<param name="enableSMD">true</param>
</interceptor-ref>
<result type="json">
<param name="enableSMD">true</param>
</result>
</action>
</package>
這里需要注意一點:” enableSMD”這個必須在interceptor和result都要配置.
Js代碼:
<s:url id="smdUrl" namespace="/nodecorate" action="SMDAction" />
<script type="text/javascript">
//load dojo RPC
dojo.require("dojo.rpc.*");
//create service object(proxy) using SMD (generated by the json result)
var service = new dojo.rpc.JsonService("${smdUrl}");
//function called when remote method returns
var callback = function(bean) {
alert("Price for " + bean.name + " is " + bean.price);
};
//parameter
var bean = {name: "Mocca"};
//execute remote method
var defered = service.doSomething(bean, 5);
//attach callback to defered object
defered.addCallback(callback);
</script>
JsonService會發出一個請求到action加載SMD,同時遠程方法會返回一個JSON對象,這個過程是Dojo給action中的方法創建了一個Proxy。因為這是異步調用過程,當遠程方法執行的時候,它會返回一個對象到callback方法中。
f) 代理的對象
當使用的注解不是繼承自Java,可能你使用代理會出現一些問題。比如:當你使用aop攔截你的action的時候。在這種情況下,這個插件不會自動發現注解的方法。為了避免這種情況發生,你需要在xml中配置ignoreInterfaces為false,這樣插件會自己查找注解的所有接口和父類。
注意:這個參數只有在Action執行的過程是通過注解來運行的時候才應該設為false。
<action name="contact" class="package.ContactAction" method="smd">
<interceptor-ref name="json">
<param name="enableSMD">true</param>
<param name="ignoreInterfaces">false</param>
</interceptor-ref>
<result type="json">
<param name="enableSMD">true</param>
<param name="ignoreInterfaces">false</param>
</result>
<interceptor-ref name="default"/>
</action>
posted @
2008-10-04 14:45 Loy Fu 閱讀(7743) |
評論 (2) |
編輯 收藏
struts2的
json plugin的位置在:http://code.google.com/p/
jsonplugin/
下載
json plugin的jar包,放到/WEB-INF/lib/目錄下就可以了
Spring + Struts + JPA的項目結構如其他例子中的一致
首先是web.xml
xml 代碼
- <?xml version="1.0" encoding="UTF-8"?>
- <web-app id="WebApp_ID" version="2.4"
- xmlns="http://java.sun.com/xml/ns/j2ee"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
- <display-name>quickstart</display-name>
- <filter>
- <filter-name>struts2</filter-name>
- <filter-class>
- org.apache.struts2.dispatcher.FilterDispatcher
- </filter-class>
- </filter>
- <filter>
- <filter-name>jpaFilter</filter-name>
- <filter-class>
- org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter
- </filter-class>
- <init-param>
- <param-name>entityManagerFactory</param-name>
- <param-value>entityManagerFactory</param-value>
- </init-param>
- </filter>
- <filter-mapping>
- <filter-name>jpaFilter</filter-name>
- <url-pattern>*.action</url-pattern>
- </filter-mapping>
- <filter-mapping>
- <filter-name>struts2</filter-name>
- <url-pattern>/*</url-pattern>
- </filter-mapping>
- <welcome-file-list>
- <welcome-file>index.jsp</welcome-file>
- </welcome-file-list>
- <listener>
- <listener-class>
- org.springframework.web.context.ContextLoaderListener
- </listener-class>
- </listener>
- </web-app>
加入
jpaFilter,是為了不讓hibernate的session過早關閉,因為有的action會通過ajax動態調用。
下面是struts.xml,注意struts.xml需要放在源代碼目錄下面:
xml 代碼
- <?xml version="1.0" encoding="UTF-8" ?>
- <!DOCTYPE struts PUBLIC
- "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
- "http://struts.apache.org/dtds/struts-2.0.dtd">
- <struts>
- <constant name="struts.objectFactory" value="spring" />
- <constant name="struts.devMode" value="true" />
- <constant name="struts.i18n.encoding" value="UTF-8"/>
- <package name="person" extends="json-default">
- <action name="list" method="execute" class="personaction">
- <result type="json"/>
- </action>
- </package>
- </struts>
這里注意,struts.objectFactory告訴struts所有的action都到spring的上下文里面去找,另外還需要注意,我們自己的包要繼承自json-default,這樣才可以在result的type屬性中使用json。
下面是spring的配置文件applicationContext.xml:
xml 代碼
- <?xml version="1.0" encoding="UTF-8"?>
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:aop="http://www.springframework.org/schema/aop"
- xmlns:tx="http://www.springframework.org/schema/tx"
- xsi:schemaLocation="
- http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
- http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
- http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">
- <bean
- class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
- <bean id="entityManagerFactory"
- class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
- <property name="dataSource" ref="dataSource" />
- <property name="jpaVendorAdapter">
- <bean
- class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
- <property name="database" value="MYSQL" />
- <property name="showSql" value="true" />
- </bean>
- </property>
- </bean>
- <bean id="dataSource"
- class="org.springframework.jdbc.datasource.DriverManagerDataSource">
- <property name="driverClassName" value="com.mysql.jdbc.Driver" />
- <property name="url" value="jdbc:mysql://localhost/extjs" />
- <property name="username" value="root" />
- <property name="password" value="" />
- </bean>
-
-
- <bean id="transactionManager"
- class="org.springframework.orm.jpa.JpaTransactionManager">
- <property name="entityManagerFactory"
- ref="entityManagerFactory" />
- </bean>
- <tx:annotation-driven transaction-manager="transactionManager" />
-
- <bean id="personService" class="com.myext.service.impl.PersonServiceJpaImpl"/>
- <bean id="personaction" class="com.myext.action.PersonPageAction">
- <property name="person" ref="personService"/>
- </bean>
- </beans>
這里的bean personaction和strutx.xml中的action class一致就可以了,下面是代碼:
action:
java 代碼
- package com.myext.action;
-
- import java.util.ArrayList;
- import java.util.List;
-
- import com.myext.service.PersonService;
-
- public class PersonPageAction {
- private int limit=10;
- private int start=0;
- private PersonService person;
- private int total=0;
- private List persons = new ArrayList();
- private boolean success=true;
- public boolean getSuccess(){
- return this.success;
- }
- public void setLimit(int limit) {
- this.limit = limit;
- }
-
- public void setStart(int start) {
- this.start = start;
- }
-
-
- public void setPerson(PersonService person) {
- this.person = person;
- }
-
- public int getTotal() {
- return total;
- }
-
- public void setTotal(int total) {
- this.total = total;
- }
-
- public List getPersons() {
- return persons;
- }
-
- public void setPersons(List persons) {
- this.persons = persons;
- }
-
- public String execute(){
- this.total = person.getTotal();
- this.persons = person.getPage(this.start, this.limit);
- return "success";
- }
- }
service:
java 代碼
- package com.myext.service.impl;
-
- import java.util.List;
-
- import javax.persistence.EntityManager;
- import javax.persistence.PersistenceContext;
- import javax.persistence.Query;
-
- import com.myext.model.Person;
- import com.myext.service.PersonService;
-
- public class PersonServiceJpaImpl implements PersonService {
- private EntityManager em;
- private static String poname = Person.class.getName();
- @PersistenceContext
- public void setEntityManager(EntityManager em){
- this.em = em;
- }
- @SuppressWarnings("unchecked")
- @Override
- public List getPage( int start, int limit) {
- Query q = this.em.createQuery("from " + poname );
- q.setFirstResult(start);
- q.setMaxResults(limit);
- return q.getResultList();
- }
- @Override
- public int getTotal() {
- return this.em.createQuery("from " + poname).getResultList().size();
- }
-
- }
頁面的代碼:
xml 代碼
- xml version="1.0" encoding="UTF-8" ?>
- <%@ page language="java" contentType="text/html; charset=UTF-8"
- pageEncoding="UTF-8"%>
- >
- <html xmlns="http://www.w3.org/1999/xhtml">
- <head>
- <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
- <title>Grid3title>
- <link rel="stylesheet" type="text/css" href="extjs/resources/css/ext-all.css" />
-
- <script type="text/javascript" src="extjs/adapter/ext/ext-base.js">script>
-
- <script type="text/javascript" src="extjs/ext-all.js">script>
- <script type="text/javascript" src="extjs/ext-lang-zh_CN.js">script>
- head>
- <body>
- <script type="text/javascript" src="grid3.js">script>
- <div id="grid3" >
- div>
- body>
- html>
grid3.js代碼
js 代碼
-
-
-
- Ext.onReady(function(){
- Ext.BLANK_IMAGE_URL = 'extjs/resources/images/default/s.gif';
- Ext.QuickTips.init();
- var sm = new Ext.grid.CheckboxSelectionModel(); //CheckBox選擇列
- var cm = new Ext.grid.ColumnModel([
- new Ext.grid.RowNumberer(), //行號列
- sm,
- {header:'編號',dataIndex:'id'},
- {header:'性別',dataIndex:'sex',renderer:function(value){
- if(value=='male'){
- return "男";
- }else{
- return "女";
- }
- }},
- {header:'名稱',dataIndex:'name'},
- {header:'描述',dataIndex:'descn'}
- ]);
-
-
- var ds = new Ext.data.Store({
- proxy: new Ext.data.HttpProxy({url:'list.action'}),//調用的動作
- reader: new Ext.data.JsonReader({
- totalProperty: 'total',
- root: 'persons',
- successProperty :'success'
- }, [
- {name: 'id',mapping:'id',type:'int'},
- {name: 'sex',mapping:'sex',type:'string'},
- {name: 'name',mapping:'name',type:'string'},
- {name: 'descn',mapping:'descn',type:'string'} //列的映射
- ])
- });
-
-
- var grid = new Ext.grid.GridPanel({
- el: 'grid3',
- ds: ds,
- sm: sm,
- cm: cm,
- width:700,
- height:280,
- bbar: new Ext.PagingToolbar({
- pageSize: 10,
- store: ds,
- displayInfo: true,
- displayMsg: '顯示第 {0} 條到 {1} 條記錄,一共 {2} 條',
- emptyMsg: "沒有記錄"
- }) //頁腳顯示分頁
- });
-
- grid.render();
- ds.load({params:{start:0, limit:10}}); //加載數據
- });
注意,這里的gridpanel一定要設置高度,否則數據是顯示不出來的。
最后啟動tomcat,在瀏覽器里輸入http://localhost:8080/extjs/grid3.jsp,就可以看到效果
posted @
2008-10-04 14:35 Loy Fu 閱讀(4532) |
評論 (2) |
編輯 收藏
摘要: http://prototype.conio.net/dist/
下載(對Ajax支持的prototype--js函數庫):
prototype-1.4.0.js
或
prototype-1.4.0.tar.gz
http://code.google.com/p/jsonplugin/downloads/list
下載(Struts2...
閱讀全文
posted @
2008-10-04 14:19 Loy Fu 閱讀(2058) |
評論 (0) |
編輯 收藏
關鍵字: Struts2 COC
摘要:介紹Struts2中的零配置(Zero Configuration),以及如何用COC來更好地簡化Struts2的配置。在第一章,我使用Maven來創建一個起點項目;第二章,以該項目為例,講解如何使用Struts2的零配置;第三章,論述第二章中的實現方式的缺陷,然后講解如何使用COC來改進這些缺陷,并進一步簡化Struts2的配置。附件是這篇文章用到的示例代碼。
一、從零開始
這里,我將建立一個新的示例項目,作為講解的起點。我使用JDK 6、Maven 2、Eclipse 3.3來建立這個示例,如果讀者對Maven2不熟也沒關系,這只是個示例。
首先,運行下邊的命令:
mvn archetype:create -DgroupId=demo.struts -DartifactId=demo-struts-coc -DarchetypeArtifactId=maven-archetype-webapp
這會建立如下的目錄結構:
|- POM.xml
|- src
|- main
|- resources
|- webapp
|- index.jsp
|- WEB-INF
|- web.xml
然后我們在src/main目錄下新建一個名為java的目錄,用來放置java代碼。在src下建立test目錄,并在test目錄下建立java目錄,用來放置測試代碼。另外,我這個示例不想使用JSP,所以我將src/main/webapp目錄下的index.jsp改為index.html。
現在,需要配置該項目要用到哪些lib。在POM.xml中加入struts2-core:
xml 代碼
- <dependency>
- <groupId>org.apache.struts</groupId>
- <artifactId>struts2-core</artifactId>
- <version>2.0.9</version>
- </dependency>
另外,我想在Eclipse里使用jetty來啟動項目并進行測試,所以在POM.xml中再加入jetty、jetty-util、servlet-api等的依賴,詳情見附件。
我希望使用Eclipse來作為這個項目的IDE,所以,我在命令行狀態下,進入這個項目所在的目錄,運行:
mvn eclipse:eclipse
然后使用Eclipse導入這個項目。如果你是第一次用Eclipse導入用Maven生成的項目,那你需要在Eclipse里配置一個名叫M2_REPO的Variable,指向你的Maven 2的repository目錄。缺省情況下,它應該位于${user.home}/.m2/repository。
OK!現在我們已經可以在Eclipse中進行工作了。
修改src/main/webapp/WEB-INF/web.xml,加入struts2的FilterDispatcher并設置filter-mapping。在這個示例中我將url-pattern設為"/app/*",也就是說,url的匹配是基于路徑來做的。這只是我的個人喜好而已,你也可以將它設成"*"。
既然是在講struts2的零配置,當然是可以不要任何配置文件的。但是為了更好地進行“配置”,我還是建立了struts.xml文件(在src/main/resources目錄下)。我不喜歡url最后都有個action后綴,現在,我在struts.xml中配置struts.action.extension,將這個后綴去掉:
xml 代碼
- <struts>
- <constant name="struts.action.extension" value="" />
- </struts>
然后我在src/test/java下建立demo/RunJetty.java文件,main方法如下:
java 代碼
- public static void main(String[] args) throws Exception {
- Server server = new Server(8080);
- File rootDir = new File(RunJetty.class.getResource("/").getPath()).getParentFile().getParentFile();
- String webAppPath = new File(rootDir, "src/main/webapp").getPath();
- new WebAppContext(server, webAppPath, "/");
- server.start();
- }
現在,在Eclipse里運行或調試這個RunJetty.java,用瀏覽器打開http://localhost:8080/看看吧。如果不出問題,應該可以訪問到webapp目錄下的index.html了。有了Jetty,你還在用MyEclipse或其它插件么?
二、零配置
首先要澄清一點,這里說的零配置并不是一點配置都沒有,只是說配置很少而已。
Struts2(我只用過Struts 2.0.6和2.0.9,不清楚其它版本是否支持零配置)引入了零配置的新特性,元數據可以通過規則和注解來表達:A "Zero Configuration" Struts application or plugin uses no additional XML or properties files. Metadata is expressed through convention and annotation.
目前,這個新特性還在測試階段,但經過一段時間的使用,我覺得這個特性已經可用。下面我講一下如何使用它。
1. Actions的定位
以前需要在xml配置文件中配置Action的name和class,如果使用零配置,所帶來的一個問題就是如何定位這些Action。我們需要在web.xml中找到struts2的filter的配置,增加一個名為actionPackages的init-param,它的值是一個以逗號分隔的Java包名列表,比如:demo.actions1,demo.actions2。struts2將會掃描這些包(包括這些包下邊的子包),在這些包下,所有實現了Action接口的或者是類名以“Action”結尾的類都會被檢查到,并被當做Action。
以前,我們寫Action必須要實現Action接口或者繼承ActionSupport。但是,上面提到的類名以"Action"結尾的類并不需要這樣做,它可以是一個POJO,Struts2支持POJO Action!
下面是actionPackages的配置示例:
xml 代碼
- <filter>
- <filter-name>struts2</filter-name>
- <filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>
- <init-param>
- <param-name>actionPackages</param-name>
- <param-value>demo.actions1,demo.actions2</param-value>
- </init-param>
- </filter>
2. 示例
現在我們建立demo.actions1.app.person和demo.actions2.app.group兩個包,在demo.actions1.app.person包下建立ListPeopleAction.java,在demo.actions2.app.group下建立ListGroupAction.java。作為示例,這兩個類只是包含一個execute方法,返回"success"或"error",其它什么都不做:
java 代碼
- public String execute() {
- return "success";
- }
在Filter的配置中,我指定actionPackages為demo.actions1,demo.actions2,當系統啟動時,Struts2就會在這兩個包下掃描到demo.actions1.app.person.ListPeopleAction和demo.actions2.app.group.ListGroupAction。
3. Action and Package name
Struts2掃描到Action后,從actionPackages指定的包開始,子包名會成為這個Action的namespace,而Action的name則由這個Action的類名決定。將類名首字母小寫,如果類名以Action結尾,則去掉"Action"后綴,形成的名字就是這個Action的名字。在如上所述的示例中,actionPackages指定為demo.actions1,demo.actions2,那么你可以這樣訪問demo.actions1.app.person.ListPeopleAction:
http://localhost:8080/app/person/listPeople
4. Results
Struts2是通過"Result"和"Results"兩個類級別的annotations來指定Results的。
作為示例,我們在webapp目錄下建兩個html文件:success.html和error.html,隨便寫點什么內容都可以。現在假設我們訪問/app/person/listPeople時,或Action返回success就轉到success.html頁面,若是error就轉到error.html頁面,這只需要在ListPeopleAction類上加上一段注解就可以了:
java 代碼
- @Results({
- @Result(name="success", type=NullResult.class, value = "/success.html", params = {}),
- @Result(name="error", type=NullResult.class, value = "/error.html", params = {})
- })
- public class ListPeopleAction {
- public String execute() {
- return "success";
- }
- }
同上,我們給ListGroupAction也加上注解。
現在,我們已經完成了一個零配置的示例。我們并沒有在xml文件里配置ListPeopleAction和ListGroupAction,但它們已經可以工作了!
用Eclipse運行RunJetty,然后用瀏覽器訪問http://localhost:8080/app/person/listPeople和http://localhost:8080/app/group/listGroup看看,是不是正是success.html(或error.html)的內容?
5. Namespaces
如上所述,namespace由包名所形成,但我們可以使用"Namespace"注解來自己指定namespace。
6. Parent Package
這個配置用得較少。Struts2提供一個"ParentPackage"注解來標識Action應該是屬于哪個package。
三、使用COC
如上所述,Struts2用注解來實現零配置。然而,這不是我喜歡的方式。在我看來,這不過是將配置從XML格式換成了注解方式,并不是真的零配置。而且,這種方式也未必比XML形式的配置更好。另外,對元數據的修改必然會導致項目的重新編譯和部署。還有,現在的Struts2版本似乎對Result注解中的params的處理有些問題。
其實,Struts2的actionPackages配置已經使用了COC,那為什么不能為Results也實現COC,從而去除這些每個Action都要寫的注解?
在嚴謹的項目中,package、action的名稱和頁面的路徑、名稱一定存在著某種關系。比如,頁面的路徑可能和package是對應的,頁面的名稱可能和action的名稱是對應的,或是根據某種法則運算得到。我們知道webwork2和struts2有個配置叫global-results。我們為什么不能根據這些對應規則寫個Result,將它配到global-results中,從而真正免去result的配置?
事實上,我推薦Struts2的使用者只用Struts2輸出XML或JSON,放棄UI,頁面這層還是使用標準的HTML、CSS和一些JS組件來展現。許多人反映Struts2慢,確實,Struts2是慢,很慢!慢在哪兒?很大一部分因素是UI這層引起的,特別是使用了過多的Struts2的tag,并使用了ajax theme。但是,如果我們放棄了Struts2的笨拙的UI,Result只輸出XML或JSON,UI則使用標準的HTML+CSS,使用JS組件(DOJO、Adobe Spry Framework、YUI-Ext等)來操作Struts2的輸出數據,情況將會如何?我們會得到一個高性能、高可配的、UI和應用服務器的職責分割更為明確、合理的、更易于靜態化部署的開發組合。
這似乎是閹割了Struts2,但是這樣閹割過的Struts2擺脫了性能低下的包袱,更輕、更現代化。
有些扯遠了,言歸正傳,不管是讓Struts2輸出XML或JSON,還是輸出頁面,我們都有辦法根據項目的規則寫一個Result,將它配到global-results中,從而大大減少Result的配置。
假設我們讓Struts2只輸出JSON,有個jsonplugin可以做這件事。使用JsonResult時,不再需要知道頁面的位置、名稱等信息,它僅僅是數據輸出,那么我們就可以將這個Result配成全局的,大部分Action將不再需要Result的配置。
作為示例,我假設我的例子中輸出的兩個html頁面(success.html和error.html)是JSON,我們看看怎么免去我例子中的兩個Action的Result注解。
首先,我們刪去ListPeopleAction和ListGroupAction兩個Action的注解,并修改struts.xml文件,加入:
xml 代碼
- <package name="demo-default" extends="struts-default">
- <global-results>
- <result name="success">/success.html</result>
- </global-results>
- </package>
請記住這只是一個示例,為了方便,我沒在項目中加入jsonplugin來作真實的演示,我只是假設這個success是json輸出,讀者可以自行使用jsonplugin來作實驗。
現在,離成功不遠了,但是項目仍然不能正常運行。我們的Action返回success,但并不會匹配到global-results中配置。為什么呢?因為,我們這里是把global-results配置到"demo-default"這個package下的,而Struts2根據actionPackages找到的Action不會匹配到這個package上。解決辦法也很簡單,還記得上面講到的Parent Package吧?給Action加個注解,指定ParentPackage為"demo-default"。但這樣可不是我喜歡的,其實有更好的辦法,我們在struts.xml中加個constant就好了:
xml 代碼
- <constant name="struts.configuration.classpath.defaultParentPackage" value="demo-default" />
現在,大功告成!運行RunJetty來測試下吧!你可以訪問/app/person/listPeople,可以訪問/app/group/listGroup,而所有的配置僅僅是web.xml和struts.xml中的幾行,我們的Java代碼中也沒有加注解。如果再加上幾百個Action呢?配置仍然就這幾行。
可是,某些Action確實需要配置怎么辦?對這些Action,你可以加注解,也可以針對這些Action來寫些XML配置。一個項目中,大部分Action的配置是可以遵從一定規則的,可以使用規則來簡化配置,只有少部分需要配置,這就是COC。
posted @
2008-10-04 14:08 Loy Fu 閱讀(1036) |
評論 (0) |
編輯 收藏
struts2.0的標簽庫(簡介)
用過struts1.x的人都知道,標簽庫有html、bean、logic、tiles,
而struts2.0里的標簽卻沒有分類,只用在jsp頭文件加上
<%@ taglib prefix="s" uri="/struts-tags" %>
就能使用struts2.0的標簽庫
下面就介紹下每個標簽的用法(有錯請指正):
A:
<s:a href=""></s:a>-----超鏈接,類似于html里的<a></a>
<s:action name=""></s:action>-----執行一個view里面的一個action
<s:actionerror/>-----如果action的errors有值那么顯示出來
<s:actionmessage/>-----如果action的message有值那么顯示出來
<s:append></s:append>-----添加一個值到list,類似于list.add();
<s:autocompleter></s:autocompleter>-----自動完成<s:combobox>標簽的內容,這個是ajax
B:
<s:bean name=""></s:bean>-----類似于struts1.x中的,JavaBean的值
C:
<s:checkbox></s:checkbox>-----復選框
<s:checkboxlist list=""></s:checkboxlist>-----多選框
<s:combobox list=""></s:combobox>-----下拉框
<s:component></s:component>-----圖像符號
D:
<s:date/>-----獲取日期格式
<s:datetimepicker></s:datetimepicker>-----日期輸入框
<s:debug></s:debug>-----顯示錯誤信息
<s:div></s:div>-----表示一個塊,類似于html的<div></div>
<s:doubleselect list="" doubleName="" doubleList=""></s:doubleselect>-----雙下拉框
E:
<s:if test=""></s:if>
<s:elseif test=""></s:elseif>
<s:else></s:else>-----這3個標簽一起使用,表示條件判斷
F:
<s:fielderror></s:fielderror>-----顯示文件錯誤信息
<s:file></s:file>-----文件上傳
<s:form action=""></s:form>-----獲取相應form的值
G:
<s:generator separator="" val=""></s:generator>----和<s:iterator>標簽一起使用
H:
<s:head/>-----在<head></head>里使用,表示頭文件結束
<s:hidden></s:hidden>-----隱藏值
I:
<s:i18n name=""></s:i18n>-----加載資源包到值堆棧
<s:include value=""></s:include>-----包含一個輸出,servlet或jsp頁面
<s:inputtransferselect list=""></s:inputtransferselect>-----獲取form的一個輸入
<s:iterator></s:iterator>-----用于遍歷集合
L:
<s:label></s:label>-----只讀的標簽
M:
<s:merge></s:merge>-----合并遍歷集合出來的值
O:
<s:optgroup></s:optgroup>-----獲取標簽組
<s:optiontransferselect doubleList="" list="" doubleName=""></s:optiontransferselect>-----左右選擇框
P:
<s:param></s:param>-----為其他標簽提供參數
<s:password></s:password>-----密碼輸入框
<s:property/>-----得到'value'的屬性
<s:push value=""></s:push>-----value的值push到棧中,從而使property標簽的能夠獲取value的屬性
R:
<s:radio list=""></s:radio>-----單選按鈕
<s:reset></s:reset>-----重置按鈕
S:
<s:select list=""></s:select>-----單選框
<s:set name=""></s:set>-----賦予變量一個特定范圍內的值
<s:sort comparator=""></s:sort>-----通過屬性給list分類
<s:submit></s:submit>-----提交按鈕
<s:subset></s:subset>-----為遍歷集合輸出子集
T:
<s:tabbedPanel id=""></s:tabbedPanel>-----表格框
<s:table></s:table>-----表格
<s:text name=""></s:text>-----I18n文本信息
<s:textarea></s:textarea>-----文本域輸入框
<s:textfield></s:textfield>-----文本輸入框
<s:token></s:token>-----攔截器
<s:tree></s:tree>-----樹
<s:treenode label=""></s:treenode>-----樹的結構
U:
<s:updownselect list=""></s:updownselect>-----多選擇框
<s:url></s:url>-----創建url
posted @
2008-10-04 14:02 Loy Fu 閱讀(395) |
評論 (0) |
編輯 收藏
1. 介紹
1.1 Model-View-Controller (MVC) 設計模式
FIXME - 需要一個對該模式一般性的介紹。(譯注:可以參考機械工業出版社的《設計模式》。)
1.2 將MVC概念映射到Struts組件中
Struts 的體系結構實現了Model-View-Controller設計模式的概念,它將這些概念映射到web應用程序的組件和概念中.
這一體系結構中每個主要的組件都將在下面做詳細的討論。
1.3 Model: 系統狀態和商業邏輯JavaBeans
基于MVC的系統中的 Model 部分可以細分為兩個概念 -- 系統的內部狀態, 能夠改變狀態的行為。用語法術語來說,我們可以把狀態信息當作名詞(事物),把行為當作動詞(事物狀態的改變)。
通常說來,你的應用程序將系統內部的狀態表示為一組一個或多個的JavaBeans,使用屬性(properties)來表示狀態的細節。依賴于你的應用程序的復雜度,這些beans可以是自包含的(以某種方式知道怎樣永久地保存它們的狀態信息),或者可以是正面的(facades),知道當被請求時怎樣從外部數據源(例如數據庫)中取得信息。Entity EJBs通常也用來表示內部狀態。
大型應用程序經常將系統可能的商業邏輯行為表示為可以被維護狀態信息的beans調用的方法。舉個例子,你有一個為每個當前用戶保存在session中的購物車bean,里面是表示當前用戶決定購買物品的屬性。這個bean有一個checkOut()方法用來驗證用戶的信用卡,將定單發給庫房以選擇貨品和出貨。別的系統分別地表示同樣的行為,或許使用Session EJBs。
在一些小型應用程序中,同樣的行為又可能嵌入到作為Controller一部分的 Action 類中。這在邏輯非常簡單或者并不想要在其它環境中重用這些商業邏輯時是恰當的。Struts框架支持所有這些方法,但建議將商業邏輯(“做什么”)和 Action 類(“決定做什么”)分離開。
1.4 View: JSP頁面和表示組件
基于Struts的應用程序中的 View 部分通常使用JSP技術來構建。JSP頁面包含稱為“模版文本”的靜態HTML(或XML)文本,加上插入的基于對特殊行為標記解釋的動態內容。JSP環境包括了其用途由JSP規范來描述的一套標準的行為標記,例如 <jsp:useBean> 。另外,還有一個用來定義你自己標記的標準機制,這些自定義的標記組織在“定制標記庫”中。
Struts包括了一個廣闊的便于創建用戶界面,并且充分國際化的定制標記庫,與作為系統 Model 部分一部分的ActionForm beans美妙地相互配合。這些標記的使用將在后面做詳細討論。
除了JSP頁面和其包含的行為及定制標記,商業對象經常需要能夠基于它們在被請求時的當前狀態將自己處理成HTML(或XML)。從這些對象處理過的輸出可以很容易地使用 <jsp:include> 標準行為標記包括在結果的JSP頁面中。
1.5 Controller: ActionServlet和ActionMapping
應用程序的 Controller 部分集中于從客戶端接收請求(典型情況下是一個運行瀏覽器的用戶),決定執行什么商業邏輯功能,然后將產生下一步用戶界面的責任委派給一個適當的View組件。在Struts中,controller的基本組件是一個 ActionServlet 類的servlet。這個servlet通過定義一組映射(由Java接口 ActionMapping 描述)來配置。每個映射定義一個與所請求的URI相匹配的路徑和一個 Action 類(一個實現 Action 接口的類)完整的類名,這個類負責執行預期的商業邏輯,然后將控制分派給適當的View組件來創建響應。
Struts也支持使用包含有運行框架所必需的標準屬性之外的附加屬性的 ActionMapping 類的能力。這允許你保存特定于你的應用程序的附加信息,同時仍可利用框架其余的特性。另外,Struts允許你定義控制將重定向到的邏輯名,這樣一個行為方法可以請求“主菜單”頁面(舉例),而不需要知道相應的JSP頁面的實際名字是什么。這個功能極大地幫助你分離控制邏輯(下一步做什么)和顯示邏輯(相應的頁面的名稱是什么)。
2. 創建Model組件
2.1 概述
你用到的應用程序的需求文檔很可能集中于創建用戶界面。然而你應該保證每個提交的請求所需要的處理也要被清楚的定義。通常說來,Model 組件的開發者集中于創建支持所有功能需求的JavaBeans類。一個特殊應用要求的beans的精確特性依賴于具體需求變化會非常的大,但是它們通常可以分成下面討論的幾種類型。然而,首先對“范圍”概念做一個簡短的回顧是有用的,因為它與beans有關。
2.2 JavaBeans和范圍
在一個基于web的應用程序中,JavaBeans可以被保存在(并從中訪問)一些不同“屬性”的集合中。每一個集合都有集合生存期和所保存的beans可見度的不同的規則。總的說來,定義生存期和可見度的這些規則被叫做這些beans的 范圍 。JSP規范中使用以下術語定義可選的范圍(在圓括號中定義servlet API中的等價物):
page - 在一個單獨的JSP頁面中可見的Beans,生存期限于當前請求。(service()方法中的局部變量) request - 在一個單獨的JSP頁面中可見的Beans,也包括所有包含于這個頁面或從這個頁面重定向到的頁面或servlet。(Request屬性)
session - 參與一個特定的用戶session的所有的JSP和servlet都可見的Beans,跨越一個或多個請求。(Session屬性)
application - 一個web應用程序的所有JSP頁面和servlet都可見的Beans。(Servlet context屬性)
記住同一個web應用程序的JSP頁面和servlets共享同樣一組bean集合是很重要的。例如,一個bean作為一個request屬性保存在一個servlet中,就象這樣:
MyCart mycart = new MyCart(...);
request.setAttribute("cart", mycart);
將立即被這個servlet重定向到的一個JSP頁面使用一個標準的行為標記看到,就象這樣:
<jsp:useBean id="cart" scope="request"
class="com.mycompany.MyApp.MyCart"/>
2.3 ActionForm Beans
Struts框架通常假定你已經為每一個你的應用程序中請求的輸入創建了一個 ActionForm bean(即一個實現了 ActionForm 接口的類)。如果你在你的 ActionMapping 配置文件中定義了這樣的beans(見“創建Controller組件”),Struts的controller servlet在調用適當的 Action 方法前將自動為你執行如下的服務:
用適當的關鍵字檢查用戶的session中是否有適當的類的bean的一個實例。
如果沒有這樣的session范圍的bean,自動建立一個新的bean并添加到用戶的session中。
對每個名字對應于bean中的一個屬性的請求參數,調用相應的set方法。這個操作類似于當你以通配符“*”選擇所有屬性使用標準的JSP行為標記 <jsp:setProperty> 。
更新的ActionForm bean在被調用時將被傳遞給Acton類的perform()方法,以使這些值能夠立即生效。
當你在寫你的ActionForm beans時,記住以下的原則:
ActionForm 接口本身不需要特殊的實現方法。它是用來標識這些特定的beans在整個體系結構中的作用。典型情況下,一個ActionForm bean只包括屬性的get方法和set方法,沒有商業邏輯。
通常在一個ActionForm bean中只有很少的輸入驗證邏輯。這樣的beans存在的主要理由是保存用戶為相關的表單所輸入的大部分近期值 -- 甚至在錯誤被檢測到時 -- 這樣同樣的頁面可以被重建,伴隨有一組出錯信息,這樣用戶僅僅需要糾正錯誤的字段。用戶輸入的驗證應該在 Action 類中執行(如果是很簡單的話),或者在適當的商業邏輯beans中執行。
為每個表單中出現的字段定義一個屬性(用相關的getXxx()和setXxx()方法)。字段名和屬性名必須按照JavaBeans的約定相匹配。例如,一個名為 username 的輸入字段將引起 setUsername() 方法被調用。
你應該注意一個“表單”在這里討論時的意義并不必須對應于用戶界面中的一個單獨的JSP頁面。在很多應用程序中一個“表單”(從用戶的觀點)延伸至多個頁面也是很平常的。想想看,例如,通常在安裝新的應用程序時使用的導航安裝程序的用戶界面。Struts鼓勵你定義一個包含所有字段屬性的單獨的ActionForm bean。不管字段實際上是顯示在哪個頁面上。同樣的,同一表單的不同的頁面應該提交到相同的Action類。如果你遵照這個建議,在大多數情況下,頁面設計者可以重新組織不同頁面中的字段而不需要改變處理邏輯。
2.4 系統狀態Beans
系統的實際狀態通常表示為一組一個或多個的JavaBeans類,其屬性定義當前狀態。例如,一個購物車系統包括一個表示購物車的bean,這個bean為每個單獨的購物者維護,這個bean中包括(在其它事物之中)一組購物者當前選擇購買的項目。分別地,系統也包括保存用戶信息(包括他們的信用卡和送貨地址)、可獲得項目的目錄和它們當前庫存水平的不同的beans。
對于小規模的系統,或者對于不需要長時間保存的狀態信息,一組系統狀態beans可以包含所有系統曾經經歷的特定細節的信息。或者經常是,系統狀態beans表示永久保存在一些外部數據庫中的信息(例如CustomerBean對象對應于表 CUSTOMERS 中的特定的一行),在需要時從服務器的內存中創建或清除。在大規模應用程序中,Entity EJBs也用于這種用途。
2.5 商業邏輯Beans
你應該把你的應用程序中的功能邏輯封裝成對為此目的設計的JavaBeans的方法調用。這些方法可以是用于系統狀態beans的相同的類的一部分,或者可以是在專門執行商業邏輯的獨立的類中。在后一種情況下,你通常需要將系統狀態beans傳遞給這些方法作為參數處理。
為了代碼最大的可重用性,商業邏輯beans應該被設計和實現為它們不知道自己被執行于web應用環境中。如果你發現在你的bean中你必須import一個 javax.servlet.* 類,你就把這個商業邏輯捆綁在了web應用環境中。考慮重新組織事物使你的 Action 類(Controller任務的一部分,在下面描述)翻譯所有從HTTP請求中請求被處理為對你的商業邏輯beans屬性set方法調用的信息,然后可以發出一個對 execute() 的調用。這樣的一個商業邏輯類可以被重用在除它們最初被構造的web應用程序以外的環境中。
依賴于你的應用程序的復雜度和范圍,商業邏輯beans可以是與作為參數傳遞的系統狀態beans交互作用的普通的JavaBeans,或者使用JDBC調用訪問數據庫的普通的JavaBeans。而對于較大的應用程序,這些beans經常是有狀態或無狀態的EJBs。
2.6 題外話: 訪問關系數據庫
很多web應用程序利用一個關系數據庫(通過一個JDBC driver訪問)來保存應用程序相關的永久數據。其它應用程序則使用Entity EJBs來實現這個目的,他們委派EJBs自己來決定怎樣維護永久狀態。如果你是使用EJBs來實現這個目的,遵照EJB規范中描述的客戶端設計模式。
對于基于直接數據庫訪問的web應用程序,一個普通的設計問題是當需要訪問低層數據庫時怎樣產生一個適當的JDBC連接對象。解決這個問題有幾種方法 -- 以下原則描述了推薦的一種方法:
創建或得到一個允許一組數據庫連接被多個用戶共享的ConnectionPool類。Struts(當前)沒有包括這樣的一個類,但是有很多這樣的類可以得到。
當應用程序初始化時,在應用程序展開(deployment)描述符中定義一個有一個“啟動時加載”值的servlet。我們將把這個servlet叫做 啟動 servlet。在大多數情況下,這個servlet不需要處理任何的請求,所以沒有一個 <servlet-mapping> 會指向它。
在啟動servlet的 init() 方法中,配置并初始化一個ConnectionPool類的實例,將其保存為一個servlet context屬性(從JSP的觀點看等同于一個application范圍的bean)。通常基于傳遞給啟動servlet初始化參數來配置聯接緩沖池是很方便的。
在啟動servlet的 destroy() 方法中,包含了釋放聯接緩沖池所打開的聯接的邏輯。這個方法將在servlet容器結束這個應用程序的時候被調用。
當 Action 類需要調用一個需要數據庫聯接的商業邏輯bean中的方法(例如“insert a new customer”)時,將執行下面的步驟:
為這個web應用程序從servelt context屬性中得到一個聯接緩沖池對象。
調用聯接緩沖池對象的 open() 方法來得到一個在 Action 類調用中使用的聯接。
調用商業邏輯bean中合適的方法,將數據庫聯接對象作為一個參數傳遞給它。
調用分配的聯接中的 close() 方法,這將引起這個聯接為了以后其它請求的重用被返回到緩沖池中。
一個通常的編程錯誤是忘記了把數據庫聯接返回給緩沖池,這將最終導致用完所有的聯接。一定要確信 Action 類的邏輯總是返回聯接,甚至在商業邏輯bean拋出一個違例時。
遵照上面推薦的設計模式意味著你能夠編寫你的商業邏輯類而不需要擔心它們怎樣得到一個JDBC聯接來使用-- 簡單地在任何需要訪問數據庫的方法中包含一個Connection參數。當你的商業邏輯類在一個web應用程序中被利用時,分配和釋放適當的聯接是 Action 類的責任。當你使用相同的商業邏輯類時,例如,在一個批處理工作中,提供一個適當的聯接是那個應用程序的責任(這不需要從緩沖池中獲得,因為大多數批處理工作運行于一個單線程環境中)。
3. 創建View組件
3.1 概述
這一章集中于創建應用程序中的 View 組件的任務,主要使用JSP技術建立。特別的,Struts除了提供了與輸入表單的交互外還提供了建立國際化應用程序的支持。幾個其它的與View相關的主題也被簡單地討論。
3.2 國際化消息
幾年之前,應用程序開發者能夠考慮到僅僅支持他們本國的只使用一種語言(或者有時候是兩種)和通常只有一種數量表現方式(例如日期、數字、貨幣值)的居民。然而,基于web技術的應用程序的爆炸性增長,以及將這些應用程序展開在Internet或其它被廣泛訪問的網絡之上,已經在很多情況下使得國家的邊界淡化到不可見。這種情況轉變成為一種對于應用程序支持國際化(經常被稱做“i18n”,因為18是字母“i”和字母“n”之間的字母個數)和本地化的需求。
Struts建立于Java平臺之上為建立國際化和本地化的應用程序提供幫助。需要熟悉的關鍵概念是:
Locale - 基礎的支持國際化的Java類是 java.util.Locale 。每個 Locale 代表一個特別的國家和語言選擇(加上一個可選的語言變量),以及一套格式假定,例如數字和日期等等。
ResourceBundle - java.util.ResourceBundle 類提供支持多種語言消息的基本工具。查看文檔中關于ResourceBundle 類以及你的JDK版本的文檔包中關于國際化的更多內容。
PropertyResourceBundle - 一個 ResourceBundle 類的標準實現允許你使用與初始化properties文件同樣的“name=value”語法來定義資源。這對于使用為用于一個web應用程序的消息準備資源包是非常方便的,因為這些消息通常都是面向文本的。
MessageFormat - java.text.MessageFormat 類允許你使用運行時指定的參數替換一個消息字符串中的一部分(在這種情況下,是一個從一個資源包得到的消息)。這在你創建一個句子的場合中是有用的,但是詞會以不同的語言按照不同的順序出現。消息中的占位符字符串{0}用第一個運行時參數替換,{1}用第二個運行時參數替換,以此類推。
MessageResources - Struts的類 org.apache.struts.util.MessageResources 使你能夠將一套資源包視做一個數據庫,并且允許你為一個特定的Locale(通常是與當前用戶相對應)請求一個特定的消息,而不是為服務器運行在其中的缺省的Locale請求消息。
對于一個國際化的應用程序,遵照JDK文檔包中國際化文檔所描述的步驟來創建一個包含每種語言的消息的屬性文件。下面舉一個例子說明。
假設你的源代碼建立在包 com.mycompany.mypackage 中,因此它保存于一個叫做(相對于你的源目錄)com/mycompany/mypackage 的目錄中。為創建一個叫做 com.mycompany.mypackage.MyResources 的資源包,你應該在目錄 com/mycompany/mypackage 中創建下列文件:
MyResources.properties - 包含你的服務器的缺省語言的消息。如果你的缺省語言是英語,你可能有一個這樣的條目:
prompt.hello=Hello
MyResources_xx.properties - 包含ISO語言編程為“xx”(查看ResourceBundle的Java文檔頁面得到一個當前列表的聯接)的同樣的消息。對于上面的消息的法語版,你可以有這個條目:
prompt.hello=Bonjour
你可以有你需要的任意多的語言的資源包文件。
當你在web應用程序展開描述符中配置controller servlet時,你需要在一個初始化參數中定義的一件事是應用程序的資源包的基礎名。在上述的情況中,這應該是 com.mycompany.mypackage.MyResources 。
3.3 表單和FormBean的交互
大部分web開發者曾經使用HTML的標準性能來建立表單,例如使用 <input> 標記。用戶希望交互程序具有一定的行為,這些期待中的一個與錯誤處理有關 -- 如果用戶出現一個錯誤,應用程序應該允許他們僅僅修改需要修改的部分 -- 而不需要重新敲入當前頁面或表單中的任何其它信息。
使用標準的HTML和JSP編程來完全實現這個期望是單調而繁重的。舉例來說,一個用戶名字段的輸入元素看起來可以象是這樣(在JSP中)
<input type="text" name="username"
value="<%= loginBean.getUsername() %>">
這很難敲對,會把沒有編程概念的HTML開發者搞糊涂,并且會在HTML編輯器中造成問題。取而代之的是,Struts提供了一種全面的基于JSP 1.1的定制標記庫功能的機制來建立表單。上面的情況使用Struts處理后將象是這樣:
<struts:text name="username"/>
沒有必要再顯式地涉及到從中獲得初始值的JavaBean。這將由框架自動處理。
3.3.1 使用Struts建立表單
一個完整的注冊表單將演示Struts相對于直接使用HTML和標準的JSP功能怎樣極大地減輕了處理表單的痛苦。考慮以下稱為logon.jsp的頁面(來自Struts的例子程序):
<%@ page language="java" %>
<%@ taglib uri="/WEB-INF/struts.tld" prefix="struts" %>
<html>
<head>
<title><struts:message key="logon.title"/></title>
<body bgcolor="white">
<struts:errors/>
<struts:form action="logon.do" name="logonForm"
type="org.apache.struts.example.LogonForm"/>
<table border="0" width="100%">
<tr>
<th align="right">
<struts:message key="prompt.username"/>
</th>
<td align="left">
<struts:text name="username" size="16"/>
</td>
</tr>
<tr>
<th align="right">
<struts:message key="prompt.password"/>
</th>
<td align="left">
<struts:password name="password" size="16"/>
</td>
</tr>
<tr>
<td align="right">
<struts:submit>
<struts:message key="button.submit"/>
</struts:submit>
</td>
<td align="right">
<struts:reset>
<struts:message key="button.reset"/>
</struts:reset>
</td>
</tr>
</table>
</struts:form>
</body>
</html>
|
下面的條目基于這個例子演示在Struts中處理表單的關鍵的特性:
taglib指令告訴JSP頁面編譯器從哪里找到Struts標記庫的 標記庫描述符 。在這種情況下,我們使用struts作為前綴來標識來自這個庫中的標記,但是可以使用任何你想用的前綴。
這個頁面使用了幾個 message 標記來從一個包含有這個應用程序所有資源的 MessageResources 對象中查找國際化的消息字符串。為了讓這個頁面能夠工作,以下的消息關鍵字必須在這些資源中被定義:
logon.title - 注冊頁面的標題
prompt.username - 一個 “Username:” 提示字符串
prompt.password - 一個 “Password:” 提示字符串
button.submit - “Submit”按鈕的標簽
button.reset - “Reset”按鈕的標簽
當用戶注冊時,應用程序可以在用戶的session中保存一個 Locale 對象。這個 Locale 將用來選擇適當語言的消息。這使得給用戶一個切換語言的可選項實現起來變的容易了 -- 僅僅改變保存的 Locale 對象,所有的消息就會自動切換。
errors 標記顯示由一個商業邏輯組件保存的任何出錯消息,或者如果沒有出錯消息保存就什么都沒有。這個標記將在下面做深入的描述。
form 標記基于指定的屬性對一個HTML <form> 元素進行處理。它也將所有在這個表單中的字段與一個保存在關鍵字 logonForm 下的session范圍的FormBean相關聯。這個bean用來為所有的具有與bean中的屬性名匹配的名字的輸入字段提供初始值。如果適當的bean沒有找到,一個新的bean將會被自動建立,使用指定的Java類名。
text 標記對一個類型為“text”的HTML <input> 元素進行處理。在這種情況下,占據瀏覽器屏幕的字符位置的數字也被指定。當頁面被執行時,是相對應的bean的 username 屬性的當前值(也就是 getUsername() 的返回值)。
password 標記使用方法類似。不同之處是當用戶敲入他們的口令時瀏覽器將回應星號字符,而不是輸入值。
submit 和 reset 標記在表單低部生成相應的按鈕。每個按鈕的文本標簽使用 message 標記建立,同時帶有提示,這樣這些值就是國際化的。
3.3.2 輸入字段類型支持
Struts為所有以下類型的輸入字段定義了標記,帶有與其相應的參考信息的超聯接。
checkboxes
hidden 字段
password 輸入字段
radio 按鈕
reset 按鈕
select 列表和嵌入的
options
submit 按鈕
text 輸入字段
textareas
在所有情況下,一個字段標記都必須嵌套在一個 form 標記中,這樣字段才知道使用哪個bean來初始化顯示的值。
3.3.3 其它有用的表示標記
在Struts的標記庫中有幾個其它的標記對于建立用戶界面是有幫助的:
enumerate 為一個指定集合的每個元素重復一次標記體(可以是一個Enumeration,一個Hashtable,一個Vector或一個對象數組)。
getProperty 從指定的bean中得到指定的屬性,并且在本頁面的其余部分作為一個page范圍的bean存在。這是訪問一個被 enumerate 使用的集合的方便的方法。
ifAttributeExists 只有在一個指定的屬性存在于一個指定的范圍中時才對標記體求值。
ifAttributeMissing 只有在一個指定的屬性不存在于一個指定的范圍中時才對標記體求值。
ifParameterEquals 只有在一個指定的請求參數具有一個指定的值時才對標記體求值。
ifParameterNotEquals 只有在一個指定的請求參數不具有一個指定的值或者不存在時才對標記體求值。
ifParameterNotNull 只有在一個指定的請求參數包含在這個請求中并且長度大于0時才對標記體求值。
ifParameterNull 只有在一個指定的請求參數不包含在這個請求中或者長度等于0時才對標記體求值。
iterate 為一個指定集合中的每個元素重復一次標記體(可以是一個Collection,一個Iterator,一個Map,或者一個對象數組)。這個標記在Java2環境中代替了 enumerate 標記。
link 生成一個超聯接,當沒有cookie支持時自動應用URL編程來維護session狀態。
parameter 處理指定請求參數的值,適當地過濾HTML中有特殊含義的字符。
property 顯示一個表單中命名的bean屬性 -- 在屬性應該是只讀時使用這個標記而不是 text 標記。
3.3.4 自動表單驗證
除了上面描述的表單和bean的交互外,如果你的bean知道怎樣驗證它接收的輸入字段,Struts還提供一種附加的機制。為了利用這個特性,使你的bean類實現 ValidatingActionForm 接口,而不是 ActionForm 接口。一個 ValidatingActionForm 增加了一個附加的方法簽名:
public String[] validate()
對于一個被controller servlet在bean屬性已經組裝但是在相應的行為類的 perform() 方法被調用之前調用的方法,validate() 方法有以下可選項:
執行適當的驗證發現沒有錯誤 -- 返回 null 或者一個非0長度字符串數組,并且controller servlet將繼續調用適當的 Action 類的 perform() 方法。
執行適當的驗證發現有錯誤 -- 返回一個內容為應該被顯示的出錯消息關鍵字(進入應用程序的MessageResources 包)的字符串數組。controller servlet將作為適合于 <struts:errors> 標記使用的請求屬性保存這個數組,并且將控制重定向回輸入表單(由這個 ActionMapping 的 inputForm 屬性標識)。
正如以前提到的,這個特性完全是可選的。如果你的form bean 僅僅實現了 ActionForm 接口,controller servlet將假設任何請求的驗證由action類完成。
3.4 其它的表示技術
盡管你的應用程序的外表和感覺可以完全基于標準的JSP能力和Struts的定制標記庫構建,你也應該考慮展開其它改進組件重用、減少管理負擔或者減少出錯的技術。在下面的部分討論幾個可選的技術。
3.4.1 特定于應用程序的定制標記
在使用Struts庫提供的定制標記之外,很容易建立特定于你創建的應用程序的標記來幫助建立用戶界面。Struts包括的例子程序用建立以下僅用于實現這個應用程序的標記演示了這個原則:
checkLogon - 檢查一個特殊的會話對象的存在,如果不存在將控制重定向到注冊頁面。這是用來捕捉這樣的情況,用戶在你的應用程序執行的中間把一個頁面做成書簽并且試圖跳過注冊,或者用戶的會話超時。
linkSubscription - 為一個詳細的定單頁面生成一個超聯接,它將需要的主關鍵字值作為一個請求屬性傳遞。這在列出與一個用戶相關的定單并提供編輯或刪除定單的聯接時使用。
linkUser - 為一個用戶的一個具體的頁面生成一個超聯接,它將它將需要的主關鍵字值作為一個請求屬性傳遞。
這些標記的源代碼在 src/example 目錄中,在包 org.apache.struts.example 里,還帶有一些其它的用在這個應用程序中的Java類。
3.4.2 有包含文件的頁面組件
在一個JSP文件(包含定制標記和beans用來訪問請求的動態數據)中創建完整的表示是一種非常普通的設計方法,在Struts包括的例子程序中被采用。然而很多應用程序要求在單獨一個頁面中顯示你的應用程序的多個邏輯上獨立的部分。
舉例來說,一個入口應用程序可以在入口的主頁面上有一些或者全部以下的功能:
訪問這個入口的一個搜索引擎。
一個或更多的“提供新聞”的顯示,含有按照用戶的注冊信息定制的感興趣的標題。
訪問與這個入口相關的討論的主題。
如果你的入口提供免費郵件帳號,還要有一個“郵件等待”的提示。
如果你能夠將工作劃分開,分配不同的開發者去做不同的片段,那么這個站點不同片段的開發就會更加簡單。然后,你可以使用JSP技術的 include 能力來將這些片段組合進一個單獨的頁面。有兩種 include 可用,依賴于你希望輸出的組合發生在什么時間:
include 指令 (<%@ include file="xxxxx" %>)在JSP頁面被編譯時處理。它用于包括不需要在請求時改變的HTML代碼。它把包括進來的文本當作靜態文本,很象C或C++中的 #include 指令。
include 行為 (<jsp:include page="xxxxx" flush="true" />)在請求時處理,并且是由服務器透明處理。這意味著你可以通過把它嵌套在一個類似ifParameterEquals的標記中有條件地執行include 。
3.4.3 圖片處理組件
一些應用程序要求動態生成圖片,就象一個股市報告站點的價格圖一樣。通常使用兩種不同的方法來實現這個需求:
處理一個執行一個servlet請求的URL的超聯接。這個servlet將使用一個圖象庫來生成圖片,設置適當的content類型(例如 image/gif),并且將圖片的字節流發送回瀏覽器。瀏覽器就會象從一個靜態文件中接收到的一樣顯示圖片。
處理HTML代碼需要下載一個創建請求的圖象的Java applet。你可以通過為在處理的代碼中的這個applet設置適當的初始化參數配置這個圖象,或者你可以讓這個applet與服務器建立自己聯接來接收這些參數。
4. 創建Controller組件
4.1 概述
現在我們理解了怎樣構造你的應用程序的Model和View組件,現在是集中到 Controller 組件的時候了。Struts包括一個實現映射一個請求URI到一個行為類的主要功能的servlet。因此你的與Controller有關的主要責任是:
為每一個可能接收的邏輯請求寫一個 Action 類(也就是,一個 Action 接口的實現)
寫一個定義類名和與每個可能的映射相關的其它信息的 ActionMapping 類(也就是,一個 ActionMapping 接口的實現)
寫行為映射配置文件(用XML)用來配置controller servlet。
為你的應用程序更新web應用程序展開描述符文件(用XML)用來包括必需的Struts組件。
給你的應用程序添加適當的Struts組件。
4.2 Action類
Action 接口定義一個單一的必須由一個 Action 類實現的方法,就象下面這樣:
public ActionForward perform(ActionServlet servlet,
ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException;
一個 Action 類的目標是處理這個請求,然后返回一個標識JSP頁面的 ActionForward 對象,控制應該重定向這個JSP頁面以生成相應的響應。在 Model 2 設計模式中,一個典型的 Action 類將在它的 perform() 方法中實現下面的邏輯:
驗證用戶session的當前狀態(例如,檢查用戶已經成功地注冊)。如果 Action 類發現沒有注冊存在,請求應該重定向到顯示用戶名和口令用于注冊的JSP頁面。應該這樣做是因為用戶可能試圖從“中間”(也就是,從一個書簽)進入你的應用程序,或者因為session已經超時并且servlet容器創建了一個新的session。
如果驗證還沒有發生(由于使用一個實現 ValidatingActionForm 接口的form bean),驗證這個 form bean 的屬性是必須的。如果發現一個問題,當作一個請求屬性保存合適的出錯信息關鍵字,然后將控制重定向回輸入表單這樣錯誤可以被糾正。
執行要求的處理來處理這個請求(例如在數據庫里保存一行)。這可以用嵌入 Action 類本身的代碼來完成,但是通常應該調用一個商業邏輯bean的一個合適的方法來執行。
更新將用來創建下一個用戶界面頁面的服務器端對象(典型情況下是request范圍或session范圍beans,定義你需要在多長時間內保持這些項目可獲得)。
返回一個標識生成響應的JSP頁面的適當的 ActionForward 對象,基于新近更新的beans。典型情況下,你將通過在你接收到的 ActionMapping 對象(如果你使用一個局部于與這個映射上的邏輯名)或者在controller servlet 本身(如果你使用一個全局于應用程序的邏輯名)上調用 findForward() 得到一個對這樣一個對象的引用。
當為 Action 類編程時要記住的設計要點包括以下這些:
controller servlet僅僅創建一個你的 Action 類的實例,用于所有的請求。這樣你需要編寫你的 Action 類使其能夠在一個多線程環境中正確運行,就象你必須安全地編寫一個servlet的 service() 方法一樣。
幫助線程安全編程的最重要的原則就是在你的 Action 類中僅僅使用局部變量而不是實例變量。局部變量創建于一個分配給(由你的JVM)每個請求線程的棧中,所以沒有必要擔心會共享它們。
盡管不應該,代表你的系統中Model部分的的beans仍有可能拋出違例。你應該在你的 perform() 方法的邏輯中捕捉所有這樣的違例,并且通過執行以下語句將它們記錄在應用程序的日志文件中(包括相應的棧跟蹤信息):
servlet.log("Error message text", exception);
作為一個通用的規則,分配很少的資源并在來自同一個用戶(在用戶的session中)的請求間保持它們會導致可伸縮性的問題。你應該在將控制重定向到適當的View組件前努力釋放這樣的資源(例如數據庫聯接) -- 甚至在你調用的一個bean拋出了一個違例時。
另外,你將會想要防止出現非常大的 Action 類。最簡單的實現途徑是將你的功能邏輯嵌入到 Action 類本身,而不是將其寫在獨立的商業邏輯beans中。除了使 Action 類難于理解和維護外,這種方法也使得難于重用這些商業邏輯代碼,因為代碼被嵌入到一個組件(Action 類)中并被捆綁運行于web應用程序環境中。
包括在Struts中的例子程序某種程度上延伸了這個設計原則,因為商業邏輯本身是嵌入到 Action 類中的。這應該被看作是在這個樣本應用程序設計中的一個bug,而不是一個Struts體系結構中的固有特性,或者是一個值得仿效的方法。
4.3 ActionMapping實現
為了成功地運行,Struts的controller servlet需要知道關于每個URI該怎樣映射到一個適當的 Action 類的幾件事。需要了解的知識封裝在一個叫做 ActionMapping 的Java接口中,它有以下屬性:
actionClass - 用于這個映射的 Action 類完整的Java類名。第一次一個特定的映射被使用,一個這個類的實例將被創建并為以后重用而保存。
formAttribute - session范圍的bean的名字,當前的這個映射的 ActionForm 被保存在這個bean之下。如果這個屬性沒有被定義,沒有 ActionForm 被使用。
formClass - 用于這個映射的 ActionForm 類完整的Java類名。如果你在使用對form beans的支持,這個類的一個實例將被創建并保存(在當前的用戶會話中)
path - 匹配選擇這個映射的請求的URI路徑。看下面如何匹配的例子。
Struts在一個叫做 ActionMappingBase 的類中包括了一個 ActionMapping 接口的方便的實現。如果你不需要為你自己的映射定義任何附加的屬性,盡管把這個類作為你的 ActionMapping 類好了,就向下面部分描述的那樣配置。然而,定義一個 ActionMapping 實現(多半是擴展 ActionMappingBase 類)來包含附加的屬性也是可能的。controller servlet知道怎樣自動配置這些定制屬性,因為它使用Struts的Digester模塊來讀配置文件。
包括在Struts的例子程序中,這個特性用來定義兩個附加的屬性:
failure - 如果Action類檢測到它接收的輸入字段的一些問題,控制應該被重定向到的上下文相關的URI。典型情況下是請求發向的JSP頁面名,它將引起表單被重新顯示(包含Action類設置的出錯消息和大部分最近的來自ActionForm bean的輸入值)。
success - 如果Action類成功執行請求的功能,控制應該被重定向到的上下文相關的URI。典型情況下是準備這個應用程序的會話流的下一個頁面的JSP頁面名。
使用這兩個額外的屬性,例子程序中的 Action 類幾乎完全獨立于頁面設計者使用的實際的JSP頁面名。這個頁面可以在重新設計時被重命名,然而幾乎不會影響到 Action 類本身。如果“下一個”JSP頁面的名字被硬編碼到 Action 類中,所有的這些類也需要被修改。
4.4 Action映射配置文件
controller servlet怎樣知道你想要得到的映射?寫一個簡單地初始化新的 ActionMapping 實例并且調用所有適當的set方法的小的Java類是可能的(但是很麻煩)。為了使這個處理簡單些,Struts包括一個Digester模塊能夠處理一個想得到的映射的基于XML的描述,同時創建適當的對象。看 API 文檔 以獲得關于Digester更多的信息。
開發者的責任是創建一個叫做 action.xml 的XML文件,并且把它放在你的應用程序的WEB-INF目錄中。(注意這個文件并不需要 DTD,因為實際使用的屬性對于不同的用戶可以是不同的)最外面的XML元素必須是<action-mappings>,在這個元素之中是嵌入的0個或更多的 <action> 元素 -- 每一個對應于你希望定義的一個映射。
來自例子程序的 action.xml 文件包括“注冊”功能的以下映射條目,我們用來說明這個需求:
<action-mappings>
<forward name="logon" path="/logon.jsp"/>
<action path="/logon"
actionClass="org.apache.struts.example.LogonAction"
formAttribute="logonForm"
formClass="org.apache.struts.example.LogonForm"
inputForm="/logon.jsp">
<forward name="success" path="/mainMenu.jsp"/>
</action>
</action-mappings>
|
就象你所看到的,這個映射匹配路徑 /logon (實際上,因為例子程序使用擴展匹配,你在一個JSP頁面指定的請求的URI結束于/logon.do)。當接收到一個匹配這個路徑的請求時,一個 LogonAction 類的實例將被創建(僅僅在第一次)并被使用。controller servlet將在關鍵字 logonForm 下查找一個session范圍的bean,如果需要就為指定的類創建并保存一個bean。
這個 action 元素也定義了一個邏輯名“success”,它在 LogonAction 類中被用來標識當一個用戶成功注冊時使用的頁面。象這樣使用一個邏輯名允許將 action 類隔離于任何由于重新設計位置而可能發生的頁面名改變。
這是第二個在任何 action 之外宣告的 forward 元素,這樣它就可以被所有的action全局地獲得。在這個情況下,它為注冊頁面定義了一個邏輯名。當你調用 mapping.findForward() 時在你的 action 代碼中,Struts首先查找這個action本地定義的邏輯名。如果沒有找到,Struts會自動為你查找全局定義的邏輯名。
4.5 Web應用程序展開描述符
設置應用程序最后的步驟是配置應用程序展開描述符(保存在文件WEB-INF/web.xml中)以包括所有必需的Struts組件。作為一個指南使用例子程序的展開描述符,我們看到下面的條目需要被創建或修改。
4.5.1 配置Action Servlet實例
添加一個條目定義action servlet本身,同時包括適當的初始化參數。這樣一個條目看起來象是這樣:
<servlet>
<servlet-name>action</servlet-name>
<servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
<init-p
aram>
<param-name>application</param-name>
<param-value>org.apache.struts.example.ApplicationResources</param-value>
</init-param>
<init-param>
<param-name>config</param-name>
<param-value>/WEB-INF/action.xml</param-value>
</init-param>
<init-param>
<param-name>debug</param-name>
<param-value>2</param-value>
</init-param>
<init-param>
<param-name>mapping</param-name>
<param-value>org.apache.struts.example.ApplicationMapping</param-value>
</init-param>
<load-on-startup>2</load-on-startup>
</servlet>
|
controller servlet支持的初始化參數在下面描述,拷貝自 ActionServlet 類的 Javadocs 。方括號描述如果你沒有為那個初始化參數提供一個值時假設的缺省值。
application - 應用程序資源包基類的Java類名。[NONE].
config - 包含配置信息的XML資源的上下文相關的路徑。[/WEB-INF/action.xml]
debug - 這個servlet的調試級別,它控制記錄多少信息到日志中。[0]
digester - 我們在 initMapping() 中利用的Digester的調試級別,它記錄到System.out而不是servlet的日志中。[0]
forward - 使用的ActionForward實現的Java類名。[org.apache.struts.action.ActionForward]
mapping - 使用的ActionMapping實現的Java類名。[org.apache.struts.action.ActionMappingBase]
nocache - 如果設置為 true,增加HTTP頭信息到所有響應中使瀏覽器對于生成或重定向到的任何響應不做緩沖。[false]
null - 如果設置為 true,設置應用程序資源使得如果未知的消息關鍵字被使用則返回 null。否則,一個包括不歡迎的消息關鍵字的出錯消息將被返回。[true]
4.5.2 配置Action Servlet映射
有兩種通常的方法來定義將被controller servlet處理的URL -- 前綴匹配和擴展匹配。每種方法的一個適當的映射條目將在下面被描述。
前綴匹配意思是你想讓所有以一個特殊值開頭(在上下文路徑部分之后)的URL傳遞給這個servlet。這樣一個條目看起來可以象是這樣:
<servlet-mapping>
<servlet-name>action</servlet-name>
<url-pattern>/execute/*</url-pattern>
</servlet-mapping>
它意味著一個匹配前面描述的 /logon 路徑的請求的URL看起來象是這樣:
http://www.mycompany.com/myapplication/execute/logon
這里 /myapplicationis 是你的應用程序展開所在的上下文路徑。
另一方面,擴展映射基于URL以一個跟著定義的一組字符的句點結束的事實而將URL匹配到action servlet 。例如,JSP處理servlet映射到 *.jsp 模式這樣它在每個JSP頁面請求時被調用。為了使用 *.do 擴展(它意味著“做某件事”)映射條目看起來應該象是這樣:
<servlet-mapping>
<servlet-name>action</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
并且一個匹配以前描述的 /logon 路徑的請求的URI可以看起來象是這樣:
http://www.mycompany.com/myapplication/logon.do
4.5.3 配置Struts標記庫
下一步,你必須添加一個定義Struts標記庫的條目。這個條目看起來應該象是這樣:
<taglib>
<taglib-uri>/WEB-INF/struts.tld</taglib-uri>
<taglib-location>/WEB-INF/struts.tld</taglib-location>
</taglib>
它告訴JSP系統到哪里去找這個庫的標記庫描述符(在你的應用程序的WEB-INF目錄,而不是在外部互聯網上的某個地方)。
4.5.4 添加Struts組件到你的應用程序中
為了在你的應用程序運行時使用Struts,你必須將 struts.tld 文件拷貝到你的 WEB-INF 目錄,將 struts.jar 文件拷貝到你的 WEB-INF/lib 。 ?
posted @
2008-10-04 13:59 Loy Fu 閱讀(240) |
評論 (0) |
編輯 收藏