﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>BlogJava-Vincent-随笔分类-杂七杂八</title><link>http://www.blogjava.net/lijiajia418/category/14434.html</link><description>Vicent's blog</description><language>zh-cn</language><lastBuildDate>Fri, 02 Mar 2007 06:59:28 GMT</lastBuildDate><pubDate>Fri, 02 Mar 2007 06:59:28 GMT</pubDate><ttl>60</ttl><item><title>正确优雅的解决用户退出问题——JSP和Struts解决方案</title><link>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65541.html</link><dc:creator>Binary</dc:creator><author>Binary</author><pubDate>Thu, 24 Aug 2006 07:56:00 GMT</pubDate><guid>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65541.html</guid><wfw:comment>http://www.blogjava.net/lijiajia418/comments/65541.html</wfw:comment><comments>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65541.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/lijiajia418/comments/commentRss/65541.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/lijiajia418/services/trackbacks/65541.html</trackback:ping><description><![CDATA[
		<p align="left">
				<font size="4">注：本文是由</font>
				<a href="/majianan">
						<font color="#4371a6">
								<font size="4">马嘉楠</font>
						</font>
				</a>
				<font size="4">翻译的javaworld.com上的一篇名为《Solving the logout problem properly and elegantly》的文章，原文请参看 </font>
				<a href="http://www.javaworld.com/javaworld/jw-09-2004/jw-0927-logout.html">
						<font color="#4371a6" size="4">Solving the logout problem properly and elegantly </font>
				</a>
				<font size="4">。<br />花了2天翻译过后才发现<strong>wolfmanchen</strong>已经捷足先登了，而且翻译得很准确（比我的好，^+^）我进行了修改，在此谢谢<strong>wolfmanchen。</strong><br />文中所有示例程序的代码可以从javaworld.com中下载，文章后面有资源链接。<br /><br />我看过之后觉得很好，希望对你也有所帮助！<br /><br /><br /></font>
				<font size="4">
						<font color="#0000ff">                            <font size="6"> 正确优雅的解决用户退出问题<br /></font>                                          <font size="5"> ------JSP和Struts解决方案</font></font>
						<br />
						<br />
				</font>
				<font size="4">
						<font color="#0000ff">
								<font size="5">摘要</font>
								<br />
						</font>
						<br />在一个有密码保护的Web应用当中，正确妥善的处理用户退出过程并不仅仅只需要调用HttpSession对象的invalidate()方法，因为现在大部分浏览器上都有后退(Back)和前进(Forward)按钮，允许用户后退或前进到一个页面。在用户退出一个Web应用之后，如果按了后退按钮，浏览器把缓存中的页面呈现给用户，这会使用户产生疑惑，他们会开始担心他们的个人数据是否安全。<br /><br />实际上，许多Web应用会弹出一个页面，警告用户退出时关闭整个浏览器，以此来阻止用户点击后退按钮。还有一些使用JavaScript，但在某些客户端浏览器中这却不一定起作用。这些解决方案大多数实现都很笨拙，且不能保证在任何情况下都100%有效，同时，它还要求用户有一定的操作经验。<br /><br />这篇文章以简单的程序示例阐述了正确解决用户退出问题的方案。作者Kevin Le首先描述了一个理想的密码保护Web应用，然后以示例程序解释问题如何产生并讨论解决问题的方案。文章虽然是针对JSP进行讨论阐述，但作者所阐述的概念很容易理解而且能够为其他Web技术所采用。最后最后，作者Kevin Le用Jakarta Struts更为优雅地解决用户退出问题。文中包含JSP和Struts的示例程序 (<em>3,700 words</em>; <strong>September 27, 2004</strong>) <!--</BLOCKQUOTE>--><br /><br /><br /><br /><br />大部分Web应用不会包含像银行账户或信用卡资料那样机密的信息，但是一旦涉及到敏感数据，就需要我们提供某些密码保护机制。例如，在一个工厂当中，工人必须通过Web应用程序访问他们的时间安排、进入他们的培训课程以及查看他们的薪金等等。此时应用SSL(Secure Socket Layer)就有些大材小用了(SSL页面不会在缓存中保存，关于SSL的讨论已经超出本文的范围)。但是这些应用又确实需要某种密码保护措施，否则，工人（在这种情况下，也就是Web应用的使用者）就可以发现工厂中所有员工的私人机密信息。<br /><br />类似上面的情况还包括位于公共图书馆、医院、<font color="#000000">网吧</font>等公共场所的计算机。在这些地方，许多用户共同使用几台计算机，此时保护用户的个人数据就显得至关重要。</font>
				<font size="4">
						<font color="#000000">同时应用程序的良好设计与实现对用户专业知识以及相关培训要求少之又少。 <br /></font>
						<br />让我们来看一下现实世界中一个完美的Web应用是怎样工作的：<br />1. 用户在浏览器中输入URL，访问一个页面。<br />2. Web应用显示一个登陆页面，要求用户输入有效的验证信息。<br />3. 用户输入用户名和密码。<br />4. 假设用户提供的验证信息是正确的，经过了验证过程，Web应用允许用户浏览他有权访问的区域。<br />5. 退出时，用户点击页面的退出按钮，Web应用显示确认页面，询问用户是否真的需要退出。一旦用户点击确定按钮，Session结束，Web应用重新定位到登陆页面。用户现在可以放心的离开而不用担心他的信息会被泄露。<br />6. 另一个用户坐到了同一台电脑前。他点击后退按钮，Web应用不应该显示上一个用户访问过的任何一个页面。<br />事实上，Web应用将一直停留在登陆页面上，除非第二个用户提供正确的验证信息，之后才可以访问他有权限的区域。<br /><br />通过示例程序，文章向您阐述了如何在一个Web应用中实现上面的功能。<br /><br /><br /><br /><br /><font color="#0000ff" size="5">一. JSP samples<br /></font><br />为了更为有效地向您说明这个解决方案，本文将从展示一个Web应用logoutSampleJSP1中碰到的问题开始。这个示例代表了许多没有正确解决退出过程的Web应用。logoutSampleJSP1包含一下JSP页面：login.jsp,  home.jsp,  secure1.jsp,  secure2.jsp,  logout.jsp,  loginAction.jsp, 和 logoutAction.jsp。其中页面home.jsp,  secure1.jsp,  secure2.jsp, 和 logout.jsp是不允许未经认证的用户访问的，也就是说，这些页面包含了重要信息，在用户登陆之前或者退出之后都不应该显示在浏览器中。login.jsp页面包含了用于用户输入用户名和密码的form。logout.jsp页面包含了要求用户确认是否退出的form。loginAction.jsp和logoutAction.jsp作为控制器分别包含了登陆和退出动作的代码。<br /><br />第二个Web示例应用logoutSampleJSP2展示了如何纠正示例logoutSampleJSP1中的问题。但是第二个示例logoutSampleJSP2自身也是有问题的。在特定情况下，退出问题依然存在。<br /><br />第三个Web示例应用logoutSampleJSP3对logoutSampleJSP2进行了改进，比较妥善地解决了退出问题。<br /><br />最后一个Web示例logoutSampleStruts展示了<font size="3"><font size="4">Jakarta</font></font>Struts如何优雅地解决退出问题。<br /><br />注意：本文所附示例在最新版本的Microsoft Internet Explorer (IE), Netscape Navigator, Mozilla, FireFox和Avant浏览器上测试通过。<br /><br /><br /><br /><font color="#0000ff" size="5">二. Login action</font><br /><br />Brian Pontarelli的经典文章 </font>
				<a href="http://www.javaworld.com/javaworld/jw-07-2004/jw-0726-security.html">
						<font color="#4371a6" size="4">《J2EE Security: Container Versus Custom》 </font>
				</a>
				<font size="4">讨论了不同的J2EE认证方法。文章同时指出，HTTP协议和基于form的认证方法并不能提供处理用户退出问题的机制。因此，解决方法便是引入用户自定义的安全实现机制，这就提供了更大的灵活性。<br /><br />在用户自定义的认证方法中，普遍采用的方法是从用户提交的form中获得用户输入的认证信息，然后到诸如LDAP (lightweight directory access protocol)或关系数据库(relational database management system, RDBMS)的安全域中进行认证。如果用户提供的认证信息是有效的，登陆动作在HttpSession对象中保存某个对象。HttpSession存在着保存的对象则表示用户已经登陆到Web应用当中。为了方便起见，本文所附的示例只在HttpSession中保存一个用户名以表明用户已经登陆。清单1是从loginAction.jsp页面中节选的一段代码以此讲解登陆动作：<br /><br /><br /><br /><br />Listing 1 <br /><font color="#0000aa">//...</font></font>
				<font color="black">
						<br />
				</font>
				<font size="4">
						<font color="#0000aa">//initialize RequestDispatcher object; set forward to home page by default</font>
				</font>
				<font color="black">
						<br />
						<font size="4">RequestDispatcher rd = request.getRequestDispatcher( </font>
				</font>
				<font size="4">
						<font color="#00bb00">"home.jsp"</font>
				</font>
				<font size="4">
						<font color="black">);<br /><br /></font>
						<font color="#0000aa">//Prepare connection and statement</font>
				</font>
				<font color="black">
						<br />
						<font size="4">rs = stmt.executeQuery( </font>
				</font>
				<font size="4">
						<font color="#00bb00">"select password from USER where userName = '"</font>
						<font color="black">+ userName + </font>
						<font color="#00bb00">"'"</font>
				</font>
				<font color="black" size="4">);<br /><b>if</b> (rs.next()) { <br /></font>
				<font color="#0000aa" size="4">//Query only returns 1 record in the result set; <br />//Only 1 </font>
				<font size="4">
						<font color="black">
								<font color="#003399">password per userName which is also the primary key<br /></font>
								<b>    if</b> (rs.getString( </font>
						<font color="#00bb00">"password"</font>
						<font color="black">).equals(password)) { </font>
						<font color="#0000aa">//If valid password</font>
				</font>
				<font color="black">
						<br />
						<font size="4">        session.setAttribute( </font>
				</font>
				<font size="4">
						<font color="#00bb00">"User"</font>
						<font color="black">, userName); </font>
						<font color="#0000aa">//Saves username string in the session object</font>
				</font>
				<font color="black">
						<br />
						<font size="4">    }<br /><b>    else</b> { </font>
				</font>
				<font size="4">
						<font color="#0000aa">//Password does not match, i.e., invalid user password</font>
				</font>
				<font color="black">
						<br />
						<font size="4">        request.setAttribute( </font>
				</font>
				<font size="4">
						<font color="#00bb00">"Error"</font>
						<font color="black">, </font>
						<font color="#00bb00">"Invalid password."</font>
				</font>
				<font size="4">
						<font color="black">); <br /><br />        rd = request.getRequestDispatcher(</font>
						<font color="#00bb00">"login.jsp"</font>
				</font>
				<font size="4">
						<font color="black">);<br />    }<br />} </font>
						<font color="#0000aa">//No record in the result set, i.e., invalid username</font>
				</font>
				<font color="black">
						<br />
						<font size="4">
								<b>    else</b> {<br /><br />        request.setAttribute( </font>
				</font>
				<font size="4">
						<font color="#00bb00">"Error"</font>
						<font color="black">, </font>
						<font color="#00bb00">"Invalid user name."</font>
				</font>
				<font size="4">
						<font color="black">);<br />        rd = request.getRequestDispatcher(</font>
						<font color="#00bb00">"login.jsp"</font>
				</font>
				<font size="4">
						<font color="black">);<br />    }<br />}<br /><br /></font>
						<font color="#0000aa">//As a controller, loginAction.jsp finally either forwards to "login.jsp" or "home.jsp"</font>
				</font>
				<font color="black">
						<br />
						<font size="4">rd.forward(request, response);<br /></font>
				</font>
				<font size="4">
						<font color="#0000aa">//...</font>
				</font>
				<font color="black">
						<br />
				</font>
		</p>
		<pre>
				<font size="4">
				</font>
		</pre>
		<br />
		<br />
		<font size="4">本文当中所附Web应用示例均以关系型数据库作为安全域，但本问所讲述的内容同样适用于其他任何类型的安全域。<br /><br /><br /><br /><font color="#0000ff" size="5">三. Logout action <br /><br /></font>退出动作包含删除用户名以及调用用户的HttpSession对象的invalidate()方法。清单2是从loginoutAction.jsp中节选的一段代码，以此说明退出动作：<br /><br /><br /><br />Listing 2 <br /><font color="#0000aa">//...</font></font>
		<font color="black">
				<br />
				<font size="4">session.removeAttribute( </font>
		</font>
		<font size="4">
				<font color="#00bb00">"User"</font>
		</font>
		<font size="4">
				<font color="black">);<br />session.invalidate();<br /></font>
				<font color="#0000aa">//...</font>
		</font>
		<font color="black">
				<br />
				<pre>
						<font size="4">
						</font>
				</pre>
				<br />
				<br />
				<font size="4">
						<font color="#0000ff" size="5">四. 阻止未经认证访问受保护的JSP页面<br /></font>
						<br />从提交的form中获取用户提交的认证信息并经过验证后，登陆动作仅仅在HttpSession对象中写入一个用户名。退出动作则刚好相反，它从HttpSession中删除用户名并调用HttpSession对象的invalidate()方法。为了使登陆和退出动作真正发挥作用，所有受保护的JSP页面必须首先验证HttpSession中包含的用户名，以便确认用户当前是否已经登陆。如果HttpSession中包含了用户名，就说明用户已经登陆，Web应用会将剩余的JSP页中的动态内容发送给浏览器。否则，JSP页将跳转到登陆页面，login.jsp。页面home.jsp,  secure1.jsp,  secure2.jsp和 logout.jsp均包含清单3中的代码段：<br /><br /><br />Listing 3 <br /></font>
		</font>
		<font size="4">
				<font color="#0000aa">//...</font>
				<font color="#4b4b4b">
				</font>
		</font>
		<font color="black">
				<br />
				<font size="4">String userName = (String) session.getAttribute( </font>
		</font>
		<font size="4">
				<font color="#00bb00">"User"</font>
				<font color="#4b4b4b">
				</font>
		</font>
		<font size="4">
				<font color="black">);<br /><b>if</b> (<b>null</b> == userName) {<br />    request.setAttribute(</font>
				<font color="#4b4b4b">
				</font>
				<font color="#00bb00">"Error"</font>
				<font color="#4b4b4b">
				</font>
				<font color="black">, </font>
				<font color="#00bb00">"Session has ended. Please login."</font>
				<font color="#4b4b4b">
				</font>
		</font>
		<font size="4">
				<font color="black">);<br />    RequestDispatcher rd = request.getRequestDispatcher(</font>
				<font color="#4b4b4b">
				</font>
				<font color="#00bb00">"login.jsp"</font>
				<font color="#4b4b4b">
				</font>
		</font>
		<font size="4">
				<font color="black">);<br />    rd.forward(request, response);<br />}<br /></font>
				<font color="#0000aa">//...</font>
				<font color="#4b4b4b">
				</font>
		</font>
		<br />
		<font size="4">
				<font color="#0000aa">//Allow the rest of the dynamic content in this JSP to be served to the browser</font>
				<font color="#4b4b4b">
				</font>
		</font>
		<br />
		<font size="4">
				<font color="#0000aa">//...</font>
				<font color="#4b4b4b">
				</font>
		</font>
		<font color="black">
				<br />
				<pre>
						<font size="4">
						</font>
				</pre>
				<br />
				<br />
				<font size="4">在这个代码段中，程序从HttpSession中<font color="#000000">检索</font>username字符串。如果username字符串为空，Web应用则自动中止执行当前页面并跳转到登陆页，同时给出错误信息“Session has ended. Please log in.”；如果不为空，Web应用继续执行，把剩余的页面提供给用户，从而使JSP页面的动态内容成为服务对象。<br /><br /><br /><br /><font color="#0000ff">五.运行logoutSampleJSP1</font><br /><br />运行logoutSampleJSP1将会出现如下几种情形：<br /><br />• 如果用户没有登陆，Web应用将会正确中止受保护页面home.jsp,  secure1.jsp,  secure2.jsp和logout.jsp中动态内容的执行。也就是说，假如用户并没有登陆，但是在浏览器地址栏中直接敲入受保护JSP页的地址试图访问，Web应用将自动跳转到登陆页面，同时显示错误信息“Session has ended.Please log in.”<br /><br />• 同样的，当一个用户已经退出，Web应用将会正确中止受保护页面home.jsp,  secure1.jsp,  secure2.jsp和logout.jsp中动态内容的执行。也就是说，用户退出以后，如果在浏览器地址栏中直接敲入受保护JSP页的地址试图访问，Web应用将自动跳转到登陆页面，同时显示错误信息“Session has ended.Please log in.”<br /><br />• 用户退出以后，如果点击浏览器上的后退按钮返回到先前的页面，Web应用将不能正确保护受保护的JSP页面——在Session销毁后（用户退出）受保护的JSP页会重新显示在浏览器中。然而，点击该页面上的任何链接，Web应用都会跳转到登陆页面，同时显示错误信息“Session has ended.Please log in.”<br /><br /><br /><br /><font color="#0000ff">六. 阻止浏览器缓存</font><br />  <br />上述问题的根源就在于现代大部分浏览器都有一个后退按钮。当点击后退按钮时，默认情况下浏览器不会从Web服务器上重新获取页面，而是简单的从浏览器缓存中重新载入页面。这个问题并不仅限于基于Java(JSP/servlets/Struts) 的Web应用当中，在基于PHP (Hypertext Preprocessor)、ASP、(Active Server Pages)、和.NET的Web应用中也同样存在。<br /><br />在用户点击后退按钮之后，浏览器到Web服务器（一般来说）或者应用服务器（在java的情况下）再从服务器到浏览器这样通常意义上的HTTP回路并没有建立。仅仅只是用户，浏览器和缓存之间进行了交互。所以即使受保护的JSP页面，例如home.jsp,  secure1.jsp,  secure2.jsp和logout.jsp包含了清单3上的代码，当点击后退按钮时，这些代码也永远不会执行的。<br /><br />缓存的好坏，真是仁者见仁智者见智。缓存事实上的确提供了一些便利，但这些便利通常只存在于静态的HTML页面或基于图形或<font color="#000000">影像</font>的页面。而另一方面，Web应用通常是面向数据的。由于Web应用中的数据频繁变更，所以与为了节省时间从缓存中读取并显示过期的数据相比，提供最新的数据显得尤为重要！<br /><br />幸运的是，HTTP头信息“Expires”和“Cache-Control”为应用程序服务器提供了一个控制浏览器和代理服务器上缓存的机制。HTTP头信息Expires告诉代理服务器它的缓存页面何时将过期。HTTP1.1规范中新定义的头信息Cache-Control在Web应用当中可以通知浏览器不缓存任何页面。当点击后退按钮时，浏览器发送Http请求道应用服务器以便获取该页面的最新拷贝。如下是使用Cache-Control的基本方法：<br /><br />• no-cache:强制缓存从服务器上获取该页面的最新拷贝<br />• no-store: 在任何情况下缓存不保存该页面<br /><br />HTTP1.0规范中的Pragma:no-cache等同于HTTP1.1规范中的Cache-Control:no-cache，同样可以包含在头信息中。 <br /><br />通过使用HTTP头信息的cache控制，第二个示例应用logoutSampleJSP2解决了logoutSampleJSP1的问题。logoutSampleJSP2与logoutSampleJSP1不同表现在如下代码段中，这一代码段加入进所有受保护的页面中：<br /><br /><br /><br /><br /></font>
		</font>
		<font size="4">
				<font color="#0000aa">//...</font>
				<font color="#4b4b4b">
				</font>
		</font>
		<font color="black">
				<br />
				<font size="4">response.setHeader( </font>
		</font>
		<font size="4">
				<font color="#00bb00">"Cache-Control"</font>
				<font color="#4b4b4b">
				</font>
				<font color="black">,</font>
				<font color="#4b4b4b">
				</font>
				<font color="#00bb00">"no-cache"</font>
				<font color="#4b4b4b">
				</font>
				<font color="black">); </font>
				<font color="#0000aa">//Forces caches to obtain a new copy of the page from the origin server</font>
				<font color="#4b4b4b">
				</font>
		</font>
		<font color="black">
				<br />
				<font size="4">response.setHeader( </font>
		</font>
		<font size="4">
				<font color="#00bb00">"Cache-Control"</font>
				<font color="#4b4b4b">
				</font>
				<font color="black">,</font>
				<font color="#4b4b4b">
				</font>
				<font color="#00bb00">"no-store"</font>
				<font color="#4b4b4b">
				</font>
				<font color="black">); </font>
				<font color="#0000aa">//Directs caches not to store the page under any circumstance</font>
				<font color="#4b4b4b">
				</font>
		</font>
		<font color="black">
				<br />
				<font size="4">response.setDateHeader( </font>
		</font>
		<font size="4">
				<font color="#00bb00">"Expires"</font>
				<font color="#4b4b4b">
				</font>
				<font color="black">, 0); </font>
				<font color="#0000aa">//Causes the proxy cache to see the page as "stale"</font>
				<font color="#4b4b4b">
				</font>
		</font>
		<font color="black">
				<br />
				<font size="4">response.setHeader( </font>
		</font>
		<font size="4">
				<font color="#00bb00">"Pragma"</font>
				<font color="#4b4b4b">
				</font>
				<font color="black">,</font>
				<font color="#4b4b4b">
				</font>
				<font color="#00bb00">"no-cache"</font>
				<font color="#4b4b4b">
				</font>
				<font color="black">); </font>
				<font color="#0000aa">//HTTP 1.0 backward compatibility</font>
				<font color="#4b4b4b">
				</font>
		</font>
		<font color="black">
				<br />
				<font size="4">String userName = (String) session.getAttribute( </font>
		</font>
		<font size="4">
				<font color="#00bb00">"User"</font>
				<font color="#4b4b4b">
				</font>
		</font>
		<font size="4">
				<font color="black">);<br /><b>if</b> (<b>null</b> == userName) {<br />    request.setAttribute(</font>
				<font color="#4b4b4b">
				</font>
				<font color="#00bb00">"Error"</font>
				<font color="#4b4b4b">
				</font>
				<font color="black">, </font>
				<font color="#00bb00">"Session has ended. Please login."</font>
				<font color="#4b4b4b">
				</font>
		</font>
		<font size="4">
				<font color="black">);<br />    RequestDispatcher rd = request.getRequestDispatcher(</font>
				<font color="#4b4b4b">
				</font>
				<font color="#00bb00">"login.jsp"</font>
				<font color="#4b4b4b">
				</font>
		</font>
		<font size="4">
				<font color="black">);<br />    rd.forward(request, response);<br />}<br /></font>
				<font color="#0000aa">//...</font>
				<font color="#4b4b4b">
				</font>
		</font>
		<font color="black">
				<br />
				<pre>
						<font size="4">
						</font>
				</pre>
				<br />
				<br />
				<font size="4">通过设置头信息和检查HttpSession对象中的用户名来确保浏览器不会缓存JSP页面。同时，如果用户未登陆，JSP页面的动态内容不会发送到浏览器，取而代之的将是登陆页面login.jsp。<br /><br /><br /><br /><font color="#0000ff" size="5">七. 运行logoutSampleJSP2</font><br /><br />运行Web示例应用logoutSampleJSP2后将会看到如下结果：<br /><br />• 当用户退出后试图点击后退按钮，浏览器不会重新显示受保护的页面，它只会显示登陆页login.jsp同时给出提示信息Session has ended. Please log in.<br /><br />• 然而，当按了后退按钮返回的页是处理用户提交数据的页面时，IE和Avant浏览器将弹出如下信息提示：<br /><br />           警告：页面已过期<br /><font size="3"><font size="4">           The page you requested was created using information you submitted in a form. This page is no longer available. As a security precaution, Internet Explorer does not automatically  resubmit your information for you.</font><br /><br /><font size="4">Mozilla和FireFox浏览器将会显示一个对话框，提示信息如下：<br /></font><br />      <font size="4">      The page you are trying to view contains POSTDATA that has expired from cache. If you  resend the data, any action from the form carried out (such as a search or online purchase) will be repeated. To resend the data, click OK. Otherwise, click Cancel.</font><br /></font><br />在IE和Avant浏览器中选择刷新或者在Mozilla和FireFox浏览器中选择重新发送数据后，前一个JSP页面将重新显示在浏览器中。显然的，这病不是我们所想看到的因为它违背了logout动作的目的。发生这一现象时，很可能是一个恶意用户在尝试获取其他用户的数据。然而，这个问题仅仅出现在点击后退按钮后，浏览器返回到一个处理POST请求的页面。<br /><br /><br /><br /><font color="#0000ff" size="5">八. 记录最后登陆时间 <br /></font><br />上述问题的发生是因为浏览器重新提交了其缓存中的数据。这本文的例子中，数据包含了用户名和密码。尽管IE浏览器给出了安全警告信息，但事实上浏览器此时起到了负面作用。<br /><br />为了解决logoutSampleJSP2中出现的问题，logoutSampleJSP3的login.jsp除了包含username和password的之外，还增加了一个称作lastLogon的隐藏表单域，此表单域将会动态的被初始化为一个long型值。这个long型值是通过调用System.currentTimeMillis()获取到的自1970年1月1日以来的毫秒数。当login.jsp中的form提交时，loginAction.jsp首先将隐藏域中的值与用户数据库中的lastLogon值进行比较。只有当lastLogon表单域中的值大于数据库中的值时Web应用才认为这是个有效的登陆。<br /><br />为了验证登陆，数据库中lastLogon字段必须用表单中的lastLogon值进行更新。上例中，当浏览器重复提交缓存中的数据时，表单中的lastLogon值不比数据库中的lastLogon值大，因此，loginAction将跳转到login.jsp页面，并显示如下错误信息“Session has ended.Please log in.”清单5是loginAction中节选的代码段：<br /><br /><br /><br /><br />清单5<br /></font>
		</font>
		<font size="4">
				<font color="#0000aa">//...</font>
				<font color="#4b4b4b">
				</font>
		</font>
		<font color="black">
				<br />
				<font size="4">RequestDispatcher rd = request.getRequestDispatcher( </font>
		</font>
		<font size="4">
				<font color="#00bb00">"home.jsp"</font>
				<font color="#4b4b4b">
				</font>
				<font color="black">); </font>
				<font color="#0000aa">//Forward to homepage by default</font>
				<font color="#4b4b4b">
				</font>
		</font>
		<br />
		<font size="4">
				<font color="#0000aa">//...</font>
				<font color="#4b4b4b">
				</font>
		</font>
		<font color="black">
				<br />
				<font size="4">
						<b>if</b> (rs.getString( </font>
		</font>
		<font size="4">
				<font color="#00bb00">"password"</font>
				<font color="#4b4b4b">
				</font>
				<font color="black">).equals(password)) { </font>
				<font color="#0000aa">//If valid password</font>
				<font color="#4b4b4b">
				</font>
		</font>
		<font color="black">
				<br />
				<font size="4">
						<b>    long</b> lastLogonDB = rs.getLong( </font>
		</font>
		<font size="4">
				<font color="#00bb00">"lastLogon"</font>
				<font color="#4b4b4b">
				</font>
		</font>
		<font size="4">
				<font color="black">);<br /><b>    if</b> (lastLogonForm &gt; lastLogonDB) {<br />        session.setAttribute(</font>
				<font color="#4b4b4b">
				</font>
				<font color="#00bb00">"User"</font>
				<font color="#4b4b4b">
				</font>
				<font color="black">, userName); </font>
				<font color="#0000aa">//Saves username string in the session object</font>
				<font color="#4b4b4b">
				</font>
		</font>
		<font color="black">
				<br />
				<font size="4">        stmt.executeUpdate( </font>
		</font>
		<font size="4">
				<font color="#00bb00">"update USER set lastLogon= "</font>
				<font color="#4b4b4b">
				</font>
				<font color="black">+ lastLogonForm + </font>
				<font color="#00bb00">" where userName = '"</font>
				<font color="#4b4b4b">
				</font>
				<font color="black">+ userName + </font>
				<font color="#00bb00">"'"</font>
				<font color="#4b4b4b">
				</font>
		</font>
		<font size="4">
				<font color="black">);<br />    }<br /><b>    else</b> {<br />        request.setAttribute(</font>
				<font color="#4b4b4b">
				</font>
				<font color="#00bb00">"Error"</font>
				<font color="#4b4b4b">
				</font>
				<font color="black">, </font>
				<font color="#00bb00">"Session has ended. Please login."</font>
				<font color="#4b4b4b">
				</font>
		</font>
		<font size="4">
				<font color="black">);<br />        rd = request.getRequestDispatcher(</font>
				<font color="#4b4b4b">
				</font>
				<font color="#00bb00">"login.jsp"</font>
				<font color="#4b4b4b">
				</font>
		</font>
		<font size="4">
				<font color="black">); }<br />    }<br /><b>else</b> { </font>
				<font color="#0000aa">//Password does not match, i.e., invalid user password</font>
				<font color="#4b4b4b">
				</font>
		</font>
		<font color="black">
				<br />
				<font size="4">    request.setAttribute( </font>
		</font>
		<font size="4">
				<font color="#00bb00">"Error"</font>
				<font color="#4b4b4b">
				</font>
				<font color="black">, </font>
				<font color="#00bb00">"Invalid password."</font>
				<font color="#4b4b4b">
				</font>
		</font>
		<font size="4">
				<font color="black">);<br />    rd = request.getRequestDispatcher(</font>
				<font color="#4b4b4b">
				</font>
				<font color="#00bb00">"login.jsp"</font>
				<font color="#4b4b4b">
				</font>
		</font>
		<font size="4">
				<font color="black">); <br />}<br /></font>
				<font color="#0000aa">//...</font>
				<font color="#4b4b4b">
				</font>
		</font>
		<font color="black">
				<br />
				<font size="4">rd.forward(request, response);<br /></font>
		</font>
		<font size="4">
				<font color="#0000aa">//...</font>
				<font color="#4b4b4b">
				</font>
		</font>
		<font color="black">
				<br />
				<pre>
						<font size="4">
						</font>
				</pre>
				<br />
				<br />
				<font size="4">为了实现上述方法，你必须记录每个用户的最后登陆时间。对于采用关系型数据库安全域来说，这点可以可以通过在某个表中加上lastLogin字段轻松实现。虽然对LDAP以及其他的安全域来说需要稍微动下脑筋，但最后登陆方法很显然是可以实现的。<br /><br />表示最后登陆时间的方法有很多。示例logoutSampleJSP3利用了自1970年1月1日以来的毫秒数。这个方法即使在许多人在不同浏览器中用一个用户账号登陆时也是可行的。<br /><br /><br /><br /><font color="#0000ff" size="5">九. 运行logoutSampleJSP3</font><br /><br />运行示例logoutSampleJSP3将展示如何正确处理退出问题。一旦用户退出，点击浏览器上的后退按钮在任何情况下都不会在浏览器中显示受保护的JSP页面。这个示例展示了如何正确处理退出问题而不需要对用户进行额外的培训。<br /><br />为了使代码更简练有效，一些冗余的代码可以剔除。一种途径就是把清单4中的代码写到一个单独的JSP页中，其他JSP页面可以通过标签</font>
				<font face="Courier New" size="4">&lt;jsp:include&gt;进行使用</font>
				<?xml:namespace prefix = jsp /?>
				<jsp:include>
						<font size="4">。<br /><br /><br /><br /><font color="#0000ff" size="5">十. Struts框架下的退出实现</font><br /><br />与直接使用JSP或JSP/servlets进行Web应用开发相比，另一个更好的可选方案是使用Struts。对于一个基于Struts的Web应用来说，添加一个处理退出问题的框架可以优雅地不费气力的实现。这归功于Struts是采用MVC设计模式的，因此可以将模型和视图代码清晰的分离。另外，Java是一个面向对象的语言，支持继承，可以比JSP中的脚本更为容易地实现代码重用。对于Struts来说，清单4中的代码可以从JSP页面中移植到Action类的execute()方法中。<br /><br />此外，我们还可以定义一个继承Struts Action类的Action基类，其execute()方法中包含了类似清单4中的代码。通过继承，其他Action类可以继承基本类中的通用逻辑来设置HTTP头信息以及检索HttpSession对象中的username字符串。这个Action基类是一个抽象类并定义了一个抽象方法executeAction()。所有继承自Action基类的子类都必须实现exectuteAction()方法而不是覆盖它。通过继承这一机制，所有继承自Action基类的子类都不必再担心退出代码接口。（plumbing实在不知道怎么翻译了，^+^，高手帮帮忙啊！原文：With this inheritance hierarchy in place, all of the base <code>Action</code>'s subclasses no longer need to worry about any plumbing logout code.）。他们将只包含正常的业务逻辑代码。清单6是基类的部分代码：<br /><br /><br /><br />清单6<br /><b>public</b><b>abstract</b><b>class</b> BaseAction <b>extends</b> Action {<br /><b>    public</b> ActionForward execute(ActionMapping mapping, ActionForm form,<br />                                                 HttpServletRequest request, HttpServletResponse response) <br />               throws IOException, ServletException {<br /><br />response.setHeader( </font>
				</jsp:include>
		</font>
		<font size="4">
				<font color="#00bb00">"Cache-Control"</font>
				<font color="#4b4b4b">
				</font>
				<font color="black">,</font>
				<font color="#4b4b4b">
				</font>
				<font color="#00bb00">"no-cache"</font>
				<font color="#4b4b4b">
				</font>
				<font color="black">); </font>
				<font color="#0000aa">//Forces caches to obtain a new copy of the page from the origin server</font>
				<font color="#4b4b4b">
				</font>
		</font>
		<font color="black">
				<br />
				<font size="4">response.setHeader( </font>
		</font>
		<font size="4">
				<font color="#00bb00">"Cache-Control"</font>
				<font color="#4b4b4b">
				</font>
				<font color="black">,</font>
				<font color="#4b4b4b">
				</font>
				<font color="#00bb00">"no-store"</font>
				<font color="#4b4b4b">
				</font>
				<font color="black">); </font>
				<font color="#0000aa">//Directs caches not to store the page under any circumstance</font>
				<font color="#4b4b4b">
				</font>
		</font>
		<font color="black">
				<br />
				<font size="4">response.setDateHeader( </font>
		</font>
		<font size="4">
				<font color="#00bb00">"Expires"</font>
				<font color="#4b4b4b">
				</font>
				<font color="black">, 0); </font>
				<font color="#0000aa">//Causes the proxy cache to see the page as "stale"</font>
				<font color="#4b4b4b">
				</font>
		</font>
		<font color="black">
				<br />
				<font size="4">response.setHeader( </font>
		</font>
		<font size="4">
				<font color="#00bb00">"Pragma"</font>
				<font color="#4b4b4b">
				</font>
				<font color="black">,</font>
				<font color="#4b4b4b">
				</font>
				<font color="#00bb00">"no-cache"</font>
				<font color="#4b4b4b">
				</font>
				<font color="black">); </font>
				<font color="#0000aa">//HTTP 1.0 backward compatibility </font>
		</font>
		<font color="black">
				<br />
				<br />
				<font size="4">
						<b>if</b> (!<b>this</b>.userIsLoggedIn(request)) {<br />    ActionErrors errors = <b>new</b> ActionErrors();<br /><br />    errors.add( </font>
		</font>
		<font size="4">
				<font color="#00bb00">"error"</font>
				<font color="#4b4b4b">
				</font>
				<font color="black">, <b>new</b> ActionError(</font>
				<font color="#4b4b4b">
				</font>
				<font color="#00bb00">"logon.sessionEnded"</font>
				<font color="#4b4b4b">
				</font>
		</font>
		<font size="4">
				<font color="black">));<br /><b>    this</b>.saveErrors(request, errors);<br /><br /><b>    return</b> mapping.findForward(</font>
				<font color="#4b4b4b">
				</font>
				<font color="#00bb00">"sessionEnded"</font>
				<font color="#4b4b4b">
				</font>
		</font>
		<font size="4">
				<font color="black">);<br />}<br /><br /><b>return</b> executeAction(mapping, form, request, response);<br />}<br /><br /><b>protected</b><b>abstract</b> ActionForward executeAction(ActionMapping mapping,  ActionForm form, HttpServletRequest request, HttpServletResponse response) <br />throws IOException, ServletException; <br /><br /><b>private</b><b>boolean</b> userIsLoggedIn(HttpServletRequest request) {<br /><b>if</b> (request.getSession().getAttribute(</font>
				<font color="#4b4b4b">
				</font>
				<font color="#00bb00">"User"</font>
				<font color="#4b4b4b">
				</font>
		</font>
		<font color="black">
				<font size="4">) == <b>null</b>) {<br /><b>    return</b> false;<br />}<br /><br /><b>return </b><b>true</b>;<br />}<br />}<br /></font>
				<pre>
						<font size="4">
						</font>
				</pre>
				<br />
				<br />
				<font size="4">清单6中的代码与清单4中的很相像，唯一区别是用ActionMapping findForward替代了RequestDispatcher forward。清单6中，如果在HttpSession中未找到username字符串，ActionMapping对象将找到名为sessionEnded的forward元素并跳转到对应的path。如果找到了，子类通过实现executeAction()方法，将执行他们自己的业务逻辑。因此，在struts-web.xml配置文件中为所有继承自Action基类的子类声明个一名为sessionEnded的forward元素并将其指向login.jsp是至关重要的。清单7以secure1 action阐明了这样一个声明：<br /><br /><br />清单7<br />&lt;action path= </font>
		</font>
		<font size="4">
				<font color="#00bb00">"/secure1"</font>
				<font color="#4b4b4b">
				</font>
		</font>
		<font color="black">
				<br />
				<font size="4">type=</font>
		</font>
		<font size="4">
				<font color="#00bb00">"com.kevinhle.logoutSampleStruts.Secure1Action"</font>
				<font color="#4b4b4b">
				</font>
		</font>
		<font color="black">
				<br />
				<font size="4">scope=</font>
		</font>
		<font size="4">
				<font color="#00bb00">"request"</font>
				<font color="#4b4b4b">
				</font>
		</font>
		<font size="4">
				<font color="black">&gt;<br />&lt;forward name=</font>
				<font color="#4b4b4b">
				</font>
				<font color="#00bb00">"success"</font>
				<font color="#4b4b4b">
				</font>
				<font color="black">path=</font>
				<font color="#4b4b4b">
				</font>
				<font color="#00bb00">"/WEB-INF/jsps/secure1.jsp"</font>
				<font color="#4b4b4b">
				</font>
		</font>
		<font size="4">
				<font color="black">/&gt;<br />&lt;forward name=</font>
				<font color="#4b4b4b">
				</font>
				<font color="#00bb00">"sessionEnded"</font>
				<font color="#4b4b4b">
				</font>
				<font color="black">path=</font>
				<font color="#4b4b4b">
				</font>
				<font color="#00bb00">"/login.jsp"</font>
				<font color="#4b4b4b">
				</font>
		</font>
		<font color="black">
				<font size="4">/&gt;<br />&lt;/action&gt;<br /><br /></font>
				<pre>
						<font size="4">
						</font>
				</pre>
				<br />
				<br />
				<font size="4">继承自BaseAction类的子类Secure1Action实现了executeAction()方法而不是覆盖它。Secure1Action类不需要执行任何退出代码，如清单8：<br /><br /><br /><br />清单8<br /><b>public</b><b>class</b> Secure1Action <b>extends</b> BaseAction {<br /><b>    public</b> ActionForward executeAction(ActionMapping mapping, ActionForm form,<br />                                                           HttpServletRequest request, HttpServletResponse response)<br />               throws IOException, ServletException {<br /><br />               HttpSession session = request.getSession(); <br /><b>               return</b> (mapping.findForward( </font>
		</font>
		<font size="4">
				<font color="#00bb00">"success"</font>
				<font color="#4b4b4b">
				</font>
		</font>
		<font color="black">
				<font size="4">));<br />         }<br />}<br /></font>
				<pre>
						<font size="4">
						</font>
				</pre>
				<br />
				<br />
				<font size="4">上面的解决方案是如此的优雅有效，它仅仅只需要定义一个基类而不需要额外的代码工作。将通用的行为方法写成一个继承StrutsAction的基类是者的推荐的，而且这是许多Struts项目的共同经验。<br /><br /><br /><br /><br /><font color="#0000ff" size="5">十一. 局限性</font><br /><br />上述解决方案对JSP或基于Struts的Web应用都是非常简单而实用的，但它还是有某些局限。在我看来，这些局限并不是至关紧要的。<br /><br />•   通过取消与浏览器后退按钮有关的缓存机制，一旦用户离开页面而没有对数据进行提交，那么页面将会丢失所有输入的数据。即使点击浏览器的后退按钮返回到刚才的页面也无济于事，因为浏览器会从服务器获取新的空白页面显示出来。一种可能的方法并不是阻止这些JSP页面包含数据数据表格。在基于JSP的解决方案当中，那些JSP页面可以删除在清单4中的代码。在基于Struts的解决方案当中，Action类需要继承自Struts的Action类而非BaseAction类。<br /><br />•  上面讲述的方法在Opera浏览器中不能工作。事实上没有适用于Opera浏览器的解决方案，因为Opera浏览器与2616 Hypertext Transfer Protocol—HTTP/1.1紧密相关。Section 13.13 of RFC 2616 states:           
