这段时间在javaeye的关于fastm的讨论,令我受益良多。特此感谢大家。非常感谢。
很多人鼓励我的精神,很多人耐心指正我的错误,也有很多人提出尖锐深刻的问题。最感谢的是,甚至有些朋友深入研究了fastm,并提出了改进方案。
并且很多朋友也讨论了fastm之外的话题,令我的知识面开阔。

我这里把这几天针对fastm的一些尖锐深刻的问题列出来。并感谢大家对fastm的深入思考、详细讨论和尖锐抨击。

(1)格式化信息分布在Template DOM和ValueSet DOM两个地方
charon 写道
还有关键的一点,即便按你所说得这么做是正常的,那么会有一个非常严重的不一致现象,或者一种臭味:有一些格式化的信息是在你的模板中的,另一些格式化信息则是你的"model"自带的。好好想一想开发哲学吧。


这个问题最深刻尖锐,击中fastm的痛处。
如果需要动态改变风格,确实需要在ValueSet DOM中设置风格。
这个问题是fastm无法解决的,因为fastm模板中不包含逻辑。

下面是我的辩护:
这是把逻辑从模板中驱逐出去必须付出的代价。
比起逻辑代码分布在模板和Java Code两个地方,我觉得,格式化信息分布在Template DOM和ValueSet DOM两个地方,这个代价还是值得的。
这个问题就是见仁见智,各有千秋了。

并且,在代码中改变界面风格,这种方式在有界面资源的桌面程序中很常见。
资源文件中存放着基本的界面控件信息。程序可以动态地改变资源文件的显示风格。
所以,我觉得,“在代码中改变界面风格”,也不是完全不可接受的。

(2)fastm的所见即所得并不是绝对的。

我在论坛里翻了半天,也没有找到是哪个朋友提出的这个尖锐深刻的问题。Sorry。
当fastm需要选择显示不同的内容的时候,模板里面同样要包括两块内容。
这样你在浏览器中看的时候,可以看到两块内容。
而程序运行的时候,却只出现一块内容。

所以,fastm显示的所见即所得也不是绝对的。

(3)违反了MVC
这个问题也确实存在。
fastm不是不能支持MVC,只是支持了MVC之后,操作不够直接。
fastm只和Servlet Response打交道就够了。
Template DOM + ValueSet DOM = result --> Response.
(4)DOM的复杂性。

其实,这个问题有些误会。fastm的DOM不是HTML DOM。没有那么复杂。

页面逻辑的嵌套层次有多少,那么fastm DOM的层次深度就有多少。
比如,这样的JSP代码
[code:1]
<c:choose>
<c:when test="${copyMade}">
here we add a line.
<h4><fmt:message key="copy.ok"/></h4>
</c:when>
<c:otherwise>
here we add a line too.
<h4><fmt:message key="copy.nok"/></h4>
</c:otherwise>
</c:choose>
[/code:1]

因为逻辑嵌套只有两层,那么对应的fastm DOM也只有两层。
fastm DOM其实没有增加复杂度。而且,fastm DOM还简化了复杂度。
这是fastm的一个优点。
ValueSet DOM是fastm实现显示逻辑AOP和显示逻辑重用的关键结构。
也是Pipeline式处理过程的数据交换中枢。

不在这里讨论了。:-) 以后证明。

(5)fastm的代码长

其实,这个问题也有些误会。
只是“看起来”,fastm的代码比较长。
我本来是把fastm代码短作为一个优势来宣传的。

其它模板技术的代码分布在模板和Java Code两端。
即使不计算TagLib的代码。fastm代码其实还要比其它的模板技术短。

而且,fastm代码只存在于Java Code里,而且很多都是公用方法 -- 页面逻辑AOP。

不在这里讨论了。:-) 以后证明。
评论
buaawhl 2004-08-18
lunawing 写道
那怎么给下拉选择框、单选框、多选框赋初值?


1. select

html
[code:1]
<select name="state">
<!-- BEGIN DYNAMIC: state-->
<option value="{value}" {selected}>{display}</option>
<!-- END DYNAMIC: state-->
</select>
[/code:1]

java

[code:1]
List options = makeOptions(all options, selected option value);
valueSet.setDynamicValueSets("state", options);

makeOptions method is a common function. It looks like.

List makeOptions(String[][] options, String selectedOption){
List valueSets = new ArrayList(options.length);
for(int i =0; i < options.length; i++){
String[] pair = options[i];
String value = pair[0];
String display = pair[1];

IValueSet valueSet = new ValueSet();
valueSet.setVariable("{value}", value);
valueSet.setVariable("{display}", display);
valueSet.setVariable("{selected}",
value.equals(selectedOption) ? "selected" : "" );

valueSets.add(valueSet);
}
return valueSets;
}
[/code:1]

2. checkbox

html

[code:1]
<input name="a" value="1" {a_checked}/>
[/code:1]

java

