posts - 41,  comments - 8,  trackbacks - 0
  置顶随笔
     摘要: * 此框架采用前后台分开,前后台都可以单独部署,前端采用轻量级的扁平化设计(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 阅读(4025) | 评论 (0)编辑 收藏
  2016年6月13日
     摘要: * 此框架采用前后台分开,前后台都可以单独部署,前端采用轻量级的扁平化设计(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 阅读(4025) | 评论 (0)编辑 收藏
  2008年10月21日
     摘要: java nio的全称是java new I/O,即一个全新的I/O控制系统,它的API的包名为java.nio,是在jdk1.4后引入的。 nio之所以为为新,在于它并没在原来I/O的基础上进行开发,而是提供了全新的类和接口,除了原来的基本功能之外,它还提供了以下新的特征:         ► 多路选择的非封锁式...  阅读全文
posted @ 2008-10-21 17:44 Loy Fu 阅读(826) | 评论 (0)编辑 收藏
  2008年10月20日
 作者:罗代均 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,进行测试
通过 http://localhost/test/test.jsp 访问,查看tomcat1的窗口,可以看到打印了一行"=========="
再刷新一次,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-app 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" version="2.4">
       <display-name>TomcatDemo</display-name>
       <distributable/>
</web-app>
注意:在你的应用的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 阅读(518) | 评论 (0)编辑 收藏
  2008年10月15日

在实际的网页开发中,大部分时间都要涉及到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 阅读(722) | 评论 (0)编辑 收藏
  2008年10月8日
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 阅读(391) | 评论 (0)编辑 收藏
  2008年10月4日

前台:

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({
url:'teaching/resourceType/resourceTypeAction.evi?method=getResourceTypeList',
      reader: new Ext.data.XmlReader({
      record: "Item"
     }, [
      {name: 'resourceTypeId', mapping: 'resourceTypeId'},
       {name: 'resourceType', mapping: 'resourceType'}
      ])
   });
resourceType.load();
var languageType = new Ext.data.Store({
url:'teaching/languageType/languageTypeAction.evi?method=getLanguageTypeList',
      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 阅读(1360) | 评论 (0)编辑 收藏
 Apache提供的一个插件包,可以把Action中的数据以JSON做个封装然后返回。

它会将整个action中的变量转化为JSON数据(根对象在JSON中数据添加一个”root”标识)。如果要使用它,Action必须遵循以下几点:

1.       返回的页面类型中”content-type”必须是”application/json”.(这个已经Internet Community采用).

2.       JSON内容必须是符合格式要求的.

3.       Actionfield必须有publicset方法.(是不是没有set方法就不会将field添加到JSON数据中,有待验证).

4.       它支持的类型有: 基本类型(int,long...String), Date, List, Map, Primitive Arrays, 其它class, 对象数组.

5.       JSON中任何的Object会被封装在listmap中,数据会被封装程Long,如果是含有的数据则会被封装程Double,数组会被封装程List.

下面给出JSON的数据格式:

{

   "doubleValue": 10.10,

   "nestedBean": {

      "name": "Mr Bean"

   },

   "list": ["A", 10, 20.20, {

      "firstName": "El Zorro"

   }],

   "array": [10, 20]

}

说明:

a.       这个插件支持以下几个注释:

注释名

简介

默认值

序列化

反序列化

name

配置JSONname

empty

yes

no

serialize

serialization

true

yes

no

deserialize

deserialization

true

no

yes

format

格式化Date字段

"yyyy-MM-dd'T'HH:mm:ss"

yes

yes

可以通过配置来显示指出要放在JSONfield,其中有个自己的验证规则需要研究.

<!-- 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数据中,如果不想这样做,需要在配置时指定ignoreHierarchyfalse:

<result type="json">

 <param name="ignoreHierarchy">false</param>

</result>

e.       枚举类型

默认处理枚举类型时,会被处理成JSON数据中name等于枚举中valuevalue等于枚举中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”这个必须在interceptorresult都要配置.

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对象,这个过程是Dojoaction中的方法创建了一个Proxy。因为这是异步调用过程,当远程方法执行的时候,它会返回一个对象到callback方法中。

f)         代理的对象

当使用的注解不是继承自Java,可能你使用代理会出现一些问题。比如:当你使用aop拦截你的action的时候。在这种情况下,这个插件不会自动发现注解的方法。为了避免这种情况发生,你需要在xml中配置ignoreInterfacesfalse,这样插件会自己查找注解的所有接口和父类。

注意:这个参数只有在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 阅读(7735) | 评论 (2)编辑 收藏
struts2json plugin的位置在:http://code.google.com/p/jsonplugin/
下载json plugin的jar包,放到/WEB-INF/lib/目录下就可以了

Spring + Struts + JPA的项目结构如其他例子中的一致
首先是web.xml
xml 代码
 
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <web-app id="WebApp_ID" version="2.4"  
  3.     xmlns="http://java.sun.com/xml/ns/j2ee"  
  4.     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  5.     xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">  
  6.     <display-name>quickstart</display-name>  
  7.     <filter>  
  8.         <filter-name>struts2</filter-name>  
  9.         <filter-class>  
  10.             org.apache.struts2.dispatcher.FilterDispatcher  
  11.         </filter-class>  
  12.     </filter>  
  13.     <filter>  
  14.         <filter-name>jpaFilter</filter-name>  
  15.         <filter-class>  
  16.             org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter  
  17.         </filter-class>  
  18.         <init-param>  
  19.             <param-name>entityManagerFactory</param-name>  
  20.             <param-value>entityManagerFactory</param-value>  
  21.         </init-param>  
  22.     </filter>  
  23.     <filter-mapping>  
  24.         <filter-name>jpaFilter</filter-name>  
  25.         <url-pattern>*.action</url-pattern>  
  26.     </filter-mapping>  
  27.     <filter-mapping>  
  28.         <filter-name>struts2</filter-name>  
  29.         <url-pattern>/*</url-pattern>  
  30.     </filter-mapping>  
  31.     <welcome-file-list>  
  32.         <welcome-file>index.jsp</welcome-file>  
  33.     </welcome-file-list>  
  34.     <listener>  
  35.         <listener-class>  
  36.             org.springframework.web.context.ContextLoaderListener  
  37.         </listener-class>  
  38.     </listener>  
  39. </web-app>  

加入jpaFilter,是为了不让hibernate的session过早关闭,因为有的action会通过ajax动态调用。
下面是struts.xml,注意struts.xml需要放在源代码目录下面:

xml 代码
 
  1. <?xml version="1.0" encoding="UTF-8" ?>  
  2. <!DOCTYPE struts PUBLIC  
  3.     "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"  
  4.     "http://struts.apache.org/dtds/struts-2.0.dtd">  
  5. <struts>  
  6.     <constant name="struts.objectFactory" value="spring" />  
  7.     <constant name="struts.devMode" value="true" />  
  8.     <constant name="struts.i18n.encoding" value="UTF-8"/>  
  9.     <package name="person" extends="json-default">  
  10.         <action name="list" method="execute" class="personaction">  
  11.             <result type="json"/>  
  12.         </action>         
  13.     </package>  
  14. </struts>  

这里注意,
struts.objectFactory告诉struts所有的action都到spring的上下文里面去找,另外还需要注意,我们自己的包要继承自json-default,这样才可以在result的type属性中使用json
下面是spring的配置文件applicationContext.xml:
xml 代码
 
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <beans xmlns="http://www.springframework.org/schema/beans"  
  3.     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  4.     xmlns:aop="http://www.springframework.org/schema/aop"  
  5.     xmlns:tx="http://www.springframework.org/schema/tx"  
  6.     xsi:schemaLocation="  
  7.     http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd  
  8.     http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd  
  9.     http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">  
  10.     <bean  
  11.         class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />  
  12.     <bean id="entityManagerFactory"  
  13.         class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">  
  14.         <property name="dataSource" ref="dataSource" />  
  15.         <property name="jpaVendorAdapter">  
  16.             <bean  
  17.                 class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">  
  18.                 <property name="database" value="MYSQL" />  
  19.                 <property name="showSql" value="true" />  
  20.             </bean>  
  21.         </property>  
  22.     </bean>  
  23.     <bean id="dataSource"  
  24.         class="org.springframework.jdbc.datasource.DriverManagerDataSource">  
  25.         <property name="driverClassName" value="com.mysql.jdbc.Driver" />  
  26.         <property name="url" value="jdbc:mysql://localhost/extjs" />  
  27.         <property name="username" value="root" />  
  28.         <property name="password" value="" />  
  29.     </bean>  
  30.   
  31.       
  32.     <bean id="transactionManager"  
  33.         class="org.springframework.orm.jpa.JpaTransactionManager">  
  34.         <property name="entityManagerFactory"  
  35.             ref="entityManagerFactory" />  
  36.     </bean>  
  37.     <tx:annotation-driven transaction-manager="transactionManager" />  
  38.     <!--Service 开始 -->  
  39.     <bean id="personService" class="com.myext.service.impl.PersonServiceJpaImpl"/>  
  40.     <bean id="personaction" class="com.myext.action.PersonPageAction">  
  41.         <property name="person" ref="personService"/>  
  42.     </bean>  
  43. </beans>  

这里的bean personaction和strutx.xml中的action class一致就可以了,下面是代码:
action:
java 代码
  1. package com.myext.action;
  2. import java.util.ArrayList;
  3. import java.util.List;
  4. import com.myext.service.PersonService;
  5. public class PersonPageAction {
  6. private int limit=10;
  7. private int start=0;
  8. private PersonService person;
  9. private int total=0;
  10. private List persons = new ArrayList();
  11. private boolean success=true;
  12. public boolean getSuccess(){
  13. return this.success;
  14. }
  15. public void setLimit(int limit) {
  16. this.limit = limit;
  17. }
  18. public void setStart(int start) {
  19. this.start = start;
  20. }
  21. public void setPerson(PersonService person) {
  22. this.person = person;
  23. }
  24. public int getTotal() {
  25. return total;
  26. }
  27. public void setTotal(int total) {
  28. this.total = total;
  29. }
  30. public List getPersons() {
  31. return persons;
  32. }
  33. public void setPersons(List persons) {
  34. this.persons = persons;
  35. }
  36. public String execute(){
  37. this.total = person.getTotal();
  38. this.persons = person.getPage(this.start, this.limit);
  39. return "success";
  40. }
  41. }
service:
java 代码
  1. package com.myext.service.impl;
  2. import java.util.List;
  3. import javax.persistence.EntityManager;
  4. import javax.persistence.PersistenceContext;
  5. import javax.persistence.Query;
  6. import com.myext.model.Person;
  7. import com.myext.service.PersonService;
  8. public class PersonServiceJpaImpl implements PersonService {
  9. private EntityManager em;
  10. private static String poname = Person.class.getName();
  11. @PersistenceContext
  12. public void setEntityManager(EntityManager em){
  13. this.em = em;
  14. }
  15. @SuppressWarnings("unchecked")
  16. @Override
  17. public List getPage( int start, int limit) {
  18. Query q = this.em.createQuery("from " + poname );
  19. q.setFirstResult(start);
  20. q.setMaxResults(limit);
  21. return q.getResultList();
  22. }
  23. @Override
  24. public int getTotal() {
  25. return this.em.createQuery("from " + poname).getResultList().size();
  26. }
  27. }
页面的代码:
xml 代码
  1. xml version="1.0" encoding="UTF-8" ?>
  2. <%@ page language="java" contentType="text/html; charset=UTF-8"
  3. pageEncoding="UTF-8"%>
  4. >
  5. <html xmlns="http://www.w3.org/1999/xhtml">
  6. <head>
  7. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  8. <title>Grid3title>
  9. <link rel="stylesheet" type="text/css" href="extjs/resources/css/ext-all.css" />
  10. <script type="text/javascript" src="extjs/adapter/ext/ext-base.js">script>
  11. <script type="text/javascript" src="extjs/ext-all.js">script>
  12. <script type="text/javascript" src="extjs/ext-lang-zh_CN.js">script>
  13. head>
  14. <body>
  15. <script type="text/javascript" src="grid3.js">script>
  16. <div id="grid3" >
  17. div>
  18. body>
  19. html>
grid3.js代码
js 代码
  1. /**
  2. * @author fox
  3. */
  4. Ext.onReady(function(){
  5. Ext.BLANK_IMAGE_URL = 'extjs/resources/images/default/s.gif';
  6. Ext.QuickTips.init();
  7. var sm = new Ext.grid.CheckboxSelectionModel(); //CheckBox选择列
  8. var cm = new Ext.grid.ColumnModel([
  9. new Ext.grid.RowNumberer(), //行号列
  10. sm,
  11. {header:'编号',dataIndex:'id'},
  12. {header:'性别',dataIndex:'sex',renderer:function(value){
  13. if(value=='male'){
  14. return "";
  15. }else{
  16. return "";
  17. }
  18. }}, //增加性别,自定义renderer,即显示的样式,可以加html代码,来显示图片等。
  19. {header:'名称',dataIndex:'name'},
  20. {header:'描述',dataIndex:'descn'}
  21. ]);
  22. var ds = new Ext.data.Store({
  23. proxy: new Ext.data.HttpProxy({url:'list.action'}),//调用的动作
  24. reader: new Ext.data.JsonReader({
  25. totalProperty: 'total',
  26. root: 'persons',
  27. successProperty :'success'
  28. }, [
  29. {name: 'id',mapping:'id',type:'int'},
  30. {name: 'sex',mapping:'sex',type:'string'},
  31. {name: 'name',mapping:'name',type:'string'},
  32. {name: 'descn',mapping:'descn',type:'string'} //列的映射
  33. ])
  34. });
  35. var grid = new Ext.grid.GridPanel({
  36. el: 'grid3',
  37. ds: ds,
  38. sm: sm,
  39. cm: cm,
  40. width:700,
  41. height:280,
  42. bbar: new Ext.PagingToolbar({
  43. pageSize: 10,
  44. store: ds,
  45. displayInfo: true,
  46. displayMsg: '显示第 {0} 条到 {1} 条记录,一共 {2} 条',
  47. emptyMsg: "没有记录"
  48. }) //页脚显示分页
  49. });
  50. //el:指定html元素用于显示grid
  51. grid.render();//渲染表格
  52. ds.load({params:{start:0, limit:10}}); //加载数据
  53. });
注意,这里的gridpanel一定要设置高度,否则数据是显示不出来的。
最后启动tomcat,在浏览器里输入http://localhost:8080/extjs/grid3.jsp,就可以看到效果
posted @ 2008-10-04 14:35 Loy Fu 阅读(4521) | 评论 (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 阅读(2048) | 评论 (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 代码
 
  1. <dependency>  
  2.     <groupId>org.apache.struts</groupId>  
  3.     <artifactId>struts2-core</artifactId>  
  4.     <version>2.0.9</version>  
  5. </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 代码
 
  1. <struts>  
  2.     <constant name="struts.action.extension" value="" />  
  3. </struts>  

然后我在src/test/java下建立demo/RunJetty.java文件,main方法如下:
java 代码
 
  1. public static void main(String[] args) throws Exception {  
  2.     Server server = new Server(8080); //也可以改成其它端口  
  3.     File rootDir = new File(RunJetty.class.getResource("/").getPath()).getParentFile().getParentFile();  
  4.     String webAppPath = new File(rootDir, "src/main/webapp").getPath();  
  5.     new WebAppContext(server, webAppPath, "/");  
  6.     server.start();  
  7. }  

现在,在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 代码
 
  1. <filter>  
  2.   <filter-name>struts2</filter-name>  
  3.   <filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>  
  4.   <init-param>  
  5.     <param-name>actionPackages</param-name>  
  6.     <param-value>demo.actions1,demo.actions2</param-value>  
  7.   </init-param>  
  8. </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 代码
 
  1. public String execute() {  
  2.     return "success";  
  3. }  

在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 代码
 
  1. @Results({  
  2.     @Result(name="success", type=NullResult.class, value = "/success.html", params = {}),  
  3.     @Result(name="error", type=NullResult.class, value = "/error.html", params = {})  
  4. })  
  5. public class ListPeopleAction {  
  6.     public String execute() {  
  7.         return "success";  
  8.     }  
  9. }  

同上,我们给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 代码
 
  1. <package name="demo-default" extends="struts-default">  
  2. <global-results>  
  3. <result name="success">/success.html</result>  
  4. </global-results>  
  5. </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 代码
 
  1. <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 阅读(1025) | 评论 (0)编辑 收藏
仅列出标题  下一页