<blockquote>User agents often have history mechanisms, such as "Back" buttons and history lists, which can be used to redisplay an entity retrieved earlier in a session. 
<p>History mechanisms and caches are different. In particular history mechanisms SHOULD NOT try to show a semantically transparent view of the current state of a resource. Rather, a history mechanism is meant to show exactly what the user saw at the time when the resource was retrieved.</p></blockquote>幸运的是，使用微软的IE和基于Mozilla的浏览器用户多余Opera浏览器。上面讲述的解决方案对大多数用户来说还是有帮助的。另外，无论是否使用上述的解决方案，Opera浏览器仍然存在用户退出问题，就Opera来说没有任何改变。然而，正如RFC2616中所说，通过像上面一样设置头文件指令，当用户点击一个链接时，Opera浏览器不会从缓存中获取页面。<br /><br /><br /><br /><br /><font color="#0000ff" size="5">十二. 结论</font><br /><br />这篇文章讲述了处理退出问题的解决方案，尽管方案简单的令人惊讶，但在所有情况下都能有效地工作。无论是对JSP还是Struts，所要做的不过是写一段不超过50行的代码以及一个记录用户最后登陆时间的方法。在有密码保护的Web应用中使用这些方案能够确保在任何情况下用户的私人数据不致泄露，同时，也能增加用户的经验。<br /><br />About the author<br />[i]Kevin H. Le[/i] has more than 12 years of experience in software development. In the first half of his career, his programming language of choice was C++. In 1997, he shifted his focus to Java. He has engaged in and successfully completed several J2EE and EAI projects as both developer and architect. In addition to J2EE, his current interests now include Web services and SOA. More information on Kevin can be found on his Website http://kevinhle.com.<br /><br /></font>
				<ul>
						<a name="resources">
						</a>
						<font size="4">
								<strong>Resources</strong>
								<br />
						</font>
						<li>
								<font size="4">Download the code that accompanies this article: <br /></font>
								<a href="http://www.javaworld.com/javaworld/jw-09-2004/logout/jw-0927-logout.zip">
										<font color="#4371a6" size="4">http://www.javaworld.com/javaworld/jw-09-2004/logout/jw-0927-logout.zip </font>
								</a>
								<br />
						</li>
						<li>
								<font size="4">"J2EE Security: Container Versus Custom," Brian Pontarelli (<em>JavaWorld,</em> July 2004): <br /></font>
								<a href="http://www.javaworld.com/javaworld/jw-07-2004/jw-0726-security.html">
										<font color="#4371a6" size="4">http://www.javaworld.com/javaworld/jw-07-2004/jw-0726-security.html </font>
								</a>
								<br />
						</li>
						<li>
								<font size="4">Hypertext Transfer Protocol—HTTP/1.1 (RFC 2616): <br /></font>
								<a href="http://www.ietf.org/rfc/rfc2616.txt">
										<font color="#4371a6" size="4">http://www.ietf.org/rfc/rfc2616.txt </font>
								</a>
								<br />
						</li>
						<li>
								<font size="4">Caching Tutorial: <br /></font>
								<a href="http://www.mnot.net/cache_docs">
										<font color="#4371a6" size="4">http://www.mnot.net/cache_docs </font>
								</a>
								<br />
						</li>
						<li>
								<font size="4">HTTP Caching in Mozilla: <br /></font>
								<a href="http://www.mozilla.org/projects/netlib/http/http-caching-faq.html">
										<font color="#4371a6" size="4">http://www.mozilla.org/projects/netlib/http/http-caching-faq.html </font>
								</a>
								<br />
						</li>
						<li>
								<font size="4">Opera knowledge base: Cache control headers: <br /></font>
								<a href="http://www.opera.com/support/search/supsearch.dml?index=82">
										<font color="#4371a6" size="4">http://www.opera.com/support/search/supsearch.dml?index=82 </font>
								</a>
								<br />
						</li>
						<li>
								<font size="4">Prevent caching in Internet Explorer: <br /></font>
								<a href="http://support.microsoft.com/default.aspx?scid=kb;EN-US;234067">
										<font color="#4371a6" size="4">http://support.microsoft.com/default.aspx?scid=kb;EN-US;234067 </font>
								</a>
								<br />
						</li>
						<li>
								<font size="4">
										<em>JavaWorld</em> has published numerous articles on Struts, including the following: </font>
								<ul>
										<li>
												<font size="4">"</font>
												<a href="http://www.javaworld.com/javaworld/jw-09-2004/jw-0913-struts.html">
														<font color="#4371a6">
																<font size="4">Struts Best Practices</font>
														</font>
												</a>
												<font size="4">," Puneet Agarwal (September 2004) </font>
										</li>
										<li>
												<font size="4">"</font>
												<a href="http://www.javaworld.com/javaworld/jw-04-2003/jw-0418-struts.html">
														<font color="#4371a6">
																<font size="4">Jump the Hurdles of Struts Development</font>
														</font>
												</a>
												<font size="4">," Michael Coen and Amarnath Nanduri (April 2003) </font>
										</li>
										<li>
												<font size="4">"</font>
												<a href="http://www.javaworld.com/javaworld/jw-02-2002/jw-0201-strutsxslt.html">
														<font color="#4371a6">
																<font size="4">Boost Struts with XSLT and XML</font>
														</font>
												</a>
												<font size="4">," Julien Mercay and Gilbert Bouzeid (February 2002)</font>
										</li>
								</ul>
						</li>
						<li>
								<font size="4">For more articles on security, browse the <strong>Security</strong> section of <em>JavaWorld'</em>s Topical Index: <br /></font>
								<a href="http://www.javaworld.com/channel_content/jw-security-index.shtml">
										<font color="#4371a6" size="4">http://www.javaworld.com/channel_content/jw-security-index.shtml </font>
								</a>
								<br />
						</li>
						<li>
								<font size="4">Also, browse the <strong>JavaServer Pages (JSP)</strong> section of <em>JavaWorld'</em>s Topical Index: <br /></font>
								<a href="http://www.javaworld.com/channel_content/jw-jsp-index.shtml?">
										<font color="#4371a6" size="4">http://www.javaworld.com/channel_content/jw-jsp-index.shtml? </font>
								</a>
								<br />
						</li>
				</ul>
		</font>