[code:1]
valueSet.setVariable("{a_checked}", "checked");
[/code:1]
buaawhl 2004-08-16
lunawing 写道
借用一下作者,用这个fastm怎么给表单赋初值的?


举个例子,比如,
HTML里面
<form action="xxx.do">
<input type=text name="name" value="{name}">
<input type=text name="id" value="{id}">
....
</form>

Java Code里面
valueSet.setVariable("{name}", xxx);
valueSet.setVariable("{id}", xxx);
charon 2004-07-30
hehe,甲方就是客户方。所以我们的立场不同。
buaawhl 2004-07-30
charon 写道
关于缓存,还有一个折衷。缓存部分的动态性的决定因子越简单,缓存越有意义。比如前面如果能够根据查询条件得到条件列表和结果列表,那么这个缓存方式比查询条件+权限比较来确定那两个缓存部分要适用的多。或者说这是一个命中率的问题。越是普适的东西约有可能被命中,而越是复杂的逻辑部分,缓存起来命中率太低,会导致这一策略的实现需要占用的资源越多(比如每个在线查询的人都有自己的那块缓存),同时收益也越少(只有发起缓存的那个人才能够用到这个缓存)。而且缓存本身也需要开销,从而缓存的意义有多大,就很难判断了。
不过我的角色和你现在的角色有很大的不同,所以考虑问题的方式和周转余地也大不相同。在项目中,我的角色是甲方的技术代表,对业务需求、技术架构、工具选择或多或少都有一些协调,所以不会只在技术的圈圈里面考虑事情。


对。每个用户都有自己的缓存,而且只能用自己的缓存。我说的那个页面缓存的scope就是Session,只能为那个用户在那个Screen所用。一旦出了那个Screen或者重新查询,以前的缓存立即无效。
这是为了满足单个用户对速度的要求。同时在线的用户数目并不大,但数据量大,关系复杂,第一次查询的时间都很长。
所有的页面处理程序,不用查询条件作为key,都通用同样的"search" 和"result"两个key,实现方案非常简单,几乎没有额外的代价。

按照查询条件来缓存,是更通用,命中率高,用户间共享Cache的一个方法,但实现难度比较大。那就是你说的,以SQL(或查询条件)为key的数据缓存。这种方案应该在DAO数据访问层实现,也许需要考虑DAO函数的signature,Cache的管理也相应复杂,还要考虑数据刷新问题。考虑到这个代价,一般不轻易使用这种方案。

另外,你说的“甲方”,是指购买产品的客户方?

我处在的角色是开发人员,中间有产品设计人员和客户直接打交道。
我考虑的主要是,如何在规定时间内多快好省地完成规定的项目,最好这一期项目中的技术积累能够用在其它的项目中。
charon 2004-07-30
关于缓存,还有一个折衷。缓存部分的动态性的决定因子越简单,缓存越有意义。比如前面如果能够根据查询条件得到条件列表和结果列表,那么这个缓存方式比查询条件+权限比较来确定那两个缓存部分要适用的多。或者说这是一个命中率的问题。越是普适的东西约有可能被命中,而越是复杂的逻辑部分,缓存起来命中率太低,会导致这一策略的实现需要占用的资源越多(比如每个在线查询的人都有自己的那块缓存),同时收益也越少(只有发起缓存的那个人才能够用到这个缓存)。而且缓存本身也需要开销,从而缓存的意义有多大,就很难判断了。
不过我的角色和你现在的角色有很大的不同,所以考虑问题的方式和周转余地也大不相同。在项目中,我的角色是甲方的技术代表,对业务需求、技术架构、工具选择或多或少都有一些协调,所以不会只在技术的圈圈里面考虑事情。
buaawhl 2004-07-30
charon 写道

较好的办法是这个判断还是放到后端代码中去做,把对应的flag传出来。


对。我也这么想。这样就能够解决循环中重复判断的问题了。
在后端先判断
access_column1 = canAccess(user, "table1 screen");
access_column2 = canAccess(user, "table2 screen");

在前端用JSP, Velocity。
[code:1]
for each....
if(access_column1) show column1 link ...
if(access_column2) show column2 link ...
end for
[/code:1]

一般来说,产品设计人员根据用户需求,一旦定下了UI,用户界面方面基本就没有商量的余地了。有的时候,产品设计人员提出很多动态交互的要求,需要大量的动态Java Script支持,但是也必须做到。
开发人员根据这些UI和业务要求,可以自主的定义DB Schema,这样也能够从一定程度缓解实现的复杂度。这个确实要非常详细准确地分析理解业务需求。

我做的项目中的页面,千篇一律,全是同样的需求模式。除了上面说的需求模式,还有头表数据列表和子表数据列表都需要的统一的分页需求。用户的目的是尽量在同一个页面中,显示尽量多的关联信息,免得他到处去找。

