2006年10月26日星期四

JSP,Velocity,Jelly and Ruby

项目中要动态产生文档,开始用JSP做了个简单原型,后来觉得代码比较混乱,换上了Velocity:一种模板语言。 虽然大同小异,但有些地方还是很方便。比如原来jsp中:
<%=person.getPostion()%>
现在只要$person.position就可以了,少了很多讨厌的百分号。还可以定义function,以及其他一些你能想到的特性。很有点脚本语言的味 道,有如prototype.js的$(),更符合编程思路,大家都喜欢,觉得这样才自然(prototype这里大概从ruby中汲取了灵感)。
还有另外一个类是的库jellyXML-based scripting and processing engine。使用了一种XML和${}的混和语法。比如:

<j:forEach items="${IMPORTS}" var="i" trim="false">
import ${i};</j:forEach>

而velocity则较为一致:

#macro( getBox $thisboxvalue $boxvalue )
#if ( $thisboxvalue == $boxvalue ) a #else b #end
#end

单从语法上讲,jelly类似jsp + jstl。While Jelly has found some success in projects such as Maven, the general consensus is that XML is a poor choice for a programming language even the creator of Jelly apologized at http://radio.weblogs.com/0112098/2004/03/26.html#a472.
其实ANT用XML作语言也有同样的问题。TheServerSide上有篇文章Allen Holub: Just Say No to XML 说明了这一点。 那为什么jsp不做成这样子呢?连rails也是这种讨厌的风格?(还好ruby的语法很方便)个人认为一则效率的考虑:velocity大概有个预先装入、解析、预编译的过程(待考证),还会将结果放在cache中。二则(更主要)多了一种语法,造成复杂和重复。
velocity还有些设计上的亮点:使用中遇到一种情况,从bean中取出属性后要作个代码页转换然后在页面上显示。由于原来bean的getXXX方 法都已经产生好,手工改费力不讨好。这时参考velocity文档时发现有一个get()的方法:当对于属性A,bean不存在getA()方法时,调用 bean的该方法。nice! 让人想到了ruby也有类似设计:Object有个method_missing的方法.也是很好的idea,方法简单但是效果很好。(参考我的blog:Ruby的method的动态特性
从脚本语言看,Groovy之类从语法上更为强大,也更复杂。如果Java有成熟的、广泛接受的脚本语言技术,velocity大概也无立足之地了。
另: Roller Weblogger的模板也是用了Velocity技术。

2006年10月20日星期五

小试Rails RJS

    RJS是Rails1.1的一个ajax的功能点。这里是一个简单明了的turturail。
    这个framework引入了一个rjs类型的文件,当control的function执行后,同名的rjs文件会被执行来动态产生出(或者说是被翻译为)javascript文件。
    rjs可以为如下的形式:
a = "fkp"
page.insert_html :bottom, 'list',
                 content_tag("li", "Fox2")
page.visual_effect :highlight, 'list', :duration => 3
page.replace_html 'header',
                  a + "b"
利用工具(for example:charles)可以得到其产生的response为:
try {
new Insertion.Bottom("list", "<li>Fox2</li>");
new Effect.Highlight("list",{duration:3});
Element.update("header2", "fkpb");
} catch (e) ......
    让人惊奇!我原来以为返回的是xml形式的(ajax必须返回xml吗?)。
    这样的做法产生一个问题就是js的代码量可能过大,当然这里是因为直接调用而显得很简短(application.js就可以放自定义的js function的地方)。这些js基本上是没有变化的,那么能不能直接传参数呢?这样产生出来的js可以cache(google web tools好像是这样做的)。而rjs的做法感觉是对于page.methodxx的返回值每次来个字符串的相加?
    rjs不仅提供了ruby语法的api来简化js的开发,更重要的是将ajax从view曾剥离出来,而且可以访问server端同名function的导出变量(@开头的?这湮没了xml在B/S间的传递)。
    跟其它的ajax框架不一样(大多可以用js直接访问后台的java function),这种方式去掉了访问的接口的定义(method的返回值没有定义),灵活性更强,因为view可能改变频繁。
    用这种模式后,契约存在两个地方:1)rjs和html。rjs用id来对html element控制,一般的ajax模式是把js和html混合写,由于id粒度过低,分开加大了耦合度;2)ruby和rjs之间的context。显然,google web tools解决了这些问题,但是需要讨厌的compile的过程。
    如果用java来实现,rjs模板可以用java写,也可以为脚本语言(jdk6有更好的支持),而主要不同可能是java代码和模板之间的contex的共享:java可能没有ruby实现的优雅。
    模板产生出来的javascriptbroswer端用如下的形式引用:
