Leopard Java Sucks

十一月 11, 2007

Leopard发布的那段时间,看到最多的消息就是批评Leopard没有对Java 6的支持,但是没有想到它对Java 5的支持也倒退了一步.

在升级到Leopard之前,在Tiger上一直使用jVi + Netbeans 6 Beta 1,用着感觉非常爽,比Eclilpse下面那些vim的集成或者插件都感觉要好.但是在升级到Leopard之后,使用了Netbeans Beta 2,发现有一个最基本的功能失效了–用”:”切换到命令模式下无法使用回车键执行.开始还以为是jVi对Netbeans Beta2的支持有问题.等了一段时间,直到jVi和Netbeans 6都发布了新版本之后,发现用最新版本还是不行,这才到jVi的论坛上面去看,结果发现有其他人也遇到了相同的问题,是Leopard的Java 5问题导致的.还不知道有什么work around或者Apple什么时候才能完全支持Java 5,只能天天用Command+S来保存文件了,真是郁闷.

唯一值得欣慰的是Apple还没有把Java给完全抛弃,希望猜测是对的.

Update: 有人高兴有人愁。可惜我不喜欢all in one的一站式采购。

Debugging DFC实在是很痛苦的一件事情,因为DFC类库十分庞大,API的层次很深,很多层次都是非常薄的对下层的一个封装。如果是DFC的使用者,只要考虑其外部接口还比较容易,但是作为API的开发者来说的话,尤其是作为一个刚入门还不太了解其内部结构的新手来说,每次都要花费很多时间在debugger上,仅仅是为了看一下一次访问的整个层次结构是什么样的。于是利用前段空下来的时间,写了一个小工具(InvokeVis)来进行Java的Call Stack的一个可视化。

目前这个工具支持四种可视化的输出格式:

普通文本格式
很简单的格式,仅仅使用缩进来表示不同层次的调用。
GraphViz Dot格式
这一格式可以使用GraphViz中的dot工具来进行转换,可以输出为gif, jpg, png, post script等多种格式。
HTML格式
这是基于Yahoo的YUI的一个HTML格式,Call Stack被可视化的表示为一颗HTML树。
XML格式
这一格式可以使用SpringGraph来进行浏览,用户可以通过Flash来交互式的浏览Call Stack。目前的代码库中自带了一个定制的SpringGraph的flash,可以查看InvokeVis输出的XML格式。

最开始我考虑支持的最主要的格式其实是Dot格式,只希望能够输出为一般的图片就可以了。但是后来发现Dot格式输出的图片的可扩展性不是很好,尤其是对于DFC这种超级巨大的类库而言,一个简单的小程序就会输出几千个节点的图片(在用GraphViz Dot工具转化成jpg时甚至因为节点过多无法输出,只能转化成gif),要想看清图片的细节,只能局限与整个图片的很小一部分,仅仅是窥豹一斑。于是就考虑输出为HTML树这种可以动态变化的方式来进行浏览,扩展性确实得到了很大提高,基本不管多大的节点数都不会有什么扩展性问题。但是又觉得HTML树看起来的时候的可视化效果不如dot格式转化出的图片出色,最后转而想到flash格式的输出,应该来说最后得到的flash的效果还是不错的,但是有部分功能还需要进一步完善,尤其是对现在这一flash中输出的是无向图而不是有向图(方向表示方法调用的发向, caller method -> callee method)这一点很不满意,如果有时间的话会把这点先改进一下。
输出效果图:

Dot format, converted by GraphViz? into gif format:

HTML format:

XML format, visualzed with SpringGraph? flash:

关于各种快速排序算法改进的综合报告

快速排序算法是一种基于分治技术的重要的排序算法,自从它被发明以来,就受到了研究人员的广泛注意。多年以来,人们对这个基本算法进行了大量的改良。我搜集并查阅了一些相关的资料,在下文中对这些改进做出一些介绍。

一、基本的快速排序算法
快速排序算法是由C.A.R. Hoare在1961年发明的一种内排序算法,其大致思想如下[5]:
首先,在要排序的序列a中选取一个中轴值,而后将a分区成为两个部分,左边的部分b中的元素均小于或者等于中轴值,右边的部分c的元素均大于或等于中轴值(分)。而后通过递归调用快速排序的过程分别对这两个部分进行排序(治)。最后将这两部分产生的结果合并即可得到最后的排序序列(合)。
给n个数进行排序时,它平均要做出Θ(nlogn)次的比较,而在最坏的情况下则需要 Θ(n^2)次比较。不过一般来说,在实际上,快速排序算法要显著的快于其他的Θ(nlogn)的算法,主要是由于其内层循环在大多数设计中都能够很有效的实现,并且这一算法可以根据实际的数据在设计上做出改进以使得最坏情况发生的概率尽量减小。[3]

二、快速排序算法的几种改进
1. 三平均分区法[1][9]
关于这一改进的最简单的描述大概是这样的:与一般的快速排序方法不同,它并不是选择待排数组的第一个数作为中轴,而是选用待排数组最左边、最右边和最中间的三个元素的中间值作为中轴。这一改进对于原来的快速排序算法来说,主要有两点优势[1]:
(1) 首先,它使得最坏情况发生的几率减小了。
(2) 其次,未改进的快速排序算法为了防止比较时数组越界,在最后要设置一个哨点。如果在分区排序时,中间的这个元素(也即中轴)是与最右边数过来第二个元素进行交换的话,那么就可以省略与这一哨点值的比较。
关于这一改进还有不同的说法,或者说关于这一改进还有更进一步的改进,在继续的改进中不仅仅是为了选择更好的中轴才进行左中右三个元素的的比较,它同时将这三个数排好序后按照其顺序放回待排数组,这样就能够保证一个长度为n的待排数组在分区之后最长的子分区的长度为n-2,而不是原来的n-1。通过这一技巧,能使得算法的运行时间减少5%左右[9]。
对于三平均分区法还可以进一步扩展,在选取中轴值时,可以从由左中右三个中选取扩大到五个元素中或者更多元素中选取,一般的,会有(2t+1)平均分区法(median-of-(2t+1),三平均分区法英文为median-of-three)。在[9]中有对(2t+1)平均分区法改进的详细分析,不过文章比较长,读起来也比较困难,所以我就看了个开头。里面对三平均分区法也做了详细的分析,并做出了理论的一个估算,其平均复杂度为 ,小于上面所说的一般的快速排序算法的平均复杂度 [9]。

2. 根据分区大小调整算法[7][8]
这一方面的改进是针对快速排序算法的弱点进行的。快速排序对于小规模的数据集性能不是很好。可能有人认为可以忽略这个缺点不计,因为大多数排序都只要考虑大规模的适应性就行了。但是快速排序算法使用了分治技术,最终来说大的数据集都要分为小的数据集来进行处理。由此可以得到的改进就是,当数据集较小时,不必继续递归调用快速排序算法,而改为调用其他的对于小规模数据集处理能力较强的排序算法来完成。[7] Introsort就是这样的一种算法,它开始采用快速排序算法进行排序,当递归达到一定深度时就改为堆排序来处理。这样就克服了快速排序在小规模数据集处理中复杂的中轴选择,也确保了堆排序在最坏情况下O(n log n)的复杂度。[8]
另一种优化改进是当分区的规模达到一定小时,便停止快速排序算法。也即快速排序算法的最终产物是一个“几乎”排序完成的有序数列。数列中有部分元素并没有排到最终的有序序列的位置上,但是这种元素并不多。可以对这种“几乎”完成排序的数列使用插入排序算法进行排序以最终完成整个排序过程。因为插入排序对于这种“几乎”完成的排序数列有着接近线性的复杂度。这一改进被证明比持续使用快速排序算法要有效的多。
另一种快速排序的改进策略是在递归排序子分区的时候,总是选择优先排序那个最小的分区。这个选择能够更加有效的利用存储空间从而从整体上加速算法的执行。[7]