<img src ="http://www.blogjava.net/lijiajia418/aggbug/65541.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/lijiajia418/" target="_blank">Binary</a> 2006-08-24 15:56 <a href="http://www.blogjava.net/lijiajia418/archive/2006/08/24/65541.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>整理一下技术路线--载至Robbin原文</title><link>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65521.html</link><dc:creator>Binary</dc:creator><author>Binary</author><pubDate>Thu, 24 Aug 2006 07:10:00 GMT</pubDate><guid>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65521.html</guid><wfw:comment>http://www.blogjava.net/lijiajia418/comments/65521.html</wfw:comment><comments>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65521.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/lijiajia418/comments/commentRss/65521.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/lijiajia418/services/trackbacks/65521.html</trackback:ping><description><![CDATA[
		<span class="postbody">一、软件开发技术 <br /><br />1）服务器端 <br /><br />在最近5年内，Java还是主流，不光是因为当前的普及程度和遗留系统问题，而且除Microsoft几乎所有大公司都投资到Java上面的原因，此外开源也是一股无法忽略的力量：除了Java方面的开源框架在推动Java，也有Linux在带动java企业应用在普及（别忘记dotnet只能在 Windows Server上面运行） <br /><br />dotnet有自己的优势，但是在五年内无法和Java取得均势，不光是因为Java普及带来的优势，也不光因为开源界对java的推动，也不光因为其他大公司在java上面的投资，而是很多公司的行业性质决定了dotnet的出局，例如电信行业，金融行业，电子政务行业等等，是根本没有可能采用 dotnet的。 <br /><br />Python和Ruby算不上后起，但是很有竞争实力，不过基于上面的原因，仍然不能成为主流。 <br /><br />在Java服务器端技术中，清晰的分为两条路线：高端的商业路线，这条路线是EJB3，J2EE5.0；低端的开源路线，这条路线是Hibernate， Spring。这两条路线也有重叠的地方，例如开源的Struts几乎成为J2EE Web层的标准，开源的Hibernate奠定了EJB3的基础。但是划分路线不是基于技术上的区别，而是基于商业运作上的区别。注重技术支持和商业服务的公司会选择前者，注重成本控制和选择自由的公司会选择后者。 <br /><br />商业路线的技术方案是：EJB3＋Struts； <br />开源路线的技术方案是：Spring＋Hibernate＋Struts/Webwork <br /><br />Struts是一个很成功的开源框架，它的地位短期内还无法动摇，JavaEye有一项使命，就是动摇Struts在Java Web领域的地位，把它赶下王座，把Webwork扶上位！ <br /><br />商业的Web层技术，JSTL算是一个不错的东西，但是和灵活的模板语言如FreeMarker相比，却有很大的差距。JSF基本上是一个没有前途的东西。商业Web层技术因为一直没有出现好的应用，这样也导致了Struts的上位。 <br /><br />服务器端业务层和持久层框架，我非常看好EJB3，原因也不用多谈了，从商业上来说，需要这样一个东西，跨国公司们也需要这样一个产品来卖，来取代糟糕的 EJB2。开源的方案里面，Spring＋Hibenrate是一个很好的商业方案的开源替代，他们不存在很直接的竞争，而是一个互补的关系。这里比较尴尬的反而是JDO：JDO是商业产品（目前没有好的开源实现），造成开源应用不会对它感兴趣，JDO没有一个像EJB容器那样的脱管环境，造成商业方案对它不感兴趣。不过有了JDO，我觉得是对EJB3，对Hibernate形成一个良好的竞争环境，这一点是非常有利的。 <br /><br />2）客户端技术 <br /><br />准确的说是RIA应用。虽然我前面对XAML进行了正面的评价，但是我认为我前面有些结论给错了。经过这段时间，我觉得，XAML即时在多年之后，也未必能够成为一个非常成功的解决方案。道理很二： <br /><br />1、XAML会带来比ActiveX更严重的安全性问题。 <br />XAML本质上就是一个本地应用程序，虽然号称可以在IE浏览器里面运行，但IE就是一个皮而已，XAML应用具备对本地资源完全的访问能力（就算IE限制也没有用，IE限制就丧失功能，那样的话，功能并不会比Javascript来得更多；不限制的话，就为所欲为了），因此只要IE具备了运行XAML的能力，黑客将可以非常轻易的通过IE进行入侵，这仅仅需要引导用户在不知不觉中访问一个恶意的网页就搞定了！用户必须面临选择：要么禁止IE对XAML的运行能力，要么接受随时被攻击的危险。 <br /><br />2、XAML应用本质上也是RIA应用，因此必须进行大量的RPC调用 <br />当前XAML采用XML Web Services进行通讯，这是一种低效的RPC。当前的XAML案例中并没有注意到RPC领域，实际上根据我现在做RIA的体验来说，RPC绝对不是一个简单的事情，要考虑的问题非常多。 <br /><br />从当前的阶段来说，最实际可用的方案有两个： <br /><br />1、AJAX <br />实际上就是基于XMLHTTP的JS异步交互，这个东西已经出现很多年了，最近随着Google应用和Sun Blueprint的推出开始火热。我原来对这个东西持否定态度，但是后来转变了。我原来否定态度的一个前提就是：XMLHTTP缺乏成熟的组件库！但是没有想到的是，现在XMLHTTP从去年下半年开始，如雨后春笋般冒出来。AJAX应用最大的好处就是充分利用现有资源，我认为应成为RIA应用的首选。 <br /><br />2、Flash <br />Flash的优势也很明显，强大的AS支持，强大的组件可视化设计，强大的交互能力和很炫的用户体验，并且Flash Remoting也已经非常成熟了。Flash的缺点就是Flash虽然嵌入网页，但是和网页没有数据交互能力，Flash另一个缺点就是不适合处理大量文本内容（HTML最适合）。现在有些人开始滥用Flash了。 <br /><br />因此比较好的方式可能是两种混用，一般不过度复杂的交互交给AJAX，非常复杂，甚至需要托拽操作的，交给Flash。 <br /><br />总结一下： <br /><br />软件开发领域服务器端技术Java是主流，两个技术路线，一个是EJB3，一个是Spring＋Hibernate，此外iBATIS也有一席之地；客户端技术就是AJAX和Flash。 <br /><br />二、数据库技术 <br /><br />基本上格局不会发生多大变化，Oracle还是高高在上，SQL Server进一步蚕食NT平台其他数据库的领地。开源方面，MySQL将一枝独秀，但是开源数据库在很多方面还是和商业数据库有无法拉近的巨大差距。这也使得商业数据库的地位不可替代。我会比较关注Oracle，MySQL这两个数据库。面向对象数据库仍然不会有什么起色。 <br /><br />三、桌面编程技术 <br /><br />我还是相信一点，对于桌面应用来说，本地代码的位置永远无法被取代，所以我总觉得XAML那样的东西效率实在很成问题。Longhorn要像成熟，也不是第一个版本就可以达到的。当前桌面应用开发技术，还是首推Delphi，不过我觉得Python是后起之秀，非常有可能在未来取代Delphi。<br /><br /><span class="postbody">初探在下一代 Windows 中编写和部署应用程序 <br /><a href="http://www.microsoft.com/china/MSDN/library/windev/longhorn/DevelopAppLonghorn.mspx" target="_blank">http://www.microsoft.com/china/MSDN/library/windev/longhorn/DevelopAppLonghorn.mspx</a><br /><br />首先，以Microsoft公司的实力和Windows操作系统的占有率来说，Longhorn迟早会被普及，而XAML的开发方式也有可能普及的。记得当初WindowsXP刚出来的时候，因为资源占用率和新的激活制度招致一片骂声，但是慢慢的，现在也都接受了下来。由此可以推断，Longhorn以其更加丰富的桌面功能和诱人的外观，会在将来成为主流。 <br /><br />但是Longhorn什么时候才会全面普及，这是很值得琢磨的问题。WindowsXP是2001年推出的，在随后的几年，Microsoft采用了一些商业手段来迫使用户升级，例如企图取消Windows98的技术支持，不再提供WindowsNT技术支持，不再销售 WindowsNT/Windows98，将Windows2000保持在一个比较高的售价的同时，对WindowsXP推出优惠价格，让 WindowsXP的售价低于Windows2000等等手段。但是直到现在，Windows2000仍然占据了非常高的份额，据我个人的观察是比 WindowsXP略高。按照这种情况来推断，Longhorn要普及，恐怕难度更大，非常多的用户现在仍然是Windows2000的死忠派， WindowsXP推广了四年还未能超过Windows2000，那么Longhorn究竟要几年才能超过WindowsXP呢？我估计四年以上是起码的。 <br /><br />XAML应用程序不同以往，它只能跑在Longhorn上面，甚至比Java和dotnet要求更严格，后者仅仅下载安装一个运行环境就可以了，但是前者要求你必须更新操作系统。XAML在IE浏览器中运行虽然肯定是下一代RIA的主流，但是不可忽视的问题是，只要Longhorn没有彻底淘汰 Windows2000/XP，软件开发商和网站开发商就不敢大面积采用XAML。而根据我的观察，现在企业中，Windows98仍有少部分市场份额。因此Longhorn必须要等待到彻底的，毫不残留的淘汰Windows98，Windows2000，WindowsXP之后，才会全面普及，而在此之前，不得不经历一个漫长的过渡期。 <br /><br />就好像现在，假设你开发桌面应用程序，你敢只针对WindowsXP开发吗？而彻底不支持98和2000吗？我想，没有哪个软件开发商敢这样做。除非 Windows2000几乎被彻底淘汰了，你才敢这样做，但是WindowsXP已经推出四年了，还没有Windows2000占用率高，哪全面淘汰究竟要几年呢？再看看现在dotnet winforms应用，推出也已经五年时间了，但是到现在仍然没有普及开来，根本的原因就是Windows2000/WindowsXP没有预装 dotnet framework。仅仅是需要打包安装一个运行环境就使得winforms五年都推广不了，更何况要求你升级操作系统呢？ <br /><br />我个人的估计是，假设2006年Longhorn如期上市，那么将需要7-9年时间来彻底淘汰Windows2000/WindowsXP。 Longhorm上面XAML应用的初步普及也至少需要4-5年时间以后才会有软件开发商大量去做（想向dotnet是2000年开始宣传和推广的，到 2004年开始普及，今年和明年才会全面普及)。因此，基于XAML应用的普及可能是在2010年以后！上面的估计中还没有包括MacOS 和Linux在桌面会否有什么表现。 <br /><br />先说说服务器端吧： <br /><br />从可预见的未来来看，服务器和客户端TCP通讯的主流方式一定是HTTP协议（即时通讯软件走UDP端口，不在讨论范围）。在基于HTTP协议之上，又分为两类：一类是SOAP协议，异构系统支持良好，但是性能很差，目前Microsoft很喜欢用这种方式；一类是轻量级二进制协议，例如Flash的 AMF协议，Resin的Hessian协议。值得一提的是，不管哪种方式，他们都支持异构的系统，所以完全可用在客户端采用dotnet，在服务器端采用Java或者Python。因此，XAML的流行不会对服务器端技术产生致命的影响（肯定会提高dotnet的服务器的市场份额）。所以我们可用抛开客户端影响，单独来看服务器端技术： <br /><br />1、Java <br />Java是当前服务器端技术当之无愧的王者，在未来五年内，也不会有任何动摇（受到dotnet和python的影响，市场份额会下降一些）。Java特别有利的一点是，现在有太多的现存系统基于Java，这些系统都不会轻易迁移到其他平台上。另外还有一个决定因素是除了Microsoft之外的几乎全部 IT大公司都在Java方面的投资巨大，放弃Java对他们来说也意味着沉重的打击，甚至毁灭性的打击。这些公司可以列很长很长，IBM，HP， Oracle，SAP，Sun，BEA，Macromedia等等。 <br /><br />2、dotnet <br />由于Microsoft的影响力，dotnet会成为为仅次于Java的第二大服务器端技术，但是Microsoft有一个隐忧，就是Linux操作系统在服务器端的高速成长。虽然现在Linux在整个服务器端市场的出货量只有13%左右，但是成长率惊人，根据我看到的资料显示，到2008年，将占据 25%以上的市场份额。考虑到很多公司是自己安装Linux，因此不会被硬件服务器厂商统计进来，因此Linux的服务器端的市场份额应该比25%高一些。并且现在主要的服务器厂商都对Linux有非常巨大的投入和支持，这些公司包括IBM，HP，Dell（只有Sun不支持），因此Linux在未来会对Windows在服务器端的市场构成最严重的威胁。 <br /><br />不要忘记dotnet只能在Windows平台上面跑，虽然有mono，但是你不可能移植MTS，COM+，SQL Server etc。所以只要Linux在服务器市场对Windows构成持续的威胁，dotnet就不可能超过Java，Java的地位还是稳稳的老大。从某种程度上来说，Java的命运是和Linux联系在一起的，只要Linux在服务器端不输于Windows，Java就稳稳压制dotnet。 <br /><br />BTW:从未来来看，Linux和Windows会在低端和中端服务器市场成为主要竞争对手，由于各自都有其不可替代性，所以双方都不可能彻底消灭对方，最大的可能性是Linux和Windows平分市场，或者Windows市场份额略高一点。 <br /><br />3、Python <br />我个人认为Python会成长为第三大服务器端技术，Python成长于开源，但是又有商业公司来商业运作，并且背后还有大公司的支持，在欧洲普及的非常好。当然最重要的原因是我觉得Python在技术上非常先进，并且技术发展方向上比较统一，不会出现Java那种吵架的事情。 <br /><br />4、PHP <br />PHP这东西是不错，Yahoo也在用，IBM现在也对他感兴趣，但是我还是要说PHP没有太广阔的前途，原因很简单，PHP没有服务端中间件，例如 Java有App Server，dotnet有IIS/MTS，Python有Zope，但是PHP他就是一个脚本，没有自己的中间件就是致命问题。Yahoo用PHP有其特定的原因，主要是从原先自己的技术迁移到PHP很方便，而IBM支持PHP，显然醉翁之意不在酒，IBM意不在推广PHP，而在于争取到那些使用 PHP的商业大客户们，向他们卖服务。 <br /><br />BTW：感觉欧洲用Python/PHP的很多，似乎开源在欧洲非常深入人心。 <br /><br />从服务器端技术来说，Java还是我们最需要下功夫去学习和掌握的，此外，我会比较倾向于钻研和应用Python，而不是dotnet。原因也很简单，跟随Micorsoft的技术会很辛苦，Microsoft产生的新概念多，他总是会猛的推出n多种技术，然后让他们在市场上自己生存，最后根据市场反馈，无情的抛弃某些东西，大力推进有市场前景的东西，这样的例子太多了，举不胜举了。我的感觉就是这种方式会让Microsft经过市场尝试在技术竞争中筛选最优秀的技术，但是对于Microsoft技术的跟随者来说，未免有点太不公平，整天吭哧吭哧被Microsoft拿来当免费的试验品来用。我特别不理解的是MSDN宇宙版，Microsoft总是把无穷无尽的文档灌给你，让你永远学不完，但实际上我真的不需要那么多概念，我只需要能够很好的完成我工作的技术，并且这个技术可以持续的完善就好了。而不是今天给我这样一个东西，明天灌给我无穷的文档，后天当我用顺手以后，又告诉我这东西作废了，你给我重新学习新东西，然后又是无穷的文档，总之很恼火。 <br /><br />所以就是：重点学习Java，有时间去学习Python，保持对dotnet的关注即可。 <br /><br /><br />客户端： <br /><br />前面说了那么多XAML的东西，都是和这有关，七年以后肯定是XAML的天下，但是五到七年之内还不是： <br /><br />1、Java <br />Java在客户端真的是扶不起的阿斗，这都怪Sun。Sun造就了Java的成功，又一手毁了Java在客户端的市场。那些个Swing和SWT的死忠团也不要和我争什么，我也懒得和你们争，你们觉得好就好吧，道不同不相与谋，你觉得好你就用你的，我觉得不好我就用别的。用不着缠着我非逼我说Java做客户端好，没必要，况且就算你逼我承认又怎样？我就是玉皇大帝金口玉言了？得到我的承认，Java就有前途了？我好像还没有那么大本领吧？就是IBM， Sun也没有那么大本领，所以好不好也不是我说了算，用不着逼我。 <br /><br />2、dotnet winforms <br />由于Windows2000/WindowsXP不带dotnet CLR，所以winforms一直没有能够普及得很好，等Longhorn一出来，又变成了XAML了，winforms又被淘汰了，所以 winforms的地位特别尴尬，但是在这5-7年中，你想开发既能够在Windows2000/WindowsXP，又能够在Longhorn上面跑的桌面程序，winforms好像又是Microsoft技术中最好的选择。所以只好一直尴尬下去。 <br /><br />3、VC，VB <br />dotnet出来以后就开始尴尬了，说用吧，好像很落伍了，都dotnet时代了，说不用吧，又没有好的替代品，现阶段开发桌面程序，还真得不得不用，而且还挺好用的。所以VC6SP5，VB6的死忠团也比较多。 <br /><br />4、Delphi <br />dotnet出来以后Borland就开始跟风了，这一跟风，连老本都跟没有了。未来的XAML时代，我也不知道Borland怎样找自己的定位，但不管怎么说，从历史来看，本地代码的应用程序永远有它一席之地！就算XAML又如何如何做得漂亮了，关键的地方，和特定资源处理相关的部分，还是本地代码的程序管用。你看VB出来多少年了，用VB开发的都是一些上层的项目级别的应用软件，一旦涉及产品领域，还是VC和Delphi管用。所以现在大家还是不得不用Delphi7阿。 <br /><br />BTW：XAML应用致力于快速开发项目级别的应用，特别是可以跑在IE浏览器里面的，因此是RIA的首选。但是毕竟也有很多不适合用RIA的场所，特别是例如我要备份某些文件，你用XAML？那性能就不用提了。所以Delphi如果好好发展VCL，封装Windows32 API，我觉得也是一条路，未必比现在跟随dotnet差。 <br /><br />5、Flash RIA <br />其实我觉得Flash不适合做RIA的，但是Flash普及率太高，XAML又离普及太遥远，而Flash现在就可以用了，所以是当前RIA的首选。不过我对Macromedia公司比较失望，如果Macromedia能够公布Flash实现细节，作为一个公开的标准向ISO提交，同时免费开源Flex，我敢说，Flash RIA会迅速普及的。等5-7年XAML的时代，由于Flash的市场占有率，XAML就未必能拼得过Flash。可惜的是Macromedia公司目光过于短浅，只知道赚眼前的小钱。 <br /><br />6、Python <br />这5-7年内，RIA应用和RCP应用不会统一，XAML才具备将RIA和RCP统一的实力。从这5-7年来看，Flash是RIA的首选，而RCP的首选，我要推荐Python。原因前面已经提过，简单总结一下： <br />1）wxWidgets是一个比MFC优雅的库，TortoiseCVS用wxWidges而不用MFC，就是因为wxWidgets好用，而不是为了可以移植。 <br />2）Python的面向对象脚本语言编程适合快速界面开发 <br />3）Python在服务器端和客户端都非常有前途，可以形成一个统一的解决方案，这一点明显比Java有优势 <br />4）Python桌面应用程序可以完全编译为本地代码，脱离Python运行环境，这一点比dotnet winforms都有优势 <br />5）Python可以不受限制的任意调用Windows32 API，所以凡是VC6可以做的事情，Python就可以做 <br /><br />试想一下，现在我们开发桌面应用程序有什么要求？ <br />一、不要附带一个JRE或者CLR的累赘 <br />二、可以快速开发 <br />三、性能要有保证 <br />四、方便的远程方法调用支持 <br />此外如果能够跨平台就最好了 <br /><br />Java前三点都不符合；dotnet winforms不符合一；VC6不符合二和四，VB6不符合三和四；Delphi7符合前四点；Flash RIA不符合三；Python全部都符合！并且请记住Python是一个完全开源免费的方案！ <br /><br />客户端技术在这5-7年中，在RIA领域我会学习一下Flash，在RCP领域我会重点学习Python，此外会观望一下XAML。</span></span>
