使用BlazeDS实现Java和Flex通信之hello world

新的项目对用户体验及用户互动要求较高,决定选用Flex作为前端的展现技术,整体框架仍然是Flex+Spring+Hibernate(考虑采用seam中)。作为入门,先从经典的Hello world开始,暂时不考虑Flex与Spring、Hibernate的集成。

Flex要实现与Java集成,开源项目BlazeDSGraniteDSFlamingo都提供了相应的解决方案,考虑到BlazeDS是Adobe官方的开源项目,因此采用BlazeDs作为Flex与Java通信的基础框架。什么是BlazeDS呢,看看官方的介绍:

BlazeDS is the server-based Java remoting and web messaging technology that enables developers to easily connect to back-end distributed data and push data in real-time to Adobe® Flex® and Adobe AIR™ applications for more responsive rich Internet application (RIA) experiences.

开发工具采用Eclipse+Flex Builder 3 Plug-in方式,不采用Flex Builder 3。先安装Eclipse,再安装Flex Builder 3 Plug-in,相关的安装配置不再赘述。

1、下载BlazeDS

下载BlazeDS Turnkey :http://flexorg.wip3.adobe.com/blazeds/3.0.x/milestone/3978/blazeds-turnkey-3.2.0.3978.zip

由于BlazeDS Turnkey中包含BlazeDS的使用例子,对于入门熟悉Flex及BlazeDS都有较好的参考价值,因此建议下载BlazeDS Turnkey。

关于blazeds-turnkey 的目录说明:

docs:BlazeDS Javadoc

resources:BlazeDS的相关支持包,包括clustering(采用jgroups)、BlazeDS与ColdFusion 集成的配置文件、BlazeDS的配置文件、BlazeDS与AJAX集成的桥、Flex的SDK、Flex的java library、BlazeDS与Tomcat、Jboss、Websphere等security集成的支持包。

sampledb:hsqldb的启动脚本及样例数据库

tomcat:Tomcat 包

blazeds.war:最小化的BlazeDS 文件,可以作为空白项目来建立BlazeDS 应用程序。

sample.war:BlazeDS的demo例子(所谓的testdrive)。

ds-console.war :BlazeDS的部署管理程序。

2、建立Java Web Project

File->New->Web Project 建立Java helloworld项目

blazeds1 在helloworld/src下,新建com.yeeach.HelloWorldService类,内容如下:

package com.yeeach;

public class HelloWorldService {
public String hello(String var1) {
return “hello ” + var1;
}
public String world(String var1) {
return “world ” + var1;
}
}

3、建立helloworld的BlazeDS开发环境

3.1、拷贝blazeds.war下的WEB-INF到helloworld的目录下,覆盖原有的WEB-INF

3.2、在helloworld下建立flex-src目录(与src同级),用于存放flex的相关代码

blazeds2 helloworld/src:用于存放项目的java代码

helloworld/flex-src:用于存放项目flex的相关代码

helloworld/WebRoot/WEB-INF/flex:存放flex的相关配置文件

3.3、设置Flex Project Nature

blazeds3

blazeds4

blazeds5

3.4、在helloworld/flex-src下,新建MXML Application :helloworld.mxml  ,内容如下:

<?xml version=”1.0″ encoding=”utf-8″?>
<mx:Application xmlns:mx=”
http://www.adobe.com/2006/mxml”
layout=”vertical”>
<mx:RemoteObject destination=”com.yeeach.HelloWorldService”
id=”helloWorldService”>
<mx:method name=”hello”
result=”sayHelloResult(event)”/>
<mx:method name=”world”
result=”sayWorldResult(event)”/>
</mx:RemoteObject>
<mx:HBox>
<mx:Label text=”输入:”/>
<mx:TextInput id=”inputStr”/>
<mx:Button label=”say hello”
click=”sayHello(event);”/>
<mx:Button label=”say world”
click=”sayWorld(event);”/>
</mx:HBox>
<mx:HBox>
<mx:Label text=”结果:”/>
<mx:TextArea id=”result”/>
</mx:HBox>

<mx:Script>

<![CDATA[
import mx.rpc.events.FaultEvent;
import mx.controls.Alert;
import mx.rpc.events.ResultEvent;

function sayHello(event:Event):void
{
var inputVar:String=inputStr.text;
helloWorldService.hello(inputVar);

}

function sayWorld(event:Event):void
{
var inputVar:String=inputStr.text;
helloWorldService.world(inputVar);

}

private function sayHelloResult(event:ResultEvent):void
{
result.text=event.result.toString();
Alert.show(event.result.toString(), "返回结果");
}

private function sayWorldResult(event:ResultEvent):void
{
result.text=event.result.toString();
Alert.show(event.result.toString(), "返回结果");
}
]]>
</mx:Script>
</mx:Application>

3.5、修改remoting-config.xml,增加对destination的说明

<destination id=”com.yeeach.HelloWorldService”>
<properties>
<source>com.yeeach.HelloWorldService</source>
</properties>
</destination>

3.6、设置Flex Build Path等相关属性

1)右键->Properties,设置Flex Build Path属性,将Main source folder修改为flex-src,然后点击“OK”

2)右键->Properties,设置Flex Applications属性,添加flex-src下的其他Application,然后点击“OK”

如果需要添加flex-src子目录下的其他Application(例如helloworld/flex-src/com/yeeach/helloworld1.mxml),目前从UI界面似乎无法正确添加,可以直接修改.actionScriptProperties,在<applications></applications>中间增加相应的Application

<applications>
<application path=”helloworld.mxml”/>

<application path=”com/yeeach.com/helloworld1.mxml”/>
</applications>

3)右键->Properties,设置Flex Compiler属性,将Flex SDK version 修改为“Use default”或“Use a specific SDK”,指向正确的Flex SDK;确认“Additional compiler arguments”配置参数正确,然后点击“OK”

blazeds6 4)右键->Properties,设置Flex Server属性,配置为正确的参数,然后点击“OK”

blazeds7

3.7、部署helloworld 应用到Tomcat