3. 不同的分区方案考虑[8]
对于快速排序算法来说,实际上大量的时间都消耗在了分区上面,因此一个好的分区实现是非常重要的。尤其是当要分区的所有的元素值都相等是,一般的快速排序算法就陷入了最坏的一种情况,也即反复的交换相同的元素并返回最差的中轴值。无论是任何数据集,只要它们中包含了很多相同的元素的话,这都是一个严重的问题,因为许多“底层”的分区都会变得完全一样。
对于这种情况的一种改进办法就是将分区分为三块而不是原来的两块:一块是小于中轴值的所有元素,一块是等于中轴值的所有元素,另一块是大于中轴值的所有元素。另一种简单的改进方法是,当分区完成后,如果发现最左和最右两个元素值相等的话就避免递归调用而采用其他的排序算法来完成。

4. 并行的快速排序[4][6]
由于快速排序算法是采用分治技术来进行实现的,这就使得它很容易能够在多台处理机上并行处理。
在大多数情况下,创建一个线程所需要的时间要远远大于两个元素比较和交换的时间,因此,快速排序的并行算法不可能为每个分区都创建一个新的线程。一般来说,会在实现代码中设定一个阀值,如果分区的元素数目多于该阀值的话,就创建一个新的线程来处理这个分区的排序,否则的话就进行递归调用来排序。[4][6]
下图就是一个并行快速排序算法伪代码,P0…n-1就是要排序的数组,s是并行算法中设定的阀值。

图 1 并行快速排序算法[4]
对于这一并行快速排序算法也有其改进。该算法的主要问题在于,分区的这一步骤总是要在子序列并行处理之前完成,这就限制了整个算法的并行程度。解决方法就是将分区这一步骤也并行处理。改进后的并行快速排序算法使用2n个指针来并行处理分区这一步骤,从而增加算法的并行程度。

三、总结
总的来说,对于快速排序算法的改进主要集中在三个方面[1]:
1 选取一个更好的中轴值
2 根据产生的子分区大小调整算法
3 不同的划分分区的方法
本文中主要介绍了其中的前两个方面,而第三个方面由于我没有找到足够的相关的资料所以介绍的较为简略。另外本文还加入了并行的快速算法的介绍,从另一个方面来介绍一下对于快速排序算法的可能的改进。

四、参考文献
[1] Roger L. Wainwright. Quicksort Algorithms with an early exit for sorted subfiles, 1987
[2] Anany Levitin 著, 潘彦 译. 算法设计与分析基础, 2004
[3] Quicksort from Wikipedia. http://en.wikipedia.org/wiki/Quick_sort
[4] Parallel Quicksort. http://www.osys.se/Archive/Papers/parallel-sort/node3.html
[5] Quicksort. http://www.iti.fh-flensburg.de/lang/algorithmen/sortieren/quick/quicken.htm
[6] Quicksort or Sample Sort Algorithm. http://www.netlib.org/utk/lsi/pcwLSI/text/node302.html
[7] Quicksort, http://www.fearme.com/misc/alg/node47.html#1242
[8] Quicksort, http://www.absoluteastronomy.com/encyclopedia/Q/Qu/Quicksort.htm
[9] H.-H. Chern and H.-K. Hwang. Transitional Behaviors of the Average Cost of Quick Sort with Median-of-(2t + 1), 2001

最近在网上看到一篇N. Alex Rupp写的“Beyond MVC: A New Look at the Servlet Infrastructure”文章,意思大致是说MVC被Struts等框架错误地应用到了Servlet架构中。我想只有对Struts有足够的了解再加上在MVC方面有足够深的功力,才敢发此言论,不是经常听人说:最熟悉自己的人是你的敌人。本人功力尚浅,没有引领风潮的能力,而且生活还得继续,只能先来熟悉熟悉Struts。

申明: 强烈建议在阅读本文之前先阅读一下N. Alex Rupp老兄的文章,如果你赞同他的看法,可能你会觉得研究Struts就没什么意义了。

说明:本文所讲的Struts知识基于Struts 1.1版本,除非特别说明,本文中的Struts都特指Struts 1.1这个版本。

目录:

精细之处一:“利用Token解决重复提交”背后的前提
精细之处二:页面流转控制中的职责分配

精细之处一:“利用Token解决重复提交”背后的前提

我们知道,可以利用同步令牌(Token)机制来解决Web应用中重复提交的问题,Struts也给出了一个参考实现。服务器端在处理到达的请求之前,会将请求中包含的令牌值与保存在当前用户会话中的令牌值进行比较,看是否匹配。在处理完该请求后,且在答复发送给客户端之前,将会产生一个新的令牌,该令牌除传给客户端以外,也会将用户会话中保存的旧的令牌进行替换。这样如果用户回退到刚才的提交页面并再次提交的话,客户端传过来的令牌就和服务器端的令牌不一致,从而有效地防止了重复提交的发生。对应于这段描述,你可能会在你的Action子类中有这么一段代码:

if (isTokenValid(request, true)) {
// your code here
return mapping.findForward("success");
} else {
saveToken(request);
return mapping.findForward("submitagain");
}

其中isTokenValid()和saveToken()都是org.apache.struts.action.Action类中的方法,而具体的Token处理逻辑都在org.apache.struts.util.TokenProcessor类中。Struts中是根据用户会话ID和当前系统时间来生成一个唯一(对于每个会话)令牌的,具体实现可以参考TokenProcessor类中的generateToken()方法。

不知道大家有没有注意到这样一个问题,因为Struts是将Token保存在Session的一个属性中,也就是说对于每个会话服务器端只保存而且只能保存一个最新Token值。对于这一点,我的同事就提出了疑问:那如果我在同一个会话中打开两个页面,那么后提交的那个页面肯定不能提交成功了。他还给出了一个实际的例子:比如现在需要把两个客户A和B的地址都改为某个值,那用户就可能同时打开两个页面,修改A,修改B,提交A,提交B,按照Struts中的处理逻辑,B的修改提交就肯定不能成功,但是这个提交操作对于用户来说并不存在操作不正确的地方。

在这里,可能有人要问:怎么可能在同一个会话中打开两个页面呢?重新打开一个IE浏览器不是重新开始了一个会话吗?不错,这种情况下是两个会话,不存在任何问题。但是,你还可以通过菜单“文件”-“新建”-“窗口”(或者快捷键Ctrl+N)来复制当前窗口,这个时候你会发现该页面与原有页面同处在一个会话当中。其实,能够发现这个问题得归功于我的那位同事对IE习惯性的操作方法。

这下我的那位同事不满意啦,他于是开始动手修改Struts中的实现方式,让每个页面(至少某类页面)在服务器端都保存有一个唯一的Token值。这样,前面所讲的客户A,B同时修改的限制就不存在了。但是不久,我的那位同事就开始意识到他正在走向一条危险的道路。首先,如果每个页面都在服务器端保存一个Token值,则服务器端保存的数据量将越来越大。而且,如果考虑这种同一个会话中打开多个页面的情况的话,就好像打开了潘多拉魔盒,将会给自己带来无穷无尽的麻烦。比如,首先打开页面P1,然后利用Ctrl+N得到页面P2,P1提交,P2提交,目前为止一切正常。但是如果此时,在P1,P2中点击“后退”按钮,然后再提交P1, P2呢,情况会是怎样?如果在P2中提交完后执行其它操作,而在P1中回退后提交,情况又是怎么样呢?如果有P1,P2,P3,那情况又是如何呢?太复杂啦!我想你也会和我们有同感,你需要考虑许多种可能的组合,而且有的时候结果并不是你想象中的那样简单。