关于缓存的问题,一种是你说的方法,把相关的数据缓存起来,并可以缓存相关的数据(比如,access_column1, access_column2,还有其它的从Dictionary中取出来的显示数据)。这时缓存的相当于View Object。这种缓存方法对任何模板技术都适用。页面处理逻辑几乎不需要额外的计算,只要把View Object取出来显示就可以了。
一种是直接缓存部分结果页面,就是我说的方法。这种方式只比前一种方式节省了一点处理时间,从空间上来说,一段连续的HTML String也不比一个View Object列表大多少。从缓存的调用方法来说,这种方法也要简单一些,只需要考虑一个String类型的数据就够了。这种缓存方案,只有一些高级的Cache TagLib可以做到,或者是fastm, 或者XMLC这种DOM模板技术。
缓存View Object, 或缓存HTML,这两种方案的实际的空间和时间差别不会很大。

在这类的UI要求中,显示逻辑和后台逻辑的界限不是很清楚,比如,权限判断的部分,可以放在后端的Java Code,也可以放在前端的模板脚本中。如果放在后端的Java Code中,还可以想办法把一些通用操作抽取出来作为公用方法,增加结构性,和可重用性。这也是我写fastm的初衷,fastm就是被这种需求逼迫出来的。fastm走向的一个极端就是,把逻辑完全从模板中驱逐了出去。

对于显示逻辑简单的页面,fastm确实没有用武之地。硬把简单的for each,if else从模板脚本中移出来,放到Java Code里面,反而更不直观,更难维护。

我一直在考虑fastm适用的范围:
(1)显示逻辑需要复杂到一定程度,HTML长到一定程度,一段for each, 或 if else 的跨度很大,超过一个屏幕的时候,程序员才会对HTML里面的脚本感到抱怨。

(2)显示逻辑的需求模式重复性很大的时候(比如前面说的格式化,权限判断,字典查询,分页等),程序员才会抱怨HTML的脚本的结构不好,和不能重用等问题。

(3)对动态生成页面的速度要求比较高的情况。由于fastm使用java code处理显示逻辑,速度比脚本快。但这个优势的用处却不是很明显。如你所说,大型网站通常都是用静态Cache之类的技术缓解大访问量,对动态要求不好。而对动态要求高的webapp网站,页面通常又不会很复杂,用户量也不会很大,而且速度瓶颈通常都在于大数据量查询,和后台业务计算上。

我一直在试图找到一个Portal/CMS类 和 Webapp(如ERP/CRM )类 这两个领域之间的一个结合点:页面处理逻辑足够复杂,动态速度要求又很高的一个领域。那才是fastm的用武之地。
charon 2004-07-30
buaawhl 写道

前面描述的“页面缓存”需求里面的例子,如果用JSP做,“查询结果列表界面”部分看起来是这个样子。(不用JSTL,一个是因为Reflection速度太慢,一个是因为使用专用框架,request被包装起来,无法直接setAttribute)。只用自定义的对应专门数据类型的TagLib,或只用Java Code。

如果存在这个问题的话,其实用什么都很难搞定。
不过对于重复的if/else判断,还是可以用数组规避的。比如
a[0] = "<a>http://....."
a[1] = ''"
b[0] = "</a>"
b[1] = ""
判断一次设置flag的0,1值,在循环中用
a[flag],b[flag]来搞定。流程可以变得很清晰,有点像Null Object模式(当然也可以直接用Null Object模式)
较好的办法是这个判断还是放到后端代码中去做,把对应的flag传出来。当然,最好的办法是根据业务需求重新组织其表现方式和内部实现。对于业务需求,多了解一个为什么,然后去更好的去规范和优化。
buaawhl 2004-07-28
charon 写道
引用

这个外键映射,是和那个权限比较连在一起的。
比如,我的表格里面有Column1, column2, column3等字段。
Column1是另一个表,比如table1的关键字。
Column2是另一个表,比如table2的关键字。
Column3是另一个表,比如table3的关键字。

显示要求如下:
如果当前用户有访问table1的权限,那么在column1的显示结果上加一个link,可以连接到table1的界面。
显示层需要根据 当前用户的权限,和table1的访问权限,来判断是否加上这个link。
当然,table1本身的界面也有权限控制。但为了更好提高用户经验,如果不让用户访问,就不提供link。否则用户去click一个自己没有权限访问的link,再给出一个警告信息,不太友好。