通过http://127.0.0.1:8080/helloworld/helloworld.swf来访问我们的hello world

3.8、分析helloworld.mxml

<?xml version=”1.0″ encoding=”utf-8″?>
<mx:Application xmlns:mx=”
http://www.adobe.com/2006/mxml”
layout=”vertical”>
<mx:RemoteObject destination=”com.yeeach.HelloWorldService”
id=”helloWorldService”>

//此处的destination=”com.yeeach.HelloWorldService”与remoting-config.xml中的id=”com.yeeach.HelloWorldService”完全匹配

//id=”helloWorldService”用来在actionscript中标识destination=”com.yeeach.HelloWorldService”,后面的helloWorldService.hello(inputVar)等都使用此id;

<mx:method name=”hello”
result=”sayHelloResult(event)”/>

//mx:method 声明java类com.yeeah.com.HelloWorldService中的hello方法及响应结果回调函数sayHelloResult
<mx:method name=”world”
result=”sayWorldResult(event)”/>
</mx:RemoteObject>
<mx:HBox>
<mx:Label text=”输入:”/>
<mx:TextInput id=”inputStr”/>
<mx:Button label=”say hello”
click=”sayHello(event);”/>
<mx:Button label=”say world”
click=”sayWorld(event);”/>
</mx:HBox>
<mx:HBox>
<mx:Label text=”结果:”/>
<mx:TextArea id=”result”/>
</mx:HBox>

<mx:Script>

<![CDATA[
import mx.rpc.events.FaultEvent;
import mx.controls.Alert;
import mx.rpc.events.ResultEvent;

function sayHello(event:Event):void
{
var inputVar:String=inputStr.text;
helloWorldService.hello(inputVar);

}

function sayWorld(event:Event):void
{
var inputVar:String=inputStr.text;
helloWorldService.world(inputVar);

}

private function sayHelloResult(event:ResultEvent):void
{
result.text=event.result.toString();
Alert.show(event.result.toString(), "返回结果");
}

private function sayWorldResult(event:ResultEvent):void
{
result.text=event.result.toString();
Alert.show(event.result.toString(), "返回结果");
}
]]>
</mx:Script>
</mx:Application>

代码文件:helloworld.rar

Java环境下Imagemagick中文图片处理问题非完美解决方案

在处理服务器与客户端交互时候,基于性能考虑(参考手机客户端网络加速技术方案实现思考),对软件介绍中的图片采用缩略图进行展现,这样涉及了实际图片缩略图的动态处理问题,采用Java的Runtime.getRuntime().exec直接来调用Imagemagick的命令来完成缩略图的动态生成。原本是一个很简单的问题,只不过由于在图片的目录路径及图片名称中存在中文,因此在处理时候又出现了FileNotFoundException,记录一下非完美解决方案。

1、系统环境:

操作系统:Red Hat Enterprise Linux AS release 4 (Nahant Update 4)

操作系统用户环境变量(参考Linux环境下资源下载中文目录及中文文件名称问题):

export LC_ALL=zh_CN.GB18030

export LANG= zh_CN.GB18030

JVM、Tomcat、数据库等相关的字符集(参考Struts2中Datetimepicker控件的中文问题):UTF-8

2、需求场景

手机客户端对于图片的请求分为两大类:图片的下载请求、图片的预览请求。

图片的下载请求:客户端请求下载实际的图片内容,此时候服务器端不需要对图片进行任何加工处理,只需要正确返回实际的图片内容。

图片的预览请求:客户端并不需要下载完整的图片内容,只需要在客户端能够预览图片,因此在分辨率及图片质量上可以稍稍降低,以节省带宽资源。在手机客户端发起图片预览请求时候,服务器端响应的所有的图片都采用缩略图(降低图片大小)及低分辨率(降低图片质量)的方式返回。

备注:通过手机浏览器或手机客户端下载1M左右以上的资源时候,如果采用Struts2的Stream Result,存在服务器端无响应的现象,具体原因没有查明,有空看看Struts2代码再说,采用传统的response写流的方式实现没有此问题。

3、实现机制

采用Runtime.getRuntime().exec(“/usr/bin/convert  -sample 75%x75% /test.jpg /test_thumb.jpg”)来动态生成缩略图。

备注:

a、考虑到jmagick并不能完整支持Imagemagick所有命令集且使用上并不是很方便,因此没有采用jmagick,直接使用Runtime.getRuntime().exec(“/usr/bin/convert  -sample 75%x75% /test.jpg /test_thumb.jpg”)的方案,使用方法具体可以参考在线主题制作技术实现方案

b、这里使用的imagemagick的命令只供测试使用,不一定是最佳的命令,具体查一下imagemagick的手册

c、由于Imagemagick生成图片相对较慢,因此并不需要每一次都生成缩略图,可以将缩略图存放在与原有图片相同的目录路径下,这样在客户端发起图片预览请求时候,服务器端先查看一下请求图片的预览图是否存在,如果存在直接返回以前生成的预览图,如果不存在则动态生成后再返回。

4、问题

在使用Runtime.getRuntime().exec调用convert来动态生成缩略图时候,由于资源在Linux服务器上存放目录及文件名称都存在中文(例如/products/material/img/101×80 /人物肖像101×8/测试.jpg),因此在调用时候会报FileNotFoundException错误。

5、方案测试

测试命令:

/usr/bin/convert -sample 75%x75%   /img/101×80/人物肖像101×8/测试.jpg  /img/101×80/人物肖像101×8/测试_thumb.jpg

试验1:

操作:操作系统编码为zh_CN.GB18030,因此在shell命令行执行convert,如果传入的字符串参数为正常的zh_CN.GBK(比zh_CN.GB18030范围小)的编码,那么不会乱码。

结果:在命令行执行,已验证。

试验2:

操作:JVM环境变量为UTF-8,Java内部本身也是采用UTF-8编码,Runtime.getRuntime().exec调用convert时候,如果将参数进行转码成zh_CN.GBK,则应该能够正常处理