此路不通,还得回来看看Struts。其实经过以上一番折腾,我们可以发现在Struts中的Token机制背后隐藏着这样一个前提:不允许你(客户端)在同一会话中打开多个页面。注意是同一会话,如果打开两个IE浏览器,那已经是两个会话啦,不受该限制。其实,这个看似不合理的规定却自有其道理:一是它极大地简化了Token的实现,二个这种限定也符合大部分人的使用习惯。

精细之处二:页面流转控制中的职责分配

我们知道,Struts的执行过程大致如下:首先,控制器接收到客户端请求,将这些请求映射至相应的Action,并调用Action的execute方法,这中间可能还涉及到ActionForm的创建和填充。Action的execute方法执行完以后,返回一个ActionForward对象,控制器根据该ActionForward对象将请求转发至下一个Action或JSP。最后,产生视图响应客户。在大的层面上,Struts是采用了MVC这种架构,没什么特别之处。但从一些小的地方,我们还是可以看出Craig R. McClanahan老兄的一些考虑。我们看到Action与控制器之间传递的是ActionForward对象,由于Action的execute方法要求返回一个ActionForward对象,所以你会经常在Action子类中看到如下语句:

return (new ActionForward(mapping.getInput()));

return (mapping.findForward("success"));

其实返回的就是一个ActionForward对象。在Action中我们根据程序执行的不同情况,决定接下来的页面走向(比如返回到输入页面或者转到下一个页面),并将这些信息保存在ActionForward对象中。而接下来控制器就可以直接利用该ActionForward对象来进行页面的流转。下面是org.apache.struts.action.RequestProcessor类的processForwardConfig()方法的摘录,该方法调用发生在Action实例调用后。

protected void processForwardConfig(HttpServletRequest
request,
HttpServletResponse
response,
ForwardConfig forward)
throws IOException, ServletException {
…

String forwardPath = forward.getPath();
String uri = null;

// paths not starting with /
should be passed through without any  processing
// (ie. they're absolute)
if (forwardPath.startsWith("/")) {
uri = RequestUtils.forwardURL(request, forward);
// get module relative uri
} else {
uri = forwardPath;
}
if (forward.getRedirect()) {
// only prepend context path for relative uri
if (uri.startsWith("/")) {
uri = request.getContextPath() + uri;
}
response.sendRedirect(response.encodeRedirectURL(uri));
}
else {
doForward(uri, request, response);
}
}

注意: ForwardConfig是ActionForward的父类

该方法首先调用ForwardConfig的getPath()方法获得下一步流转的路径,在某些条件下还需要进行一些拼装得到正确的URI,最后根据该URI进行页面跳转。可见在processForwardConfig()方法中只是对ActionForward进行了一些“技术上”的处理,没有任何和业务相关的内容,这样就将控制器(ActionServlet)和Action完全分开来,两者互不影响,达到了功能模块之间松散耦合的目的。

模块间(系统间)松散耦合一直是OO设计所追求的,但是具体如何去实现这样一种松散耦合却不是那么容易做到的。Struts中的设计给了我们一些启示:模块间相互关联影响因素的传递可以用对象的形式来包装起来。其实,个人觉得Struts中的做法还可以稍微有一点点改进,就是在ActionForward中提供一个getURI()方法来给出最终的URI岂不是更好?

参考:

1、Beyond MVC: A New Look at the Servlet Infrastructure

2、Allen Holub的Build user interfaces for object-oriented systems系列文章,可以从这篇文章中学到很多面向对象设计方面的知识,虽然作者并不认为MVC是一种面向对象的方法,但是我们这些MVC的实践者仍然可以从中学到面向对象的知识。

3、Struts 1.1的介绍性文章:深入Struts 1.1

4、Apache Struts Website

5、关于重复提交问题的讨论及其解决方案,可以参考《Core J2EE Patterns》一书(中文版《J2EE核心模式》)。

Deepak Alur,John Crupi,Dan Malks: Core J2EE Patterns-Best Practices and Design Strategies

[转]Portal实现原理

三月 6, 2005

Portal实现原理

1.Portal用例
读者可以在下面三个网站上注册自己的用户,体会Portal的功能。
http://my.msn.com
http://my.yahoo.com
http://my.liferay.com

My MSN的功能最灵活强大,用户可以任意拖放操作栏目(column)和内容版块(content)的位置和个数。
My Liferay只能选择固定的栏目(column)布局,但可以在本栏目(column)内移动内容版块(content)的位置。
My Yahoo只能选择固定的栏目(column)布局,而且不能移动内容版块(content)的位置。

Portal的结构分为三层。
(1) Page
(2) Column,或者称为Pane
(3) Content,或者称为Portlet

我们来看看Portal的整个操作流程。
(1) 每个Column的下方都有一个[Add Content]按钮,让用户选择加入自己喜欢的内容。
从这里,我们知道,Portal系统里面有一个公用的Common Portlet Repository,供用户选用。

JSR168 Portlet规范里面定义了Portlet Deployment Discriptor。Common Portlet Repository以这个Portlet Deployment Discriptor的格式存放。

开源项目JetSpeed的XReg文件用来存放Common Portlet Repository的定义。

(2) 加入Content之后,用户的Page和Column里面就多了这个Content。下次用户登陆的时候,就会看到自己订制的Portal版面。
从这里,可以看出,Portal系统会纪录用户的个人Portal配置信息 � User Portal Config。

开源项目JetSpeed的PSML文件用来存放User Portal Config的定义。

——- 综上。
Add Content的整个流程为:
Common Portlet Repository –> Add Content –> Personal Portal Config

Display Portal的整个流程为:
从Personal Portal Config读取用户配置的Portlet ID –> 根据Portlet ID,从Common Portlet Repository查找详细的Portlet定义 –> 根据这个详细的Portlet定义显示这个Portlet。

2.Portal实现
我们考虑如何用Java来实现Portal。

2.1 Dynamic Include
首先,我们采用最简单的思路,我们用100个JSP文件(1.jsp, 2.jsp, 3.jsp, … 100.jsp等),代表100个Portlet。
用户页面MyPage.jsp包含用户选定的多个Portlet。
现在,假设用户选取的Portlet为1.jsp, 3.jsp, 7.jsp等3个Portlet,那么我们如何在MyPage.jsp中显示这些Portlet?最直观的做法是,用jsp:include。比如:

<table>
<tr><td>
<jsp:include page=”1.jsp” />
</td></tr>
<tr><td>
<jsp:include page=”3.jsp” />
</td></tr>
<tr><td>
<jsp:include page=”7.jsp” />
</td></tr>
</table>

由于<jsp:include>只能指定固定的jsp文件名,不能动态指定jsp文件名。我们需要把<jsp:include>翻译为Java code � RequestDispatcher.include();
下面我们换成这种写法。

java代码:
<table>
<tr><td>
<% request.getRequestDispatcher(”1.jsp”).include(request, response); />
</td></tr>
<tr><td>
<% request.getRequestDispatcher(”3.jsp”).include(request, response); />
</td></tr>
<tr><td>
<% request.getRequestDispatcher(”7.jsp”).include(request, response); />
</td></tr>
</table>

进一步改进MyPage.jsp。

java代码:
<% String[] fileNames = {“1.jsp”, “3.jsp”, “7.jsp”}; %>
<table>
<% for(int i = 0; i < fileNames.length; i++) {
String fileName = fileName s[i]; %>
<tr><td>
<% request.getRequestDispatcher(fileName).include(request, response); />
</td></tr>
<% } // end for %>
</table>

其中的fileNames的内容可以各种各样,只要RequestDispatcher能够处理。
比如Velocity,fileNames = {“1.vm”, “3.vm”, “7.vm”};
比如URL,fileNames = {“/portlet1.do”, “/portlet3.do”, “/portlet4.do”};
我们可以看到,如果我们从用户配置中读取fileNames的内容,这就是一个简单的Portal实现。