这里有几个问题。
1 这里逻辑不算复杂,但是判断比较多,如果循环的话会有一些开销。但是,对于fastm的情况,这些判断是不存在的,所以缓存的必要性值得考虑。
因为部分页面缓存运作时,实际上你的引擎解释或者匹配部分已经展开了,可以节省的开销远不如稍前部分截获的全页缓存显著。
2 这个东西看来现在还没有实现。把权限判断放到页面里面会导致缓存部分的适用性。就部分缓存(假设一定要用)而言,要缓存的东西应当满足两个条件之一:
a 缓存的是准静态的东西。
b 缓存的东西不仅仅只针对一个用户。或者说能够在存续期内被频繁命中。
对于大型应用而言第二个情况比第一个更加有意义。如果把权限作进去,同一个查询条件,得到的查询条件和查询结果列表在的普遍适用性大为缩小,缓存的收益和付出的代价相比就会削弱很多。
你的这个例子正好处于a-b之间,既不是准静态的,也不是普适的,缓存究竟能带来多少收益,个人认为必须在重负载情况下测算过。
对于大型网站而言,比如热门搜索,对于那些频繁出现的关键词,实际上更好地解决办法是全页缓存,或者以一定的时间间隔来生成相应的静态页面。
3 实际上是个业务问题。也是我最不理解的地方。但这个东西和具体的业务和实现相关,扯起来扯不清。
但是我有一个结论,就是说除了那个详细内容(你的那个页面第三部分)需要区分当前用户是否有写权限之外,其它的那些根据外键的连接中的权限控制没有必要。非常重要的一点是,在这里通过外键点击进行的访问必须是不可写的,或者说你的这个查询能够解决的问题的极限是通过查询得到相应的条目并修改这些条目的内容,而不是去修改支撑这些条目的背景表。


“解决办法是全页缓存,或者以一定的时间间隔来生成相应的静态页面。”
这是大型内容管理网站或Portal门户的一般做法。

我做的这个webapp是一个应用程序(比如财务系统,进销存系统), 数据量庞大,表关系复杂,动态要求性很高,查询的开销大,页面生成的开销也很大。而用户都是一些财务人员,管理人员,同时在线的人数一般在10 - 20人左右。
数据从各种终端前台数据库批量的导入,用户一般不输入和修改数据,只是进行查询(通常都是复杂计算的结果)。

前面描述的“页面缓存”需求里面的例子,如果用JSP做,“查询结果列表界面”部分看起来是这个样子。(不用JSTL,一个是因为Reflection速度太慢,一个是因为使用专用框架,request被包装起来,无法直接setAttribute)。只用自定义的对应专门数据类型的TagLib,或只用Java Code。

[code:1]
<% for ..... { %>

<mytag:access screenId="XXX" >
<a href="the url to the screen XXX">
</mytag: access>

<%= dictionary.find(obj.column1) %> -- obj.column1

<mytag:access screenId="XXX" >
</a>
</mytag: access>

....

<%} // end for %>
[/code:1]

access tag用来判断当前用户(in session)是否有访问“XXX”的权限。如果有,那么显示那个 link <a href = > </a>。如果没有访问这个权限,那么不显示那个link。用户看不到那里的 link.

至于显示字段的值,通常也没有那么直接。
数据库里存放的都是整数,由一个自定义的数据字典翻译为显示字符串。
比如(不是真实业务数据),
1 ---- One.
2 ---- Two


这种JSP方案的缺点:
(1)那个<mytag: access> 权限判断在循环里面,判断多次。其实只要判断一次就可以。但如何把这个部分抽取出来,是个难题。
(2)很难进行局部Cache。我调查过的那个Apache Cache TagLib, 也满足不了要求。那个Apache Cache TagLib不够强大和灵活,不能根据不同的请求进行get, set, 或invalidate Cache。

如果用Velocity, freemarker,如何解决这两个缺点?
charon 2004-07-28
引用

这个外键映射,是和那个权限比较连在一起的。
比如,我的表格里面有Column1, column2, column3等字段。
Column1是另一个表,比如table1的关键字。
Column2是另一个表,比如table2的关键字。
Column3是另一个表,比如table3的关键字。

显示要求如下:
如果当前用户有访问table1的权限,那么在column1的显示结果上加一个link,可以连接到table1的界面。
显示层需要根据 当前用户的权限,和table1的访问权限,来判断是否加上这个link。
当然,table1本身的界面也有权限控制。但为了更好提高用户经验,如果不让用户访问,就不提供link。否则用户去click一个自己没有权限访问的link,再给出一个警告信息,不太友好。

这里有几个问题。
1 这里逻辑不算复杂,但是判断比较多,如果循环的话会有一些开销。但是,对于fastm的情况,这些判断是不存在的,所以缓存的必要性值得考虑。
因为部分页面缓存运作时,实际上你的引擎解释或者匹配部分已经展开了,可以节省的开销远不如稍前部分截获的全页缓存显著。
2 这个东西看来现在还没有实现。把权限判断放到页面里面会导致缓存部分的适用性。就部分缓存(假设一定要用)而言,要缓存的东西应当满足两个条件之一:
a 缓存的是准静态的东西。
b 缓存的东西不仅仅只针对一个用户。或者说能够在存续期内被频繁命中。
对于大型应用而言第二个情况比第一个更加有意义。如果把权限作进去,同一个查询条件,得到的查询条件和查询结果列表在的普遍适用性大为缩小,缓存的收益和付出的代价相比就会削弱很多。
你的这个例子正好处于a-b之间,既不是准静态的,也不是普适的,缓存究竟能带来多少收益,个人认为必须在重负载情况下测算过。
对于大型网站而言,比如热门搜索,对于那些频繁出现的关键词,实际上更好地解决办法是全页缓存,或者以一定的时间间隔来生成相应的静态页面。
3 实际上是个业务问题。也是我最不理解的地方。但这个东西和具体的业务和实现相关,扯起来扯不清。
但是我有一个结论,就是说除了那个详细内容(你的那个页面第三部分)需要区分当前用户是否有写权限之外,其它的那些根据外键的连接中的权限控制没有必要。非常重要的一点是,在这里通过外键点击进行的访问必须是不可写的,或者说你的这个查询能够解决的问题的极限是通过查询得到相应的条目并修改这些条目的内容,而不是去修改支撑这些条目的背景表。
buaawhl 2004-07-27
charon 写道
buaawhl 写道
关于页面生成逻辑的复杂性。
比如,很多字段都有一个link, 连接到自己的模块(外键的映射)。
这个link应不应该显示,还需要有权限控制,检查这个用户是否有访问这个link的权限。如果有,显示link, 如果没有,则不显示。
如果有些字段为空,那么也会有一个link,连接到一个“新生成”页面。
有些纪录被“软删除”,需要用特殊的标记或风格显示。
等等,之类的要求。