private static final String CMD=”/usr/bin/convert “;
private static final String PRAM=” -sample 75%x75%  “;

inputPath=new String(inputPath.getBytes(“UTF-8″),”GBK”);
outPutPath=new String(outPutPath.getBytes(“UTF-8″),”GBK”);
Process pc=Runtime.getRuntime().exec(CMD+PRAM+inputPath+” “+outPutPath);

结果:以上代码不行,将getBytes的编码换成GBK、ISO8859-1,输出编码换成UTF-8、ISO8859-1等也一样

试验3:

操作:将JVM环境变量修改为GBK

结果:似乎也不行

试验4:

操作:在java代码中,将中文路径/img/101×80/人物肖像101×8/测试.jpg  /img/101×80/人物肖像101×8/测试_thumb.jpg写入普通文本文件/home/client.aouu.com/img/img2.txt,然后在命令行调用convert来生成

Java代码(示例,不规范):

FileWriter os = new FileWriter(“/home/client.aouu.com/img/img1.txt”);
String imgPath=inputPath+” “+outPutPath;
os.write(imgPath);
os.close();
Process pc=Runtime.getRuntime().exec(“/home/client.aouu.com/img/img.sh”);
pc.waitFor();

img.sh:

#!/bin/sh

cat /home/client.aouu.com/img/img1.txt |xargs /usr/bin/convert  -sample 75%x75%

结果:不行,报告-bash: ./img.sh: cannot execute binary file

试验5:

操作:与试验4操作相同

img.sh:

#!/bin/sh

iconv -f UTF-8 -t GBK -c /home/client.aouu.com/img/img1.txt -o /home/client.aouu.com/img/img2.txt
cat /home/client.aouu.com/img/img2.txt |xargs /usr/bin/convert  -sample 75%x75%

结果:OK

结果分析:

尽管在java代码中将中文路径、中文文件名称的编码转码成GBK,但java内部本身是UTF-8编码,因此JVM通过Runtime.getRuntime().exec传给/usr/bin/convert的参数实际上仍然为UTF-8编码(?是什么编码,尚待验证),并不是GBK编码,因此convert获取输入参数后无法在操作系统找到对应的文件。

通过FileWriter写入文件的字符集也为UTF-8,通过iconv强制将UTF-8编码转化为GBK,这样convert能够正常处理。

具体的原因及机理尚待进一步考察验证,现在尚无好的办法,姑且凑合使用此种方案再说。

Struts2 doubleselect标签中select框缺省selected实现

    在项目中,省市下拉框联动采用的是Struts2的doubleselect标签,需要根据业务需求实现两个下拉框动态的缺省值(selected)。

业务场景:

    在代理商管理中,增加代理商时候选择代理商所属的省市,然后增加代理商的销售人员,但代理商销售人员销售产品,如果客户在客户库中没有相关信息,需要增加客户,此时侯应当缺省根据代理商所属的省市信息,在增加客户时候,客户所在省市的缺省selected的值应当为代理商所在的省市信息。

 

主要实现逻辑如下:

    采用doubleselect标签的value和doublevalue属性,在action中定义两个select框缺省值参数(例子中是defaultItem、doubleDefaultItem)的get、set方法,在action方法中根据业务逻辑(在增加客户时候,客户所在省市缺省为销售员所在省市)调用set方法设定两个select框的缺省值,然后在页面通过value和doublevalue方法获取设定的缺省值。

实现样例如下:

1. Action

package com.mobilesoft.esales.webapp.action;

import java.util.ArrayList;

import java.util.HashMap;

import java.util.Map;

import org.apache.log4j.Logger;

public class DoubleListAction extends BaseAction {

private static final Logger logger = Logger.getLogger(DoubleListAction.class);

private String defaultItem;

private String doubleDefaultItem;

public String execute() {

return SUCCESS;

    }

public String doubleSelectTest(){

       Map map=new HashMap();

       ArrayList list1=new ArrayList();

       list1.add(“11″);

       list1.add(“12″);

       list1.add(“13″);

       map.put(“1″, list1);

       ArrayList list2=new ArrayList();

       list2.add(“21″);

       list2.add(“22″);

       list2.add(“23″);

       map.put(“2″, list2);

       ArrayList list3=new ArrayList();

       list3.add(“31″);

       list3.add(“32″);

       list3.add(“33″);

       map.put(“3″, list3);

       setDefaultItem(“2″);

       setDoubleDefaultItem(“23″);

       getRequest().setAttribute(“defaultItem”, getDefaultItem());

       getRequest().setAttribute(“doubleDefaultItem”, getDoubleDefaultItem());

       getRequest().setAttribute(“map”, map);

return SUCCESS;

    }

public String getDefaultItem() {

return defaultItem;

    }

public void setDefaultItem(String defaultItem) {

this.defaultItem = defaultItem;

    }

public String getDoubleDefaultItem() {

return doubleDefaultItem;

    }

public void setDoubleDefaultItem(String doubleDefaultItem) {

this.doubleDefaultItem = doubleDefaultItem;

    }

}

2. doubleselect.jsp

<%@ taglib prefix=”s” uri=”/struts-tags” %>

<%@ page language=”java” errorPage=”/error.jsp” pageEncoding=”GBK” contentType=”text/html;charset=GBK” %>

<html>

<head>

<title>Struts 2 Cool Tags – &lt;s:doubeselect/ &gt;</title>

<s:head />

</head>

<body>

<h2>Doubleselect 缺省值selected使用数据演示:</h2>

<s:form name=”form1″>

<s:doubleselect label=”缺省值测试”

    list=”#request.map.keySet()”        doubleList=”#request.map[top]”

name=”doubleselect1″                doubleName=”doubleselect2″

value=”#request.defaultItem”       doubleValue=”#request.doubleDefaultItem”

    formName=”form1″

/>

</s:form>

</body>

</html>

3. struts.xml

<action name=”doubleSelectTest” method=”doubleSelectTest” class=”com.mobilesoft.esales.webapp.action.DoubleListAction”>

<result name=”success”>test/doubleselect.jsp</result>