java代码:
<% String[] fileNames = (String[])session.getAttribute(“portlets.config”); %>
<table>
<% for(int i = 0; i < fileNames.length; i++) {
String fileName = fileNames[i]; %>
<tr><td>
<% request.getRequestDispatcher(fileName).include(request, response); />
</td></tr>
<% } // end for %>
</table>

2.2 Portlet Interface
下面我们来扩展这个例子。
假设每个Portlet都规定实现一个Portlet接口。

java代码:
interface Portlet {
void render(request, response);
};

MyPage.jsp如下:

<% String[] portletClassNames = (String[])session.getAttribute(“portlets.config”); %>
<table>
<% for(int i = 0; i < portletClassNames.length; i++) {
String className = portletClassNames[i];
Portlet portlet = (Portlet)Class.forName(className).newInstance(); %>
<tr><td>
<% portlet. render (request, response); />
</td></tr>
<% } // end for %>
</table>

Portlet类的示例代码如下:

public class Portlet7{
public void render(request, response){
request.getRequestDispatcher(“7.jsp”).include(request, response);
}
};

上述代码是Portal显示Portlet的核心流程的一个简化版本。
JSR168 Portlet规范里面定义了真正的Portlet接口定义。

2.3 Portlet Action
Portlet的操作包括,最大化/最小化/恢复/关闭/编辑/帮助/上下移动,等等。
这些操作都有对应的Action类。
开源项目JetSpeed的module/actions/controls目录下面包含Maximize, Minimize, Close等Action类。
开源项目Liferay的portal/action目录下面包含Maximize, Minimize, Close等Action类。

Portal的操作不仅包括上述Portlet的操作,而且包括其它更高级别的操作。
比如,Add/Move Page, Add/Move Column, 换Layout, 换Skin,之类。

2.4 Portlet Cache
我们操作Portlet的时候,往往只操作某个特定的Portlet,或者只是变化Portlet的位置。这时候,页面中大多数的Porlet的内容是不变的,只有一小块Portlet变化。
我们需要把Portlet的内容缓存起来。Portlet接口有一个render(request, response)方法,我们可以订制定制response类,截获portlet的输出,保存到Portal系统的内容Cache当中。
比如,前面提到liferay开源项目,其StringServletResponse类把Portlet的输出保存到一个String当中。

相关摘要
  • (一蓑烟雨任平生)Portal Server的机制与一般的Web Framework是独立的,Portal Server有自己的容器或者引擎来对Portlet进行处理,每个Portlet类似于Servlet。Portlet现在有两种标准,一种是以Jetspeed为主的老版本,IBM的Websphere Portal Server原先的核心API也采用Jetspeed的API,另一种是目前JCP组织制订的JSR 168标准,BEA和IBM都在自己的产品里实现了该标准,但还没有成熟。IBM的WSAD开发工具里对Portal有两种项目类型,分别支持这两种标准。

    可以将Portal做为表现层的一种类型集成到你的Web Framework中。

  • (一蓑烟雨任平生)Jetspeed是一个门户的应用管理系统,应用程序是构建在Turbine这个Web Framework上面的,可以认为Jetspeed是个用Turbine Framework开发的一个应用程序。
  • (whitehorse)portal 包括 portal server 和 portlet container ;JSR-168定义了portlet 通用api ,portlet container 的提供商需要遵循这个API,这样开发出的portlet 可以在任何一个实现jsr-168规范的portlet container下顺利移植。
    portal server 是各个开发商自行提供的,用来接受用户用求转发给相应的portlet,调整portal page 布局,单点登录等等;
    portlet container就是在servlet container上又包装了一层,portlet 类似于servlet;
    portal server 的实现一般采用 web framework 技术来构建; 比如liferay采用了struts + tiles;exo poral 采用了 jsf 等等。portlet开发在遵循规范的基础上可以采用web framework .
  • (kingkii)free portal server: Jetspeed, liferay, Jportal, etc
  • (flyisland)
  • baichenhong 写道:
    我觉的Portal的作用就是整合,故名意思 门户 嘛就是把所有的集成起来放到一起,你有oa,erp,crm 但是你觉得分开使用很不方便,那么好我可以把他们整合到一起,只要你登陆一次就可以使用所有的系统,这应该是一个很有用的东西

    你短短一句话包含的东西可是超级多。

    所谓集成分为很多层次的,Portal关注的是用户集成,包括访问界面、访问手段等。访问界面的集成并不要求应用程序一定要部署在一起。

    至于“只要你登陆一次就可以使用所有的系统”,一般称之为单点登陆“Single Sign-on”。Portal服务器基本都提供了认证框架,在此框架下开发的新应用实现SSO是很简单的;但如果对异构的系统进行SSO,这又是一个复杂而庞大的话题了。

  • (tommy_kin)拖放布局不是Portal的核心技术,只是一个personalize的功能而已,Portal功能在于应用的集成。所谓一站式访问。
参考资料:

在Struts中reset方法有什么作用(转)

创建人:王艺
创建时间:2003年6月15日星期日

第一步:
对象的可视范围:request、session、application、page。
Request:在一个请求周期内有效。就是从你点击页面上的一个按钮开始到服务器返回响应页面为止(包括响应页面)。
Session:在一个用户与服务器建立连接的整个过程中有效。
Application:在整个web应用程序内有效。
Page:仅在一个jsp页面内有效。

第二步:
ActionForm在你确定的有效期(可视范围)内是唯一的。

第三步:
在每次为ActionForm赋值前调用它的reset方法。作用是使ActionForm中的值恢复初始状态。在应用中我们可以通过在reset中为变量赋初值的方式,使得页面上的某个对象有显示值。

第四步:
可视范围与赋值前的初始化结合。
由于第二步所述特性,如果可视范围是request,则reset方法并不是很重要,因为你每次调用时都会产生一个新的ActionForm实例,所以你所操作的ActionForm不会与别人分享同时也就不会受别人的影响;如果可视范围是session,由于在session范围内此ActionForm是唯一的,所以你在session范围内需要用到此ActionForm的地方调用的都是同一个ActionForm,要是你没有在reset中对变量赋初值那么前一次调用ActionForm是为它赋的值将在此次调用时有效,这到也没什么。但是,如果恰巧再次调用时你仅仅需要为ActionForm中的一部分变量赋值,那么其余的变量将保持上一次得到的值,这样你就得到了一个“新旧混合体”,我想这多半不是你所期望的;如果可视范围是application,那其影响就更是不难理解了,这时不但是你自己会影响你自己,使用应用的其他用户的操作也会影响到你。

第五步:
知道了reset方法的作用和ActionForm在scope内唯一的特性后就为我们灵活处理ActionForm的行为提供了基础。比如说你现在需要跨过多个页面收集数据信息,这时你就可以把scope设置为session,并且不实现reset方法��这样在每个页面put数据时都不会将之前收集的数据清空,最后在你收集完数据后在Action中调用ActionForm中你自定义的初始化方法,如:resetField。
在具体的我也想不出了,还是要大家在应用时多多体会这些特性,这样才能把架构的威力发挥到最大。

XML数据的概念建模: 方法和工具
内容:
引言
1. 关于 XML 的建模
2. 进行 XML 概念建模的一般性问题
3. 三种解决方案
4. 总结及引申
参考资料
关于作者
对本文的评价
订阅:
developerWorks 时事通讯
developerWorks 订阅
(订阅CD 和下载)

李霞joylee4u@yahoo.com.cn
在读研究生, 武汉理工大学计算机科学与技术学院
2005 年 1 月

随着XML应用开发的深入,我们需要一种统一的建模方法来针对不同的应用为 XML 数据进行概念建模。本文概览了当前处理 XML 概念建模问题的几种方法及工具,了解这些方法将为我们对实际XML应用建模提供很好的思路。