这里需要的不是一个页面缓存的方案。如果因为有了页面局部缓存而把某些复杂逻辑放到了模板中去实现,本身就是一个错误的设计思路。
或者,从你的fastm的思路去思考问题,这么多的逻辑你根本就进不了模板。你需要的是一个干净的局部解决方案,是一个模板和后端代码怎么分配职责的问题,而不是靠页面缓存来解决问题。
不到万不得已,不要把权限放到模板层去做控制,失去的比得到的多。至于那个外键映射,不理解这个东西。难道要通过这个连接去改动可选项?


这个外键映射,是和那个权限比较连在一起的。
比如,我的表格里面有Column1, column2, column3等字段。
Column1是另一个表,比如table1的关键字。
Column2是另一个表,比如table2的关键字。
Column3是另一个表,比如table3的关键字。

显示要求如下:
如果当前用户有访问table1的权限,那么在column1的显示结果上加一个link,可以连接到table1的界面。
显示层需要根据 当前用户的权限,和table1的访问权限,来判断是否加上这个link。
当然,table1本身的界面也有权限控制。但为了更好提高用户经验,如果不让用户访问,就不提供link。否则用户去click一个自己没有权限访问的link,再给出一个警告信息,不太友好。
buaawhl 2004-07-27
庄表伟 写道
说实话,我认为在Web显示的方面动用AOP,是有点杀鸡用牛刀了。


确实是有一点“杀鸡用牛刀”。:-)
我当时只是碰到了一个要求,需要把页面中的所有数字各种类型的格式,都改一遍。120个页面,改了几次,都改不全。还有其它的各种痛苦的经历,才萌生了写fastm的想法。
charon 2004-07-27
buaawhl 写道
关于页面生成逻辑的复杂性。
比如,很多字段都有一个link, 连接到自己的模块(外键的映射)。
这个link应不应该显示,还需要有权限控制,检查这个用户是否有访问这个link的权限。如果有,显示link, 如果没有,则不显示。
如果有些字段为空,那么也会有一个link,连接到一个“新生成”页面。
有些纪录被“软删除”,需要用特殊的标记或风格显示。
等等,之类的要求。


这里需要的不是一个页面缓存的方案。如果因为有了页面局部缓存而把某些复杂逻辑放到了模板中去实现,本身就是一个错误的设计思路。
或者,从你的fastm的思路去思考问题,这么多的逻辑你根本就进不了模板。你需要的是一个干净的局部解决方案,是一个模板和后端代码怎么分配职责的问题,而不是靠页面缓存来解决问题。
不到万不得已,不要把权限放到模板层去做控制,失去的比得到的多。至于那个外键映射,不理解这个东西。难道要通过这个连接去改动可选项?
庄表伟 2004-07-27
说实话,我认为在Web显示的方面动用AOP,是有点杀鸡用牛刀了。
buaawhl 2004-07-27
Readonly 写道

先向几位唐大哥请安了, 偶又回来了, 下面是正文:

偶知道的几种Dynamic Proxy技术如你所说只能支持interface, 面向interface, OOP的第一守则了吧, 这个就不用多说了, 拿cglib来举例:
SimpleInvocationHandler.java
[code:1]
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date;

import net.sf.cglib.proxy.InvocationHandler;

public class SimpleInvocationHandler implements InvocationHandler {
public static final SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd");
private Object o = null;

public SimpleInvocationHandler(Object o) {
this.o = o;
}

public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {
Object r = m.invoke(o, args);
if(m.getName().matches("get.*Date")) {
return (new FDate((Date)r));
}
return r;
}

private class FDate extends Date {
public FDate (Date date) {
super(date.getTime());
}

public String toString() {
return f.format(this);
}
}
}
[/code:1]
Unit test code:
[code:1]
import java.util.Date;

import junit.framework.TestCase;
import net.sf.cglib.proxy.InvocationHandler;
import net.sf.cglib.proxy.Proxy;