</action>

 

Technorati 标签: ,,,

在jmesa中使用hsql支持复杂的hsql查询

在目前的框架中,使用的是Hibernate Criteria来实现复杂的分页、排序、过滤等操作,基本上能够完成大部分复杂的查询分页、排序处理,但对于统计分析这样的复杂查询,Criteria实现还是有点力不从心,比较麻烦,尤其是在多表查询的情况下。

由于对于整型字段在目前在列表的输入框中输入过滤条件,会由于HttpServletRequest为String类型,而Hibernate为整型字段,导致对于在数据库中非String类型的字段过滤报错,因此暂时去除对过滤的支持功能,后续有空再研究解决方案,方法如下:

       HtmlRow row = table.getRow();

       row.setFilterable(false);

这样处理后,意味着我们在jmesa中实际上只需要处理分页、排序操作(导出为txt、excel、pdf等与此关系不大),因此对jmesa增加对hsql语句查询的支持,而不采用Criteria的方式,方法如下:

1. 对排序类HibernateSort

package com.mobilesoft.esales.dao.hibernate;

import java.util.ArrayList;

import java.util.List;

import org.hibernate.Criteria;

import org.hibernate.criterion.Order;

public class HibernateSort implements CriteriaCommand {

    List<Sort> sorts = new ArrayList<Sort>();

    public void addSort(String property, String order) {

        sorts.add(new Sort(property, order));

    }

    public Criteria execute(Criteria criteria) {

        for (Sort sort : sorts) {

            buildCriteria(criteria, sort.getProperty(), sort.getOrder());

        }

        return criteria;

    }

    public List getSortList(){

        return this.sorts;

    }

    private void buildCriteria(Criteria criteria, String property, String order) {

        if (order.equals(Sort.ASC)) {

            criteria.addOrder(Order.asc(property));

        } else if (order.equals(Sort.DESC)) {

            criteria.addOrder(Order.desc(property));

        }

    }

    public static class Sort {

        public final static String ASC = “asc”;

        public final static String DESC = “desc”;

        private final String property;

        private final String order;

        public Sort(String property, String order) {

            this.property = property;

            this.order = order;

        }

        public String getProperty() {

            return property;

        }

        public String getOrder() {

            return order;

        }

    }

}

2. 对DAO类PersonDAO

增加getPersonWithFilterAndSort2和getPersonCountWithFilter2:

public int getPersonCountWithFilter(final HibernateFilter filter) {

        Integer count = (Integer) getHibernateTemplate().execute(new HibernateCallback() {

public Object doInHibernate(Session session)

throws HibernateException, SQLException {

                Criteria criteria = session.createCriteria(Person.class);

                criteria = filter.execute(criteria);

                criteria.setProjection(Projections.rowCount()).uniqueResult();

return criteria.uniqueResult();

            }

        });

return count.intValue();

    }

public int getPersonCountWithFilter2(final HibernateFilter filter) {

        Long count = (Long) getHibernateTemplate().execute(new HibernateCallback() {

public Object doInHibernate(Session session)

throws HibernateException, SQLException {

              StringBuffer querySql=new StringBuffer(“select count(person) from Person as person “);

                  Query query  =  session.createQuery(querySql.toString());

                List list  =  query.list();

return list.get(0);

            }

        });

return count.intValue();

    }

public List<Person> getPersonWithFilterAndSort(final HibernateFilter filter, final HibernateSort sort, final int rowStart, final int rowEnd) {

        List applications = (List) getHibernateTemplate().execute(new HibernateCallback() {

public Object doInHibernate(Session session)

throws HibernateException, SQLException {

                Criteria criteria = session.createCriteria(Person.class);

                criteria = filter.execute(criteria);

                criteria = sort.execute(criteria);

                criteria.setFirstResult(rowStart);

                criteria.setMaxResults(rowEnd – rowStart);

return criteria.list();

            }

        });

return applications;

    }   

public List<Person> getPersonWithFilterAndSort2(final HibernateFilter filter, final HibernateSort sort, final int rowStart, final int rowEnd) {

            List applications = (List) getHibernateTemplate().execute(new HibernateCallback() {

public Object doInHibernate(Session session)

throws HibernateException, SQLException {

                  List sorts =sort.getSortList();

                  Iterator iterator=sorts.iterator();

                  StringBuffer sortSql=new StringBuffer(” “);

int i=1;

while(iterator.hasNext()){

                      HibernateSort.Sort field=(HibernateSort.Sort)iterator.next();

                      String property=field.getProperty();

                      String order=field.getOrder();

if(i>1)

                         sortSql.append(” , “);

else

                         sortSql.append(” order by “);

                      sortSql.append(property);

                      sortSql.append(” “);

                      sortSql.append(order);

                      i++;

                  }

                  StringBuffer querySql=new StringBuffer(“select person from Person as person “);

                  querySql.append(sortSql);

                  Query query  =  session.createQuery(querySql.toString());

logger.fatal(“$HibernateCallback.doInHibernate(Session) “+querySql.toString()); //$NON-NLS-1$

                    query.setFirstResult(rowStart);                   

                    query.setMaxResults(rowEnd-rowStart);

                    List list  =  query.list();

return  list;

                }

            });

return applications;

    }

3. PersonService及PersonServiceImpl

在PersonService中增加接口声明

public int getPersonCountWithFilter2(HibernateFilter filter);

public Collection<Person> getPersonWithFilterAndSort2(HibernateFilter filter, HibernateSort sort, int rowStart, int rowEnd);

在PersonServiceImpl增加实现:

public int getPersonCountWithFilter2(HibernateFilter filter) {

return personDAO.getPersonCountWithFilter2(filter);

    }