引言
XML 技术是互联网下一个发展阶段的关键技术,它已成为互联网上表现结构化和半结构化数据的标准格式和数据交换的主要标准。在这种背景下,把 XML 作为一种数据库模型并从中提取信息在数据库领域逐渐受到注意。而且,XML 技术和关系数据库技术的融合能使数据更通用,能被异构系统接受,因此成为很自然的趋势。大量的 XML 文档由于不同应用之间交换数据而临时产生,而且也存在像数据库一样需要系统可靠地管理永久性 XML 数据的需要。为此,已经有许多的研究工作致力于融合 XML 应用和关系数据库。同样,参照关系数据库设计与建模的成功经验,我们也需要一种统一的建模方法学来针对不同的应用为 XML 数据进行建模。

1. 关于 XML 的建模
提到 XML,我们自然会想到它的半结构化特征。XML 是典型的树形结构,然而这种结构既然包含了元数据和数据,目的是为了能让机器更好的理解,它的物理表现也就不那么直接的利于人的理解。其定义语言的文本描述形式阅读和理解起来都有不便,因此我们需要可视化和标准化的设计方法和工具。比如利用现有的一些工具,如 XML SPY,可以通过设计更直观的图形结构来生成物理的 XML Schema/DTD (参见图1)。这样使得设计方便一些了,但是,这仍然没有脱离实现,而更好的关注应用领域。因此,我们需要更高层的概念模型来指导对 XML 数据结构的设计。

图1 XML Spy 绘制的 Schema 结构图
图1  XML Spy 绘制的 Schema 结构图

目前常用的描述 XML 数据结构和内容的文档定义语言主要有 DTD 和 XML Schema。作为 W3C 的推荐标准,XML Schema 虽然在确认 XML 文档上非常有用,但它并不能胜任需要理解所表现数据的语义的任务。而且,直接由 XML Schema 来定义 XML 文档结构的设计方法是以 XML 为中心的,这样过早的陷于底层的实现结构了,而好的设计应该以应用为中心。为了让设计者站在一个更高的角度,从更抽象的层面去关注应用领域,最好使用概念模型。

数据库设计是数据库应用的基础和主要内容。关系数据库的设计方法学经过长期的发展已经非常成熟,它对我们设计 XML 数据库提供了很好的借鉴作用和指导意义。回顾关系数据库的建模,我们知道,传统的关系数据库的设计方法大致如下:关系数据库的建模应该先从一个特定的应用领域分析入手,进行概念建模( Conceptual Modeling ),得到概念模型(即 ER 模型),然后,将 ER 模型转换为关系模型,即逻辑模型,再生成物理模型,即实际存储的物理表结构。

而概念模型的设计是整个设计过程中一个相当重要的步骤,因为它独立于最终的开发平台和实现环境。对于不熟悉底层实现细节的用户来说,概念模型是一种容易理解、方便使用的表示形式,如数据库设计中的 ER 图、软件开发中的数据流图( DFD )、面向对象设计中的 UML 图等。同时,一个好的概念模型的设计也会为其逻辑、物理模型的设计提供了一个良好的基础。

2. 进行 XML 概念建模的一般性问题
上面我们强调了为 XML 进行概念建模的必要性,那么,这种概念模型必须要能满足 XML不同于关系数据的全新特性。很明显,XML的结构与传统的结构化数据模型存在着一些差异,因此目前尚没有一种合适的机制来生成和描述 XML 的概念模型。

XML 语言的半结构化特征给 XML 建模带来了一些细节性问题,这里以 XML Schema 为标准,由于其强大的定义功能和灵活性,给模型的抽象带来了困难。比如,XML 文档的内容是有序的,不同元素之间有特定的顺序;一个元素可由其它子元素构成,子元素有顺序和可变的数目等;一个文档的模式可能是简单的,也可能是复杂的嵌套结构;由于其半结构化的特性,XML 不能直接支持多对多的关系;XML Schema 中会涉及到不同的类型,比如简单类型和复杂类型,元素的不同实例又可能会有不同的结构;单个元素的结构也会很复杂,元素可能会含有子元素,再加上可选、必选和多值等特性,增加了其结构的复杂性;XML 文档中的元素还会包含混合内容,比如具备原子性和不具原子性的值;XML 具有的名称空间这样的特性增加了建模的复杂性,要支持它比较困难。

总之,由于 XML 数据具有关系模型不能直接表示的半结构化特征,而且相对于关系模型,XML Schema 描述的文档结构可以十分复杂和自由,所以试图用一种模型来支持 XML Schema 的所有特性,这在当前还有一定困难。

3. 三种解决方案
目前已有的 XML 概念建模的解决方案主要有3种:一种是采用 UML 来设计 XML 模式,另一种则基于扩展 ER 模型,我们暂且称之为 XER,它即是用支持 XML 特性的扩展 ER 模型来为 XML Schema 建模。还有一种办法则是另起炉灶,它是一套全新的专门针对 XML 的建模方法:AOM( Asset Oriented Modeling )方法。

3.1 UML
作为较早出现的一种解决方案,UML 能为设计 XML 标准词汇( vocabulary )提供帮助[1]。下面的图2就是一个最简单的 UML 类图,它为 XML Schema 的一个 Book 实体描述了概念模型。类 Book 含有图示的3个属性,类的属性名后的[ ]中表示的是该属性出现的次数。本例中的[1..* ]就表示一本书可以有一到多个作者。

图2 Book 的 UML 模型
图2   Book 的 UML 模型

由于本文关注的是概念建模,这里只简要介绍使用 UML 为 XML Schema 进行三层建模的方法,具体实现过程可以参考后面的资料[1,2]。利用 UML 建模的过程分为三个部分:利用 UML 的类图进行概念建模、使用 UML Profile 进行逻辑建模、利用 UML Profile 到 XML Schema 的自动映射实现物理建模,即将 UML 模型转化成 XML Schema 了。由于 UML 使用 Profile 作为扩展机制,它就容易适应某些特定的领域。通过 Profile,用户针对 XML Schema 可以定义和使用自己的元素。这就允许用户能针对 XML 的特性来改进和扩展 UML 的描述能力。

从这个简单例子,我们可以看出,这里 Book 的属性看不出顺序之分,但在 XML Schema中,排序却是重要的特性, 要使 UML 支持它需要一些技巧。而且,在此 UML 图中也不能明显区分元素和属性。结合实际,假如再给 Book 添加一个自定义为复杂类型的 Chapter 元素,UML 默认的数据类型也不支持,UML 的表达中还需要引入新实体和联系。另外,UML 还缺乏针对 XML 的一些重要概念,如:名称空间、键等。

可见,UML 无法涵盖 XML Schema 所提供的全部功能和丰富内容,对 XML 的建模细节处理得不完整。但是,在 XML 模式设计领域,如能适当地扩展 UML Profile,将弥补基本 UML 模型中的差距。

至于用 UML 来设计 XML 模式的实现工具,常见的画 UML 图的工具基本都能胜任,但是必须结合 XML 的需求对 Profile 进行必要的扩展。比如 IBM 的 Rational Rose,另外流行的 Eclipse 平台上也有众多的可视化建模插件,典型的如:EMF,hyperModel 等。

3.2 XER
XER 是一种以 ER 模型为基础的支持 XML 可视化概念建模的方法[3]。由于它基于扩展的 ER 模型,参照 ER 的组成部分,这种 XER 的组成部分也包括实体和联系等。具体来说,结合 XML 的特性,XER 的实体包括有序实体、无序实体、混合实体;XER 联系则与 XML Schema 中元素的 minOccurs、maxOccurs 取值有关;另外 XER 也支持泛化、聚合等概念。可以用代表不同 XML 特征的图元来表示这些概念。仍以有序实体 Book 为例子,下面的图3、前文的图1和清单1展示了使用 XER 为Book 的 XML Schema 进行建模的三层模型,分别是概念模型、逻辑模型、物理模型。其中图1是用 XML Spy 绘制的相应 Schema 结构图,它表述了 Book 实体的逻辑层模型。