public class TestProxy extends TestCase {
public interface IFoo {
public Date getBarDate();
public String getBarString();
}

public class Foo implements IFoo {
private Date barDate;
private String barString;

public Foo(Date date, String string) {
barDate = date;
barString = string;
}

public Date getBarDate() {
return barDate;
}

public String getBarString() {
return barString;
}
}

public void testGetProxyInstance() throws Exception {
Foo foo = new Foo(new Date(0), "huluhulu");
InvocationHandler handler = new SimpleInvocationHandler(foo);
IFoo proxy = (IFoo) Proxy.newProxyInstance(TestProxy.class.getClassLoader(), new Class[] { IFoo.class }, handler);
assertEquals("huluhulu", proxy.getBarString());
assertEquals("1970-01-01", proxy.getBarDate().toString());
}
}
[/code:1]

偶知道AspectWerkz ( http://aspectwerkz.codehaus.org/features.html ) 这种半静态的方式则可支持POJO, 不过从来没有用过, 你自己去看文档玩吧.

AOP只是嗡嗡作响的苍蝇, 一个设计良好的OO架构, 完全可以让这只苍蝇滚蛋, 而格式化用AOP来处理更是吃力不讨好.

眼看着fastm将在四不象的道路上堕落, 可惜, 可惜是别人家的孩子, 各位唐大哥们就少说两句, 让这个孩子耳根清净些吧


多谢你的代码。很漂亮的实现。
我前面的意思是说,每个需要显示的Bean都不同。因为我们需要显示的Bean不一定每个都有interface。 那么为每个Bean都额外声明一个interface,代价还是挺大的。

buaawhl 写道
Readonly 写道

剩下的所谓显示逻辑的AOP, 自定义Layout, Portal, Cache等等都是buzzword.

显示逻辑AOP?? 难度我们不能用ViewObject来做? valueset dom的封装代码不就和组装ViewObject的一样么......


显示逻辑AOP, ViewObject做不到。因为每个ViewObject都是不同的。除非硬性规定统一所有的View Object.

而ValueSet DOM只有一种。所有的数据都通过一个方法setVariable(name, value)设置。name和value都能够被截取,才可能做到AOP。

对于不同的ViewObject,用什么手段都无法实现AOP。
如果用Dynamic Proxy,你需要为所有的View Object都定义相应的interface。这显然是不现实的。
如果用AspectJ, 你需要把所有的View Object的所有符合pattern的方法都定义到pointcut里面。这显然也是不现实的。
buaawhl 2004-07-27
charon 写道

这还差不多。在这之前,你根本就没有以歧义较少的方式解释过你的这个所谓的缓存,我就是想歪曲,也无从歪起。hehe
在这里,本身你的这个解法不是正解,至少从我的角度来看,你排除的frame是一个,iframe是另一个,dlee推崇的xmlhttp也是一个,只不过后面两个都有一定的js代码量。如果你的应用是关键应用,但是采用的是这种页面缓存的方法,相比于三个正解,既没有查询结果同步性的优点,服务器却多了n多负载,我该怎么评价这个解决方案?
还有,我有一点点的疑问的地方,是你的这个东西是不是在fastm基础上作的,如果是,那么何来复杂的页面生成逻辑。如果不是,那你这个例子能够说明fastm实现这种缓存的必要性吗?
按照正经的mvc2解法,复杂的东西都在后端搞定了,velocity也好,freemarker也好,甚至你的这个fastm,前面都是非常简洁的,你的那个页面生成逻辑复杂的东东,不会是来自jsp的蛮荒时代作品吧?那么说吧,如果页面分为三部分,那么查询结果列表的那一部分最多一次显示10行左右,你是怎么个把这部分的生成逻辑搞得那么复杂的?我愿意洗耳恭听。
最后,退一万步讲,即使是你这个例子有意义,你的这个说明还是太模糊,关键的一点是你获取缓存页面片断的key是哪个?


关于页面生成逻辑的复杂性。
比如,很多字段都有一个link, 连接到自己的模块(外键的映射)。
这个link应不应该显示,还需要有权限控制,检查这个用户是否有访问这个link的权限。如果有,显示link, 如果没有,则不显示。
如果有些字段为空,那么也会有一个link,连接到一个“新生成”页面。
有些纪录被“软删除”,需要用特殊的标记或风格显示。
等等,之类的要求。

至于,获取缓存页面的key是哪一个,这个属于我希望听到的解决方案的内容。
先不要问我的解决方案。我先把整个需求说清楚。

排除frame,主要是为了兼容一些老的浏览器。另外也是为了测试方便。HttpUnit Test测frame很困难。
关于缓存的页面部分的存取。
“查询头表”请求把生成的(前两个部分)页面缓存起来。
“显示detail和子表列表”请求把缓存的(前两个部分)页面取出来,直接显示,只换第3个部分。
Readonly 2004-07-26
buaawhl 写道
Readonly 写道
强烈克制自己做唐僧的欲望, 用代码来演示想法:

[code:1]
public class ViewObject {
private Object entity
public ViewObject(Object entity){
this.entity = entity;
}
}

public interface ViewObjectFactory {
public void ViewObject createViewObject(Obejct entity);
}
[/code:1]

拦截createViewObject方法, pointcut使用正则表达式, 使ViewObject的get*Date()被拦截, 变成return format(entity.get*Date()); 而其他的getter都只是简单地代理调用entity.getter

另外这种格式化的代码用AOP来实现, 纯粹只是一个噱头, AOP, AOP, 只不过是炒冷饭而已......


:-) 我喜欢这种直截了当的直观交流方式。多谢你的代码。还有一点需要请教。
你采用的是Dynamic Proxy, 还是AspectJ的静态织入?
“拦截createViewObject方法”,然后在里面用reflection判断get*Date(), 还是用pointcut定义get*Date()?
我的理解是用reflection做到比较直观和容易。
用pointcut定义get*Date()的方法,能具体一点吗?