<script src="/javascripts/prototype.js?1159875410" type="text/javascript"></script>
<script src="/javascripts/effects.js?1159875410" type="text/javascript"></script>
还不知这些number是做何用处。

2006年10月19日星期四

XUL中的UI重用

XUL是Mozilla的界面描述语言,用来解决跨平台的问题。
XUL的Overlays特性令人感兴趣,其描述为:
Overlays are XUL files used to describe extra content for the UI. They are a general mechanism for adding UI for additional components, overriding small pieces of a XUL file without having to resupply the whole UI, and reusing particular pieces of the UI.
有点类似OO中的继承和方法重写。
一般来说,页面级的重用是比较困难的。JSF(组件级的重用)或者jsp tag只是重用了页面的一部分,开发起来也是费周折。

2006年9月21日星期四

XP中的人生哲学

我过去对极限编程一直不是很热忱,抱有很多的偏见。首先就这个名称从字面上而言,极限?好像是噱头,或是旁门左道的东西。
其实里面很多都是朴素的道理。
比如:增量开发。“对于一个婴儿来说,他的力量就是成长的力量。”,“真正的光明决不是永没有黑暗的时间,只是永不被黑暗所掩蔽罢了。真正的英雄决不是永没有卑下的情操,只是永不被卑下的情操所屈服罢了。所以在你要战胜外来的敌人之前,先得战胜你内在的敌人;你不必害怕沉沦堕落,只消你能不断的自拔与更新。”
比如:测试驱动。告诉我们的道理是做事前要先确定好目标,有的放矢。或者“没有反省的生活是没有意义的。”

2006年9月9日星期六

Hibernate乐观离线锁(Appfuse Practise)


一个在Hibernate中的实践操作描述的很好的文档在这里。原理上的描述在Addison Wesley : Patterns of Enterprise Application Architecture Chapter 16里面有详尽的描写,这本Martin Fowler撰写的书称乐观锁定为Optimistic Offline Lock,Hibernate的实现基本上跟这本书里面的一样。里面有副时序图,画的相当的明白。(copy from the book. copyleft.)

另外一本书Hibernate in Action将乐观锁划分为Application Transacation,而悲观锁则划分为Database Transaction(Hibernate总是使用数据库的锁定机制,从不在内存中锁定对象!见:Hibernate的reference document)。
Appfuse实践
Appfuse可以generate一个model的CRUD所有jsf页面,这可以作为实践上的起步。然后,version字段应该在页面上不能修改,而是作为一个hidden的字段,这样在显示record详细信息界面上就需要有两个hidden字段:
<h:inputHidden value="#{personForm.person.id}" id="id"/>
<h:inputHidden value="#{personForm.person.version}" id="version"/>
Patterns of Enterprise Application Architecture里面的做法是将version作为session变量,都是可以的。只要这个值是读取对象时取出来的就可以。
这样做后,如果两个不同的用户同时修改一条记录时,hibernate就会报错Row was updated or deleted by another transaction。

2006年8月30日星期三

appfuse(1.9.3)中的几个小问题