<img src ="http://www.blogjava.net/lijiajia418/aggbug/65521.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/lijiajia418/" target="_blank">Binary</a> 2006-08-24 15:10 <a href="http://www.blogjava.net/lijiajia418/archive/2006/08/24/65521.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>理解企业应用框架－－本文原载于《程序员》杂志 </title><link>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65520.html</link><dc:creator>Binary</dc:creator><author>Binary</author><pubDate>Thu, 24 Aug 2006 07:08:00 GMT</pubDate><guid>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65520.html</guid><wfw:comment>http://www.blogjava.net/lijiajia418/comments/65520.html</wfw:comment><comments>http://www.blogjava.net/lijiajia418/archive/2006/08/24/65520.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/lijiajia418/comments/commentRss/65520.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/lijiajia418/services/trackbacks/65520.html</trackback:ping><description><![CDATA[人们总是偏爱“大词”。一个表达方式，如果听起来足够响亮，写在纸上能够吸引眼球，那就会变成很多人的新宠。但同样是这些大词，经过太多人的传递、消费之后，原本的含义反而像硬币上的图案一样被磨损殆尽：几乎没有人知道这些说法到底是指什么了。在IT业界，“平台（platform）”、“框架（framework）”、“构架（architecture）”等等就是这种人见人爱的大词。几乎每个厂商都愿意请来其中的一位、甚至多位为自己推销。久而久之，这些说法似乎适用于各个领域、各个层面：所有的软件系统都是“平台”，所有的开发者都在自矜于独有的“框架”。原本有确切意义的“好词”，经过这一番争夺和滥用，也只能衰减为所谓的“buzzwords”，供市场营销人士们玩味了。 <br />我想让这些词中的一个——“框架”——荡污涤垢，重现青春。要完成这样的任务，必须动用重典才行。软件业圣经《设计模式》对框架有如下定义：“A framework is a set of cooperating classes that make up a reusable design for a specific class of software（一个框架，就是一组相互协作的类，对于特定的一类软件，框架构成了一种可重用的设计）”。这个定义虽然主要着眼于面向对象的软件开发，但已经基本上给出了这个词的核心含义：框架是软件系统的设计、开发过程中的一个概念，它强调对以完成的设计、代码的重复使用，并且，一个框架主要适用于实现某一特定类型的软件系统。 <br />为了更好地说明框架是什么，也许还应该看看框架不是什么。 <br />框架不是现成可用的应用系统。它仍是一个半成品，等待后来者做“二次开发”，实现为具体的应用系统。 <br />框架不是“平台”。后者的概念更加浮泛和模糊——人们说的一个平台，可以是一种操作系统，一种应用服务器，一种数据库软件，一种通信中间件等等，因此“平台”几乎成了所有系统软件的统称。在平台的大家族中，框架的概念可能与近来人们常说的“应用平台”最为接近，但平台主要指提供特定服务的系统软件，而框架则更侧重于设计、开发过程，或者可以说，框架通过调用平台提供的服务而起作用。 <br />框架不是工具包（toolkit）/类库（library） /API。目前流行的很多框架中，就包括了大量的类库和API，但是调用API并不就是在使用框架开发。仅仅使用API时，开发者完成系统的主体部分，并不时地调用类库实现特定任务。而框架构成了通用的、具有一般性的系统主体部分，“二次开发者”只是像做填空题一样，根据具体业务，完成特定应用系统中与众不同特殊的部分。 <br />框架不是构架（architecture）。构架确定了系统整体结构、层次划分、不同部分之间的协作等设计考虑。框架比构架更具体，更偏重于技术实现。确定框架后，构架也随之确定，而对于同一种构架（比如web开发中的MVC），可以通过多种框架（比如Apache Struts或Apache Velocity）实现。 <br /><br />2 <br /><br />那么，在企业应用系统开发中，框架具有什么样的意义？要阐明这一点，大概要看一看在这个领域里软件开发方式的演变。在计算机应用普及之前，只有少数大企业才负担得起企业信息系统软件，这一类的软件开发也已委托定制（custom-made software）为主。在企业信息化基础设施逐步完备之后，多数中、小企业也要在预算不高的前提下实施企业应用系统，按照以前的方式逐个定制开发，是这种类型的企业难以承受的。因此，对于一些需求简明的系统，往往会购买现成软件（shrink-wrapped software）解决问题。但是各个企业具体业务不同，需求很难统一，现成软件只能满足最通用的情况和最一致的操作（比如财会系统、网站内容发布系统等），对于头绪众多的业务处理就难以胜任了。 <br />如何最大程度地萃取不同企业应用系统的共性，重复使用已经完成的设计和代码，对企业应用系统中典型场景给出最佳解决方案——这是一个“一般性”的问题；如何让一个早先完成的软件产品贴切地适应极为多变、复杂的企业需求——这是一个“特殊性”的问题。作为对这一组冲突的一种解决方案，不少厂商推出了自己的企业应用框架。这些框架往往是从大量的委托项目开发中精选出的系统“不变项”，因此具有很强的普适性和实用性。 <br />目前，主流企业应用框架中大都包含对以下问题的现成解决方案： <br />* 持久性（persistence）：实现数据存储、处理，数据与对象映射，数据缓存（caching）。 <br />* 事务（transaction）：确保一组关联操作正常、完整的执行。 <br />* 安全性（security）：保证系统的通信安全、数据安全。 <br />* 负载均衡（load balance）：在大量并发访问时，保持系统可用。 <br />* 监控（system monitoring/management）：监控系统运行状况，设置系统参数。 <br />* 日志（logging）：记录系统运行情况和异常，记录特定用户操作。 <br />* 应用集成 （application integration）：与其他系统、应用程序集成。 <br />* 认证/权限/组织角色管理（authentication/authorization）：管理系统用户、组织职权结构，限制特定用户对特定功能、特定数据的访问。 <br />* 业务模型（domain model）：管理系统中业务对象的属性、字段。 <br />* 业务逻辑（business logic/rules）：实现业务规则和业务逻辑。 <br />* 工作流（work flow）：实现多用户、多环节之间的业务处理流程。 <br />* 文件管理（file management）：管理文档，实现系统内部的文件传递。 <br />* 报表/打印 （reporting/printing）：实现数据打印，实现报表的定制和输出。 <br />* 门户/信息发布 （portal solution）：发布企业相关的信息、新闻，提供企业客户的访问入口。 <br />* 通信（communication/messaging）：系统内部的消息、通知；系统与外部角色（比如企业客户）之间通过不同通信媒介（电话、网站、邮件等）的互动。 <br />* 特定行业/领域模块 （business modules）：实现特定行业、流域相关的业务模块。 <br />以上诸方面中，除了前四项目前主要由应用服务器解决之外，其他的部分本身都是专门的软件开发领域。框架的作用，在于确定上述每种因素的具体技术实现，并规定它们在系统中的组织方式和协作方式，从而给出完整的企业应用解决方案。 <br />企业应用框架的特点首先是，当应用框架确定之后，系统的整个构架，也就是主体结构就已经固定。因此框架的选取往往是方案选型的首要问题。 <br />其次，人们常常听信“组件式开发”的一面之词，认为系统搭建的过程类似于搭积木，好像是用胶水代码（glue code）拼合现成的组件或模块。其实采用框架开发时，系统的构建过程更类似于填空——系统骨架早已完成，开发者填写特定的代码，由系统来调用。《设计模式》中提到的“好莱坞原则（the Hollywood principle——Don't call us, we'll call you）”，非常符合我们谈的这种情况。很多框架还允许下游厂商开发系统插件（plug-ins），以满足特定需要——这是另一种形式的“填空”。 <br />另外，对于实现具体应用系统的二次开发者来说，不少任务都无需通过编程实现。比如要给一个业务模型增添一个新字段，或是要设置一种新的工作流程，这些工作都可以通过简单的图形用户界面（GUI）操作，或是修改部署描述符（DD），或是编写脚本来完成。也就是说，相当多（而不是全部）的开发任务是通过声明/配置的（declarative），而不是编程的（programmatic）的方式实现的。系统往往会在启动或运行时载入相关的配置，据此实现特定的功能。 <br /><br />企业应用框架是菜场里的半成品。当我们面对要么自己下厨、要么去饭馆吃饭的选择时，我们往往会采取这种省时省力的折衷方案。但是选择之所以为选择，就因为其中肯定包含对收益和代价的权衡，都隐含着复杂的利弊关系（pros and cons）。下面我们也来检讨一下企业应用框架的情况： <br />Pros： <br />* 缩短开发周期 <br />毫无疑问，采用框架的开发，要比一切从头做起快速、高效得多。通过一般化（generalization）和重用（reuse）机制，框架能最大限度地加快一个特定应用系统的实现。 <br />* 客户化 <br />如上所述，基于框架的系统有很多功能通过配置而不是编程实现，这样也给用户带来了一定便利。比如，企业内部的IT人员经过一定培训，就能够自己完成一种新的工作流程的设置。这对于不断变化的业务需求是一个很理想的解决方案。 <br />* 不重新发明轮子 <br />框架对于大量典型场景给出了最优的实践。在具体开发时，与其无视前人的成果，重新构思答案，不如套用这些成熟、稳定的做法。这不仅能加快开发进度，更能够提升系统的质量和健壮性。 <br />* 可维护性/知识共享 <br />完全通过委托开发完成的系统很难由其他厂商维护。框架往往是多个企业、大量开发者实践的成果，因此能在一定程度上打破上述壁垒，增进系统的可维护性。当框架使用者形成社区之后，还能实现更高层次上的知识共享。 <br />Cons: <br />* 太多 <br />半成品总有其代价。超市配好的一包菜里，老是又我们用不到的调料——但是我们却不得不为之付费。同样，为了达到一般性和普适性，框架总比紧凑、贴切的特定应用多出不少内容。二次开发完成后，企业获得的只是一种特定的实现，却要为所有的客户化可能性付费，为所有用不上的功能付费。这是一个相当让人尴尬的事实。 <br />* 太少 <br />框架总是一种限制。就像半成品菜限制了我们的烹调方法，框架也限制了我们实际应用的可能性。当框架本身设计的足够普适时，我们不太会感到类似的限制。但是，情况往往正好相反——面对一个足够特殊的需求，二次开发者总有一种冲破框架的渴望。最后的解决办法，往往是狡计、妥协和框架补丁的结合体。 <br />* 效率 <br />上面说过，基于框架的系统中，具体功能经常是通过配置实现的。与硬编码（hard-coded）的方式相比较，这虽然能提供很大的灵活性，但也往往牺牲了运行时的效率。 <br />* 锁定 <br />一个采用特定框架的系统几乎肯定被锁定在这个厂商的产品上。换言之，框架意味着all or nothing式的态度，很难调和两种不同的框架，各取所长，更难把应用系统从一个框架迁移到另一个——这往往要求系统的全部改写。 <br />* 学习曲线 <br />一种框架也就是一种方言。要精通特定框架的开发，你要熟悉其中的所有的用法、思路和弱点。对于开发者，这也意味着比较陡峭的学习曲线。 <br /><br />3 <br /><br />上面谈到的种种弊端，还属于一般开发框架共有的缺陷。对于市面上流行的很多企业应用框架来说，更大的问题是框架产品自身的价格过高。我在别处也讲过，企业应用系统项目往往不能靠运行安装程序，再作简单的设置就完成，而是一个复杂、漫长、不断尝试/修改的过程，或者说，更近似于一种服务而不是简单的产品销售。但是框架厂商的产品（或者说是半成品）价格过高，经常就蚕食了整个系统的大部分开发预算，使方案总价偏重于框架本身而不是后期开发。对于需求不甚符合原有框架，需要大量开发的项目，或是需求本身不够清晰的项目，这都几乎肯定会导致整个项目的失败。 <br /><br />软件工程宗师F. Brooks曾经表述过这样一个道理：没有银弹（No Silver Bullet/NSB）。那意思是说，没有一种万应药能够戏剧性地提升软件开发的效率和质量。最近的很多舆论好像是专门和这个经典论述抬杠，动不动就把一些特殊的解决方案奉为银弹。对于企业应用开发而言，基于框架的开发模式是多种选择中的一种。在不少情况下，这种选择是不错的，但同时应该留意随之而来的风险，更不该以为，选定了框架就一定能保证项目成功。 <br />很多企业应用项目的难点，在于客户自身缺乏规范的企业管理制度和合格的计算机应用水平。客户不具备成型的业务流程，也无法明晰表达需求，不知道怎样的应用形式对自身业务更合理：这种需求不清、或是需求剧烈变更的困境是困扰大量企业应用开发项目的症结。简言之，企业应用项目的成败经常是“业务”、“技术”、“管理”三种因素共同作用的结果，而单纯引入框架，只能解决部分“技术问题”。如果过于乐观地估计框架在其中的作用，甚至认为它能解决任何项目的任何问题，这对于本领域的各个环节（厂商、项目开发商、企业客户）都只能起到消极作用。 <br />我个人的建议是：在搭建企业应用系统时，针对应用情况不同、预算/时限不同、对系统指标要求不同，有多种替代方案可以从中选择。当需求明确、固定，又有现成产品完全满足需要时，或者当企业想要以极低预算消除某个业务瓶颈时，应该优先考虑现成产品；在需求明确、固定，但很难被现成产品完全覆盖时，可以选择应用框架，并由合格开发商完成实施；在需求不够明确，或者预感到需求会发生剧烈变更时，采用开发源码的应用框架，从而避免高昂的初期投资，并“软化”框架带来的种种限制，是另一种可供选择的思路。还是那个比方，一顿饭怎么吃，究竟是下馆子、买半成品或者全由自己动手，还要视具体情形而定——我也希望，每个企业都能吃上可口顺心的应用大餐。 <img src ="http://www.blogjava.net/lijiajia418/aggbug/65520.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/lijiajia418/" target="_blank">Binary</a> 2006-08-24 15:08 <a href="http://www.blogjava.net/lijiajia418/archive/2006/08/24/65520.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>