在我的印象中,Dynamic Proxy只能够截获Interface的方法。
我前面讲过了,不可能为不同的Entity,都声明一个getter, setter interface.
那么get*Date()是怎么被截获的?
hibernate用了cglib继承(extends)VO,动态实现PO。这种方法到是可能把一些代码加入到子类的实现当中去。但同样要为每一个entity建立一个配置问文件。

你给出的代码用的是哪一种AOP技术?
JDK Dynamic Proxy? CGLIB? AspectJ? Spring AOP? JBoss AOP?

sorry, 我的理解能力有限,希望能够多指教。


先向几位唐大哥请安了, 偶又回来了, 下面是正文:

偶知道的几种Dynamic Proxy技术如你所说只能支持interface, 面向interface, OOP的第一守则了吧, 这个就不用多说了, 拿cglib来举例:
SimpleInvocationHandler.java
[code:1]
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date;

import net.sf.cglib.proxy.InvocationHandler;

public class SimpleInvocationHandler implements InvocationHandler {
public static final SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd");
private Object o = null;

public SimpleInvocationHandler(Object o) {
this.o = o;
}

public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {
Object r = m.invoke(o, args);
if(m.getName().matches("get.*Date")) {
return (new FDate((Date)r));
}
return r;
}

private class FDate extends Date {
public FDate (Date date) {
super(date.getTime());
}

public String toString() {
return f.format(this);
}
}
}
[/code:1]
Unit test code:
[code:1]
import java.util.Date;

import junit.framework.TestCase;
import net.sf.cglib.proxy.InvocationHandler;
import net.sf.cglib.proxy.Proxy;

public class TestProxy extends TestCase {
public interface IFoo {
public Date getBarDate();
public String getBarString();
}

public class Foo implements IFoo {
private Date barDate;
private String barString;

public Foo(Date date, String string) {
barDate = date;
barString = string;
}

public Date getBarDate() {
return barDate;
}

public String getBarString() {
return barString;
}
}

public void testGetProxyInstance() throws Exception {
Foo foo = new Foo(new Date(0), "huluhulu");
InvocationHandler handler = new SimpleInvocationHandler(foo);
IFoo proxy = (IFoo) Proxy.newProxyInstance(TestProxy.class.getClassLoader(), new Class[] { IFoo.class }, handler);
assertEquals("huluhulu", proxy.getBarString());
assertEquals("1970-01-01", proxy.getBarDate().toString());
}
}
[/code:1]