清单1 Book 实体对应的 XML Schema-物理层


<xs:element name="BOOK">
<xs:complexType>
<xs:sequence>
<xs:element name="title" />
<xs:element name="author" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="Chapter" minOccurs="1" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="title"/>
<xs:element name="abstract"/>
<xs:element name="section" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
<xs:attribute name="chapno" type="xs:ID"/>
</xs:complexType>
</xs:element>
</xs:sequence>
<xs:attribute name="isbn" type="xs:ID"/>
</xs:complexType>
</xs:element>

图3 XER 关系图-概念层
图3  XER 关系图-概念层

本例中,在由 XER 表达的概念模型图3中,Book 是一个复杂类型,它包含一个为复杂类型的 Chapter 元素,这里的 XER 联系的两端表达了这样的关系:一个 Book 包含一到多个 Chapter(1:M),而每一个 Chapter 都属于一个 Book(1:1)。我们可以看到,由于 XER 在原 ER 的图元上加入很多新特性,增强了 ER 基本图元的描述能力,能更好的支持对 XML Schema 的表达,它比 UML 的表达能力更丰富一些,比如图3中对排序、区分元素与属性和对键的支持。

使用 XER 方法的实现工具可以是 Visio,PowerDesigner 等能绘制 ER 模型的工具,但由于 XER 引入了新的图元,也需要对这些建模工具的图元进行扩展。目前,PowerDesigner 也提供了基于 Eclipse 的插件版本[4],以利于扩展。

3.3 AOM 方法
Assert Oriented Modeling 是一种全新的建模方法[5]。它的特点是表达能力强、图形简洁和模块化。虽然提出的时间较短,但发展很快,已基本形成了自己的体系。在 AOM 方法中,引入了一个重要概念 Asset,用它来统一表示关系数据库中严格区分的实体和联系,即实体和联系都用 Asset 来表示。联系则用弧表示。它同样也定义了一系列自己的图元,以支持 XML 的特性,包括刚提到的 Asset、弧等基本图元,还支持属性、注释、约束、键、操作等概念,AOM 模型还区分层次,支持命名空间。这里给出它的一个简单图例,足以显示它较强的表达能力。

图4 AOM 模型图例
图4  AOM 模型图例

AOM 不仅有自己的理论,还有配套的图形化建模工具 KLEEN[5]。KLEEN 已被实现为 Eclipse 平台上的一个插件,它支持基于 Asset 的模型的创建及确认,并能从概念模型生成代码。它能从 http://www.aomodeling.org 下载得到。目前 KLEEN 的最新版本是0.9.7。

3.4 比较与评价
目前三种建模方法都处在研究之中。比较而言,UML 的优点是能够较为容易地创建模型,图形简洁明了,并且能确保模型的一致性,利于 Schema 的复用。但是,UML 省略了很多底层的细节,其中某些是 XML 的重要特性,因此不够友好。XER 根据 XML 的特征对 ER 进行了合适扩展,用它来为 XML Schema 进行概念建模更适合有传统数据库设计背景的用户。但 XER 暂时还不支持 XML 的名称空间。

AOM 则可以说是一种为 XML 度身订做的建模方法,且更加开放。AOM 的出现是由于传统建模方法难以处理日益复杂的数据模型:基于 ER 建模的方法毕竟更适合结构化数据;而概念建模对于以面向对象的数据模型为基础的 UML 来说显得过于复杂,它欠缺数据库建模的一些重要概念,比如键。当然相比之下,AOM 方法不如前两者成熟,有丰富的经验可以借鉴,相对来说它的资料和技术支持都还有不足。

在工具方面,我们发现这样的趋势,可视化工具都趋向于做成 Eclipse 平台的插件,更加的开放和可扩展。

4. 总结及引申
以上我们谈的是 XML 文档的概念建模,也可以考虑将上面的几种方法引申到 XML数据库的概念建模上。借鉴关系数据库的经验,对于 XML 也有可能集成子模式,得到总体概念模式-XML 数据库模式。并且,设计 XML 模式和 XML 数据库的模式的工作也可灵活选择,可以采用自顶向下或自底向上的方法。但数据库模式将会在更抽象的层次上提供一个集成化的全局外部模式。由于同一个数据库概念模式会产生许多不同的 XML Schema,所以 XML 数据库模式更利于用户理解总体上的概念模式。

并且随着关于 XML 新的研究的开展,我们感到,在 XML 建模方面还有融合领域本体的可能性和趋势。同时,不管选择以上三种方法中的哪一种,概念建模仅仅是第一步,我们还需要考虑从 XML Schema(DTD)到其概念模型之间的双向映射算法即自动转换问题,这是得到实用设计工具的必备工作。实际上,由于 XML Schema 的复杂性,这样的转换工作有一定难度。

综上所述,我们纵览了当前处理 XML 概念建模问题的几种方法及工具,旨在抛砖引玉。其实围绕 XML 的三层建模过程还有很多细节问题,而且目前三种解决方案都还未稳定,但是了解这些方法将为我们对 XML 应用建模甚至设计自己的概念模型提供很好的思路,利于指导实际 XML 应用的设计工作。

参考资料

  1. Carlson D,Modeling XML Vocabularies with UML(Part I、II、III),http://www.xmlmodeling.com,2001
  2. IBM developerWorks 中国网站,Benoit Marchal的UML建模系列文章:使用 XML:UML、XMI 和代码生成 (共四部分), http://www-900.ibm.com/developerWorks/cn/xml/x-wxxm23/
  3. Arijit Sengupta, Sriram Mohan, Rahul Doshi. XER-Extensible Entity Relationship Modeling,http://www.idealliance.org/proceedings/xml03,2003
  4. PowerDesigner 11 beta Online Documentation
  5. http://www.aomodeling.org
关于作者
李霞, 武汉理工大学计算机科学与技术学院,武汉,430063。E-mail:joylee4u@yahoo.com.cn,研究兴趣:XML与数据库、数据建模、工作流系统等

[转]CSS制作标签卡Tab效果
2005-1-31 16:45:09

CSS制作标签卡Tab效果

亚马逊网站应该不会陌生吧?对它页面上方标签卡(Tab)方式的导航条(如下图)还有印象么?

__

__amazon.com这种方式得导航引起了相当多人的效仿。那么,它是如何做到的呢?有过网页设计经验的人应该不难明白,如果不知道细节的话,通过察看源代码就能知道,它实际上是通过在表格中插入事先制作好的作为标签卡的图片来制作的,标签卡的效果通过颜色来控制,比如上图中的“YOUR STORE”这张图片和底下子栏目的颜色一致,背景都为深蓝色,这样看上就向一张卡片了。

__不过,现在网页设计的趋势是XHTML+CSS来完成。那么,如果不用图片加表格的方法,有没有办法仅仅利用CSS来制作呢?有的,可以通过项目列表的CSS设定来做到。

__

__这张图,就是利用这种方法来制作的。

__下面,我们就分别来学习CSS的标签卡制作。

利用列表元素制作标签卡

__通常情况下,项目列表的排列方式是垂直的,并在前头带有特定的项目符号,如下:

  • 项目列表一
  • 项目列表二
  • 项目列表三
  • 项目列表四

__它所对应的HTML代码是这个样子:

__<ul>
<li>项目列表一</li>
<li>项目列表二</li>
<li>项目列表三</li>
<li>项目列表四</li>
</ul>
__那是否你曾想到过,项目列表也可以不垂直排列,而是水平分布呢?在Html中无论如何是做不到这点的。可是CSS却提供了这种方法。