public Collection<Person> getPersonWithFilterAndSort2(HibernateFilter filter, HibernateSort sort, int rowStart, int rowEnd) {

return personDAO.getPersonWithFilterAndSort2(filter, sort, rowStart, rowEnd);


4. PersonAction

将获取数据修改为调用getPersonWithFilterAndSort2,获取总数修改为getPersonCountWithFilter2

if (!limit.isComplete()) {

// deal with Criteria

//int totalRows = personService.getPersonCountWithFilter(hibernateFilter);

// deal with hsql

int totalRows = personService.getPersonCountWithFilter2(hibernateFilter);

            tableFacade.setTotalRows(totalRows);

        }

        HibernateSort hibernateSort = getHibernateSort(limit);

int rowStart = limit.getRowSelect().getRowStart();

int rowEnd = limit.getRowSelect().getRowEnd();

// deal with Criteria

//Collection<Person> items = personService.getPersonWithFilterAndSort(hibernateFilter, hibernateSort, rowStart, rowEnd);

// deal with hsql

        Collection<Person> items = personService.getPersonWithFilterAndSort2(hibernateFilter, hibernateSort, rowStart, rowEnd);

        tableFacade.setItems(items); // Do not forget to set the items back on the tableFacade.

 

5、参考

http://code.google.com/p/jmesa/wiki/LimitExample

 

用UrlRewriteFilter实现不同WebContext间的url rewrite

    在新平台中,由于需要保持与原有CP、SP的接口的不变(目前的接口都为基于http协议的),例如原来与CP的接口为:

http://www.yeeach.com/platform/retail.do?act=verifyIp。

为了保证系统平滑的迁移,老的平台需要保留一段时间,新的平台需要以新的Webcontext方式进行部署,例如新的应用的web路径为:

http://www.yeeach.com/platformnew,与cp的接口假定新平台的处理接口为http://www.yeeach.com/platformnew/ retail.action?act=verifyIp,也即需要进行如下的迁移处理:

http://www.yeeach.com/platform/retail.do?act=verifyIp        –>         http://www.yeeach.com/platformnew/retail.action?act=verifyIp

    此种情况下与为了搜索引擎优化而进行的URL优化(也即现在流行的所谓的REST)将

http://www.yeeach.com/platform/retail.do/act/verifyIP –> http://www.yeeach.com/platform/retail.do?act=verifyIp

的场景并不相同

    同时由于跨了不同的Web Context,因此采用strtus2的struts.action.extension=action,do来实现action不同后缀的方式也行不通。

    可行的解决方案有两种:

1、  利用前端的apache或lighttpd的mod_rewrite来实现

2、  利用java开源项目UrlRewriteFilter http://tuckey.org/urlrewrite/来实现类似mod_rewrite的功能

    由于目前尚未有太多的静态页面内容,因此尚未部署apache或lighttpd(做门户时候再基于架设lighttpd),同时考虑到相对独立性,因此先采用纯java的UrlRewriteFilter解决方案来实现,简单描述一下实现方法:

1、  下载及安装UrlRewriteFilter

http://urlrewritefilter.googlecode.com/files/urlrewritefilter-3.1.0.zip 下载 UrlRewriteFilter 3.1.0版本

2、将urlrewrite-3.1.0.jar部署到旧的platform/WEB-INF/lib下

3、修改web.xml,增加如下内容

        <filter>

            <filter-name>UrlRewriteFilter</filter-name>

            <filter-class>org.tuckey.web.filters.urlrewrite.UrlRewriteFilter</filter-class>

            <init-param>

                <param-name>logLevel</param-name>

                <param-value>WARN</param-value>

            </init-param>

        </filter>

        <filter-mapping>

            <filter-name>UrlRewriteFilter</filter-name>

            <url-pattern>/*</url-pattern>

        </filter-mapping>

4、在旧的webapp platform中部署urlrewrite.xml到WEB-INF/下,内容如下

<?xml version=”1.0″ encoding=”utf-8″?>

<!DOCTYPE urlrewrite PUBLIC “-//tuckey.org//DTD UrlRewrite 3.1//EN”

        “http://tuckey.org/res/dtds/urlrewrite3.1.dtd”>

<urlrewrite use-query-string=”true”>

    <rule>

        <from>^/retail.do\?act=(.*)$</from>

        <to type=”forward”>/platformnew/retail.action?act=$1</to>

    </rule>

</urlrewrite>

注意事项:

1)、实例的请求形式为:

http://www.yeeach.com/platform/retail.do?act=verifyIp         –>         http://www.yeeach.com/platformnew/retail.action?act=verifyIp

2)、对于to type可以为redirect、forward等,由于forward不能跨不同context之间,因此只能采用redirect方式。对于在统一Webcontext请求可以采用forward方式

3)、from和to都支持类似于apache mod_rewrite正则表达式用法,具体参看文档

4)、对于urlrewritefilter 3.x版本,对于诸如/retail.action?act=verifyIp这样的参数字符串,缺省情况下urlrewritefilter并不处理,由参数use-query-string决定,缺省为false。需要显性设置为true:<urlrewrite use-query-string=”true”>

摘自:http://urlrewritefilter.googlecode.com/svn/trunk/src/doc/manual/3.1/index.html

use-query-string    (optional) 

  false (default)       The query string will not be appended to the url that the “from” element matches against.

  true                     The query string will be appended to the url that the “from” element matches against.

 

5)、一定要注意在from 的查询url中的?需要进行转义处理:\?,不然会报错。因为?字符在正则表达式是0或1多个字符。to中不需要转义

6)、对url中&字符在xml文件中,用:&amp;

 

Hibernate Criteria使用实例

在使用jmesa作为组件来实现分页、导入、排序、过滤组件时候,对paging、sort、filter的处理,使用的是Hibernate的Criteria函数,对于单表使用Criteria方法相对容易,但对于多表操作,手册上没有现成的样例可以借鉴。总结一下Criteria的一些用法,以方便在对多表数据复杂操作时候也能够使用jmesa,简化分页、导出等日常操作。

1、目前使用Criteria用于取总数及排序过滤的用法例子

public int getPersonCountWithFilter(final HibernateFilter filter) {

Integer count = (Integer) getHibernateTemplate().execute(new HibernateCallback() {

public Object doInHibernate(Session session)

throws HibernateException, SQLException {

Criteria criteria = session.createCriteria(Person.class);

criteria.add(Expression.eq(“id”,27));

criteria = filter.execute(criteria);

criteria.setProjection(Projections.rowCount()).uniqueResult();

return criteria.uniqueResult();

}

});

return count.intValue();

}

public List<Person> getPersonWithFilterAndSort(final HibernateFilter filter, final HibernateSort sort, final int rowStart, final int rowEnd) {

List applications = (List) getHibernateTemplate().execute(new HibernateCallback() {

public Object doInHibernate(Session session)

throws HibernateException, SQLException {

Criteria criteria = session.createCriteria(Person.class);

criteria = filter.execute(criteria);

criteria = sort.execute(criteria);

criteria.setFirstResult(rowStart);

criteria.setMaxResults(rowEnd – rowStart);

return criteria.list();

}

});

return applications;

}

2、数据库表结构,以sys_user和sys_user_role为例子

CREATE TABLE `sys_user_role` (

`user_id` int(11) NOT NULL,

`role_id` int(11) NOT NULL,

`user_name` varchar(100) default NULL,

`role_name` varchar(100) default NULL,

PRIMARY KEY (`role_id`,`user_id`)

) ;

INSERT INTO `sys_user_role` VALUES (’1′, ’1′, ‘liang1′, ‘admin1′);

INSERT INTO `sys_user_role` VALUES (’2′, ’2′, ‘liang2′, ‘admin2′);

INSERT INTO `sys_user_role` VALUES (’3′, ’3′, ‘liang3′, ‘admin3′);

INSERT INTO `sys_user_role` VALUES (’4′, ’4′, ‘liang4′, ‘admin4′);

INSERT INTO `sys_user_role` VALUES (’5′, ’5′, ‘liang5′, ‘anonymous’);

INSERT INTO `sys_user_role` VALUES (’1′, ’5′, ‘liang1′, ‘admin5′);

CREATE TABLE `sys_user` (

`user_id` int(11) NOT NULL,

`mobile` varchar(15) default NULL,

`imei` varchar(20) default NULL,

`user_name` varchar(100) NOT NULL,

`password` varchar(50) default NULL,

`user_type` varchar(40) default ‘normal’ ,

`login_type` varchar(20) default NULL,

`customer_id` int(11) default NULL,

`customer_name` varchar(200) default NULL,

`root_company_id` int(11) default NULL,

`root_company_name` varchar(255) default NULL,

`compayn_id` int(11) default NULL,

`company_name` varchar(255) default NULL,

`email` varchar(100) default NULL,

`email2` varchar(100) default NULL,

`nickname` varchar(100) default NULL,

`sex` varchar(10) default NULL ,

`status` varchar(50) default NULL,

`credit_amount` decimal(10,2) default NULL,

`credit_rank` varchar(20) default NULL,

`money` decimal(10,2) default NULL,

`integral` decimal(10,2) default ’0.00′,

`website` varchar(200) default NULL,

`pwd_modify_date` datetime default NULL,

`pwd_duration` varchar(10) default NULL,

`signature` text,

`twitter` varchar(255) default NULL,

`qq` varchar(20) default NULL,

`msn` varchar(50) default NULL,

`icq` varchar(50) default NULL,

`yahoo` varchar(30) default NULL,

`gtalk` varchar(30) default NULL,

`blog` varchar(255) default NULL,

`interest` text,

`safe_question` varchar(100) default NULL,

`safe_answer` varchar(100) default NULL,

`safe_question2` varchar(100) default NULL,

`safe_answer2` varchar(100) default NULL,

`safe_question3` varchar(100) default NULL,

`safe_answer3` varchar(100) default NULL,

`icon` varchar(100) default NULL,

`icon2` varchar(100) default NULL,

`icon3` varchar(100) default NULL,

`is_test` tinyint(1) default NULL ,

`is_admin` tinyint(1) default NULL,

`fax` varchar(20) default NULL,

`home_phone` varchar(50) default NULL,

`office_phone` varchar(20) default NULL,

`birthday` char(19) default NULL,

`vocation` varchar(20) default NULL,

`education` varchar(50) default NULL,

`address` varchar(255) default NULL,

`postcode` varchar(20) default NULL,

`description` text,

`creator` varchar(40) default NULL,

`begin_date` datetime default NULL,

`end_date` datetime default NULL,

`create_date` datetime default NULL,

`modify_user` varchar(40) default NULL,

`modify_date` datetime default NULL,

`last_login_type` varchar(20) default NULL,

`last_login_id` varchar(20) default NULL,

`last_login_date` datetime default NULL,

PRIMARY KEY (`user_id`)

) ;

INSERT INTO `sys_user` VALUES (’1′, ’13911111111′, null, ‘liang1′, ‘liang1′, ‘normal’, null, null, ‘liang1′, null, null, null, null, null, null, null, null, null, null, null, null, ’0.00′, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null);

INSERT INTO `sys_user` VALUES (’2′, ’13922222222′, null, ‘liang2′, ‘liang2′, ‘normal’, null, null, ‘liang2′, null, null, null, null, null, null, null, null, null, null, null, null, ’0.00′, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null);

INSERT INTO `sys_user` VALUES (’3′, ’13933333333′, null, ‘liang3′, ‘liang3′, ‘normal’, null, null, ‘liang3′, null, null, null, null, null, null, null, null, null, null, null, null, ’0.00′, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null);

INSERT INTO `sys_user` VALUES (’4′, ’13944444444′, null, ‘liang4′, ‘liang4′, ‘normal’, null, null, ‘liang4′, null, null, null, ‘li’, null, null, null, null, null, null, null, null, ’0.00′, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null);

INSERT INTO `sys_user` VALUES (’5′, ’13955555555′, null, ‘liang5′, ‘liang5′, ‘normal’, null, null, ‘liang5′, null, null, null, null, null, null, null, null, null, null, null, null, ’0.00′, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null);

3、hbm映射文件

注意:

由于在目前的数据模型及程序中并没有使用数据库的外键约束,直接通过程序来控制外键约束关系。因此在所有的hbm中并没有使用hibernate 的one-to-many关联。要使用Criteria实现多表较为复杂的操作,需要加上one-to-many映射。但这与目前的程序实现存在冲突,解决方法如下:

由于目前使用Criteria只用于查询及统计分析部分,可以单独建立一个映射文件及映射类,用于查询及统计分析操作,例如对于SysUser表,可以建立一个SysUser-jmesa.hbm.xml,对此映射文件,将<class name=”com.mobilesoft.esales.model.SysUser” table=”sys_user” catalog=”mysql”>修改为:

<class name=”com.mobilesoft.esales.model.SysUserJmesa” table=”sys_user” >

one-to-many:

<set name=”userRoles” table=”sys_user_role” >

<key column=”role_id” />

<one-to-many class=”com.mobilesoft.esales.model.SysUserRole” />

</set>

由于只是演示,简单起见,直接用的是原有的映射文件及映射类。

3.1、SysUser.hbm.xml

<?xml version=”1.0″ encoding=”utf-8″?>

<!DOCTYPE hibernate-mapping PUBLIC “-//Hibernate/Hibernate Mapping DTD 3.0//EN”

“http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd”>

<hibernate-mapping>

<class name=”com.mobilesoft.esales.model.SysUser” table=”sys_user” catalog=”mysql”>

<id name=”userId” type=”java.lang.Integer”>

<column name=”user_id” />

<generator class=”native” />

</id>

<property name=”mobile” type=”java.lang.String”>

<column name=”mobile” length=”15″ />

</property>

<!—

省略掉其他内容

–>

<set name=”userRoles” table=”sys_user_role” >

<key column=”user_id” />

<one-to-many class=”com.mobilesoft.esales.model.SysUserRole” />

</set>

</class>

</hibernate-mapping>

3.2、SysUserRole.hbm.xml

<?xml version=”1.0″ encoding=”utf-8″?>

<!DOCTYPE hibernate-mapping PUBLIC “-//Hibernate/Hibernate Mapping DTD 3.0//EN”

“http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd”>

<!–

Mapping file autogenerated by MyEclipse Persistence Tools

–>

<hibernate-mapping>

<class name=”com.mobilesoft.esales.model.SysUserRole” table=”sys_user_role” catalog=”mysql”>

<composite-id name=”id” class=”com.mobilesoft.esales.model.SysUserRoleId”>

<key-property name=”roleId” type=”java.lang.Integer”>

<column name=”role_id” />

</key-property>

<key-property name=”userId” type=”java.lang.Integer”>

<column name=”user_id” />

</key-property>

</composite-id>

<property name=”userName” type=”java.lang.String”>

<column name=”user_name” length=”100″ />

</property>

<property name=”roleName” type=”java.lang.String”>

<column name=”role_name” length=”100″ />

</property>

</class>

</hibernate-mapping>

4、测试用例

import java.util.Iterator;

import java.util.List;

import junit.framework.TestCase;

import org.apache.log4j.Logger;

import org.hibernate.FetchMode;

import org.hibernate.criterion.Projections;

import org.hibernate.criterion.Restrictions;

import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.mobilesoft.esales.dao.hibernate.SysUserDAO;

import com.mobilesoft.esales.model.SysUser;

import com.mobilesoft.esales.model.SysUserRole;

/**

* Hibernate Criteria用法测试用例

*

* @author liangchuan@mobile-soft.cn

*

*/

public class TestCriteria extends TestCase {

private static final Logger logger = Logger.getLogger(TestCriteria.class);

private static ClassPathXmlApplicationContext context = null;

private static SysUserDAO dao;

static {

context = new ClassPathXmlApplicationContext(new String[] {

“applicationContext.xml”, “applicationContext-resources.xml”,

“applicationContext-dao.xml”, “applicationContext-service.xml” });

}

protected void setUp() throws Exception {

}

/**

* 演示使用Criteria实现:select userId from SysUser as user

*/

public void testSelectId() {

dao = (SysUserDAO) context.getBean(“SysUserDAO”);

List mylist=dao.getHibernateTemplate().getSessionFactory().openSession().createCriteria(SysUser.class)

.setProjection(

Projections.projectionList().add(

Projections.property(“userId”)

)

).list();

Iterator iterator=mylist.iterator();

while(iterator.hasNext()){

logger.fatal(“id is :”+iterator.next());

}

}

/**

* 演示使用Criteria实现:select user.*,userRole.* from SysUser as user ,SysUserRole userRole where user.userId=userRole.id

*/

public void testJoin1() {

dao = (SysUserDAO) context.getBean(“SysUserDAO”);

List mylist=dao.getHibernateTemplate().getSessionFactory().openSession().createCriteria(SysUser.class)

.setFetchMode(“userRoles”,FetchMode.JOIN).list();

Iterator iterator=mylist.iterator();

while(iterator.hasNext()){

SysUser user=(SysUser)iterator.next();

logger.fatal(“testJoin1:userid is :”+user.getUserId()+” userName is “+user.getUserName());

}

}

/**

* 演示使用Criteria实现:select user.* ,userRole.* from SysUser as user ,SysUserRole userRole

* where user.userId=userRole.id and user.userId=1

*/

public void testJoin2() {

dao = (SysUserDAO) context.getBean(“SysUserDAO”);

List mylist=dao.getHibernateTemplate().getSessionFactory().openSession().createCriteria(SysUser.class)

.setFetchMode(“userRoles”,FetchMode.JOIN)

.add(Restrictions.eq(“userId”,1))

.list();

Iterator iterator=mylist.iterator();

while(iterator.hasNext()){

SysUser user=(SysUser)iterator.next();

logger.fatal(“testJoin2:userid is :”+user.getUserId()+” userName is “+user.getUserName());

}

}

/**

* 演示使用Criteria实现:select user.* ,userRole.* from SysUser as user ,SysUserRole userRole

* where user.userId=userRole.id and userRole.id.roleId=2

* 演示createAlias的使用

*/

public void testJoin3() {

dao = (SysUserDAO) context.getBean(“SysUserDAO”);

List mylist=dao.getHibernateTemplate().getSessionFactory().openSession().createCriteria(SysUser.class)

.setFetchMode(“userRoles”,FetchMode.JOIN)

.createAlias(“userRoles”, “b”)

.add(Restrictions.eq(“b.id.roleId”,2))

.list();

Iterator iterator=mylist.iterator();

while(iterator.hasNext()){

SysUser user=(SysUser)iterator.next();

logger.fatal(“testJoin3:userid is :”+user.getUserId()+” userName is “+user.getUserName());

}

}

/**

* 演示使用Criteria实现:select count(userId) from SysUser as user ,SysUserRole userRole

* where user.userId=userRole.id

* 同时演示createAlias的使用

*/

public void testJoin4() {

dao = (SysUserDAO) context.getBean(“SysUserDAO”);

List mylist=dao.getHibernateTemplate().getSessionFactory().openSession().createCriteria(SysUser.class)

.setFetchMode(“userRoles”,FetchMode.JOIN)

.setProjection( Projections.projectionList().add( Projections.count(“userId”) ))

.list();

Iterator iterator=mylist.iterator();

while(iterator.hasNext()){

logger.fatal(“testJoin4:count(userId) is :”+iterator.next());

}

}

/**

* 演示使用Criteria实现:select count(id.roleId) from SysUserRole userRole

* 同时演示createAlias的使用

*/

public void testJoin5() {

dao = (SysUserDAO) context.getBean(“SysUserDAO”);

List mylist=dao.getHibernateTemplate().getSessionFactory().openSession().createCriteria(SysUserRole.class)

.setProjection( Projections.projectionList().add( Projections.count(“id.roleId”) ))

.list();

Iterator iterator=mylist.iterator();

while(iterator.hasNext()){

logger.fatal(“testJoin5:count(roleId) is :”+iterator.next());

}

}

}

5、Model

5.1、SysUser.java

没有什么特别的,直接用myeclipse生成,然后在SysUser中添加上:

private java.util.Set userRoles = new HashSet();

public java.util.Set getUserRoles() {

return userRoles;

}

public void setUserRoles(java.util.Set userRoles) {

this.userRoles = userRoles;

}

5.2、SysUserRole.java

package com.mobilesoft.esales.model;

/**

* SysUserRole entity.

*

* @author MyEclipse Persistence Tools

*/

public class SysUserRole implements java.io.Serializable {

// Fields

private SysUserRoleId id;

private String userName;

private String roleName;

// Constructors

/** default constructor */

public SysUserRole() {

}

/** minimal constructor */

public SysUserRole(SysUserRoleId id) {

this.id = id;

}

/** full constructor */

public SysUserRole(SysUserRoleId id, String userName, String roleName) {

this.id = id;

this.userName = userName;

this.roleName = roleName;

}

// Property accessors

public SysUserRoleId getId() {

return this.id;

}

public void setId(SysUserRoleId id) {

this.id = id;

}

public String getUserName() {

return this.userName;

}

public void setUserName(String userName) {

this.userName = userName;

}

public String getRoleName() {

return this.roleName;

}

public void setRoleName(String roleName) {

this.roleName = roleName;

}

}

6、参考文档

http://www.devarticles.com/c/a/Java/Hibernate-Criteria-Queries-in-Depth/

http://www.devx.com/Java/Article/28754/1954

 

Technorati 标签: ,,,

Why most large-scale web sites not written in Java

Gigaspaces公司的大牛Nati Shalom及其同事Geva Perry 的关于在web2.0时代,java在高性能、大容量、高负荷的互联网应用领域所扮演的角色进行了较为理智和深入的思考,在TheServerSideArtima上也引起了激烈的讨论。由于Nati ShalomGeva Perry  ,都算得上Java的大牛,其所在公司Gigaspaces开发的产品GigaSpaces eXtreme Application Platform (XAP)也是基于Java的高性能平台,因此其观点算得上对Java在互联网时代的定位的反思,相比较而言国内的“我该学习Java还是学习.Net之类的讨论”层次明显得不一样。

讨论的一些相关内容:

Why most large-scale web sites not written in Java

Why most large-scale Web sites are not written in Java [Personal View]

Large-Scale Web Sites and Java

y1po13h7kl8WSSz36U09lQcrdOfz0gLB0jbAlnLlGMewxrqDFf58IZEUmqLCtbQ5qQzwL73puADD3c

一些Web2.0应用杰出代表的系统架构

我比较赞同Geva Perry Large-Scale Web Sites and Java的观点:

my intuition is that the trend for Web apps that are coming from start-ups or large pure web players (as opposed to web apps from airlines, banks, etc.) is definitely towards the LAMP stack. However, Java will still remain strong for a while and  especially for Web apps that have to deal with more complex processes in the back-end.

对互联网企业的Web应用而言,强调的是开发、部署、维护的敏捷性,因此我觉得Web层的应用还是采用像RoR、Django、LAMP(CakePHP)的应用架构相对方便,另外在性能调优(例如Cache、页面静态化)方便,像LAMP这样的架构还是较为成熟;在诸如像电子支付这样关注事务完整性、在MVC业务层有复杂的业务逻辑处理(例如交易、结算)的互联网应用领域采用Java架构还是较为恰当的;当然像银行业务、电信系统、电子政务系统这样传统的企业应用领域(Workflow、ESB、Rule Engine、SoA、消息服务等)Java还是会占据较大的优势。

归根结底,采用什么样的语言及架构还是需要根据业务模式的需要来决定。

一些相关的文档,值得一读

http://fishtrain.com/2007/09/26/interview-with-gigaspaces

http://royal.pingdom.com/?p=95

http://royal.pingdom.com/?p=173

http://natishalom.typepad.com/nati_shaloms_blog/2007/10/why-most-scalab.html

http://natishalom.typepad.com/nati_shaloms_blog/2007/10/why-most-large-.html

http://gevaperry.typepad.com/main/2007/10/large-scale-web.html