偶知道AspectWerkz ( http://aspectwerkz.codehaus.org/features.html ) 这种半静态的方式则可支持POJO, 不过从来没有用过, 你自己去看文档玩吧.

AOP只是嗡嗡作响的苍蝇, 一个设计良好的OO架构, 完全可以让这只苍蝇滚蛋, 而格式化用AOP来处理更是吃力不讨好.

眼看着fastm将在四不象的道路上堕落, 可惜, 可惜是别人家的孩子, 各位唐大哥们就少说两句, 让这个孩子耳根清净些吧
charon 2004-07-26
引用

如果这三个界面部分分别存在于3个frame中,那么不存在页面缓存的问题。当这三个界面存在于同一个页面中的时候,就需要处理局部缓存。
处理过程如下:
(1)用户在“查询条件界面”中输入查询条件,然后submit。
这个时候发出的请求是“初次查询”。注意,这里查询的是头表。
(2)后台程序查出结果之后,把结果放在“查询结果列表界面”中返回。这个时候,用户输入的查询参数,也要体现在“查询条件界面”一起返回。保证用户能够看到自己刚才输入的查询条件。
注意,这里显示的结果是头表的结果集。
注意,这里的程序需要进行第一次页面缓存,(记住,不是数据缓存,我们现在谈的是页面缓存)把“查询条件界面”和“查询结果列表界面”一起缓存起来。
(3)用户在“查询结果列表界面”里面click列表中的某个结果link,这个时候发出的请求是,“显示该结果的详细内容和子表列表”。
后台程序把子表列表返回到“查询结果Detail和子表列表界面”中返回给用户。
注意,在这个第(3)步中,页面中变动的部分只有“查询结果Detail和子表列表界面”这个部分,而“查询条件界面”和“查询结果列表界面”这两个部分是不变化的。这两个“页面”部分需要缓存。注意,是生成的HTML需要缓存,而不是数据需要缓存。
(注意,先不要讲什么“数据缓存”效率高,还是“页面缓存”效率高的问题。在我的需求中,我显示的数据非常复杂,是一个复和查询,页面生成的逻辑也很复杂,开销很大,确实需要缓存页面,而不是数据)

我以上说的,说的够清楚吗?你能够完全理解吗?有不清楚的地方,欢迎发问。不要自作主张地曲解我的原意。


这还差不多。在这之前,你根本就没有以歧义较少的方式解释过你的这个所谓的缓存,我就是想歪曲,也无从歪起。hehe
在这里,本身你的这个解法不是正解,至少从我的角度来看,你排除的frame是一个,iframe是另一个,dlee推崇的xmlhttp也是一个,只不过后面两个都有一定的js代码量。如果你的应用是关键应用,但是采用的是这种页面缓存的方法,相比于三个正解,既没有查询结果同步性的优点,服务器却多了n多负载,我该怎么评价这个解决方案?
还有,我有一点点的疑问的地方,是你的这个东西是不是在fastm基础上作的,如果是,那么何来复杂的页面生成逻辑。如果不是,那你这个例子能够说明fastm实现这种缓存的必要性吗?
按照正经的mvc2解法,复杂的东西都在后端搞定了,velocity也好,freemarker也好,甚至你的这个fastm,前面都是非常简洁的,你的那个页面生成逻辑复杂的东东,不会是来自jsp的蛮荒时代作品吧?那么说吧,如果页面分为三部分,那么查询结果列表的那一部分最多一次显示10行左右,你是怎么个把这部分的生成逻辑搞得那么复杂的?我愿意洗耳恭听。
最后,退一万步讲,即使是你这个例子有意义,你的这个说明还是太模糊,关键的一点是你获取缓存页面片断的key是哪个?
charon 2004-07-26
引用

我前面给出的例子是一个非常常见的例子。只要是稍微复杂一点的项目,页面局部缓存也是常见的需求。你却没有听说过,也没有这个概念。这个较量对你是不公平的。但我觉得对你来说,真正地思考一些实际问题,是有益的。

hehe, 拜托仔细回过头去看看我的帖子,如果你能够以一种靠得住的推理得出我没有听说过局部页面缓存,我立马就算服了你。
你的唯一推理就是说如果有人对某个做法感到不屑,那么这个人就是不懂,没听说过。hehe.
另外,牛皮不是靠吹的。hehe.
dlee 2004-07-26
fastm 至少有一个巨大的优点,就是代码完全受作者控制,所以完全可以向作者所希望的方向发展(这和我们自己的框架的优点是相同的)。如果完全依赖开源软件,除非你是核心开发人员,可以左右它的发展方向,否则遇到不顺手的地方你就只能另开一个分支了。这个问题并不是一个小问题,现在大家都没有意识到。所以以前我说“不重新发明轮子”只是相对的而不是绝对的。
buaawhl 2004-07-26
dlee 写道
大家消消气,我看不出任何在这个问题上争论到刺刀见红,人身攻击的必要。确实以前我也骂过 EJB、骂过 Struts,但是现在不骂了。现在似乎更接受 dhj1 那个的成本说。你们最熟悉的框架就是成本最低的框架。所以如果现在你们最熟悉 Struts,最好是继续用下去。赚到足够的钱以后再想着换到什么更好的框架上。

比如我们现在表示层完全用 JS 来写,我们开发了很多控件,可以解决我们遇到的绝大部分问题。也许我们这个框架设计上有很多缺陷,你们的那个 Velocity, Freemarker 比它好上一万倍,但是我们也只能采用改良的方法逐步改进我们的框架。或者借鉴你们好的设计思路,看看是否有可能在我们的这个体系中实现。让我推倒重来,另起炉灶,那不是要我的命吗?

所以大家还是宽容一些,模糊一些比较好。我一般在论坛上喜欢谈一些务虚的东西,实实在在谈代码,还是算了吧。


sorry. dlee。

sorry, charon,
如果你觉得,我对你进行了人身攻击,请接受我的真诚道歉。我绝对没有这个意思。只是一时兴起,激动了而已。千万不要介意。我也没有觉得你对我进行了人身攻击,只是对我的观点进行了攻击。

还有,就我们讨论的话题,你如果喜欢讨论的话,还请继续讨论。我们心平气和地面对共有的问题。
如果你也喜欢谈些务虚的东西,或者对这个话题没有什么太大的兴趣,那么这个话题就略过去了吧。这是各人的兴趣和风格所至,没有必要强求一致。

sorry.
buaawhl
搜索本博客
其他分类
存档
最新评论