__首先,我们把项目列表放入到div标记中,如下:

__<div id=”horizonlist”>

__<ul>
<li>项目列表一</li>
<li>项目列表二</li>
<li>项目列表三</li>
<li>项目列表四</li>
</ul>

__</div>

__然后,我们为这个id为horizonlist的Div设定如下样式:

#horizonlist {//*设定div的Box属性*//
border: 1px solid #000;
margin: 2em;
width: 80%;
padding: 5px;
font-family: Verdana, sans-serif;
}

#horizonlist ul, #horizonlist li {//*设定限制于horizonlist的div内的ul和li的属性*//
display: inline;
margin: 0;
padding: 0;
color: #339;
font-weight: bold;
}

__此样式作用于所给项目列表的结果如下:

  • 项目列表一
  • 项目列表二
  • 项目列表三
  • 项目列表四

__可以看到,此时的项目列表成了水平放置,而且列表前的符号自动消失。之所以这样,关键在于属性display的设置值inline的作用。display用来改变元素的显示值,可以将元素类型线上,块和清单项目相互变换,其中取值inline的作用是“删除元素前后的分行符,使其并入其它元素流中”。在这里,inline取消了每个列表项目后的换行,而成为一行显示。

__顺着这个思路下去,如果我们给每个列表项目设定Box属性,那不就有了类似标签卡的效果出来了么:

  • 项目列表一
  • 项目列表二
  • 项目列表三
  • 项目列表四

__我们来看看这个例子的代码:

__<div id=”tabdemo”>
<ul>
<li>项目列表一</li>
<li>项目列表二</li>
<li class=”selected”>项目列表三</li>
<li>项目列表四</li>
</ul>
</div>

__和上面的例子不同,这里的项目列表三多了类名“selected”,用来表示当前被选中的标签卡。

__相应的CSS属性设定如下:

__#tabdemo ul li {
margin-left: 0;
margin-bottom: 0;
padding: 2px 15px 5px;
border: 1px solid #000;
list-style: none;//*不显示列表符号*//
display: inline;//*取消项目之间的分行*//
background-color: #ffc;
}

#tabdemo ul li.selected {//*设定被选中的列表的效果*//
border-bottom: 1px solid #fff;
background-color: #fff;
list-style: none;
display: inline;
}

__如果你希望每个标签卡之间有一定的距离,可以修改#tabdemo ul li此设定中的margin-left属性值,比如改为2,就可以看到类似早先给出的那张蓝色标签卡的样子。

__接下来我们来进一步修饰上面这个标签卡,先来看效果果。

__可以看到,每个标签卡之间不再紧贴一起,底下出现了一条连续的横线,当鼠标移动到每个标签卡的时候,出现了浮动的效果。

__一起来分析一下代码:

__<div id=”container”>

<ul id=”beautytab”>
<li><a xhref=”http://blogger.org.cn/blog/#” mce_href=”http://blogger.org.cn/blog/#” class=”selectedtab”>标签卡一</a></li>
<li><a xhref=”http://blogger.org.cn/blog/#” mce_href=”http://blogger.org.cn/blog/#”>标签卡二</a></li>
<li><a xhref=”http://blogger.org.cn/blog/#” mce_href=”http://blogger.org.cn/blog/#”>标签卡三</a></li>
<li><a xhref=”http://blogger.org.cn/blog/#” mce_href=”http://blogger.org.cn/blog/#”>标签卡四</a></li>
</ul>

</div>

__这个标签卡放在id为container的块div中。列表的id为beautytab,其中的列表项目标签卡一设定了一个类“selectedtab”,表示当前被选的标签卡类。

__对应的CSS设定如下:

__#container
{//*设定包含列表的div的Box属性*//
width: 500px;
padding: 30px;
border: 1px solid #ccc;
background: #fff;
}

#beautytab
{//*设定项目列表Ul元素的属性,其中background用来设定连贯于各个列表项目下的横线,否则它们会彼此分离,用了一张事先准备好的图片,让它放置在底部,水平重复*//
height: 20px;
margin: 0;
padding-left: 10px;
background: url(’/Upload/editor/bottom.gif’) repeat-x bottom;
}

#beautytab li
{//*设定各个列表项目的属性,display属性设定取消项目间的分行,list-style-type设定取消列表项目前的符号*//
margin: 0;
padding: 0;
display: inline;
list-style-type: none;
}

#beautytab a:link, #beautytab a:visited
{//*设定标签卡中超链接的文字的属性*//
float: left;
background: #f3f3f3;
font-size: 12px;
line-height: 14px;
font-weight: bold;
padding: 2px 10px 2px 10px;
margin-right: 4px;
border: 1px solid #ccc;
text-decoration: none;
color: #666;
}

#beautytab a:link.selectedtab, #beautytab a:visited.selectedtab
{//*设定当前被选中的标签卡中超链接的属性*//
border-bottom: 1px solid #fff;
background: #fff;
color: #000;
}

#beautytab a:hover
{//*设定超链接鼠标浮动效果*//
background: #fff;
}
__如果手头上有漂亮的修饰图片,我们还可以进一步制作出类似下图的漂亮雅致的标签卡来。

__

__至于如何制作,用兴趣的人可以自己动手做做看。

[转]Java打包详解

一月 18, 2005

Java打包详解

lightsword 于 2004年 10月08日 发表

兄弟,对java着迷吗,或者是为了自己的生计,不论怎样都欢迎你进入精彩java世界兄弟,对java着迷吗,或者是为了自己的生计,不论怎样都欢迎你进入精彩java世界,welcome!可能你刚刚对每个人说:Hello World!也或者……ok!这已经足够了。那就让我们开始吧,开始这个魔幻世界的旅程:

jar文件听说过吗,没有?或者陌生!好,没关系,这就是我们的第一站:打包发布。

为什么会有这个玩意呢,首先,这是jar的全称:JavaTM Archive (JAR) file,是的,就是java存档文件。这有点类似zip文件,想一想它是干什么的用的呢,压缩!?没错就是要压缩,将我们原先零散的东西放到一下,重新组织,所有这些目的只有一个:方便!好了,不用管他是怎么压缩的,我们的重点是哪些是我们要压缩的(输入),还有压缩成了什么(输出),进而将它发布(部署)。

那我们的输入(要压缩的东西)主要是class文件,还有辅助的资源(这其中可能有图片,jsp文件,html文件等等)。Jar技术在jdk1.1版本中就已存在,在1.2中又有了增强。接下来说说jar的好处吧,这是官方的描述:安全,快速下载,压缩,猎取包,版本化包,可携。

说了这么多,我们现在开始实施。

先打开命令提示符(win2000或在运行筐里执行cmd命令,win98为DOS提示符),输入jar �help,然后回车(如果你盘上已经有了jdk1.1或以上版本),看到什么:

用法:jar {ctxu}[vfm0Mi] [jar-文件] [manifest-文件] [-C 目录] 文件名 …

选项:

-c 创建新的存档
-t 列出存档内容的列表
-x 展开存档中的命名的(或所有的〕文件
-u 更新已存在的存档
-v 生成详细输出到标准输出上
-f 指定存档文件名
-m 包含来自标明文件的标明信息
-0 只存储方式;未用ZIP压缩格式
-M 不产生所有项的清单(manifest〕文件
-i 为指定的jar文件产生索引信息
-C 改变到指定的目录,并且包含下列文件:

如果一个文件名是一个目录,它将被递归处理。

清单(manifest〕文件名和存档文件名都需要被指定,按’m’ 和 ‘f’标志指定的相同顺序。

示例1:将两个class文件存档到一个名为 ‘classes.jar’ 的存档文件中:
jar cvf classes.jar Foo.class Bar.class

示例2:用一个存在的清单(manifest)文件 ‘mymanifest’ 将 foo/ 目录下的所有文件存档到一个名为 ‘classes.jar’ 的存档文件中:
jar cvfm classes.jar mymanifest -C foo/ .

来个小例子试试看:
我们只有一个HelloWorld,如下:

public class HelloWorld{
public static void main(String[] args){
System.out.println(“Hi, Hello World!”);
}
}

我将这个java文件存到C盘跟目录下,ok,接下来,

在先前打开的命令提示符下(跳转到C盘提示符下),我们输入javac HelloWorld.java,然后继续输入:jar cvf hello.jar HelloWorld.class,回车后去你的C盘看看,多了什么,没错 hello.jar 。

基本的步骤我们现在都知道了,你可以自己去尝试一下随着jar后面的参数的不同,结果有什么变化。

紧接着我们看看如何运行我们的jar包。

在进入正题之前,你要先打开我们刚刚做好的jar包看看,多了什么呢,META-INF目录?再看看里面是什么,还有一个MANIFEST.MF文件是不是?用文本编辑器(我这里是UltraEdit)打开它看看:
Manifest-Version: 1.0
Created-By: 1.4.2 (Sun Microsystems Inc.)

就是这样。这里我们对它进行修改,加一句:Main-Class: HelloWorld (在第三行)。这个就是我们之前写的那个类,也就是我们的入口类。也即,
Manifest-Version: 1.0
Created-By: 1.4.2 (Sun Microsystems Inc.)
Main-Class: HelloWorld

接下来,我们在命令提示符里执行:
jar umf MANIFEST.MF app.jar

这样我们使用了我们自己的MANIFEST.MF文件对原来默认的进行了更新。你不妨可以再进去看看是不是添上了Main-Class: HelloWorld这一句。

Ok,这个最后的一步了,来验证我们做的一切,在命令提示符中输入:
java -jar hello.jar(执行)

出现了什么,��Hi, Hello World!
我们再来看看jar文件在tomcat中发布,注意:在tomcat中我们就不能再用jar这种格式,而改war格式,它是专门用于web应用的,其实整个过程下来基本上和jar是类似的:

先准备我们要打包的资源。

找到存放tomcat的webapps目录,进到其中,新建一个文件夹,这里命名为hello,再进去新建WEB-INF文件夹,再进去新建classes文件夹,此时我们也将我们唯一的servlet,HelloWorld.java放到这里,在与classes目录同级下建立一文件web.xml。Ok,目前我们初步建立了一个简单的web应用。

在命令提示符下进到先前创制的hello目录下,执行 jar cvf hello.war * ,我们便得到hello.war。将它拷贝至webapps目录下,ok,来看最后一步,打开tomcat的目录conf中的server.xml,加入:
reloadable=”true”/>
大功告成!运行它,启动tomcat,后在浏览器中输入http://localhost:8080/hello/HelloWorld,有了吗?

好了,就这么多,希望对你有点帮助。

补充:
############

jar基本操作:

############

1. 创建jar文件
jar cf jar-file input-file(s)
c—want to Create a JAR file.
f—want the output to go to a file rather than to stdout.
eg: 1)jar cf myjar.jar query_maintain_insert.htm
2)jar cvf myjar.jar query_maintain_insert.htm
v—Produces verbose(详细的) output.
3)jar cvf myjar.jar query_maintain_insert.htm mydirectory
4)jar cv0f myjar.jar query_maintain_insert.htm mydirectory
0—don’t want the JAR file to be compressed.
5)jar cmf MANIFEST.MF myjar.jar yahh.txt
m—Used to include manifest information from an existing manifest file.
6)jar cMf MANIFEST.MF myjar.jar yahh.txt
M—the default manifest file should not be produced.
7)jar cvf myjar.jar *
*—create all contents in current directory.
2. 察看jar文件
jar tf jar-file
t—want to view the Table of contents of the JAR file.
eg: 1)jar vft yahh.jar
v—Produces verbose(详细的) output.
3. 提取jar文件
jar xf jar-file [archived-file(s)]
x—want to extract files from the JAR archive.
eg: 1)jar xf yahh.jar yahh.txt(仅提取文件yahh.txt)
2)jar xf yahh.jar alex/yahhalex.txt(仅提取目录alex下的文件yahhalex.txt)
3)jar xf yahh.jar(提取该jar包中的所有文件或目录)
4. 修改Manifest文件
jar cmf manifest-addition jar-file input-file(s)
m—Used to include manifest information from an existing manifest file.

5. 更新jar文件

jar uf jar-file input-file(s)
u—want to update an existing JAR file.

复杂性的计量算法的复杂性是算法运行所需要的计算机资源的量,需要的时间资源的量称作时间复杂性,需要的空间(即存储器)资源的量称作空间复杂性。这个量应该集中反映算法中所采用的方法的效率,而从运行该算法的实际计算机中抽象出来。换句话说,这个量应该是只依赖于算法要解的问题的规模、算法的输入和算法本身的函数。如果分别用N、IA来表示算法要解问题的规模、算法的输入和算法本身,用C表示算法的复杂性,那么应该有:

C =F(N,I,A)

其中F(N,I,A)N,I,A的一个确定的三元函数。如果把时间复杂性和空间复杂性分开,并分别用TS来表示,那么应该有:

T =T(N,I,A) (2.1)

S =S(N,I,A) (2.2)

通常,我们让A隐含在复杂性函数名当中,因而将(2.1)和(2.2)分别简写为

T =T(N,I)

S =S(N,I)

由于时间复杂性和空间复杂性概念类同,计算方法相似,且空间复杂性分析相对地简单些,所以下文将主要地讨论时间复杂性。

下面以T(N,I)为例,将复杂性函数具体化。

根据T(N,I)的概念,它应该是算法在一台抽象的计算机上运行所需的时间。设此抽象的计算机所提供的元运算有k种,他们分别记为O1,O2 ,..,Ok;再设这些元运算每执行一次所需要的时间分别为t1,t2,..,tk 。对于给定的算法A,设经过统计,用到元运算Oi的次数为eii=1,2,..,k ,很明显,对于每一个i,1<=i<=k,eiNI的函数,即ei=ei(N,I)。那么有:

(2.3)

其中ti,i=1,2,..,k,是与N,I无关的常数。

显然,我们不可能对规模N的每一种合法的输入I都去统计ei(N,I),i=1,2,…,k。因此T(N,I)的表达式还得进一步简化,或者说,我们只能在规模为N的某些或某类有代表性的合法输入中统计相应的ei , i=1,2,…,k,评价时间复杂性。

下面只考虑三种情况的复杂性,即最坏情况、最好情况和平均情况下的时间复杂性,并分别记为Tmax(N )、Tmin(N)和Tavg(N )。在数学上有:

(2.4)

(2.5)

(2.6)

其中,DN是规模为N的合法输入的集合;I *DN中一个使T(N,I *)达到Tmax(N)的合法输入,DN中一个使T(N,)到Tmin(N)的合法输入;而P(I)是在算法的应用中出现输入I 的概率。

以上三种情况下的时间复杂性各从某一个角度来反映算法的效率,各有各的用处,也各有各的局限性。但实践表明可操作性最好的且最有实际价值的是最坏情况下的时间复杂性。下面我们将把对时间复杂性分析的主要兴趣放在这种情形上。

一般来说,最好情况和最坏情况的时间复杂性是很难计量的,原因是对于问题的任意确定的规模N达到了Tmax(N)的合法输入难以确定,而规模N的每一个输入的概率也难以预测或确定。我们有时也按平均情况计量时间复杂性,但那时在对P(I)做了一些人为的假设(比如等概率)之后才进行的。所做的假设是否符合实际总是缺乏根据。因此,在最好情况和平均情况下的时间复杂性分析还仅仅是停留在理论上。

下一页 »