1.如果创建了一个新的应用,ApplicationResources_zh_CN.properties在创建过程中被转换为乱码。简单的做法就是用appfuse原来的文件直接覆盖。
2.displaytag_zh_CN.properties没被转换。修改build.xml的copy-resources task:
<!-- Copy any resource or configuration files -->
<target name="copy-resources" depends="prepare"
description="Copy .properties and .xml files from source directory">
<copy todir="${build.dir}/web/classes" includeEmptyDirs="no">
<fileset dir="web/WEB-INF/classes">
<exclude name="ApplicationResources_zh*.properties"/>
<exclude name="displaytag_zh_CN.properties"/>
<include name="*.properties"/>
<include name="*.xml"/>
<include name="*.vm"/>
</fileset>
<filterset refid="variables.to.replace"/>
</copy>
<native2ascii src="web/WEB-INF/classes" dest="${build.dir}/web/classes"
includes="*_zh*.properties" encoding="UTF-8"/>
<generate-database-properties/>
<copy todir="${build.dir}/web/classes" file="database.properties"/>
</target>
3.appgen的ant install的做法
ant install的所做的工作包括了ant[default]的工作,好像文档写的不是很准确。而且这个appgen一股脑的将ibtias and hibernate的dao都产生出来,没有一个设置目标DAO的地方。
忘了在哪里看到的说法:j2ee开发中deploy是一个非常关键的过程,确实真知灼见。
发布过程要有几个特征:
1.方便(自动化的发布);
2.灵活(定制);
3.有升级的功能(持续的升级);
4.速度要快。这样才能减少测试-开发-测试需要的round时间。

4.中文编码的问题
刚开始遇到中文问题的时候,觉得小case:统一将编码改为gb2312,原来就是经常这么干的。后来网上查了会儿,原来大家现在都用UTF-8了,就像google一样,cool!
appfuse本身就是utf-8的配置,问题在于数据库的编码,需要在my.ini中加入:
[mysqld]
default-character-set=utf8
或者mysqld命令行带上参数。
但是ant setup-db后,依然乱码。后来发现新建表的character set为latin1,column也为latin1,god! 都改为utf8后问题解决。而如何让ant setup-db自动创建utf-8的table呢?
所以appfuse的中文问题就是所有地方都要用utf-8编码!

2006年8月16日星期三

continuation in Spring Web Flow

   平心而论,SWF是现在java中对continuation支持最好的和最现实的,虽然这种支持是模拟的。其他的一些框架都有点技术上的理想而造成的不成熟。
    SWF默认使用的控制是SimpleFlowExecutionRepository,这是不支持continuation的,比如浏览器回退、新开浏览器等。如果要支持,要使用ContinuationFlowExecutionRepository。SWF这样的设置是很灵活的,因为一般的应用可以使用SimpleFlowExecutionRepository来避免continuation引起的不必要的开销。
    以swf-sample中的guessnumber为例(guess number是一个经典的例子),修改/web-inf/dispatcher-servlet.xml相关内容如下:
     <bean name="/play.htm" class="org.springframework.webflow.executor.mvc.FlowController">
            <property name="flowExecutor" ref="flowExecutor"/>
     </bean>
    <bean id="flowExecutor" class="org.springframework.webflow.executor.FlowExecutorImpl">
        <constructor-arg ref="repositoryFactory"/>
    </bean>
    <bean id="repositoryFactory" class="org.springframework.webflow.execution.repository.continuation.ContinuationFlowExecutionRepositoryFactory">
        <constructor-arg ref="flowRegistry"/>
    </bean>   
   
如此后,应用就支持浏览器后退键了。这里的后退键是跟后台逻辑关联在一起的:按下后退键,相当于刚才的猜测不算,再猜一次,而计数也“后退”(或者说“恢复”)到前面到状态。如果是购物篮应用,后退就表示“退回”刚才购买的商品。(这种方式也可能造成用户感觉上的混乱)
    这种功能用一般的会话变量(比如将计数存在session中、将购物篮存在session中)是很难处理的。这里的会话变量比如计数、购物篮是一个会话活动中多个步骤的结果的记录,用一个变量是无法记录这些步骤的先后顺序的。而一般的会话变量只是一个步骤中标志或者状态的记录,一个变量就足够使用。
    SWF的解决方法就是在每个步骤中添加一个标志,当这个步骤提交时,后台根据提交上来的标志恢复(在回退的情况下)当时这个步骤的上下文,这样就可以在work flow中继续运行下去。
    例如当步骤1->2->3->4,当浏览器在步骤4时回退时,浏览器会把步骤2提交的数据再次提交。
    用户每次提交,后台都要形成一个上下文的continuation(每次提交的数据都可能不一样),这可能会占用大量的资源。