﻿<?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-cmd-文章分类-开源软件</title><link>http://www.blogjava.net/cmd/category/7474.html</link><description /><language>zh-cn</language><lastBuildDate>Wed, 28 Feb 2007 07:44:04 GMT</lastBuildDate><pubDate>Wed, 28 Feb 2007 07:44:04 GMT</pubDate><ttl>60</ttl><item><title>Hibernate中的事务控制和并发</title><link>http://www.blogjava.net/cmd/articles/49407.html</link><dc:creator>静夜思</dc:creator><author>静夜思</author><pubDate>Thu, 01 Jun 2006 01:31:00 GMT</pubDate><guid>http://www.blogjava.net/cmd/articles/49407.html</guid><wfw:comment>http://www.blogjava.net/cmd/comments/49407.html</wfw:comment><comments>http://www.blogjava.net/cmd/articles/49407.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/cmd/comments/commentRss/49407.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/cmd/services/trackbacks/49407.html</trackback:ping><description><![CDATA[
		<p style="text-indent: 2em;">
				<font size="2">
						<u>Hibernate的事务和并发控制很容易掌握。Hibernate直接
使用JDBC连接和JTA资源，不添加任何附加锁定 行为。我们强烈推荐你花点时间了解JDBC编程，ANSI SQL查询语言和你使用
的数据库系统的事务隔离规范。Hibernate只添加自动版本管理，而不会锁 定内存中的对象，也不会改变数据库事务的隔离级别。基本上，使用
Hibernate就好像直接使用JDBC(或者JTA/CMT)来访问你的数据库资源。<br /></u>
				</font>
		</p>
		<p>
				<font size="2"> 
除了自动版本管理，针对行级悲观锁定，Hibernate也提供了辅助的API，它使用了 SELECT FOR
UPDATE的SQL语法。本章后面会讨论这个API。 我们从Configuration层、SessionFactory层, 和
Session层开始讨论Hibernate的并行控制、数据库事务和应用 程序的长事务。 <br /><br /><strong>12.1.Session和事务范围(transaction scopes)</strong><br />一个SessionFactory对象的创建代价很昂贵，它是线程安全的对象，它被设计成可以 为所有的应用程序线程所共享。它只创建一次，通常是在应用程序启动的时候，由一个 Configuraion的实例来创建。 <br />一
个Session的对象是轻型的，非线程安全的，对于单个业务进程，单个的
工作单元而言，它只被使用一次，然后就丢弃。只有在需要的时候，Session
才会获取一个JDBC的Connection（或一个Datasource）
对象。所以你可以放心的打开和关闭Session，甚至当你并不确定一个特定的请
求是否需要数据访问时，你也可以这样做。(一旦你实现下面提到的使用了请求拦截的模式，这就 变得很重要了。 <br />此外我们还要考虑数据库事务。数据库事务应该尽可能的短，降低数据库锁定造成的资源争用。 数据库长事务会导致你的应用程序无法扩展到高的并发负载。 <br />一
个操作单元(Unit of work)的范围是多大？单个的Hibernate Session能跨越多个
数据库事务吗？还是一个Session的作用范围对应一个数据库事务的范围？应该何时打开
Session，何时关闭Session？，你又如何划分数据库事务的边界呢？ <br /><br /><strong>12.1.1.操作单元(Unit of work)</strong><br />首
先，别再用session-per-operation这种反模式了，也就是说，在单个线程中，
不要因为一次简单的数据库调用，就打开和关闭一次Session！数据库事务也是如此。
应用程序中的数据库调用是按照计划好的次序，分组为原子的操作单元。（注意，这也意味着，应用程
序中，在单个的SQL语句发送之后，自动事务提交(auto-commit)模式失效了。这种模式专门为SQL控制台操作设计的。
Hibernate禁止立即自动事务提交模式，或者期望应用服务器禁止立即自动事务提交模式。） <br />在多用户的client/server应用程
序中，最常用的模式是 每个请求一个会话(session-per-request)。
在这种模式下，来自客户端的请求被发送到服务器端（即Hibernate持久化层运行的地方），一 个新的Hibernate
Session被打开，并且执行这个操作单元中所有的数据库操作。
一旦操作完成（同时发送到客户端的响应也准备就绪），session被同步，然后关闭。你也可以使用单
个数据库事务来处理客户端请求，在你打开Session之后启动事务，在你关闭
Session之前提交事务。会话和请求之间的关系是一对一的关系，这种模式对 于大多数应用程序来说是很棒的。 <br />真正的挑战在于如何去实现这
种模式：不仅Session和事务必须被正确的开始和结束，
而且他们也必须能被数据访问操作访问。用拦截器来实现操作单元的划分，该拦截器在客户端请求达到服
务器端的时候开始，在服务器端发送响应（即，ServletFilter）之前结束。我们推荐 使用一个ThreadLocal 变量，把
Session绑定到处理客户端请求的线 程上去。这种方式可以让运行在该线程上的所有程序代码轻松的访问Session（就像访问一
个静态变量那样）。你也可以在一个ThreadLocal 变量中保持事务上下文环境，不过这依赖
于你所选择的数据库事务划分机制。这种实现模式被称之为 ThreadLocal Session和 Open Session in
View。你可以很容易的扩展本文前面章节展示的 HibernateUtil 辅助类来实现这种模式。当然，你必须找到一种实现拦截器的方法，并
且可以把拦截器集成到你的应用环境中。请参考Hibernate网站上面的提示和例子。 <br /><br /><strong>12.1.2.应用程序事务(Application transactions)</strong><br />session-per-request模式不仅仅是一个可以用来设计操作单元的有用概念。很多业务处理流程都需 要一系列完整的和用户之间的交互，即用户对数据库的交叉访问。在基于web的应用和企业 应用中，跨用户交互的数据库事务是无法接受的。考虑下面的例子： <br />在界面的第一屏，打开对话框，用户所看到的数据是被一个特定的 Session 和数据 库事务载入(load)的。用户可以随意修改对话框中的数据对象。 <br />5分钟后，用户点击“保存”，期望所做出的修改被持久化；同时他也期望自己是唯一修改这个信息的人，不会出现 修改冲突。 <br />从用户的角度来看，我们把这个操作单元称为应用程序长事务（application transaction）。 在你的应用程序中，可以有很多种方法来实现它。 <br />头一个幼稚的做法是，在用户思考的过程中，保持Session和数据库事务是打开的， 保持数据库锁定，以阻止并发修改，从而保证数据库事务隔离级别和原子操作。这种方式当然是一个反模式， 因为数据库锁定的维持会导致应用程序无法扩展并发用户的数目。 <br />很
明显，我们必须使用多个数据库事务来实现一个应用程序事务。在这个例子中，维护业务处理流程的
事务隔离变成了应用程序层的部分责任。单个应用程序事务通常跨越多个数据库事务。如果仅仅只有一
个数据库事务（最后的那个事务）保存更新过的数据，而所有其他事务只是单纯的读取数据（例如在一
个跨越多个请求/响应周期的向导风格的对话框中），那么应用程序事务将保证其原子性。这种方式比听
起来还要容易实现，特别是当你使用了Hibernate的下述特性的时候： <br />自动版本化 - Hibernate能够自动进行乐观并发控制 ，如果在用户思考 的过程中发生并发修改冲突，Hibernate能够自动检测到。 <br />脱
管对象（Detached Objects）- 如果你决定采用前面已经讨论过的
session-per-request模式，所有载入的实例在用户思考的过程
中都处于与Session脱离的状态。Hibernate允许你把与Session脱离的对象重新关联到Session
上，并且对修改进行持久化，这种模式被称为
session-per-request-with-detached-objects。自动版本化被用来隔离并发修改。 <br />长生命周期的
Session （Long Session）- Hibernate 的Session
可以在数据库事务提交之后和底层的JDBC连接断开，当一个新的客户端请求到来的时候，它又重新连接上底层的
JDBC连接。这种模式被称之为session-per-application-transaction，这种情况可
能会造成不必要的Session和JDBC连接的重新关联。自动版本化被用来隔离并发修改。 <br />session-per-request-with-detached-objects 和 session-per-application-transaction 各有优缺点，我们在本章后面乐观并发 控制那部分再进行讨论。 </font>
		</p>
		<p>
				<font size="2">
						<strong>12.1.3.关注对象标识(Considering object identity)</strong>
						<br />应用程序可能在两个不同的Session中并发访问同一持久化状态，但是， 一个持久化类的实例无法在两个 Session中共享。因此有两种不同的标识语义： <br />数据库标识 <br />foo.getId().equals( bar.getId() ) <br />JVM 标识 <br />foo==bar <br />对
于那些关联到 特定Session （也就是在单个Session的范围内）上的对象来说，这
两种标识的语义是等价的，与数据库标识对应的JVM标识是由Hibernate来保
证的。不过，当应用程序在两个不同的session中并发访问具有同一持久化标 识的业务对象实例的时候，这个业务对象的两个实例事实上是不相同的（从
JVM识别来看）。这种冲突可以通过在同步和提交的时候使用自动版本化和乐 观锁定方法来解决。 <br />这种方式把关于并发的头疼问题留给了
Hibernate和数据库；由于在单个线程内，操作单元中的对象识别不
需要代价昂贵的锁定或其他意义上的同步，因此它同时可以提供最好的可伸缩性。只要在单个线程只持有一个
Session，应用程序就不需要同步任何业务对象。在Session 的范围内，应用程序可以放心的使用==进行对象比较。 <br />不过，应用程序
在Session的外面使用==进行对象比较可能会 导致无法预期的结果。在一些无法预料的场合，例如，如果你把两个脱管对象实例放进同一个
Set的时候，就可能发生。这两个对象实例可能有同一个数据库标识（也就是说，
他们代表了表的同一行数据），从JVM标识的定义上来说，对脱管的对象而言，Hibernate无法保证他们
的的JVM标识一致。开发人员必须覆盖持久化类的equals()方法和 hashCode()
方法，从而实现自定义的对象相等语义。警告：不要使用数据库标识
来实现对象相等，应该使用业务键值，由唯一的，通常不变的属性组成。当一个瞬时对象被持久化的时
候，它的数据库标识会发生改变。如果一个瞬时对象（通常也包括脱管对象实例）被放入一
个Set，改变它的hashcode会导致与这个Set的关系中断。虽 然业务键值的属性不象数据库主键那样稳定不变，但是你只需要保证在同一个Set
中的对象属性的稳定性就足够了。请到Hibernate网站去寻求这个问题更多的详细的讨论。请注意，这不是一
个有关Hibernate的问题，而仅仅是一个关于Java对象标识和判等行为如何实现的问题。 <br /><br /><strong>12.1.4.常见问题</strong><br />决
不要使用反模式session-per-user-session或者
session-per-application（当然，这个规定几乎没有例外）。请注意，
下述一些问题可能也会出现在我们推荐的模式中，在你作出某个设计决定之前，请务必理解该模式的应用前提。 <br />Session
是一个非线程安全的类。如果一个Session 实例允许共享的话，那些支持并发运行的东东，例如HTTP request，session
beans,或者是 Swing workers，将会导致出现资源争用（race condition）。如果在HttpSession中有
Hibernate 的Session的话（稍后讨论），你应该考虑同步访问你的Http session。
否则，只要用户足够快的点击浏览器的“刷新”，就会导致两个并发运行线程使用同一个 Session。 <br />一个由Hibernate抛出的异常意
味着你必须立即回滚数据库事务，并立即关闭Session （稍后会展开讨论）。如果你的Session绑定到一个应用程序上，你必
须停止该应用程序。回滚数据库事务并不会把你的业务对象退回到事务启动时候的状态。这
意味着数据库状态和业务对象状态不同步。通常情况下，这不是什么问题，因为异常是不可 恢复的,你必须在回滚之后重新开始执行。 <br />Session
缓存了处于持久化状态的每个对象（Hibernate会监视和检查脏数据）。
这意味着，如果你让Session打开很长一段时间，或是仅仅载入了过多的数据，
Session占用的内存会一直增长，直到抛出OutOfMemoryException异常。这个 问题的一个解决方法是调用clear()
和evict()来管理 Session的缓存，但是如果你需要大批量数据操作的话，最好考虑 使用存储过程。在第14章 批量处理（Batch
processing）中有一些解决方案。在用户会话期间一直保持 Session打开也意味着出现脏数据的可能性很高。 <br /><br /><strong>12.2.数据库事务声明</strong><br />数
据库（或者系统）事务的声明总是必须的。在数据库事务之外，就无法和数据库通讯（这可能会让那些习惯于
自动提交事务模式的开发人员感到迷惑）。永远使用清晰的事务声明，即使只读操作也是如此。进行
显式的事务声明并不总是需要的，这取决于你的事务隔离级别和数据库的能力，但不管怎么说，声明事务总归有益无害。 <br />一个Hibernate应用
程序可以运行在非托管环境中（也就是独立运行的应用程序，简单Web应用程序，
或者Swing图形桌面应用程序），也可以运行在托管的J2EE环境中。在一个非托管环境中，Hibernate
通常自己负责管理数据库连接池。应用程序开发人员必须手工设置事务声明，换句话说，就是手工启
动，提交，或者回滚数据库事务。一个托管的环境通常提供了容器管理事务，例如事务装配通过可声 明的方式定义在EJB session
beans的部署描述符中。可编程式事务声明不再需要，即使是 Session 的同步也可以自动完成。 <br />让持久层具备可移植性是人们的理想。
Hibernate提供了一套称为Transaction的封装API，
用来把你的部署环境中的本地事务管理系统转换到Hibernate事务上。这个API是可选的，但是我们强烈 推荐你使用，除非你用CMT
session bean。 <br />通常情况下，结束 Session 包含了四个不同的阶段: <br />同步session(flush,刷出到磁盘） <br />提交事务 <br />关闭session <br />处理异常 <br />session的同步(flush,刷出）前面已经讨论过了，我们现在进一步考察在托管和非托管环境下的事务声明和异常处理。 <br /><br /><strong>12.2.1.非托管环境</strong><br />如果Hibernat持久层运行在一个非托管环境中，数据库连接通常由Hibernate的连接池机制 来处理。 <br /><br /></font>
		</p>
		<table style="border-width: 1px;" fixed="" table-layout="" align="center" border="1" bordercolor="#e0e0e0" cellpadding="4" cellspacing="1" width="95%">
				<tbody>
						<tr>
								<td style="height: 25px;" bgcolor="#f6f6f6" valign="top">
										<font style="color: rgb(176, 176, 176);" size="2">代码内容</font>
										<font size="2">
												<br />session/transaction处理方式如下所示： <br />//Non-managed environment idiom <br />Session sess = factory.openSession(); <br />Transaction tx = null; <br />try { <br />tx = sess.beginTransaction(); <br /><br />// do some work <br />... <br /><br />tx.commit(); <br />} <br />catch (RuntimeException e) { <br />if (tx != null) tx.rollback(); <br />throw e; // or display error message <br />} <br />finally { <br />sess.close(); <br />} </font>
								</td>
						</tr>
				</tbody>
		</table>
		<font size="2">
				<br />你不需要显式flush() Session - 对commit()的调用会自动触发session的同步。 <br />调用 close() 标志session的结束。 close()方法重要的暗示是，session释放了JDBC连接。 <br />这段Java代码是可移植的，可以在非托管环境和JTA环境中运行。 <br />你
很可能从未在一个标准的应用程序的业务代码中见过这样的用法；致命的（系统）异常应该总是
在应用程序“顶层”被捕获。换句话说，执行Hibernate调用的代码（在持久层）和处理
RuntimeException异常的代码（通常只能清理和退出应用程序）应该在不同
的应用程序逻辑层。这对于你设计自己的软件系统来说是一个挑战，只要有可能，你就应该使用 J2EE/EJB容器服务。异常处理将在本章稍后进行讨论。
<br />请注意，你应该选择 org.hibernate.transaction.JDBCTransactionFactory (这是默认选项). </font>
		<p align="left">
				<font size="2">
						<strong>12.2.2.使用JTA</strong>
						<br />如果你的持久层运行在一个应用服务器中（例如，在EJB session beans的后面），Hibernate获取 的每个数据源连接将自动成为全局JTA事务的一部分。Hibernate提供了两种策略进行JTA集成。 <br />如果你使用bean管理事务（BMT），可以通过使用Hibernate的 Transaction API来告诉 应用服务器启动和结束BMT事务。因此，事务管理代码和在非托管环境下是一样的。 <br /><br /></font>
		</p>
		<table style="border-width: 1px;" fixed="" table-layout="" align="center" border="1" bordercolor="#e0e0e0" cellpadding="4" cellspacing="1" width="95%">
				<tbody>
						<tr>
								<td style="height: 25px;" bgcolor="#f6f6f6" valign="top">
										<font style="color: rgb(176, 176, 176);" size="2">代码内容</font>
										<font size="2">
												<br />// BMT idiom <br />Session sess = factory.openSession(); <br />Transaction tx = null; <br />try { <br />tx = sess.beginTransaction(); <br /><br />// do some work <br />... <br /><br />tx.commit(); <br />} <br />catch (RuntimeException e) { <br />if (tx != null) tx.rollback(); <br />throw e; // or display error message <br />} <br />finally { <br />sess.close(); <br />} </font>
								</td>
						</tr>
				</tbody>
		</table>
		<font size="2">
				<br />在CMT
方式下，事务声明是在session bean的部署描述符中，而不需要编程。
除非你设置了属性hibernate.transaction.flush_before_completion和
hibernate.transaction.auto_close_session为true，
否则你必须自己同步和关闭Session。Hibernate可以为你自动同步和关闭
Session。你唯一要做的就是当发生异常时进行事务回滚。幸运的是， 在一个CMT
bean中，事务回滚甚至可以由容器自动进行，因为由session bean方法抛出的未处理的
RuntimeException异常可以通知容器设置全局事务回滚。这意味着
在CMT中，你完全无需使用Hibernate的Transaction API 。 <br />请注意，当你配置Hibernate事务工厂的时候，在
一个BMT session bean中，你应该选择
org.hibernate.transaction.JTATransactionFactory，在一个 CMT session
bean中选择org.hibernate.transaction.CMTTransactionFactory。
记住，同时也要设置org.hibernate.transaction.manager_lookup_class。 <br />如果你使用CMT环
境，并且让容器自动同步和关闭session，你可能也希望在你代码的不同部分使用
同一个session。一般来说，在一个非托管环境中，你可以使用一个ThreadLocal
变量来持有这个session，但是单个EJB方法调用可能会在不同的线程中执行（举例来说，一个session bean调用另一个session
bean）。如果你不想在应用代码中被传递Session对 象实例的问题困扰的话，那么SessionFactory 提供的
getCurrentSession()方法就很适合你，该方法返回一个绑定到JTA事务
上下文环境中的session实例。这也是把Hibernate集成到一个应用程序中的最简单的方法！这个“当
前的”session总是可以自动同步和自动关闭（不考虑上述的属性设置）。我们的session/transaction 管理代码减少到如下所示： <br /><br /></font>
		<table style="border-width: 1px;" fixed="" table-layout="" align="center" border="1" bordercolor="#e0e0e0" cellpadding="4" cellspacing="1" width="95%">
				<tbody>
						<tr>
								<td style="height: 25px;" bgcolor="#f6f6f6" valign="top">
										<font style="color: rgb(176, 176, 176);" size="2">代码内容</font>
										<font size="2">
												<br />// CMT idiom <br />Session sess = factory.getCurrentSession(); <br /><br />// do some work <br />... </font>
								</td>
						</tr>
				</tbody>
		</table>
		<font size="2">
				<br />换
句话来说，在一个托管环境下，你要做的所有的事情就是调用
SessionFactory.getCurrentSession()，然后进行你的数据访问，把其余的工作
交给容器来做。事务在你的session bean的部署描述符中以可声明的方式来设置。session的生命周期完全 由Hibernate来管理。
<br />对after_statement连接释放方式有一个警告。因为JTA规范的一个很愚蠢的限制，Hibernate不可能自动清理任何未关闭的
ScrollableResults
或者Iterator，它们是由scroll()或iterate()产生的。你must通过在finally块中，显式调用
ScrollableResults.close()或者Hibernate.close(Iterator)方法来释放底层数据库游标。(当然，大部分
程序完全可以很容易的避免在CMT代码中出现scroll()或iterate()。) <br /><br /><strong>12.2.3.异常处理</strong><br />如
果 Session 抛出异常 (包括任何SQLException), 你应该立即回滚数据库事务，调用 Session.close() ，丢弃该
Session实例。Session的某些方法可能会导致session
处于不一致的状态。所有由Hibernate抛出的异常都视为不可以恢复的。确保在 finally 代码块中调用close()方法，以关闭掉
Session。 <br />HibernateException是一个非检查期异常（这不同于Hibernate老的版本），
它封装了Hibernate持久层可能出现的大多数错误。我们的观点是，不应该强迫应用程序开发人员
在底层捕获无法恢复的异常。在大多数软件系统中，非检查期异常和致命异常都是在相应方法调用
的堆栈的顶层被处理的（也就是说，在软件上面的逻辑层），并且提供一个错误信息给应用软件的用户
（或者采取其他某些相应的操作）。请注意，Hibernate也有可能抛出其他并不属于
HibernateException的非检查期异常。这些异常同样也是无法恢复的，应该 采取某些相应的操作去处理。 <br />在和数据库进行交互
时，Hibernate把捕获的SQLException封装为Hibernate的
JDBCException。事实上，Hibernate尝试把异常转换为更有实际含义
的JDBCException异常的子类。底层的SQLException可以
通过JDBCException.getCause()来得到。Hibernate通过使用关联到
SessionFactory上的SQLExceptionConverter来
把SQLException转换为一个对应的JDBCException
异常的子类。默认情况下，SQLExceptionConverter可以通过配置dialect
选项指定；此外，也可以使用用户自定义的实现类（参考javadocs
SQLExceptionConverterFactory类来了解详情）。标准的 JDBCException子类型是： <br />JDBCConnectionException - 指明底层的JDBC通讯出现错误 <br />SQLGrammarException - 指明发送的SQL语句的语法或者格式错误 <br />ConstraintViolationException - 指明某种类型的约束违例错误 <br />LockAcquisitionException - 指明了在执行请求操作时，获取 所需的锁级别时出现的错误。 <br />GenericJDBCException - 不属于任何其他种类的原生异常 <br /><br /><strong>12.3.乐观并发控制(Optimistic concurrency control)</strong><br />唯
一能够同时保持高并发和高可伸缩性的方法就是使用带版本化的乐观并发控制。版本检查使用版本号、
或者时间戳来检测更新冲突（并且防止更新丢失）。Hibernate为使用乐观并发控制的代码提供了三种可
能的方法，应用程序在编写这些代码时，可以采用它们。我们已经在前面应用程序长事务那部分展示了
乐观并发控制的应用场景，此外，在单个数据库事务范围内，版本检查也提供了防止更新丢失的好处。 <br />12.3.1.应用程序级别的版本检查(Application version checking) <br />未
能充分利用Hibernate功能的实现代码中，每次和数据库交互都需要一个新的 Session，而且开发人员必须在显示数据之前从数据库中重
新载入所有的持久化对象实例。这种方式迫使应用程序自己实现版本检查来确保 应用程序事务的隔离，从数据访问的角度来说是最低效的。这种使用方式和
entity EJB最相似。 <br />// foo is an instance loaded by a previous Session <br />session = factory.openSession(); <br />Transaction t = session.beginTransaction(); <br />int oldVersion = foo.getVersion(); <br />session.load( foo, foo.getKey() ); // load the current state <br />if ( oldVersion!=foo.getVersion ) throw new StaleObjectStateException(); <br />foo.setProperty("bar"); <br />t.commit(); <br />session.close(); <br />version 属性使用 来映射，如果对象 是脏数据，在同步的时候，Hibernate会自动增加版本号。 <br />当
然，如果你的应用是在一个低数据并发环境下，并不需要版本检查的话，你照样可以使用 这种方式，只不过跳过版本检查就是了。在这种情况下，最晚提交生效
（last commit wins）就是你的应用程序长事务的默认处理策略。
请记住这种策略可能会让应用软件的用户感到困惑，因为他们有可能会碰上更新丢失掉却没 有出错信息，或者需要合并更改冲突的情况。 <br />很明显，手
工进行版本检查只适合于某些软件规模非常小的应用场景，对于大多数软件应用场景
来说并不现实。通常情况下，不仅是单个对象实例需要进行版本检查，整个被修改过的关
联对象图也都需要进行版本检查。作为标准设计范例，Hibernate使用长生命周期
Session的方式，或者脱管对象实例的方式来提供自动版本检查。 <br /><br /><strong>12.3.2.长生命周期session和自动版本化 </strong><br />单
个 Session实例和它所关联的所有持久化对象实例都被用于整个
应用程序事务。Hibernate在同步的时候进行对象实例的版本检查，如果检测到并发修
改则抛出异常。由开发人员来决定是否需要捕获和处理这个异常（通常的抉择是给用户 提供一个合并更改，或者在无脏数据情况下重新进行业务操作的机会）。
<br />在等待用户交互的时候， Session 断开底层的JDBC连接。这种方式 以数据库访问的角度来说是最高效的方式。应用程序不需要关心版本检查或脱管对象实例 的重新关联，在每个数据库事务中，应用程序也不需要载入读取对象实例。 <br /><br /></font>
		<table style="border-width: 1px;" fixed="" table-layout="" align="center" border="1" bordercolor="#e0e0e0" cellpadding="4" cellspacing="1" width="95%">
				<tbody>
						<tr>
								<td style="height: 25px;" bgcolor="#f6f6f6" valign="top">
										<font style="color: rgb(176, 176, 176);" size="2">代码内容</font>
										<font size="2">
												<br />// foo is an instance loaded earlier by the Session <br />session.reconnect(); // Obtain a new JDBC connection <br />Transaction t = session.beginTransaction(); <br />foo.setProperty("bar"); <br />t.commit(); // End database transaction, flushing the change and checking the version <br />session.disconnect(); // Return JDBC connection </font>
								</td>
						</tr>
				</tbody>
		</table>
		<font size="2">
				<br />foo
对象始终和载入它的Session相关联。 Session.reconnect()获取一个新的数据库连接（或者
你可以提供一个），并且继续当前的session。Session.disconnect()
方法把session与JDBC连接断开，把数据库连接返回到连接池（除非是你自己提供的数据
库连接）。在Session重新连接上数据库连接之后，你可以对任何可能被其他事务更新过
的对象调用Session.lock()，设置LockMode.READ
锁定模式，这样你就可以对那些你不准备更新的数据进行强制版本检查。此外，你并不需要 锁定那些你准备更新的数据。 <br />假若对disconnect()和reconnect()的显式调用发生得太频繁了，你可以使用hibernate.connection.release_mode来代替。 <br />如
果在用户思考的过程中，Session因为太大了而不能保存，那么这种模式是有 问题的。举例来说，一个HttpSession应该尽可能的小。由于
Session是一级缓存，并且保持了所有被载入过的对象，因此
我们只应该在那些少量的request/response情况下使用这种策略。而且在这种情况下， Session
里面很快就会有脏数据出现，因此请牢牢记住这一建议。 <br />此外，也请注意，你应该让与数据库连接断开的Session对持久层保持 关闭状态。换句话说，使用有状态的EJB session bean来持有Session， 而不要把它传递到web层（甚至把它序列化到一个单独的层），保存在HttpSession中。 </font>
		<p align="left">
				<font size="2">
						<strong>12.3.3.脱管对象(deatched object)和自动版本化 <br /></strong>这
种方式下，与持久化存储的每次交互都发生在一个新的Session中。
然而，同一持久化对象实例可以在多次与数据库的交互中重用。应用程序操纵脱管对象实例 的状态，这个脱管对象实例最初是在另一个Session
中载入的，然后 调用 Session.update()，Session.saveOrUpdate(), 或者 Session.merge()
来重新关联该对象实例。 <br /><br /></font>
		</p>
		<table style="border-width: 1px;" fixed="" table-layout="" align="center" border="1" bordercolor="#e0e0e0" cellpadding="4" cellspacing="1" width="95%">
				<tbody>
						<tr>
								<td style="height: 25px;" bgcolor="#f6f6f6" valign="top">
										<font style="color: rgb(176, 176, 176);" size="2">代码内容</font>
										<font size="2">
												<br />// foo is an instance loaded by a previous Session <br />foo.setProperty("bar"); <br />session = factory.openSession(); <br />Transaction t = session.beginTransaction(); <br />session.saveOrUpdate(foo); // Use merge() if "foo" might have been loaded already <br />t.commit(); <br />session.close(); </font>
								</td>
						</tr>
				</tbody>
		</table>
		<font size="2">
				<br />Hibernate会再一次在同步的时候检查对象实例的版本，如果发生更新冲突，就抛出异常。 <br />如果你确信对象没有被修改过，你也可以调用lock() 来设置 LockMode.READ（绕过所有的缓存，执行版本检查），从而取 代 update()操作。 <br /><br /><strong>12.3.4.定制自动版本化行为</strong><br />对于特定的属性和集合，通过为它们设置映射属性optimistic-lock的值 为false，来禁止Hibernate的版本自动增加。这样的话，如果该属性 脏数据，Hibernate将不再增加版本号。 <br />遗
留系统的数据库Schema通常是静态的，不可修改的。或者，其他应用程序也可能访问同一数据
库，根本无法得知如何处理版本号，甚至时间戳。在以上的所有场景中，实现版本化不能依靠 数据库表的某个特定列。在的映射中设置
optimistic-lock="all"可以在没有版本或者时间戳属性映射的情况下实现
版本检查，此时Hibernate将比较一行记录的每个字段的状态。请注意，只有当Hibernate能够比
较新旧状态的情况下，这种方式才能生效，也就是说， 你必须使用单个长生命周期Session模式，而不能使用
session-per-request-with-detached-objects模式。 <br />有些情况下，只要更改不发生交错，并发修改也是允许的。当你在 的映射中设置optimistic-lock="dirty"，Hibernate在同步的时候将只比较有脏 数据的字段。 <br />在
以上所有场景中，不管是专门设置一个版本/时间戳列，还是进行全部字段/脏数据字段比较，
Hibernate都会针对每个实体对象发送一条UPDATE（带有相应的 WHERE语句
）的SQL语句来执行版本检查和数据更新。如果你对关联实体 设置级联关系使用传播性持久化（transitive
persistence），那么Hibernate可能会执行不必 要的update语句。这通常不是个问题，但是数据库里面对on update点火
的触发器可能在脱管对象没有任何更改的情况下被触发。因此，你可以在 的映射中，通过设置select-before-update="true"
来定制这一行为，强制Hibernate SELECT这个对象实例，从而保证， 在更新记录之前，对象的确是被修改过。 <br /><br /><strong>12.4.悲观锁定(Pessimistic Locking)</strong><br />用户其实并不需要花很多精力去担心锁定策略的问题。通常情况下，只要为JDBC连接指定一下隔 离级别，然后让数据库去搞定一切就够了。然而，高级用户有时候希望进行一个排它的悲观锁定， 或者在一个新的事务启动的时候，重新进行锁定。 <br />Hibernate总是使用数据库的锁定机制，从不在内存中锁定对象！ <br />类LockMode 定义了Hibernate所需的不同的锁定级别。一个锁定 可以通过以下的机制来设置: <br />当Hibernate更新或者插入一行记录的时候，锁定级别自动设置为LockMode.WRITE。 <br />当用户显式的使用数据库支持的SQL格式SELECT ... FOR UPDATE 发送SQL的时候，锁定级别设置为LockMode.UPGRADE <br />当用户显式的使用Oracle数据库的SQL语句SELECT ... FOR UPDATE NOWAIT 的时候，锁定级别设置LockMode.UPGRADE_NOWAIT <br />当Hibernate在“可重复读”或者是“序列化”数据库隔离级别下读取数据的时候，锁定模式 自动设置为LockMode.READ。这种模式也可以通过用户显式指定进行设置。 <br />LockMode.NONE 代表无需锁定。在Transaction结束时， 所有的对象都切换到该模式上来。与session相关联的对象通过调用update() 或者saveOrUpdate()脱离该模式。 <br />"显式的用户指定"可以通过以下几种方式之一来表示: <br />调用 Session.load()的时候指定锁定模式(LockMode)。 <br />调用Session.lock()。 <br />调用Query.setLockMode()。 <br />如
果在UPGRADE或者UPGRADE_NOWAIT锁定模式下调
用Session.load()，并且要读取的对象尚未被session载入过，那么对象 通过SELECT ... FOR
UPDATE这样的SQL语句被载入。如果为一个对象调用 load()方法时，该对象已经在另一个较少限制的锁定模式下被载入了，那
么Hibernate就对该对象调用lock() 方法。 <br />如果指定的锁定模式是READ, UPGRADE 或
UPGRADE_NOWAIT，那么Session.lock()就 执行版本号检查。（在UPGRADE 或者UPGRADE_NOWAIT
锁定模式下，执行SELECT ... FOR UPDATE这样的SQL语句。） <br />如果数据库不支持用户设置的锁定模式，Hibernate将使用适当的替代模式（而不是扔出异常）。 这一点可以确保应用程序的可移植性。 </font>
<img src ="http://www.blogjava.net/cmd/aggbug/49407.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/cmd/" target="_blank">静夜思</a> 2006-06-01 09:31 <a href="http://www.blogjava.net/cmd/articles/49407.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Lucene基本使用介绍</title><link>http://www.blogjava.net/cmd/articles/42730.html</link><dc:creator>静夜思</dc:creator><author>静夜思</author><pubDate>Sun, 23 Apr 2006 14:31:00 GMT</pubDate><guid>http://www.blogjava.net/cmd/articles/42730.html</guid><wfw:comment>http://www.blogjava.net/cmd/comments/42730.html</wfw:comment><comments>http://www.blogjava.net/cmd/articles/42730.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/cmd/comments/commentRss/42730.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/cmd/services/trackbacks/42730.html</trackback:ping><description><![CDATA[
		<font size="2">一.  概述<br /><br />
随着系统信息的越来越多，怎么样从这些信息海洋中捞起自己想要的那一根针就变得非常重要了，全文检索是通常用于解决此类问题的方案，而Lucene则为实现全文检索的工具，任何应用都可通过嵌入它来实现全文检索。<br /><br />
二.  环境搭建<br /><br />
从lucene.apache.org上下载最新版本的lucene.jar，将此jar作为项目的build path，那么在项目中就可以直接使用lucene了。<br /><br />
三.  使用说明<br /><br />
3.1.       基本概念<br /><br />
这里介绍的主要为在使用中经常碰到一些概念，以大家都比较熟悉的数据库来进行类比的讲解，使用Lucene进行全文检索的过程有点类似数据库的这个过程，
table---à查询相应的字段或查询条件----à返回相应的记录，首先是IndexWriter，通过它建立相应的索引表，相当于数据库中的
table，在构建此索引表时需指定的为该索引表采用何种方式进行构建，也就是说对于其中的记录的字段以什么方式来进行格式的划分，这个在Lucene中
称为Analyzer，Lucene提供了几种环境下使用的Analyzer：SimpleAnalyzer、StandardAnalyzer、
GermanAnalyzer等，其中StandardAnalyzer是经常使用的，因为它提供了对于中文的支持，在表建好后我们就需要往里面插入用于
索引的记录，在Lucene中这个称为Document，有点类似数据库中table的一行记录，记录中的字段的添加方法，在Lucene中称为
Field，这个和数据库中基本一样，对于Field
Lucene分为可被索引的，可切分的，不可被切分的，不可被索引的几种组合类型，通过这几个元素基本上就可以建立起索引了。在查询时经常碰到的为另外几
个概念，首先是Query，Lucene提供了几种经常可以用到的Query：TermQuery、MultiTermQuery、
BooleanQuery、WildcardQuery、PhraseQuery、PrefixQuery、PhrasePrefixQuery、
FuzzyQuery、RangeQuery、SpanQuery，Query其实也就是指对于需要查询的字段采用什么样的方式进行查询，如模糊查询、语
义查询、短语查询、范围查询、组合查询等，还有就是QueryParser，QueryParser可用于创建不同的Query，还有一个
MultiFieldQueryParser支持对于多个字段进行同一关键字的查询，IndexSearcher概念指的为需要对何目录下的索引文件进行
何种方式的分析的查询，有点象对数据库的哪种索引表进行查询并按一定方式进行记录中字段的分解查询的概念，通过IndexSearcher以及Query
即可查询出需要的结果，Lucene返回的为Hits.通过遍历Hits可获取返回的结果的Document，通过Document则可获取Field中
的相关信息了。<br /><br />
通过对于上面在建立索引和全文检索的基本概念的介绍希望能让你对Lucene建立一定的了解。<br /><br />
3.2.       全文检索需求的实现<br /><br />
索引建立部分的代码：<br /><br />
private void createIndex(String indexFilePath) throws Exception{<br /><br />
        IndexWriter iwriter=getWriter(indexFilePath);<br /><br />
        Document doc=new Document();<br /><br />
        doc.add(Field.Keyword("name","jerry"));<br /><br />
        doc.add(Field.Text("sender","bluedavy@gmail.com"));<br /><br />
        doc.add(Field.Text("receiver","google@gmail.com"));<br /><br />
        doc.add(Field.Text("title","用于索引的标题"));<br /><br />
        doc.add(Field.UnIndexed("content","不建立索引的内容"));<br /><br />
        Document doc2=new Document();<br /><br />
        doc2.add(Field.Keyword("name","jerry.lin"));<br /><br />
        doc2.add(Field.Text("sender","bluedavy@hotmail.com"));<br /><br />
        doc2.add(Field.Text("receiver","msn@hotmail.com"));<br /><br />
        doc2.add(Field.Text("title","用于索引的第二个标题"));<br /><br />
        doc2.add(Field.Text("content","建立索引的内容"));<br /><br />
        iwriter.addDocument(doc);<br /><br />
        iwriter.addDocument(doc2);<br /><br />
        iwriter.optimize();<br /><br />
        iwriter.close();<br /><br />
    }<br /><br />
    <br /><br />
    private IndexWriter getWriter(String indexFilePath) throws Exception{<br /><br />
        boolean append=true;<br /><br />
        File file=new File(indexFilePath+File.separator+"segments");<br /><br />
        if(file.exists())<br /><br />
            append=false; <br /><br />
        return new IndexWriter(indexFilePath,analyzer,append);<br /><br />
    }<br /><br />
3.2.1.       对于某字段的关键字的模糊查询<br /><br />
Query query=new WildcardQuery(new Term("sender","*davy*"));<br /><br />
        <br /><br />
        Searcher searcher=new IndexSearcher(indexFilePath);<br /><br />
        Hits hits=searcher.search(query);<br /><br />
        for (int i = 0; i &lt; hits.length(); i++) {<br /><br />
            System.out.println(hits.doc(i).get("name"));<br /><br />
        }<br /><br />
3.2.2.       对于某字段的关键字的语义查询<br /><br />
Query query=QueryParser.parse("索引","title",analyzer);<br /><br />
        <br /><br />
        Searcher searcher=new IndexSearcher(indexFilePath);<br /><br />
        Hits hits=searcher.search(query);<br /><br />
        for (int i = 0; i &lt; hits.length(); i++) {<br /><br />
            System.out.println(hits.doc(i).get("name"));<br /><br />
        }<br /><br />
3.2.3.       对于多字段的关键字的查询<br /><br />
Query query=MultiFieldQueryParser.parse("索引",new String[]{"title","content"},analyzer);<br /><br />
        <br /><br />
        Searcher searcher=new IndexSearcher(indexFilePath);<br /><br />
        Hits hits=searcher.search(query);<br /><br />
        for (int i = 0; i &lt; hits.length(); i++) {<br /><br />
            System.out.println(hits.doc(i).get("name"));<br /><br />
        }<br /><br />
3.2.4.       复合查询(多种查询条件的综合查询)<br /><br />
Query query=MultiFieldQueryParser.parse("索引",new String[]{"title","content"},analyzer);<br /><br />
        Query mquery=new WildcardQuery(new Term("sender","bluedavy*"));<br /><br />
        TermQuery tquery=new TermQuery(new Term("name","jerry"));<br /><br />
        <br /><br />
        BooleanQuery bquery=new BooleanQuery();<br /><br />
        bquery.add(query,true,false);<br /><br />
        bquery.add(mquery,true,false);<br /><br />
        bquery.add(tquery,true,false);<br /><br />
        <br /><br />
        Searcher searcher=new IndexSearcher(indexFilePath);<br /><br />
        Hits hits=searcher.search(bquery);<br /><br />
        for (int i = 0; i &lt; hits.length(); i++) {<br /><br />
            System.out.println(hits.doc(i).get("name"));<br /><br />
        }<br /><br />
四.  总结<br /><br />
相信大家通过上面的说明能知道Lucene的一个基本的使用方法，在全文检索时建议大家先采用语义时的搜索，先搜索出有意义的内容，之后再进行模糊之类的
搜索，^_^，这个还是需要根据搜索的需求才能定了，Lucene还提供了很多其他更好用的方法，这个就等待大家在使用的过程中自己去进一步的摸索了，比
如对于Lucene本身提供的Query的更熟练的掌握，对于Filter、Sorter的使用，自己扩展实现Analyzer，自己实现Query等
等，甚至可以去了解一些关于搜索引擎的技术(切词、索引排序 etc)等等。</font>
<img src ="http://www.blogjava.net/cmd/aggbug/42730.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/cmd/" target="_blank">静夜思</a> 2006-04-23 22:31 <a href="http://www.blogjava.net/cmd/articles/42730.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Mysql 内部函数的使用</title><link>http://www.blogjava.net/cmd/articles/42411.html</link><dc:creator>静夜思</dc:creator><author>静夜思</author><pubDate>Fri, 21 Apr 2006 13:05:00 GMT</pubDate><guid>http://www.blogjava.net/cmd/articles/42411.html</guid><wfw:comment>http://www.blogjava.net/cmd/comments/42411.html</wfw:comment><comments>http://www.blogjava.net/cmd/articles/42411.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/cmd/comments/commentRss/42411.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/cmd/services/trackbacks/42411.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 算数运算子																										+, -, *, /								除于								 0 								会等于								 NULL								。																																												比较运算子																								...&nbsp;&nbsp;<a href='http://www.blogjava.net/cmd/articles/42411.html'>阅读全文</a><img src ="http://www.blogjava.net/cmd/aggbug/42411.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/cmd/" target="_blank">静夜思</a> 2006-04-21 21:05 <a href="http://www.blogjava.net/cmd/articles/42411.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>apache正在孵化一个优良血统的web framework---shale</title><link>http://www.blogjava.net/cmd/articles/42298.html</link><dc:creator>静夜思</dc:creator><author>静夜思</author><pubDate>Fri, 21 Apr 2006 05:34:00 GMT</pubDate><guid>http://www.blogjava.net/cmd/articles/42298.html</guid><wfw:comment>http://www.blogjava.net/cmd/comments/42298.html</wfw:comment><comments>http://www.blogjava.net/cmd/articles/42298.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/cmd/comments/commentRss/42298.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/cmd/services/trackbacks/42298.html</trackback:ping><description><![CDATA[
		<font size="2">好久没有关注struts了，记得自己只使用过他的1.0的版本，赫赫，技术有点老，只用过了他的最基本的页面流的跳转，马马虎虎，印象不是很好（大概是自己的水平低的缘故），最近在关注webwork，这回是用心研究。今天去了apache一趟，看看apache关于struts action2.0的官方的roadmap，的确是基于webwork，也可以说是webwork的2.3版本，这下子有点暗暗自喜，我对struts并不感冒，我挺钟情于os的产品线。看来这些个功夫下的还是值得的，以后可以无缝的衔接过来。<br /><br />在浏览的过程中发现了一个并行的项目叫做shale(页沿，海边的岩石是一种分层的地貌)。<br />在ibm上的一片介绍：<br />http://www-128.ibm.com/developerworks/cn/java/j-shale0228/<br /><br />觉得这个东西有点意思，是基于jsf的，老大说在将来有可能......<br />赫赫，将来有可能的事情太多了，每个都研究快吐血了！</font>
		<br />
<img src ="http://www.blogjava.net/cmd/aggbug/42298.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/cmd/" target="_blank">静夜思</a> 2006-04-21 13:34 <a href="http://www.blogjava.net/cmd/articles/42298.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>有空关注一个国人ajax产品</title><link>http://www.blogjava.net/cmd/articles/41774.html</link><dc:creator>静夜思</dc:creator><author>静夜思</author><pubDate>Tue, 18 Apr 2006 16:14:00 GMT</pubDate><guid>http://www.blogjava.net/cmd/articles/41774.html</guid><wfw:comment>http://www.blogjava.net/cmd/comments/41774.html</wfw:comment><comments>http://www.blogjava.net/cmd/articles/41774.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/cmd/comments/commentRss/41774.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/cmd/services/trackbacks/41774.html</trackback:ping><description><![CDATA[
		<font size="2">http://www.bstek.com/product.asp</font>
<img src ="http://www.blogjava.net/cmd/aggbug/41774.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/cmd/" target="_blank">静夜思</a> 2006-04-19 00:14 <a href="http://www.blogjava.net/cmd/articles/41774.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>一个用于J2EE应用程序的Backbase Ajax前端</title><link>http://www.blogjava.net/cmd/articles/41773.html</link><dc:creator>静夜思</dc:creator><author>静夜思</author><pubDate>Tue, 18 Apr 2006 16:12:00 GMT</pubDate><guid>http://www.blogjava.net/cmd/articles/41773.html</guid><wfw:comment>http://www.blogjava.net/cmd/comments/41773.html</wfw:comment><comments>http://www.blogjava.net/cmd/articles/41773.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/cmd/comments/commentRss/41773.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/cmd/services/trackbacks/41773.html</trackback:ping><description><![CDATA[
		<font size="2">动态HTML技术已经出现了多年。最近，Google的最新Web应用程序GMail、Google Suggests和Google
Maps，在前端页面中重新引入了基于标准的DHTML开发模型。Google证明了，DHTML开发模型能够让开发人员创建具有可视化吸引力和高度交互
式的Rich Internet Application（丰富网络应用程序，RIA）。</font>
		<p>
				<font size="2">　　Adaptive Path公司的Jesse James Garrett为这个基于标准的RIA开发模型创造了术语<a href="http://www.adaptivepath.com/publications/essays/archives/000385.php" target="_blank">Ajax (Asynchronous JavaScript + XML)</a>。与传统的基于页面的Web应用程序模型相比，Ajax有3点不同之处：</font>
		</p>
		<ul>
				<li>
						<font size="2">有一个客户端引擎担任用户界面（UI）和服务器之间的中介。</font>
				</li>
				<li>
						<font size="2">用户行为由客户端引擎处理，而不是生成发往服务器的页面请求。</font>
				</li>
				<li>
						<font size="2">XML数据在客户端引擎和服务器之间传输。</font>
				</li>
		</ul>
		<p>
				<font size="2">　　换言之，Ajax解决方案包括一个客户端引擎，它用于呈现用户界面，并使用XML格式与服务器通信。这个引擎由很多JavaScript函数组成，位于Web浏览器中，它不需要插件，也不需要用户安装。</font>
		</p>
		<p>
				<font size="2">　　基于Ajax的RIA正在迅速成为Web应用程序前端的基准，因为它可以同时提供二者的优点：丰富性和可达性。Ajax应用程序和桌面应用程序
一样丰富，响应高度灵敏，并且可以在一个页面上提供所有数据，无需刷新页面。它们还拥有基于标准的浏览器应用程序的可达性特点，这类应用程序可以在不具备
浏览器插件或客户端applet的情况下进行部署。</font>
		</p>
		<p>
				<font size="2">　　Backbase所提供的Ajax软件具有以下特点：基于标准、功能全面且易于使用。Backbase Presentation
Client (BPC)基于Ajax技术，它使用称为Backbase XML (BXML)的附加标签扩展了DHTML。Backbase XML
Server Edition for J2EE
(BXS)包含了一些服务器端的组件，利用这些组件，J2EE开发人员可以快速开发J2EE应用程序的Ajax前端。</font>
		</p>
		<p>
				<font size="2">　　在本文中，我使用Backbase为Java Pet Store开发了一个基于Ajax的前端。该案例分析说明了如何使用Backbase技术作为J2EE应用程序的Ajax表示层。您可以查看文中所描述的应用程序的在线演示，网址是<a href="http://www.backbase.com/xmlserver" target="_blank">http://www.backbase.com/xmlserver</a>。</font>
		</p>
		<p>
				<font size="2">
						<strong>Backbase Ajax表示层</strong>
				</font>
		</p>
		<p>
				<font size="2">　　Web开发人员应该能够轻松创建具有以下特点的Rich Internet Application
(RIA)：完全基于HTML标准（W3C），不需要最终用户安装插件，速度超快，能够在所有浏览器上进行操作，并与J2EE运行时和开发环境完全集成。
RIA利用客户端（Web浏览器）资源创建和管理用户界面，从而为最终用户提供一个响应灵敏而且具有应用程序风格的用户界面。</font>
		</p>
		<p>
				<font size="2">　　这种方法最近被称为Ajax。Ajax这个术语的灵感来源于Gmail、Google Maps和Google
Suggests这类应用程序，它把现有的浏览器技术提高到了一个新的水平上。RIA从根本上改进了在线应用程序的可用性和有效性。Ajax
RIA只使用标准的浏览器技术（如JavaScript、XHTML和XMLHttpRequest对象）就做到了这一点。通过使用
XMLHttpRequest，在将数据异步加载到界面中时就无需刷新页面。</font>
		</p>
		<p>
				<font size="2">　　Backbase在J2EE架构中提供一个Ajax表示层，它结合了目前的J2EE服务器和先进的富客户端技术的优点。Backbase表示层
控制了富用户界面的每个方面：与最终用户的交互模型，与后端系统的集成，以及整个客户端-服务器通信。Backbase直接提供了用于聚合来自任意位置的
XML的下一个范型，将数据绑定到先进的富用户界面控件，并在一个统一的富用户界面中交付组合应用程序。</font>
		</p>
		<p>
				<font size="2">　　Backbase表示层由一个客户机和一个服务器组成。Backbase Presentation Client
(BPC)是一个基于Ajax的GUI引擎，它允许开发人员以声明性的方式快速构建RIA。Backbase
XML(BXML)是对XHTML的扩展。它为开发人员提供了交付富前端功能的附加标签(B tag)。Backbase XML Server
(BXS)提供一种XML流水线架构，利用它可以从Web服务、数据库或Java对象获取数据，可以聚合和转换这些数据，并将其绑定到BPC中的UI元
素。BPC和BXS相结合，可以在Web浏览器和应用服务器之间搭建一座功能强大的桥梁，并提供一个分布在客户端和服务器上的完整的富Internet表
示层。</font>
		</p>
		<p>
				<font size="2">　　图1说明了在逻辑和物理应用程序架构中，Backbase所处的位置。应用程序由一个J2EE后端和一个基于Ajax的RIA前端组成。从逻辑
上说，Backbase提供了表示层，而J2EE提供了业务逻辑和数据层。从物理上说，表示层分布在客户端和服务器上。在客户端上，Backbase使用
BPC扩展了浏览器。在服务器上，Backbase使用BXS扩展了应用服务器。</font>
		</p>
		<p>
				<font size="2">
						<img src="http://dev2dev.bea.com.cn/images/051102/0511020101.jpg" height="235" width="301" />
				</font>
		</p>
		<p>
				<font size="2">图1. Backbase富Internet表示层</font>
		</p>
		<p>
				<font size="2">
						<strong>Pet Store案例分析</strong>
				</font>
		</p>
		<p>
				<font size="2">　　我们将使用Java Pet Store作为案例来分析如何为J2EE应用程序添加Backbase RIA前端。Java Pet
Store Demo是Sun Microsystems提供的一个示例应用程序，其目的是为了演示如何使用Java 2 Platform,
Enterprise Edition(J2EE)构建Web应用程序（详情请参见<a href="http://java.sun.com/developer/releases/petstore" target="_blank">http://java.sun.com/developer/releases/petstore</a>）。</font>
		</p>
		<p>
				<font size="2">　　Java Pet Store是业内一个著名的参考应用程序（pet store还有.NET和Flash版本）。由于以下两个原因，它成为为J2EE应用程序添加基于Ajax的RIA前端的完美案例：</font>
		</p>
		<ul>
				<li>
						<font size="2">Java Pet Store是一个完整的Web应用程序。</font>
				</li>
				<p>
						<font size="2">Sun设计Pet Store的目的是演示所有常见的Web应用程序功能。通过使用Pet Store作为案例，我可以说明为J2EE应用程序添加RIA层的所有方面。</font>
				</p>
				<p>
						<font size="2">作为一个典型的在线商店，它包含以下功能：</font>
				</p>
				<ul>
						<li>
								<font size="2">浏览产品类别。</font>
						</li>
						<li>
								<font size="2">在购物车中添加和删除物品。</font>
						</li>
						<li>
								<font size="2">填写订单表单。</font>
						</li>
						<li>
								<font size="2">提交订单。</font>
						</li>
				</ul>
				<li>
						<font size="2">Java Pet Store有一个传统的HTML前端。</font>
				</li>
				<p>
						<font size="2">使用RIA前端的目的是提供更简单和响应更灵敏的GUI，以及通常更为丰富的Web用户体验。我将说明，如何通过Backbase RIA技术极大地改进应用程序的前端，同时无需对后端和总体系统需求做任何修改。</font>
				</p>
				<p>
						<font size="2">Pet Store的RIA前端将通过以下方式改善可用性：</font>
				</p>
				<li>
						<font size="2">把前端变为一个单页面的界面（SPI）。</font>
				</li>
				<li>
						<font size="2">提供更先进的UI控件（如模态弹出式菜单）。</font>
				</li>
				<li>
						<font size="2">使用可视化效果（例如，把宠物放入购物车）。</font>
				</li>
				<li>
						<font size="2">更加有效地利用电脑屏幕的操作区域。</font>
				</li>
		</ul>
		<p>
				<font size="2">
						<strong>RIA Pet Store前端</strong>
				</font>
		</p>
		<p>
				<font size="2">　　在这一节中，我将讨论经过改进的新Pet Store RIA前端。</font>
		</p>
		<p>
				<font size="2">　　下面的两个屏幕快照演示了前端的改进。要获得对Backbase RIA前端更直观的感受，请访问<a href="http://www.backbase.com/xmlserver" target="_blank">http://www.backbase.com/xmlserver</a>上的在线演示，或者到<a href="http://www.backbase.com/download" target="_blank">http://www.backbase.com/download</a>下载Backbase社区版本。</font>
		</p>
		<p>
				<font size="2">下面两个图对两个前端进行了可视化的比较。图2显示的是原来静态的多页面HTML前端。图3显示的是新的Backbase SPI前端：
</font>
		</p>
		<p>
				<font size="2">
						<img src="http://dev2dev.bea.com.cn/images/051102/0511020102.jpg" height="186" width="325" />
				</font>
		</p>
		<p>
				<font size="2"> 图2. 原始HTML前端 </font>
		</p>
		<p>
				<font size="2">
						<img src="http://dev2dev.bea.com.cn/images/051102/0511020103.jpg" height="232" width="325" />
				</font>
		</p>
		<p>
				<font size="2">图3. 新Backbase前端</font>
		</p>
		<p>
				<font size="2">　　Backbase为创建丰富的单页面Web界面提供了许多可能性。下面列出了一些Pet Store所使用的例子。</font>
		</p>
		<ul>
				<li>
						<font size="2">选项卡式的单页面浏览</font>
				</li>
				<p>
						<font size="2">在Web界面上，不同的动物种类（狗、猫等等）被表示为不同的选项卡。点击一个选项卡就会打开相应的类别，显示可供出售的宠物。</font>
				</p>
				<p>
						<font size="2">在Backbase SPI中，无需刷新页面就可以打开选项卡。BPC只从服务器请求所需的数据，然后更新客户端的视图。SPI机制可以极大地缩短响应时间，让客户随心所欲地在类别之间来回穿梭。</font>
				</p>
				<li>
						<font size="2">活动的多功能界面</font>
				</li>
				<p>
						<font size="2">界面有三个主要功能——类别浏览、购物车和页面引导历史记录，它们在界面上都是一直可见的。因此，购物者总是能够查看购物车的当前内容或最近看过的宠物的记录。</font>
				</p>
				<p>
						<font size="2">这些功能是高度同步的：浏览一个宠物时，历史记录将自动更新为在记录中显示该宠物。定购一个宠物时，它将被添加到购物车中。上述一切都发生在客户端的一个页面上（例如，无需重新加载页面就可以更新界面的各个部分）。</font>
				</p>
				<li>
						<font size="2">界面变化的流畅可视化效果</font>
				</li>
				<p>
						<font size="2">进行浏览时，客户将会看到不断变化的界面视图。例如，他可以按照价格和名称对宠物进行排序。界面需要根据新的排列顺序显示更新以后的宠物清单。</font>
				</p>
				<p>
						<font size="2">在Backbase RIA前端中，以前的视图被使用可视化效果的新视图所代替，新视图向最终用户显示什么正在改变。图4说明了如何通过流畅的定位效果，把按名称排列的顺序转变为按价格排列的顺序：
</font>
				</p>
				<p>
						<font size="2">
								<img src="http://dev2dev.bea.com.cn/images/051102/0511020104.jpg" height="56" width="313" />
						</font>
				</p>
				<p>
						<font size="2">图4.类别视图的排列顺序转换</font>
				</p>
				<li>
						<font size="2">用于提高转换速度的信息栏验证</font>
				</li>
		</ul>
		<p>
				<font size="2">　　为了执行购买，购买者必须在一份表单中填入个人详细信息。Backbase极大地简化了这个购买过程，通过客户端的信息栏验证提供即时的反馈，并在提供所有数据的过程中提供逐步的指南和概述。</font>
		</p>
		<p>
				<font size="2">　　图5显示了在填写表单的第一个步骤中，对于e-mail地址信息栏的验证。当购买者填写下一栏时，就会提供即时的反馈。
</font>
		</p>
		<p>
				<font size="2">
						<img src="http://dev2dev.bea.com.cn/images/051102/0511020105.jpg" height="135" width="325" />
				</font>
		</p>
		<p>
				<font size="2">图5. 信息栏验证—e-mail栏</font>
		</p>
		<p>
				<font size="2">
						<strong>Backbase RIA Pet Store的架构</strong>
				</font>
		</p>
		<p>
				<font size="2">　　增强Pet Store（或其他任何Web应用程序）的前端时，我们将继续依赖于以下两条架构基本原则：</font>
		</p>
		<ul>
				<li>
						<font size="2">最终用户仍然使用标准的Web浏览器访问Pet Store，无需添加任何插件。</font>
				</li>
				<li>
						<font size="2">由J2EE业务逻辑和数据组成的整个后端保持不变。</font>
				</li>
		</ul>
		<p>
				<font size="2">　　现有的后端在开发期间是完全孤立的，而且不会改变，这个事实对于架构师和IT管理人员十分有利。通过一个规整的、模块化的架构，他们将能够控制风险和成本，同时显著提高Web应用程序的用户友好性。</font>
		</p>
		<p>
				<font size="2">　　Backbase的富表示层技术由两个模块组成，它们将被加入到架构中。在客户端，BPC管理着SPI，并通过异步响应事件来处理与最终用户之
间的交互。在服务器端，Backbase XML
Server这个灵活的XML管道可以连接到任意服务器端的数据源，包括Web服务、文件、数据库或本地Java对象。图6说明了BPC和BXS如何共同
为RIA提供一个声明式的、基于XML的端到端表示层。
</font>
		</p>
		<p>
				<font size="2">
						<img src="http://dev2dev.bea.com.cn/images/051102/0511020106.jpg" height="149" width="299" />
				</font>
		</p>
		<p>
				<font size="2">图6. 声明式的端到端表示层</font>
		</p>
		<p>
				<font size="2">
						<strong>Backbase表示客户端</strong>
				</font>
		</p>
		<p>
				<font size="2">　　BPC是一个基于Ajax的GUI引擎，它运行在标准的Web浏览器中。运行时，BPC被加载到浏览器中，然后它会接收BXML代码，构造对应的B树，并不断地把这种表示转换为浏览器所呈现的DOM树。图7说明了运行时转换过程。
</font>
		</p>
		<p>
				<font size="2">
						<img src="http://dev2dev.bea.com.cn/images/051102/0511020107.jpg" height="212" width="296" />
				</font>
		</p>
		<p>
				<font size="2">图7. BPC运行时</font>
		</p>
		<p>
				<font size="2">
						<strong>Backbase XML</strong>
				</font>
		</p>
		<p>
				<font size="2">　　Backbase XML (BXML)是XHTML的扩展。开发人员通过创建BXML应用程序来开发富前端，包括BXML标签、标准的XHTML和CSS。BXML是一种声明性语言，它包含了XHTML中所没有的标签（B标签）</font>
		</p>
		<p>
				<font size="2">　　BXML包含用于下列用途的标签：</font>
		</p>
		<ul>
				<li>
						<font size="2">定义屏幕分区(&lt;b:panel&gt;) </font>
				</li>
				<li>
						<font size="2">交互式客户端控制(&lt;b:menu&gt;) </font>
				</li>
				<li>
						<font size="2">处理标准的用户交互事件(onClick) </font>
				</li>
				<li>
						<font size="2">处理高级的用户交互事件(拖放和调整大小) </font>
				</li>
				<li>
						<font size="2">管理客户端状态</font>
				</li>
				<li>
						<font size="2">处理可视化效果(使修改任意CSS属性的过程动画化) </font>
				</li>
				<li>
						<font size="2">数据绑定</font>
				</li>
				<li>
						<font size="2">使用XSLT的一个子集进行客户端转换</font>
				</li>
		</ul>
		<p>
				<font size="2">
						<strong>用于J2EE的Backbase XML Server</strong>
				</font>
		</p>
		<p>
				<font size="2">　　Backbase XML Server (BXS)是一个服务器端的引擎，用于把BPC链接到任意J2EE后端。和BPC一样，BXS是完全基于XML的，其编程是声明性的。它使用一种XML管道架构，提供功能强大的服务器端转换和聚合。</font>
		</p>
		<p>
				<font size="2">　　BXS附带一些用于访问最常用的数据源（包括Web服务、数据库、文件系统和本地Java对象）的开箱即用任务。我们使用Backbase标签对从这些源获得的数据进行聚合，然后使用XSLT进行转换。结果以无格式XML数据或BXML表示代码的形式返回给BPC。</font>
		</p>
		<p>
				<font size="2">　　BXS还提供一些应用服务，包括身份验证、授权、日志记录和用户跟踪。图8显示了BXS的总体架构。</font>
		</p>
		<p>
				<font size="2">
						<img src="http://dev2dev.bea.com.cn/images/051102/0511020108.jpg" height="217" width="302" />
				</font>
		</p>
		<p>
				<font size="2">图8. BXS架构</font>
		</p>
		<p>
				<font size="2">
						<strong>Eclipse开发工具</strong>
				</font>
		</p>
		<p>
				<font size="2">　　为了让J2EE开发人员可以只使用一种开发工具就能创建完整的Web应用程序，包括富前端，Backbase提供了一个Eclipse插件。如图9所示，该插件提供了在Eclipse中突出显示语法和Backbase标签代码自动完成的功能。
</font>
		</p>
		<p>
				<font size="2">
						<img src="http://dev2dev.bea.com.cn/images/051102/0511020109.jpg" height="235" width="317" />
				</font>
		</p>
		<p>
				<font size="2">图9. Backbase Eclipse插件</font>
		</p>
		<p>
				<font size="2">　　注意：Eclipse的可视化拖放开发插件还处在开发阶段。</font>
		</p>
		<p>
				<font size="2">
						<strong>部署到BEA WebLogic</strong>
				</font>
		</p>
		<p>
				<font size="2">　　BXS是一个与标准兼容的J2EE应用程序，可以将其部署到任何J2EE应用服务器上。图10显示了如何使用WebLogic控制台把BXS部署到<a href="http://dev2dev.bea.com/wlserver/" target="_blank">BEA WebLogic Server</a>。
</font>
		</p>
		<p>
				<font size="2">
						<img src="http://dev2dev.bea.com.cn/images/051102/0511020111.jpg" height="251" width="325" />
				</font>
		</p>
		<p>
				<font size="2">图10. 把BXS部署到BEA WebLogic</font>
		</p>
		<p>
				<font size="2">
						<strong>实现Backbase RIA Pet Store</strong>
				</font>
		</p>
		<p>
				<font size="2">　　下面的顺序图包括更多详细信息，可以帮助您更好地理解如何实现Backbase pet store。该顺序图显示了在应用程序的初始化加载期间BPC与BXS之间的交互，如图11所示，它包括以下4个步骤：</font>
		</p>
		<ul>
				<li>
						<font size="2">初始化：用户在浏览器中输入宠物商店的URL；对BPC进行初始化。</font>
				</li>
				<li>
						<font size="2">应用程序布局：触发正在构造的事件；BPC构建整体应用程序布局；宠物类别被加载并显示在选项卡中。</font>
				</li>
				<li>
						<font size="2">默认数据：默认情况下加载狗的类别；最初显示8张狗的图片，并带有向前/向后和排序功能。</font>
				</li>
		</ul>
		<p>
				<font size="2">　　用户交互：用户点击Next按钮便可显示编号从9到16的狗图片。
</font>
		</p>
		<p>
				<font size="2">
						<img src="http://dev2dev.bea.com.cn/images/051102/0511020110.jpg" height="365" width="329" />
				</font>
		</p>
		<p>
				<font size="2">图11.顺序图：富商店前端</font>
		</p>
		<ul>
				<li>
						<font size="2">初始化</font>
				</li>
				<p>
						<font size="2">从用户在浏览器中输入宠物商店的URL开始，这将导致从Web服务器请求一个索引页面。</font>
				</p>
				<p>
						<font size="2">索引页面包含用于实例化BPC的代码。索引页面是XHTML和BXML标签的结合，包含负责启动富前端的初始化事件处理程序。</font>
				</p>
				<p>
						<font size="2">BPC初始化代码：</font>
				</p>
				<pre class="code">
						<font size="2">&lt;...&gt;&lt;body onload="bpc.boot('/Backbase/')"&gt;<br /><br />&lt;...&gt;<br /><br />  &lt;xmp b:backbase="true"<br /><br />          style="display:none;height:100%;"&gt;<br /><br />    &lt;s:loading&gt;<br /><br />      &lt;div style="position:absolute;width:20%;<br /><br />                     top: 50px;left: 35%;"&gt;<br /><br />        &lt;center&gt;Please wait while loading...<br /><br />        &lt;/center&gt;<br /><br />      &lt;/div&gt;<br /><br />    &lt;/s:loading&gt;<br /><br />    &lt;...&gt;<br /><br />    &lt;!-- Include petshop specific behaviors --&gt;<br /><br />    &lt;s:include b:url="petshop.xml"/&gt;<br /></font>
				</pre>
				<li>
						<font size="2">应用程序布局</font>
				</li>
				<p>
						<font size="2">加载页面之后，BPC就会处理正在构造的事件，以便开始构建总体的应用程序布局。</font>
				</p>
				<p>
						<font size="2">应用程序布局由几个面板组成，它们将屏幕划分为几个部分。顶行有一个固定高度的宠物商店徽标，接下来的主行是实际的商店，大小可以调整。主行分为两列，左边一列是产品类别，右边一列是购物车和历史记录。</font>
				</p>
				<font size="2">
产品类别使用选项卡式的导航，每个宠物类别一个选项卡。这些选项卡是动态构造的，具体过程是通过BXS从一个XML文件加载类别，然后通过一个客户端模板把这些类别转换为选项卡，该转换模板的BPC代码如下：
</font>
				<pre class="code">
						<font size="2">&lt;s:task b:action="transform"<br /><br />    b:stylesheet="b:xml('categories')"<br /><br />    b:xmldatasource="b:url('categories.xml')"<br /><br />    b:destination="id('main-content')" <br /><br />    b:mode="aslastchild" /&gt;<br /><br /></font>
				</pre>
				<p>
						<font size="2">下面是用于从文件系统把类别加载为XML的BXS代码：</font>
				</p>
				<pre class="code">
						<font size="2">&lt;bsd:pipeline equals="categories.xml"<br /><br />                                 access="public"&gt;<br /><br />    &lt;bsd:readxml input="file:/categories.xml"/&gt;<br /><br />&lt;/bsd:pipeline&gt;<br /></font>
				</pre>
				<p>
						<font size="2">下面是用于创建选项卡式导航的BPC客户端模板：</font>
				</p>
				<pre class="code">
						<font size="2">&lt;b:tabrow&gt;<br /><br />  &lt;s:for-each b:select="categories/category"&gt;<br /><br />    &lt;b:tab&gt;<br /><br />      &lt;s:attribute b:name="b:followstate"&gt;<br /><br />        id('&lt;s:value-of b:select="name"/&gt;')<br /><br />      &lt;/s:attribute&gt;<br /><br />      &lt;s:value-of b:select="name"/&gt;<br /><br />    &lt;/b:tab&gt;<br /><br />  &lt;/s:for-each&gt;<br /><br />&lt;/b:tabrow&gt;<br /></font>
				</pre>
				<p>
						<font size="2">所有BPC代码（用蓝色表示）都在客户端执行，而所有BXS代码（用红色表示）都在服务器端执行。注意，在本例中，我选择了在客户端进行转换，因为
数据集很小。下面我会给出一个在服务器端转换的例子。两种转换都要用到XSLT语法。Backbase的一个强大功能就是，前端开发人员可以根据情况选择
在客户端还是服务器端处理表示逻辑。语法似乎允许轻松地把代码从客户端移到服务器端，或者反之。</font>
				</p>
				<p>
						<font size="2">以上的代码示例应该可以使您了解到，借助于Backbase，Ajax编程变得多么轻松。结合了DHTML的声明性方法则更容易上手。使用附加的B
标签不仅可以使界面更加丰富，而且可以使开发人员的效率更高。诸如&lt;b:tab&gt;之类的单个标签可以代替多行HTML和JavaScript
代码，而且保证可以用于各种浏览器。</font>
				</p>
				<li>
						<font size="2">默认数据</font>
				</li>
				<p>
						<font size="2">显示商店前端时，默认情况下显示的是狗的类别。对于本案例，BXS负责此项操作。BXS从一个Web服务获得数据，将其放入缓存，然后生成BXML
表示代码，再把这些表示代码发回给BPC。服务器还通过一项配置设置确定一个页面上可以显示的动物数量，并根据需要加入了Next和Previous按
钮。最后，服务器还提供了按照名称或价格进行排序的功能。</font>
				</p>
				<p>
						<font size="2">下面的代码片断演示了服务器功能。外部管道products-overview.xml首先调用catalog.xml子管道。该子管道要么返回缓
存中的宠物信息，要么调用另一个子管道catalog.ws。在缓存没有命中的情况下，内部管道catalog.ws会从Web服务获取宠物信息。</font>
				</p>
				<p>
						<font size="2">外部管道获得宠物信息，然后进行XSLT转换，从而以4x2表格显示这些信息，并带有Next和Previouse按钮，然后把BXML格式的代码发回给BPC。BPC呈现它接收到的BXML。</font>
				</p>
				<p>
						<font size="2">有3个嵌套的BXS管道分别用于从Web服务获取数据、将其放入缓存，以及通过XSLT转换创建BXML输出：
</font>
				</p>
				<pre class="code">
						<font size="2">&lt;bsd:pipeline equals="products-overview.xml"<br /><br />                              access="public"/&gt;<br /><br />  &lt;bsd:callpipe pipe="catalog.xml"/&gt;<br /></font>
				</pre>
				<pre class="code">
						<font size="2">&lt;bsd:pipeline equals="catalog.xml" access="private"&gt;<br /><br />  &lt;bsd:exist field="{global:petstore-catalog}"&gt;<br /><br />    &lt;bsd:readxml&gt;{global:petstore-catalog}<br /><br />    &lt;/bsd:readxml&gt;<br /><br />    &lt;bsd:otherwise&gt;<br /><br />      &lt;bsd:callpipe pipe="catalog.ws"/&gt;<br /></font>
				</pre>
				<pre class="code">
						<font size="2">&lt;bsd:pipeline equals="catalog.ws"<br /><br />                               access="private"&gt;<br /><br />  &lt;bsd:try&gt;<br /><br />    &lt;bsd:callws wsdl="PetstoreCatalog.wsdl"<br /><br />                               method="getAll"/&gt;<br /><br />    &lt;bsd:callpipe pipe="strip-root-ns"/&gt;<br /><br />    &lt;bsd:catch&gt;<br /><br />      &lt;bsd:xslt xslt="error.xslt"&gt;<br /><br />        &lt;bsd:param name="errormsg"&gt;{error:message}<br /><br />        &lt;/bsd:param&gt;<br /><br />        &lt;bsd:param name="errorsrc"&gt;{error:source}<br /><br />        &lt;/bsd:param&gt;<br /><br />      &lt;/bsd:xslt&gt;<br /><br />    &lt;/bsd:catch&gt;<br /><br />  &lt;/bsd:try&gt;<br /><br />&lt;/bsd:pipeline&gt;<br />      &lt;bsd:writexml&gt;{global:petstore-catalog}<br /><br />      &lt;/bsd:writexml&gt;<br /><br />    &lt;/bsd:otherwise&gt;<br /><br />  &lt;/bsd:exist&gt;<br /><br />&lt;/bsd:pipeline&gt;<br />&lt;bsd:extractfilter xpath=<br /><br />  "category[name/text()='{requestparam:category}']"/&gt; <br /><br />  &lt;bsd:xslt xslt="products/products-overview.xslt"&gt;<br /><br />    &lt;bsd:param name="category"&gt;<br /><br />      {requestparam:category}<br /><br />    &lt;/bsd:param&gt;<br /><br />    &lt;bsd:param name="stepsize"&gt;<br /><br />      {global:stepsize}<br /><br />    &lt;/bsd:param&gt;<br /><br />    &lt;bsd:param name="sortorder"&gt;<br /><br />      {requestparam:sortorder}<br /><br />    &lt;/bsd:param&gt;<br /><br />    &lt;bsd:param name="sortfield"&gt;<br /><br />      {requestparam:sortfield}<br /><br />    &lt;/bsd:param&gt;<br /><br />  &lt;/bsd:xslt&gt;<br /><br />&lt;/bsd:pipeline&gt;<br /></font>
				</pre>
				<p>
						<font size="2">代码示例再次清楚地说明了，借助于Backbase，以声明性的方式创建Ajax前端是多么容易的事情。例如，只要使用带有一个WSDL引用作为属性的&lt;bsd:callws&gt;标签，就可以调用一个Web服务。</font>
				</p>
				<li>
						<font size="2">用户交互</font>
				</li>
				<p>
						<font size="2">现在，最终用户可以与宠物商店类别进行交互。可以使用Next或Previous按钮或者排序功能在动物类别中进行浏览。或者，只要点击一下相应的选项卡，就可以转到另一个类别中。</font>
				</p>
				<p>
						<font size="2">BPC和BXS对这种交互进行了无缝处理。显示已经在客户端上的数据时，无需与服务器进行任何通信。例如，购物者已经从狗类别转到了猫类别，然后再
回到狗类别。客户端仍然拥有狗类别的数据，所以可以马上显示出来，这使得购物体验变得更完美。其他的类别需要从BXS获取。BXS要么立即从其缓存返回它
们，要们访问Web服务来获得新数据。</font>
				</p>
		</ul>
		<p>
				<font size="2">　　为了详细说明Backbase Ajax宠物商店的实现，我把重点放在了初始化的步骤上。完整的宠物商店（可以从<a href="http://www.backbase.com/xmlserver" target="_blank">http://www.backbase.com/xmlserver</a>下载）还包括以下功能：</font>
		</p>
		<ul>
				<ul>
						<li>
								<font size="2">商店前端</font>
						</li>
						<ul>
								<li>
										<font size="2">初始化。</font>
								</li>
								<li>
										<font size="2">使用从文件加载的宠物类别创建选项卡。</font>
								</li>
								<li>
										<font size="2">默认情况下从Web服务加载Dog选项卡。</font>
								</li>
								<li>
										<font size="2">通过缓存浏览Dog并对其进行排序。</font>
								</li>
						</ul>
						<li>
								<font size="2">宠物详细情况</font>
						</li>
						<ul>
								<li>
										<font size="2">使用跟踪聚合来自缓存和数据库的宠物详细情况。</font>
								</li>
								<li>
										<font size="2">创建可视化历史记录。</font>
								</li>
						</ul>
						<li>
								<font size="2">购物车</font>
						</li>
						<ul>
								<li>
										<font size="2">使用跟踪添加到购物车。</font>
								</li>
						</ul>
						<li>
								<font size="2">登录</font>
						</li>
						<ul>
								<li>
										<font size="2">登录和身份验证。</font>
								</li>
						</ul>
						<li>
								<font size="2">退出</font>
						</li>
						<ul>
								<li>
										<font size="2">退出和授权。</font>
								</li>
								<li>
										<font size="2">确认。</font>
								</li>
						</ul>
				</ul>
		</ul>
		<p>
				<font size="2">
						<strong>结束语</strong>
				</font>
		</p>
		<p>
				<font size="2">　　最近有很多人都在研究Ajax。Ajax的优点已经在实践中得到了证明。定制Ajax的缺点在于它的复杂性和不兼容性。大量客户端
JavaScript的出现意味着开发人员很可能陷入到浏览器实现差别的泥潭中去。另外，JavaScript这种语言不适用于复杂的应用程序。</font>
		</p>
		<p>
				<font size="2">　　为了开发易于管理的、可伸缩的和适应未来变化的Ajax解决方案，开发人员所需使用的工具应该具有比定制部件开发更多的功能。Backbase
Ajax软件提供了一个功能全面的客户端GUI管理引擎(Backbase Presentation
Client)、一个灵活的服务器端XML管道(Backbase XML
Server)和一种声明性的基于标签的UI语言，BXML(Backbase eXtensible Markup
Language)。该方法具有几个优点。</font>
		</p>
		<p>
				<font size="2">　　首先，Backbae易于使用。它的声明性语言水平地扩展了DHTML；它完全对开发人员隐藏了浏览器兼容性的问题；而且它带有一套开发和调试工具。</font>
		</p>
		<p>
				<font size="2">　　其次，Backbase是一个功能全面的Ajax
GUI管理系统。Backbase的先进性大大超过了其他Ajax框架，它完全把重点放在提供一个部件库或客户端－服务器通信（如DWR）上。在控件和客
户端－服务器通信的基础上，Backbase提供了用于如下用途的标签：提供电影效果，随需应变的数据加载，数据绑定和客户端的数据转换，对于Back和
Forward按钮的支持，完善的GUI状态管理，等等。所有这些功能对于目前的Ajax Web应用程序来说都是必需的。</font>
		</p>
		<p>
				<font size="2">　　最后，Backbase是以兼容的方式提供所有客户端和服务器端的功能。用户可以使用富Ajax前端扩展现有的应用程序，同时无需修改后端。对于整个表示层来说，它的架构是时新的、模块化的，而且它基于XML。</font>
		</p>
		<p>
				<font size="2">
						<strong>参考资料</strong>
				</font>
		</p>
		<ul>
				<li>
						<font size="2">
								<a href="http://java.sun.com/developer/releases/petstore/" target="_blank">Java Pet Store Demo</a>。</font>
				</li>
				<li>
						<font size="2">
								<a href="http://www.adaptivepath.com/publications/essays/archives/000385.php" target="_blank">Ajax: A New Approach to Web Applications</a>，作者Jesse James Garrett（Adaptive Path，2005年2月）。</font>
				</li>
		</ul>
		<p>
				<font size="2">
						<strong>原文出处</strong>
				</font>
		</p>
		<p>
				<font size="2">A Backbase Ajax Front-end for J2EE Applications</font>
		</p>
		<p>
				<font size="2">
						<a href="http://dev2dev.bea.com/pub/a/2005/08/backbase_ajax.html" target="_blank">http://dev2dev.bea.com/pub/a/2005/08/backbase_ajax.html</a>
				</font>
		</p>
		<!--文章其他信息-->
		<div class="dot001">
				<font size="2">
						<img src="http://dev2dev.bea.com.cn/images/_.gif" alt="" height="1" width="100%" />
				</font>
		</div>
		<table border="0" cellpadding="3" cellspacing="0" width="100%">
				<tbody>
						<tr valign="bottom">
								<td colspan="2" height="20">
										<font size="2"> <span class="h2b">作者简介</span></font>
								</td>
						</tr>
						<tr>
								<td align="center" valign="top" width="0">
										<font size="2">
												<br />
										</font>
								</td>
								<td>
										<font size="2">
												<a href="http://dev2dev.bea.com/pub/au/324" target="_blank">Mark Schiefelbein</a>自2005年2月以来一直担任Backbase的产品管理主管。Mark极大地推动了Backbase Rich Internet Application的全球推广。</font>
								</td>
						</tr>
				</tbody>
		</table>
<img src ="http://www.blogjava.net/cmd/aggbug/41773.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/cmd/" target="_blank">静夜思</a> 2006-04-19 00:12 <a href="http://www.blogjava.net/cmd/articles/41773.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>OSWorkflow 概念</title><link>http://www.blogjava.net/cmd/articles/34641.html</link><dc:creator>静夜思</dc:creator><author>静夜思</author><pubDate>Fri, 10 Mar 2006 04:48:00 GMT</pubDate><guid>http://www.blogjava.net/cmd/articles/34641.html</guid><wfw:comment>http://www.blogjava.net/cmd/comments/34641.html</wfw:comment><comments>http://www.blogjava.net/cmd/articles/34641.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/cmd/comments/commentRss/34641.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/cmd/services/trackbacks/34641.html</trackback:ping><description><![CDATA[<p><font size="2"><strong>前 言</strong></font></p>
<p><font size="2">&nbsp;&nbsp;&nbsp; 本文没有抛出可运行的范例，仅仅是程序片断而已，不过在 OSWorkflow 的 Wiki 上，Quake Wang
已把官方入门教程完整地翻译成中文了，有兴趣的读者可去阅读。关于 OSWorkflow
更加细节性的内容，可参考官方手册，相信你在了解了入门教程后，可轻松阅读官方手册。<br>&nbsp;&nbsp;&nbsp; <br></font><font size="2"><strong>OSWorkflow 概念</strong></font></p>
<p><font size="2">&nbsp;&nbsp;&nbsp; 在商用和开源世界里，OSWorkflow 都不同于这些已有的工作流系统。最大不同在于 OSWorkflow
有着非常优秀的灵活性。在开始接触 OSWorkflow 时可能较难掌握（有人说不适合工作流新手入门），比如，OSWorkflow
不要求图形化工具来开发工作流，而推荐手工编写 xml
格式的工作流程描述符。它能为应用程序开发者提供集成，也能与现有的代码和数据库进行集成。这一切似乎给正在寻找快速“即插即用”工作流解决方案的人制造
了麻烦，但研究发现，那些“即插即用”方案也不能在一个成熟的应用程序中提供足够的灵活性来实现所有需求。<br>&nbsp;&nbsp;&nbsp; <br></font><font size="2"><strong>OSWorkflow 优势</strong></font></p>
<p><font size="2">&nbsp;&nbsp;&nbsp; OSWorkflow 给你绝对的灵活性。OSWorkflow
被认为是一种“低级别”工作流实现。与其他工作流系统能用图标表现“loops(回路)”和“conditions(条件)”相比，OSWorkflow
只是手工“编码(coded)”来实现的。但这并不能说实际的代码是需要完全手工编码的，脚本语言能胜任这种情形。OSWorkflow
不希望一个非技术用户修改工作流程，虽然一些其他工作流系统提供了简单的 GUI
用于工作流编辑，但像这样改变工作流，通常会破坏这些应用。所以，进行工作流调整的最佳人选是开发人员，他们知道该怎么改变。不过，在最新的版本中，
OSWorkflow 也提供了 GUI 设计器来协助工作流的编辑。</font></p>
<p><font size="2">&nbsp;&nbsp;&nbsp; OSWorkflow 基于有限状态机概念。每个 state 由 step ID 和 status 联合表现（可简单理解为
step 及其 status 表示有限状态机的 state）。一个 state 到另一 state 的 transition 依赖于
action 的发生，在工作流生命期内有至少一个或多个活动的 state。这些简单概念展现了 OSWorkflow
引擎的核心思想，并允许一个简单 XML 文件解释工作流业务流程。</font></p>
<p> </p>

<p><font size="2"><strong>OSWorkflow 核心概念</strong><br>&nbsp;&nbsp;&nbsp; <br><strong>step（步骤）<br></strong>&nbsp;&nbsp;&nbsp;
一个 step 是工作流所处的位置。可能从一个 step 流转到另外一个 step（或者有时候还是停留在一样的 step）。举例来说，一个
OA 系统的请假流程，它的 step 名称可能有“本部门审批阶段”，“办公室审批阶段”，“总经理审批阶段”等。<br>&nbsp;<br><strong>status（状态）<br></strong>&nbsp;&nbsp;&nbsp; 工作流 status 是一个用来描述工作流程中具体步骤状态的字符串。OSWorkflow 的有 Underway（进行中）、Queued（等候处理中）、Finished（完成）三种 status。<br>&nbsp;<br><strong>action（动作）<br></strong>&nbsp;&nbsp;&nbsp;
action 指定了可能发生在 step 内的转变，会导致 step 的变更。在 OA 系统中，“本部门审批阶段”可能有“拒绝”或“批准”两个
action。action 和 step 之间的关系是，step 说明“在哪里”，action 说明“可以去哪里”。 一个 action
典型地由两部分组成：可以执行此动作的 condition（条件），以及执行此动作的 result（结果）。<br>&nbsp;<br><strong>condition（条件）</strong><br>&nbsp;&nbsp;&nbsp; 类似于逻辑判断，可包含“AND”和“OR”逻辑。比如一个请假流程中的“本部门审批阶段”，该阶段利用“AND”逻辑，判断流程状态是否为等候处理中，以及审批者是否为本部门主管。<br>&nbsp;&nbsp;&nbsp; <br><strong>result（结果）</strong><br>&nbsp;&nbsp;&nbsp;
Result 代表指向新的 step 及其 step status，也可能进入 split 或者 join。Result 分为两种，
contidional-result （有条件结果），只有条件为真时才使用该结果，和
unconditional-result（无条件结果），当条件不满足或没有条件时使用该结果。</font></p>
<p><font size="2"><strong>split/join（分离/连接）</strong><br>流程的切分和融合。很简单的概念，split 提供多个 result；join 则判断多个 current step 的状态，提供一个 result。</font></p>
<p> </p>

<p><font size="2"><strong>OSWorkflow 包用途分析及代码片断</strong><br>&nbsp;&nbsp;&nbsp; <br><strong>com.opensymphony.workflow</strong> <br>&nbsp;&nbsp;&nbsp;
该包为整个 OSWorkflow 引擎提供核心接口。例如 com.opensymphony.workflow.Workflow
接口，可以说，实际开发中的大部分工作都是围绕该接口展开的，该接口有
BasicWorkflow、EJBWorkflow、OfbizWorkflow 三个实现类。</font></p>
<p><font size="2"><strong>com.opensymphony.workflow.basic <br></strong>&nbsp;&nbsp;&nbsp; 该包有两个类，BasicWorkflow 与 BasicWorkflowContext。BasicWorkflow 不支持事务，尽管依赖持久实现，事务也不能包裹它。BasicWorkflowContext 在实际开发中很少使用。</font></p>
<p>
<table style="width: 417px; height: 54px;" border="1" cellpadding="1" cellspacing="1" width="417">
<tbody>
<tr>
<td><font size="2">&nbsp;&nbsp;public void setWorkflow(int userId) {<br>&nbsp;&nbsp;Workflow workflow = new BasicWorkflow(Integer.toString(userId));<br>&nbsp;}</font></td></tr></tbody></table></p>
<p><font size="2"><strong>com.opensymphony.workflow.config</strong><br>&nbsp;&nbsp;&nbsp;
该包有一个接口和两个该接口的实现类。在 OSWorkflow 2.7
以前，状态由多个地方的静态字段维护，这种方式很方便，但是有很多缺陷和约束。最主要的缺点是无法通过不同配置运行多个 OSWorkflow
实例。实现类 DefaultConfiguration 用于一般的配置文件载入。而 SpringConfiguration 则是让
Spring 容器管理配置信息。</font></p>
<p>
<table style="width: 418px; height: 23px;" border="1" cellpadding="1" cellspacing="1" width="418">
<tbody>
<tr>
<td><font size="2">&nbsp;&nbsp;public void setConfiguration(SpringConfiguration configuration) {<br>&nbsp;&nbsp;SpringConfiguration configuration = configuration;<br>workflow.setConfiguration(configuration);<br>&nbsp;}</font></td></tr></tbody></table></p>
<p><font size="2"><strong>com.opensymphony.workflow.ejb <br></strong>&nbsp;&nbsp;&nbsp; 该包有两个接口
WorkflowHome 和 WorkflowRemote。该包的若干类中，最重要的是 EJBWorkflow，该类和
BasicWorkflow 的作用一样，是 OSWorkflow 的核心，并利用 EJB 容器管理事务，也作为工作流 session bean
的包装器。</font></p>
<p><font size="2"><strong>com.opensymphony.workflow.loader</strong> <br>&nbsp;&nbsp;&nbsp; 该包有若干类，用得最多的是 XxxxDescriptor，如果在工作流引擎运行时需要了解指定的动作、步骤的状态、名字，等信息时，这些描述符会起到很大作用。</font></p>
<p>
<table style="width: 418px; height: 23px;" border="1" cellpadding="1" cellspacing="1" width="418">
<tbody>
<tr>
<td><font size="2">&nbsp;&nbsp;public String findNameByStepId(int stepId,String wfName) {<br>&nbsp;&nbsp;WorkflowDescriptor wd = workflow.getWorkflowDescriptor(wfName);<br>&nbsp;&nbsp;StepDescriptor stepDes = wd.getStep(stepId);<br>&nbsp;&nbsp;return stepDes.getName();<br>&nbsp;}</font></td></tr></tbody></table></p>
<p><font size="2"><strong>com.opensymphony.workflow.ofbiz <br></strong>&nbsp;&nbsp;&nbsp; OfbizWorkflow 和 BasicWorkflow 在很多方面非常相似，除了需要调用 ofbiz 的 TransactionUtil 来包装事务。</font></p>
<p><font size="2"><strong>com.opensymphony.workflow.query</strong> <br>&nbsp;&nbsp;&nbsp;
该包主要为查询而设计，但不是所有的工作流存储都支持查询。通常，Hibernate 和 JDBC 都支持，而内存工作流存储不支持。值得注意的是
Hibernate 存储不支持混合型查询（例如，一个查询同时包含了 history step 上下文和 current step
上下文）。执行一个查询，需要创建 WorkflowExpressionQuery 实例，接着调用 Workflow 对象的 query
方法来得到最终查询结果。</font></p>
<p>
<table style="width: 419px; height: 23px;" border="1" cellpadding="1" cellspacing="1" width="419">
<tbody>
<tr>
<td>
<p><font size="2">&nbsp;&nbsp;public List queryDepAdmin(int userId,int type) {<br>&nbsp;&nbsp;int[] arr = getSubPerson(userId,type);</font></p>
<p><font size="2">&nbsp;&nbsp;//构造表达式<br>&nbsp;&nbsp;Expression[] expressions = new Expression[1 + arr.length];<br>&nbsp;&nbsp;Expression expStatus = new FieldExpression(FieldExpression.STATUS,<br>&nbsp;&nbsp;&nbsp;&nbsp;FieldExpression.CURRENT_STEPS, FieldExpression.EQUALS, "Queued");<br>&nbsp;&nbsp;expressions[0] = expStatus;</font></p>
<p><font size="2">&nbsp;&nbsp;for (int i = 0; i &lt; arr.length; i++) {<br>&nbsp;&nbsp;&nbsp;Expression expOwner = new FieldExpression(FieldExpression.OWNER,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;FieldExpression.CURRENT_STEPS, FieldExpression.EQUALS,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Integer.toString(arr[i]));<br>&nbsp;&nbsp;&nbsp;expressions[i + 1] = expOwner;<br>&nbsp;&nbsp;}</font></p>
<p><font size="2">&nbsp;&nbsp;//查询未完成流编号<br>&nbsp;&nbsp;List wfIdList = null;<br>&nbsp;&nbsp;try {<br>&nbsp;&nbsp;&nbsp;WorkflowExpressionQuery query = new WorkflowExpressionQuery(<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;new NestedExpression(expressions, NestedExpression.AND));<br>&nbsp;&nbsp;&nbsp;wfIdList = workflow.query(query);<br>&nbsp;&nbsp;} catch (Exception e) {<br>&nbsp;&nbsp;&nbsp;e.printStackTrace();<br>&nbsp;&nbsp;}</font></p></td></tr></tbody></table><font size="2"><br><strong>com.opensymphony.workflow.soap</strong> <br>&nbsp;&nbsp;&nbsp; OSWorkflow 通过 SOAP 来支持远端调用。这种调用借助 WebMethods 实现。</font></p>
<p><font size="2"><strong>com.opensymphony.workflow.spi <br></strong>&nbsp;&nbsp;&nbsp; 该包可以说是 OSWorkflow 与持久层打交道的途径，如当前工作流的实体，其中包括：EJB、Hibernate、JDBC、Memory、Ofbiz、OJB、Prevayler。</font></p>
<p>
<table style="width: 422px; height: 23px;" border="1" cellpadding="1" cellspacing="1" width="422">
<tbody>
<tr>
<td><font size="2">&nbsp;&nbsp;HibernateWorkflowEntry hwfe = (HibernateWorkflowEntry) getHibernateTemplate()<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.find("from HibernateWorkflowEntry where Id="<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;+ wfIdList.get(i)).get(0);</font></td></tr></tbody></table></p>
<p><font size="2"><strong>com.opensymphony.workflow.util</strong> <br>该包是 OSWorkflow 的工具包，包括了对 BeanShell、BSF、EJB Local、EJB Remote、JNDI 的支持。</font></p>
<p> </p>

<p><font size="2"><strong>小 结</strong></font></p><font size="2">
&nbsp;&nbsp;&nbsp; 由于本人所在公司希望在 OA 系统中引入工作流引擎，经过分析决定采用 OSWorkflow 引擎。利用
OSWorkflow，已经在系统中实现了请假条流程原型，该流程结合 OA 系统中已有的 RBAC 模型进行逐级审核。我个人认为要用
OSWorkflow 让某个流程跑起来似乎很麻烦，主要是需要扩展和自己实现的太多。<br>&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; 另外，引用一段 Quake
Wang 的原话：电子政务/OA 如果要使用workflow engine的话，shark，jbpm 之类的workflow
engine有点杀鸡用牛刀的味道。shark 和 jbpm
都强迫你使用它的用户模型，怎样把企业现有的用户模型（包括组织结构）映射过来是很繁琐的事情，比如常见的 OA
应用中，申请者对应的部门负责人为下一个流程的人工参与者，使用 shark 或者 jbpm
都得绕一圈，通过现有的人力资源系统，获得用户，再对应过来。这还仅仅是一个简单的需求，更不用说国内企业千奇百怪的组织结构，以及各种特殊流程，用
wfmc 或者其他所谓的 workflow 通用标准去做不怎么标准的事情。吃力不讨好。用 osworkflow 这种基于状态机的
workflow engine 反而会轻松很多，而且它也没有强迫你使用它的用户模型。另外纠正一点：osworkflow 不仅仅支持简单的
BeanShell，还支持 java class，bsf，ejb。如果做电子政务/OA 的话，觉得目前 osworkflow 是最适用的
opensource workflow engine。</font><img src ="http://www.blogjava.net/cmd/aggbug/34641.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/cmd/" target="_blank">静夜思</a> 2006-03-10 12:48 <a href="http://www.blogjava.net/cmd/articles/34641.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>hibernate二级缓存攻略</title><link>http://www.blogjava.net/cmd/articles/34516.html</link><dc:creator>静夜思</dc:creator><author>静夜思</author><pubDate>Thu, 09 Mar 2006 09:36:00 GMT</pubDate><guid>http://www.blogjava.net/cmd/articles/34516.html</guid><wfw:comment>http://www.blogjava.net/cmd/comments/34516.html</wfw:comment><comments>http://www.blogjava.net/cmd/articles/34516.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/cmd/comments/commentRss/34516.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/cmd/services/trackbacks/34516.html</trackback:ping><description><![CDATA[<font size="2"><span class="postbody">
hibernate的session提供了一级缓存，每个session，对同一个id进行两次load，不会发送两条sql给数据库，但是session关闭的时候，一级缓存就失效了。
<br>

<br>
二级缓存是SessionFactory级别的全局缓存，它底下可以使用不同的缓存类库，比如ehcache、oscache等，需要设置hibernate.cache.provider_class，我们这里用ehcache，在2.1中就是
<br>
hibernate.cache.provider_class=net.sf.hibernate.cache.EhCacheProvider
<br>
如果使用查询缓存，加上
<br>
hibernate.cache.use_query_cache=true
<br>

<br>

<br>
缓存可以简单的看成一个Map，通过key在缓存里面找value。
<br>

<br>
<span style="font-weight: bold;">Class的缓存</span>
<br>
对于一条记录，也就是一个PO来说，是根据ID来找的，缓存的key就是ID，value是POJO。无论list，load还是iterate，只要读
出一个对象，都会填充缓存。但是list不会使用缓存，而iterate会先取数据库select
id出来，然后一个id一个id的load，如果在缓存里面有，就从缓存取，没有的话就去数据库load。假设是读写缓存，需要设置：
<br>
&lt;cache usage="read-write"/&gt;
<br>
如果你使用的二级缓存实现是ehcache的话，需要配置ehcache.xml
<br>&lt;cache name="com.xxx.pojo.Foo" maxElementsInMemory="500"
eternal="false" timeToLiveSeconds="7200" timeToIdleSeconds="3600"
overflowToDisk="true" /&gt;
<br>其中eternal表示缓存是不是永远不超时，timeToLiveSeconds是缓存中每个元素（这里也就是一个POJO）的超时时间，如
果eternal="false"，超过指定的时间，这个元素就被移走了。timeToIdleSeconds是发呆时间，是可选的。当往缓存里面put
的元素超过500个时，如果overflowToDisk="true"，就会把缓存中的部分数据保存在硬盘上的临时文件里面。
<br>
每个需要缓存的class都要这样配置。如果你没有配置，hibernate会在启动的时候警告你，然后使用defaultCache的配置，这样多个class会共享一个配置。
<br>
当某个ID通过hibernate修改时，hibernate会知道，于是移除缓存。
<br>这样大家可能会想，同样的查询条件，第一次先list，第二次再iterate，就可以使用到缓存了。实际上这是很难的，因为你无法判断什么时候
是第一次，而且每次查询的条件通常是不一样的，假如数据库里面有100条记录，id从1到100，第一次list的时候出了前50个id，第二次
iterate的时候却查询到30至70号id，那么30-50是从缓存里面取的，51到70是从数据库取的，共发送1+20条sql。所以我一直认为
iterate没有什么用，总是会有1+N的问题。
<br>（题外话：有说法说大型查询用list会把整个结果集装入内存，很慢，而iterate只select
id比较好，但是大型查询总是要分页查的，谁也不会真的把整个结果集装进来，假如一页20条的话，iterate共需要执行21条语句，list虽然选择
若干字段，比iterate第一条select
id语句慢一些，但只有一条语句，不装入整个结果集hibernate还会根据数据库方言做优化，比如使用mysql的limit，整体看来应该还是
list快。）
<br>
如果想要对list或者iterate查询的结果缓存，就要用到查询缓存了
<br>

<br>
<span style="font-weight: bold;">查询缓存</span>
<br>
首先需要配置hibernate.cache.use_query_cache=true
<br>
如果用ehcache，配置ehcache.xml，注意hibernate3.0以后不是net.sf的包名了
<br>
&lt;cache name="net.sf.hibernate.cache.StandardQueryCache" 
<br>
   maxElementsInMemory="50" eternal="false" timeToIdleSeconds="3600" 
<br>
   timeToLiveSeconds="7200" overflowToDisk="true"/&gt;
<br>
&lt;cache name="net.sf.hibernate.cache.UpdateTimestampsCache" 
<br>
   maxElementsInMemory="5000" eternal="true" overflowToDisk="true"/&gt;
<br>
然后
<br>
query.setCacheable(true);//激活查询缓存
<br>
query.setCacheRegion("myCacheRegion");//指定要使用的cacheRegion，可选
<br>
第二行指定要使用的cacheRegion是myCacheRegion，即你可以给每个查询缓存做一个单独的配置，使用setCacheRegion来做这个指定，需要在ehcache.xml里面配置它：
<br>&lt;cache name="myCacheRegion" maxElementsInMemory="10"
eternal="false" timeToIdleSeconds="3600" timeToLiveSeconds="7200"
overflowToDisk="true" /&gt;
<br>
如果省略第二行，不设置cacheRegion的话，那么会使用上面提到的标准查询缓存的配置，也就是net.sf.hibernate.cache.StandardQueryCache
<br>

<br>
对于查询缓存来说，缓存的key是根据hql生成的sql，再加上参数，分页等信息（可以通过日志输出看到，不过它的输出不是很可读，最好改一下它的代码）。
<br>
比如hql：
<br>
from Cat c where c.name like ?
<br>
生成大致如下的sql：
<br>
select * from cat c where c.name like ?
<br>
参数是"tiger%"，那么查询缓存的key*大约*是这样的字符串（我是凭记忆写的，并不精确，不过看了也该明白了）：
<br>
select * from cat c where c.name like ? , parameter:tiger%
<br>
这样，保证了同样的查询、同样的参数等条件下具有一样的key。
<br>现在说说缓存的value，如果是list方式的话，value在这里并不是整个结果集，而是查询出来的这一串ID。也就是说，不管是list方
法还是iterate方法，第一次查询的时候，它们的查询方式很它们平时的方式是一样的，list执行一条sql，iterate执行1+N条，多出来的
行为是它们填充了缓存。但是到同样条件第二次查询的时候，就都和iterate的行为一样了，根据缓存的key去缓存里面查到了value，value是
一串id，然后在到class的缓存里面去一个一个的load出来。这样做是为了节约内存。
<br>
可以看出来，查询缓存需要打开相关类的class缓存。list和iterate方法第一次执行的时候，都是既填充查询缓存又填充class缓存的。
<br>
<span style="font-weight: bold;">这里还有一个很容易被忽视的重要问题，即打开查询缓存以后，即使是list方法也可能遇到1+N的问题！</span>相
同条件第一次list的时候，因为查询缓存中找不到，不管class缓存是否存在数据，总是发送一条sql语句到数据库获取全部数据，然后填充查询缓存和
class缓存。但是第二次执行的时候，问题就来了，如果你的class缓存的超时时间比较短，现在class缓存都超时了，但是查询缓存还在，那么
list方法在获取id串以后，将会一个一个去数据库load！因此，class缓存的超时时间一定不能短于查询缓存设置的超时时间！如果还设置了发呆时
间的话，保证class缓存的发呆时间也大于查询的缓存的生存时间。这里还有其他情况，比如class缓存被程序强制evict了，这种情况就请自己注意
了。
<br>

<br>
另外，如果hql查询包含select字句，那么查询缓存里面的value就是整个结果集了。
<br>

<br>
当hibernate更新数据库的时候，它怎么知道更新哪些查询缓存呢？
<br>
hibernate在一个地方维护每个表的最后更新时间，其实也就是放在上面net.sf.hibernate.cache.UpdateTimestampsCache所指定的缓存配置里面。
<br>当通过hibernate更新的时候，hibernate会知道这次更新影响了哪些表。然后它更新这些表的最后更新时间。每个缓存都有一个生成时
间和这个缓存所查询的表，当hibernate查询一个缓存是否存在的时候，如果缓存存在，它还要取出缓存的生成时间和这个缓存所查询的表，然后去查找这
些表的最后更新时间，如果有一个表在生成时间后更新过了，那么这个缓存是无效的。
<br>
可以看出，只要更新过一个表，那么凡是涉及到这个表的查询缓存就失效了，因此查询缓存的命中率可能会比较低。
<br>

<br>
<span style="font-weight: bold;">Collection缓存</span>
<br>
需要在hbm的collection里面设置
<br>
&lt;cache usage="read-write"/&gt;
<br>
假如class是Cat，collection叫children，那么ehcache里面配置
<br>
&lt;cache name="com.xxx.pojo.Cat.children" 
<br>
   maxElementsInMemory="20" eternal="false" timeToIdleSeconds="3600" timeToLiveSeconds="7200" 
<br>
   overflowToDisk="true" /&gt;
<br>
Collection的缓存和前面查询缓存的list一样，也是只保持一串id，但它不会因为这个表更新过就失效，一个collection缓存仅在这个collection里面的元素有增删时才失效。
<br>
这样有一个问题，如果你的collection是根据某个字段排序的，当其中一个元素更新了该字段时，导致顺序改变时，collection缓存里面的顺序没有做更新。
<br>

<br>
<span style="font-weight: bold;">缓存策略</span>
<br>
只读缓存（read-only）：没有什么好说的
<br>
读/写缓存（read-write）:程序可能要的更新数据
<br>
不严格的读/写缓存（nonstrict-read-write）：需要更新数据，但是两个事务更新同一条记录的可能性很小，性能比读写缓存好
<br>
事务缓存（transactional）：缓存支持事务，发生异常的时候，缓存也能够回滚，只支持jta环境，这个我没有怎么研究过
<br>

<br>
读写缓存和不严格读写缓存在实现上的区别在于，读写缓存更新缓存的时候会把缓存里面的数据换成一个锁，其他事务如果去取相应的缓存数据，发现被锁住了，然后就直接取数据库查询。
<br>
在hibernate2.1的ehcache实现中，如果锁住部分缓存的事务发生了异常，那么缓存会一直被锁住，直到60秒后超时。
<br>
不严格读写缓存不锁定缓存中的数据。
<br>

<br>

<br>
<span style="font-weight: bold;">使用二级缓存的前置条件</span>
<br>你的hibernate程序对数据库有独占的写访问权，其他的进程更新了数据库，hibernate是不可能知道的。你操作数据库必需直接通过
hibernate，如果你调用存储过程，或者自己使用jdbc更新数据库，hibernate也是不知道的。hibernate3.0的大批量更新和删
除是不更新二级缓存的，但是据说3.1已经解决了这个问题。
<br>
这个限制相当的棘手，有时候hibernate做批量更新、删除很慢，但是你却不能自己写jdbc来优化，很郁闷吧。
<br>
SessionFactory也提供了移除缓存的方法，你一定要自己写一些JDBC的话，可以调用这些方法移除缓存，这些方法是：
<br>
 void evict(Class persistentClass)
<br>
          Evict all entries from the second-level cache.
<br>
 void evict(Class persistentClass, Serializable id)
<br>
          Evict an entry from the second-level cache.
<br>
 void evictCollection(String roleName)
<br>
          Evict all entries from the second-level cache.
<br>
 void evictCollection(String roleName, Serializable id)
<br>
          Evict an entry from the second-level cache.
<br>
 void evictQueries()
<br>
          Evict any query result sets cached in the default query cache region.
<br>
 void evictQueries(String cacheRegion)
<br>
          Evict any query result sets cached in the named query cache region.
<br>不过我不建议这样做，因为这样很难维护。比如你现在用JDBC批量更新了某个表，有3个查询缓存会用到这个表，用evictQueries
(String cacheRegion)移除了3个查询缓存，然后用evict(Class
persistentClass)移除了class缓存，看上去好像完整了。不过哪天你添加了一个相关查询缓存，可能会忘记更新这里的移除代码。如果你的
jdbc代码到处都是，在你添加一个查询缓存的时候，还知道其他什么地方也要做相应的改动吗？
<br>

<br>
----------------------------------------------------
<br>

<br>
<span style="font-weight: bold;">总结：</span>
<br>
不要想当然的以为缓存一定能提高性能，仅仅在你能够驾驭它并且条件合适的情况下才是这样的。hibernate的二级缓存限制还是比较多的，不方便用jdbc可能会大大的降低更新性能。在不了解原理的情况下乱用，可能会有1+N的问题。不当的使用还可能导致读出脏数据。
<br>
如果受不了hibernate的诸多限制，那么还是自己在应用程序的层面上做缓存吧。
<br>在越高的层面上做缓存，效果就会越好。就好像尽管磁盘有缓存，数据库还是要实现自己的缓存，尽管数据库有缓存，咱们的应用程序还是要做缓存。因为
底层的缓存它并不知道高层要用这些数据干什么，只能做的比较通用，而高层可以有针对性的实现缓存，所以在更高的级别上做缓存，效果也要好些吧。
</span></font><img src ="http://www.blogjava.net/cmd/aggbug/34516.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/cmd/" target="_blank">静夜思</a> 2006-03-09 17:36 <a href="http://www.blogjava.net/cmd/articles/34516.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>一个用于J2EE应用程序的Backbase Ajax前端</title><link>http://www.blogjava.net/cmd/articles/34227.html</link><dc:creator>静夜思</dc:creator><author>静夜思</author><pubDate>Wed, 08 Mar 2006 04:11:00 GMT</pubDate><guid>http://www.blogjava.net/cmd/articles/34227.html</guid><wfw:comment>http://www.blogjava.net/cmd/comments/34227.html</wfw:comment><comments>http://www.blogjava.net/cmd/articles/34227.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/cmd/comments/commentRss/34227.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/cmd/services/trackbacks/34227.html</trackback:ping><description><![CDATA[<font size="2">动态HTML技术已经出现了多年。最近，Google的最新Web应用程序GMail、Google Suggests和Google
Maps，在前端页面中重新引入了基于标准的DHTML开发模型。Google证明了，DHTML开发模型能够让开发人员创建具有可视化吸引力和高度交互
式的Rich Internet Application（丰富网络应用程序，RIA）。</font>
<p><font size="2">　　Adaptive Path公司的Jesse James Garrett为这个基于标准的RIA开发模型创造了术语<a href="http://www.adaptivepath.com/publications/essays/archives/000385.php" target="_blank">Ajax (Asynchronous JavaScript + XML)</a>。与传统的基于页面的Web应用程序模型相比，Ajax有3点不同之处：</font></p>
<ul><li><font size="2">有一个客户端引擎担任用户界面（UI）和服务器之间的中介。</font></li><li><font size="2">用户行为由客户端引擎处理，而不是生成发往服务器的页面请求。</font></li><li><font size="2">XML数据在客户端引擎和服务器之间传输。</font></li></ul>
<p><font size="2">　　换言之，Ajax解决方案包括一个客户端引擎，它用于呈现用户界面，并使用XML格式与服务器通信。这个引擎由很多JavaScript函数组成，位于Web浏览器中，它不需要插件，也不需要用户安装。</font></p>
<p><font size="2">　　基于Ajax的RIA正在迅速成为Web应用程序前端的基准，因为它可以同时提供二者的优点：丰富性和可达性。Ajax应用程序和桌面应用程序
一样丰富，响应高度灵敏，并且可以在一个页面上提供所有数据，无需刷新页面。它们还拥有基于标准的浏览器应用程序的可达性特点，这类应用程序可以在不具备
浏览器插件或客户端applet的情况下进行部署。</font></p>
<p><font size="2">　　Backbase所提供的Ajax软件具有以下特点：基于标准、功能全面且易于使用。Backbase Presentation
Client (BPC)基于Ajax技术，它使用称为Backbase XML (BXML)的附加标签扩展了DHTML。Backbase XML
Server Edition for J2EE
(BXS)包含了一些服务器端的组件，利用这些组件，J2EE开发人员可以快速开发J2EE应用程序的Ajax前端。</font></p>
<p><font size="2">　　在本文中，我使用Backbase为Java Pet Store开发了一个基于Ajax的前端。该案例分析说明了如何使用Backbase技术作为J2EE应用程序的Ajax表示层。您可以查看文中所描述的应用程序的在线演示，网址是<a href="http://www.backbase.com/xmlserver" target="_blank">http://www.backbase.com/xmlserver</a>。</font></p>
<p><font size="2"><strong>Backbase Ajax表示层</strong></font></p>
<p><font size="2">　　Web开发人员应该能够轻松创建具有以下特点的Rich Internet Application
(RIA)：完全基于HTML标准（W3C），不需要最终用户安装插件，速度超快，能够在所有浏览器上进行操作，并与J2EE运行时和开发环境完全集成。
RIA利用客户端（Web浏览器）资源创建和管理用户界面，从而为最终用户提供一个响应灵敏而且具有应用程序风格的用户界面。</font></p>
<p><font size="2">　　这种方法最近被称为Ajax。Ajax这个术语的灵感来源于Gmail、Google Maps和Google
Suggests这类应用程序，它把现有的浏览器技术提高到了一个新的水平上。RIA从根本上改进了在线应用程序的可用性和有效性。Ajax
RIA只使用标准的浏览器技术（如JavaScript、XHTML和XMLHttpRequest对象）就做到了这一点。通过使用
XMLHttpRequest，在将数据异步加载到界面中时就无需刷新页面。</font></p>
<p><font size="2">　　Backbase在J2EE架构中提供一个Ajax表示层，它结合了目前的J2EE服务器和先进的富客户端技术的优点。Backbase表示层
控制了富用户界面的每个方面：与最终用户的交互模型，与后端系统的集成，以及整个客户端-服务器通信。Backbase直接提供了用于聚合来自任意位置的
XML的下一个范型，将数据绑定到先进的富用户界面控件，并在一个统一的富用户界面中交付组合应用程序。</font></p>
<p><font size="2">　　Backbase表示层由一个客户机和一个服务器组成。Backbase Presentation Client
(BPC)是一个基于Ajax的GUI引擎，它允许开发人员以声明性的方式快速构建RIA。Backbase
XML(BXML)是对XHTML的扩展。它为开发人员提供了交付富前端功能的附加标签(B tag)。Backbase XML Server
(BXS)提供一种XML流水线架构，利用它可以从Web服务、数据库或Java对象获取数据，可以聚合和转换这些数据，并将其绑定到BPC中的UI元
素。BPC和BXS相结合，可以在Web浏览器和应用服务器之间搭建一座功能强大的桥梁，并提供一个分布在客户端和服务器上的完整的富Internet表
示层。</font></p>
<p><font size="2">　　图1说明了在逻辑和物理应用程序架构中，Backbase所处的位置。应用程序由一个J2EE后端和一个基于Ajax的RIA前端组成。从逻辑
上说，Backbase提供了表示层，而J2EE提供了业务逻辑和数据层。从物理上说，表示层分布在客户端和服务器上。在客户端上，Backbase使用
BPC扩展了浏览器。在服务器上，Backbase使用BXS扩展了应用服务器。</font></p>
<p> <font size="2"><img src="http://dev2dev.bea.com.cn/images/051102/0511020101.jpg" height="235" width="301"></font> </p>
<p><font size="2">图1. Backbase富Internet表示层</font></p>
<p><font size="2"><strong>Pet Store案例分析</strong></font></p>
<p><font size="2">　　我们将使用Java Pet Store作为案例来分析如何为J2EE应用程序添加Backbase RIA前端。Java Pet
Store Demo是Sun Microsystems提供的一个示例应用程序，其目的是为了演示如何使用Java 2 Platform,
Enterprise Edition(J2EE)构建Web应用程序（详情请参见<a href="http://java.sun.com/developer/releases/petstore" target="_blank">http://java.sun.com/developer/releases/petstore</a>）。</font></p>
<p><font size="2">　　Java Pet Store是业内一个著名的参考应用程序（pet store还有.NET和Flash版本）。由于以下两个原因，它成为为J2EE应用程序添加基于Ajax的RIA前端的完美案例：</font></p>
<ul><li><font size="2">Java Pet Store是一个完整的Web应用程序。</font></li><p><font size="2">Sun设计Pet Store的目的是演示所有常见的Web应用程序功能。通过使用Pet Store作为案例，我可以说明为J2EE应用程序添加RIA层的所有方面。</font></p><p><font size="2">作为一个典型的在线商店，它包含以下功能：</font></p><ul><li><font size="2">浏览产品类别。</font></li><li><font size="2">在购物车中添加和删除物品。</font></li><li><font size="2">填写订单表单。</font></li><li><font size="2">提交订单。</font></li></ul><li><font size="2">Java Pet Store有一个传统的HTML前端。</font></li><p><font size="2">使用RIA前端的目的是提供更简单和响应更灵敏的GUI，以及通常更为丰富的Web用户体验。我将说明，如何通过Backbase RIA技术极大地改进应用程序的前端，同时无需对后端和总体系统需求做任何修改。</font></p><p><font size="2">Pet Store的RIA前端将通过以下方式改善可用性：</font></p><li><font size="2">把前端变为一个单页面的界面（SPI）。</font></li><li><font size="2">提供更先进的UI控件（如模态弹出式菜单）。</font></li><li><font size="2">使用可视化效果（例如，把宠物放入购物车）。</font></li><li><font size="2">更加有效地利用电脑屏幕的操作区域。</font></li></ul>

<p><font size="2"><strong>RIA Pet Store前端</strong></font></p>
<p><font size="2">　　在这一节中，我将讨论经过改进的新Pet Store RIA前端。</font></p>
<p><font size="2">　　下面的两个屏幕快照演示了前端的改进。要获得对Backbase RIA前端更直观的感受，请访问<a href="http://www.backbase.com/xmlserver" target="_blank">http://www.backbase.com/xmlserver</a>上的在线演示，或者到<a href="http://www.backbase.com/download" target="_blank">http://www.backbase.com/download</a>下载Backbase社区版本。</font></p>
<p><font size="2">下面两个图对两个前端进行了可视化的比较。图2显示的是原来静态的多页面HTML前端。图3显示的是新的Backbase SPI前端：
</font></p>
<p> <font size="2"><img src="http://dev2dev.bea.com.cn/images/051102/0511020102.jpg" height="186" width="325"></font> </p>
<p><font size="2"> 图2. 原始HTML前端 </font></p>
<p> <font size="2"><img src="http://dev2dev.bea.com.cn/images/051102/0511020103.jpg" height="232" width="325"></font> </p>
<p><font size="2">图3. 新Backbase前端</font></p>
<p><font size="2">　　Backbase为创建丰富的单页面Web界面提供了许多可能性。下面列出了一些Pet Store所使用的例子。</font></p>
<ul><li><font size="2">选项卡式的单页面浏览</font></li><p><font size="2">在Web界面上，不同的动物种类（狗、猫等等）被表示为不同的选项卡。点击一个选项卡就会打开相应的类别，显示可供出售的宠物。</font></p><p><font size="2">在Backbase SPI中，无需刷新页面就可以打开选项卡。BPC只从服务器请求所需的数据，然后更新客户端的视图。SPI机制可以极大地缩短响应时间，让客户随心所欲地在类别之间来回穿梭。</font></p><li><font size="2">活动的多功能界面</font></li><p><font size="2">界面有三个主要功能——类别浏览、购物车和页面引导历史记录，它们在界面上都是一直可见的。因此，购物者总是能够查看购物车的当前内容或最近看过的宠物的记录。</font></p><p><font size="2">这些功能是高度同步的：浏览一个宠物时，历史记录将自动更新为在记录中显示该宠物。定购一个宠物时，它将被添加到购物车中。上述一切都发生在客户端的一个页面上（例如，无需重新加载页面就可以更新界面的各个部分）。</font></p><li><font size="2">界面变化的流畅可视化效果</font></li><p><font size="2">进行浏览时，客户将会看到不断变化的界面视图。例如，他可以按照价格和名称对宠物进行排序。界面需要根据新的排列顺序显示更新以后的宠物清单。</font></p><p><font size="2">在Backbase RIA前端中，以前的视图被使用可视化效果的新视图所代替，新视图向最终用户显示什么正在改变。图4说明了如何通过流畅的定位效果，把按名称排列的顺序转变为按价格排列的顺序：
</font></p><p> <font size="2"><img src="http://dev2dev.bea.com.cn/images/051102/0511020104.jpg" height="56" width="313"></font> </p><p><font size="2">图4.类别视图的排列顺序转换</font></p><li><font size="2">用于提高转换速度的信息栏验证</font></li></ul>
<p><font size="2">　　为了执行购买，购买者必须在一份表单中填入个人详细信息。Backbase极大地简化了这个购买过程，通过客户端的信息栏验证提供即时的反馈，并在提供所有数据的过程中提供逐步的指南和概述。</font></p>
<p><font size="2">　　图5显示了在填写表单的第一个步骤中，对于e-mail地址信息栏的验证。当购买者填写下一栏时，就会提供即时的反馈。
</font></p>
<p> <font size="2"><img src="http://dev2dev.bea.com.cn/images/051102/0511020105.jpg" height="135" width="325"></font> </p>
<p><font size="2">图5. 信息栏验证—e-mail栏</font></p>
<p><font size="2"><strong>Backbase RIA Pet Store的架构</strong></font></p>
<p><font size="2">　　增强Pet Store（或其他任何Web应用程序）的前端时，我们将继续依赖于以下两条架构基本原则：</font></p>
<ul><li><font size="2">最终用户仍然使用标准的Web浏览器访问Pet Store，无需添加任何插件。</font></li><li><font size="2">由J2EE业务逻辑和数据组成的整个后端保持不变。</font></li></ul>
<p><font size="2">　　现有的后端在开发期间是完全孤立的，而且不会改变，这个事实对于架构师和IT管理人员十分有利。通过一个规整的、模块化的架构，他们将能够控制风险和成本，同时显著提高Web应用程序的用户友好性。</font></p>
<p><font size="2">　　Backbase的富表示层技术由两个模块组成，它们将被加入到架构中。在客户端，BPC管理着SPI，并通过异步响应事件来处理与最终用户之
间的交互。在服务器端，Backbase XML
Server这个灵活的XML管道可以连接到任意服务器端的数据源，包括Web服务、文件、数据库或本地Java对象。图6说明了BPC和BXS如何共同
为RIA提供一个声明式的、基于XML的端到端表示层。
</font></p>
<p> <font size="2"><img src="http://dev2dev.bea.com.cn/images/051102/0511020106.jpg" height="149" width="299"></font> </p>
<p><font size="2">图6. 声明式的端到端表示层</font></p>
<p><font size="2"><strong>Backbase表示客户端</strong></font></p>
<p><font size="2">　　BPC是一个基于Ajax的GUI引擎，它运行在标准的Web浏览器中。运行时，BPC被加载到浏览器中，然后它会接收BXML代码，构造对应的B树，并不断地把这种表示转换为浏览器所呈现的DOM树。图7说明了运行时转换过程。
</font></p>
<p> <font size="2"><img src="http://dev2dev.bea.com.cn/images/051102/0511020107.jpg" height="212" width="296"></font> </p>
<p><font size="2">图7. BPC运行时</font></p>
<p><font size="2"><strong>Backbase XML</strong></font></p>
<p><font size="2">　　Backbase XML (BXML)是XHTML的扩展。开发人员通过创建BXML应用程序来开发富前端，包括BXML标签、标准的XHTML和CSS。BXML是一种声明性语言，它包含了XHTML中所没有的标签（B标签）</font></p>
<p><font size="2">　　BXML包含用于下列用途的标签：</font></p>
<ul><li><font size="2">定义屏幕分区(&lt;b:panel&gt;) </font></li><li><font size="2">交互式客户端控制(&lt;b:menu&gt;) </font></li><li><font size="2">处理标准的用户交互事件(onClick) </font></li><li><font size="2">处理高级的用户交互事件(拖放和调整大小) </font></li><li><font size="2">管理客户端状态</font></li><li><font size="2">处理可视化效果(使修改任意CSS属性的过程动画化) </font></li><li><font size="2">数据绑定</font></li><li><font size="2">使用XSLT的一个子集进行客户端转换</font></li></ul>
<p><font size="2"><strong>用于J2EE的Backbase XML Server</strong></font></p>
<p><font size="2">　　Backbase XML Server (BXS)是一个服务器端的引擎，用于把BPC链接到任意J2EE后端。和BPC一样，BXS是完全基于XML的，其编程是声明性的。它使用一种XML管道架构，提供功能强大的服务器端转换和聚合。</font></p>
<p><font size="2">　　BXS附带一些用于访问最常用的数据源（包括Web服务、数据库、文件系统和本地Java对象）的开箱即用任务。我们使用Backbase标签对从这些源获得的数据进行聚合，然后使用XSLT进行转换。结果以无格式XML数据或BXML表示代码的形式返回给BPC。</font></p>
<p><font size="2">　　BXS还提供一些应用服务，包括身份验证、授权、日志记录和用户跟踪。图8显示了BXS的总体架构。</font></p>
<p> <font size="2"><img src="http://dev2dev.bea.com.cn/images/051102/0511020108.jpg" height="217" width="302"></font> </p>
<p><font size="2">图8. BXS架构</font></p>
<p><font size="2"><strong>Eclipse开发工具</strong></font></p>
<p><font size="2">　　为了让J2EE开发人员可以只使用一种开发工具就能创建完整的Web应用程序，包括富前端，Backbase提供了一个Eclipse插件。如图9所示，该插件提供了在Eclipse中突出显示语法和Backbase标签代码自动完成的功能。
</font></p>
<p> <font size="2"><img src="http://dev2dev.bea.com.cn/images/051102/0511020109.jpg" height="235" width="317"></font> </p>
<p><font size="2">图9. Backbase Eclipse插件</font></p>
<p><font size="2">　　注意：Eclipse的可视化拖放开发插件还处在开发阶段。</font></p>
<p><font size="2"><strong>部署到BEA WebLogic</strong></font></p>
<p><font size="2">　　BXS是一个与标准兼容的J2EE应用程序，可以将其部署到任何J2EE应用服务器上。图10显示了如何使用WebLogic控制台把BXS部署到<a href="http://dev2dev.bea.com/wlserver/" target="_blank">BEA WebLogic Server</a>。
</font></p>
<p> <font size="2"><img src="http://dev2dev.bea.com.cn/images/051102/0511020111.jpg" height="251" width="325"></font> </p>
<p><font size="2">图10. 把BXS部署到BEA WebLogic</font></p>
<p><font size="2"><strong>实现Backbase RIA Pet Store</strong></font></p>
<p><font size="2">　　下面的顺序图包括更多详细信息，可以帮助您更好地理解如何实现Backbase pet store。该顺序图显示了在应用程序的初始化加载期间BPC与BXS之间的交互，如图11所示，它包括以下4个步骤：</font></p>
<ul><li><font size="2">初始化：用户在浏览器中输入宠物商店的URL；对BPC进行初始化。</font></li><li><font size="2">应用程序布局：触发正在构造的事件；BPC构建整体应用程序布局；宠物类别被加载并显示在选项卡中。</font></li><li><font size="2">默认数据：默认情况下加载狗的类别；最初显示8张狗的图片，并带有向前/向后和排序功能。</font></li></ul>
<p><font size="2">　　用户交互：用户点击Next按钮便可显示编号从9到16的狗图片。
</font></p>
<p> <font size="2"><img src="http://dev2dev.bea.com.cn/images/051102/0511020110.jpg" height="365" width="329"></font> </p>
<p><font size="2">图11.顺序图：富商店前端</font></p>
<ul><li><font size="2">初始化</font></li><p><font size="2">从用户在浏览器中输入宠物商店的URL开始，这将导致从Web服务器请求一个索引页面。</font></p><p><font size="2">索引页面包含用于实例化BPC的代码。索引页面是XHTML和BXML标签的结合，包含负责启动富前端的初始化事件处理程序。</font></p><p><font size="2">BPC初始化代码：</font></p><pre class="code"><font size="2">&lt;...&gt;&lt;body onload="bpc.boot('/Backbase/')"&gt;<br><br>&lt;...&gt;<br><br>  &lt;xmp b:backbase="true"<br><br>          style="display:none;height:100%;"&gt;<br><br>    &lt;s:loading&gt;<br><br>      &lt;div style="position:absolute;width:20%;<br><br>                     top: 50px;left: 35%;"&gt;<br><br>        &lt;center&gt;Please wait while loading...<br><br>        &lt;/center&gt;<br><br>      &lt;/div&gt;<br><br>    &lt;/s:loading&gt;<br><br>    &lt;...&gt;<br><br>    &lt;!-- Include petshop specific behaviors --&gt;<br><br>    &lt;s:include b:url="petshop.xml"/&gt;<br></font></pre><li><font size="2">应用程序布局</font></li><p><font size="2">加载页面之后，BPC就会处理正在构造的事件，以便开始构建总体的应用程序布局。</font></p><p><font size="2">应用程序布局由几个面板组成，它们将屏幕划分为几个部分。顶行有一个固定高度的宠物商店徽标，接下来的主行是实际的商店，大小可以调整。主行分为两列，左边一列是产品类别，右边一列是购物车和历史记录。</font></p><font size="2">
产品类别使用选项卡式的导航，每个宠物类别一个选项卡。这些选项卡是动态构造的，具体过程是通过BXS从一个XML文件加载类别，然后通过一个客户端模板把这些类别转换为选项卡，该转换模板的BPC代码如下：
</font><pre class="code"><font size="2">&lt;s:task b:action="transform"<br><br>    b:stylesheet="b:xml('categories')"<br><br>    b:xmldatasource="b:url('categories.xml')"<br><br>    b:destination="id('main-content')" <br><br>    b:mode="aslastchild" /&gt;<br><br></font></pre><p><font size="2">下面是用于从文件系统把类别加载为XML的BXS代码：</font></p><pre class="code"><font size="2">&lt;bsd:pipeline equals="categories.xml"<br><br>                                 access="public"&gt;<br><br>    &lt;bsd:readxml input="file:/categories.xml"/&gt;<br><br>&lt;/bsd:pipeline&gt;<br></font></pre><p><font size="2">下面是用于创建选项卡式导航的BPC客户端模板：</font></p><pre class="code"><font size="2">&lt;b:tabrow&gt;<br><br>  &lt;s:for-each b:select="categories/category"&gt;<br><br>    &lt;b:tab&gt;<br><br>      &lt;s:attribute b:name="b:followstate"&gt;<br><br>        id('&lt;s:value-of b:select="name"/&gt;')<br><br>      &lt;/s:attribute&gt;<br><br>      &lt;s:value-of b:select="name"/&gt;<br><br>    &lt;/b:tab&gt;<br><br>  &lt;/s:for-each&gt;<br><br>&lt;/b:tabrow&gt;<br></font></pre><p><font size="2">所有BPC代码（用蓝色表示）都在客户端执行，而所有BXS代码（用红色表示）都在服务器端执行。注意，在本例中，我选择了在客户端进行转换，因为
数据集很小。下面我会给出一个在服务器端转换的例子。两种转换都要用到XSLT语法。Backbase的一个强大功能就是，前端开发人员可以根据情况选择
在客户端还是服务器端处理表示逻辑。语法似乎允许轻松地把代码从客户端移到服务器端，或者反之。</font></p><p><font size="2">以上的代码示例应该可以使您了解到，借助于Backbase，Ajax编程变得多么轻松。结合了DHTML的声明性方法则更容易上手。使用附加的B
标签不仅可以使界面更加丰富，而且可以使开发人员的效率更高。诸如&lt;b:tab&gt;之类的单个标签可以代替多行HTML和JavaScript
代码，而且保证可以用于各种浏览器。</font></p><li><font size="2">默认数据</font></li><p><font size="2">显示商店前端时，默认情况下显示的是狗的类别。对于本案例，BXS负责此项操作。BXS从一个Web服务获得数据，将其放入缓存，然后生成BXML
表示代码，再把这些表示代码发回给BPC。服务器还通过一项配置设置确定一个页面上可以显示的动物数量，并根据需要加入了Next和Previous按
钮。最后，服务器还提供了按照名称或价格进行排序的功能。</font></p><p><font size="2">下面的代码片断演示了服务器功能。外部管道products-overview.xml首先调用catalog.xml子管道。该子管道要么返回缓
存中的宠物信息，要么调用另一个子管道catalog.ws。在缓存没有命中的情况下，内部管道catalog.ws会从Web服务获取宠物信息。</font></p><p><font size="2">外部管道获得宠物信息，然后进行XSLT转换，从而以4x2表格显示这些信息，并带有Next和Previouse按钮，然后把BXML格式的代码发回给BPC。BPC呈现它接收到的BXML。</font></p><p><font size="2">有3个嵌套的BXS管道分别用于从Web服务获取数据、将其放入缓存，以及通过XSLT转换创建BXML输出：
</font></p><pre class="code"><font size="2">&lt;bsd:pipeline equals="products-overview.xml"<br><br>                              access="public"/&gt;<br><br>  &lt;bsd:callpipe pipe="catalog.xml"/&gt;<br></font></pre><pre class="code"><font size="2">&lt;bsd:pipeline equals="catalog.xml" access="private"&gt;<br><br>  &lt;bsd:exist field="{global:petstore-catalog}"&gt;<br><br>    &lt;bsd:readxml&gt;{global:petstore-catalog}<br><br>    &lt;/bsd:readxml&gt;<br><br>    &lt;bsd:otherwise&gt;<br><br>      &lt;bsd:callpipe pipe="catalog.ws"/&gt;<br></font></pre><pre class="code"><font size="2">&lt;bsd:pipeline equals="catalog.ws"<br><br>                               access="private"&gt;<br><br>  &lt;bsd:try&gt;<br><br>    &lt;bsd:callws wsdl="PetstoreCatalog.wsdl"<br><br>                               method="getAll"/&gt;<br><br>    &lt;bsd:callpipe pipe="strip-root-ns"/&gt;<br><br>    &lt;bsd:catch&gt;<br><br>      &lt;bsd:xslt xslt="error.xslt"&gt;<br><br>        &lt;bsd:param name="errormsg"&gt;{error:message}<br><br>        &lt;/bsd:param&gt;<br><br>        &lt;bsd:param name="errorsrc"&gt;{error:source}<br><br>        &lt;/bsd:param&gt;<br><br>      &lt;/bsd:xslt&gt;<br><br>    &lt;/bsd:catch&gt;<br><br>  &lt;/bsd:try&gt;<br><br>&lt;/bsd:pipeline&gt;<br>      &lt;bsd:writexml&gt;{global:petstore-catalog}<br><br>      &lt;/bsd:writexml&gt;<br><br>    &lt;/bsd:otherwise&gt;<br><br>  &lt;/bsd:exist&gt;<br><br>&lt;/bsd:pipeline&gt;<br>&lt;bsd:extractfilter xpath=<br><br>  "category[name/text()='{requestparam:category}']"/&gt; <br><br>  &lt;bsd:xslt xslt="products/products-overview.xslt"&gt;<br><br>    &lt;bsd:param name="category"&gt;<br><br>      {requestparam:category}<br><br>    &lt;/bsd:param&gt;<br><br>    &lt;bsd:param name="stepsize"&gt;<br><br>      {global:stepsize}<br><br>    &lt;/bsd:param&gt;<br><br>    &lt;bsd:param name="sortorder"&gt;<br><br>      {requestparam:sortorder}<br><br>    &lt;/bsd:param&gt;<br><br>    &lt;bsd:param name="sortfield"&gt;<br><br>      {requestparam:sortfield}<br><br>    &lt;/bsd:param&gt;<br><br>  &lt;/bsd:xslt&gt;<br><br>&lt;/bsd:pipeline&gt;<br></font></pre><p><font size="2">代码示例再次清楚地说明了，借助于Backbase，以声明性的方式创建Ajax前端是多么容易的事情。例如，只要使用带有一个WSDL引用作为属性的&lt;bsd:callws&gt;标签，就可以调用一个Web服务。</font></p><li><font size="2">用户交互</font></li><p><font size="2">现在，最终用户可以与宠物商店类别进行交互。可以使用Next或Previous按钮或者排序功能在动物类别中进行浏览。或者，只要点击一下相应的选项卡，就可以转到另一个类别中。</font></p><p><font size="2">BPC和BXS对这种交互进行了无缝处理。显示已经在客户端上的数据时，无需与服务器进行任何通信。例如，购物者已经从狗类别转到了猫类别，然后再
回到狗类别。客户端仍然拥有狗类别的数据，所以可以马上显示出来，这使得购物体验变得更完美。其他的类别需要从BXS获取。BXS要么立即从其缓存返回它
们，要们访问Web服务来获得新数据。</font></p></ul>
 <p><font size="2">　　为了详细说明Backbase Ajax宠物商店的实现，我把重点放在了初始化的步骤上。完整的宠物商店（可以从<a href="http://www.backbase.com/xmlserver" target="_blank">http://www.backbase.com/xmlserver</a>下载）还包括以下功能：</font></p>
<ul><ul><li><font size="2">商店前端</font></li><ul><li><font size="2">初始化。</font></li><li><font size="2">使用从文件加载的宠物类别创建选项卡。</font></li><li><font size="2">默认情况下从Web服务加载Dog选项卡。</font></li><li><font size="2">通过缓存浏览Dog并对其进行排序。</font></li></ul><li><font size="2">宠物详细情况</font></li><ul><li><font size="2">使用跟踪聚合来自缓存和数据库的宠物详细情况。</font></li><li><font size="2">创建可视化历史记录。</font></li></ul><li><font size="2">购物车</font></li><ul><li><font size="2">使用跟踪添加到购物车。</font></li></ul><li><font size="2">登录</font></li><ul><li><font size="2">登录和身份验证。</font></li></ul><li><font size="2">退出</font></li><ul><li><font size="2">退出和授权。</font></li><li><font size="2">确认。</font></li></ul></ul></ul>
<p><font size="2"><strong>结束语</strong></font></p>
<p><font size="2">　　最近有很多人都在研究Ajax。Ajax的优点已经在实践中得到了证明。定制Ajax的缺点在于它的复杂性和不兼容性。大量客户端
JavaScript的出现意味着开发人员很可能陷入到浏览器实现差别的泥潭中去。另外，JavaScript这种语言不适用于复杂的应用程序。</font></p>
<p><font size="2">　　为了开发易于管理的、可伸缩的和适应未来变化的Ajax解决方案，开发人员所需使用的工具应该具有比定制部件开发更多的功能。Backbase
Ajax软件提供了一个功能全面的客户端GUI管理引擎(Backbase Presentation
Client)、一个灵活的服务器端XML管道(Backbase XML
Server)和一种声明性的基于标签的UI语言，BXML(Backbase eXtensible Markup
Language)。该方法具有几个优点。</font></p>
<p><font size="2">　　首先，Backbae易于使用。它的声明性语言水平地扩展了DHTML；它完全对开发人员隐藏了浏览器兼容性的问题；而且它带有一套开发和调试工具。</font></p>
<p><font size="2">　　其次，Backbase是一个功能全面的Ajax
GUI管理系统。Backbase的先进性大大超过了其他Ajax框架，它完全把重点放在提供一个部件库或客户端－服务器通信（如DWR）上。在控件和客
户端－服务器通信的基础上，Backbase提供了用于如下用途的标签：提供电影效果，随需应变的数据加载，数据绑定和客户端的数据转换，对于Back和
Forward按钮的支持，完善的GUI状态管理，等等。所有这些功能对于目前的Ajax Web应用程序来说都是必需的。</font></p>
<p><font size="2">　　最后，Backbase是以兼容的方式提供所有客户端和服务器端的功能。用户可以使用富Ajax前端扩展现有的应用程序，同时无需修改后端。对于整个表示层来说，它的架构是时新的、模块化的，而且它基于XML。</font></p>
<p><font size="2"><strong>参考资料</strong></font></p>
<ul><li><font size="2"><a href="http://java.sun.com/developer/releases/petstore/" target="_blank">Java Pet Store Demo</a>。</font></li><li><font size="2"><a href="http://www.adaptivepath.com/publications/essays/archives/000385.php" target="_blank">Ajax: A New Approach to Web Applications</a>，作者Jesse James Garrett（Adaptive Path，2005年2月）。</font></li></ul>
<p><font size="2"><strong>原文出处</strong></font></p>
<p><font size="2">A Backbase Ajax Front-end for J2EE Applications</font></p>
<p><font size="2"><a href="http://dev2dev.bea.com/pub/a/2005/08/backbase_ajax.html" target="_blank">http://dev2dev.bea.com/pub/a/2005/08/backbase_ajax.html</a></font>
</p>

			      <!--文章其他信息-->
			       <div class="dot001"><font size="2"><img src="http://dev2dev.bea.com.cn/images/_.gif" alt="" height="1" width="100%"></font></div>
                     
                     <table border="0" cellpadding="3" cellspacing="0" width="100%"><tbody><tr valign="bottom">
                       <td colspan="2" height="20"><font size="2">&nbsp;<span class="h2b">作者简介</span></font></td>
                     </tr><tr><td align="center" valign="top" width="0"><font size="2"><br></font></td><td><font size="2"><a href="http://dev2dev.bea.com/pub/au/324" target="_blank">Mark Schiefelbein</a>自2005年2月以来一直担任Backbase的产品管理主管。Mark极大地推动了Backbase Rich Internet Application的全球推广。</font></td></tr></tbody></table><img src ="http://www.blogjava.net/cmd/aggbug/34227.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/cmd/" target="_blank">静夜思</a> 2006-03-08 12:11 <a href="http://www.blogjava.net/cmd/articles/34227.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>FreeMarker设计指南</title><link>http://www.blogjava.net/cmd/articles/34148.html</link><dc:creator>静夜思</dc:creator><author>静夜思</author><pubDate>Tue, 07 Mar 2006 12:56:00 GMT</pubDate><guid>http://www.blogjava.net/cmd/articles/34148.html</guid><wfw:comment>http://www.blogjava.net/cmd/comments/34148.html</wfw:comment><comments>http://www.blogjava.net/cmd/articles/34148.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/cmd/comments/commentRss/34148.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/cmd/services/trackbacks/34148.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: FreeMarker设计指南        1、快速入门        （1）模板 + 数据模型 = 输出        l&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; FreeMarker基于设计者和程序员是具有不同专业技能的不同个体的观念        l&nbsp;&nbsp;&nbsp;&nbsp;...&nbsp;&nbsp;<a href='http://www.blogjava.net/cmd/articles/34148.html'>阅读全文</a><img src ="http://www.blogjava.net/cmd/aggbug/34148.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/cmd/" target="_blank">静夜思</a> 2006-03-07 20:56 <a href="http://www.blogjava.net/cmd/articles/34148.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>简化Spring</title><link>http://www.blogjava.net/cmd/articles/34020.html</link><dc:creator>静夜思</dc:creator><author>静夜思</author><pubDate>Tue, 07 Mar 2006 03:38:00 GMT</pubDate><guid>http://www.blogjava.net/cmd/articles/34020.html</guid><wfw:comment>http://www.blogjava.net/cmd/comments/34020.html</wfw:comment><comments>http://www.blogjava.net/cmd/articles/34020.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/cmd/comments/commentRss/34020.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/cmd/services/trackbacks/34020.html</trackback:ping><description><![CDATA[<p><font size="2"><strong>序</strong></font> </p>

<p><font size="2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;人人都爱Spring加Hibernate。<br>&nbsp;&nbsp;&nbsp;&nbsp;但Spring 
MVC+hibernate的Sample如Appfuse的代码却远算不得最简洁优美好读，如果在自己的项目中继续发挥我们最擅长的依样画葫芦大法，美好愿望未必会实现。 
<br>&nbsp;&nbsp;&nbsp;&nbsp; 所以，Pramatic精神不灭。这个系列就是探寻最适合自己的Spring+Hibernate模式。 </font></p>

<p><font size="2">&nbsp;<strong>I-配置文件简化</strong></font> </p>

<p><font size="2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;我厌倦一切配置文件繁重的框架。 <br>&nbsp;&nbsp;&nbsp;&nbsp; 
最好的情况是，<strong>框架提供极端灵活复杂的配置方式，但只在你需要的时候</strong>。<br>&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp; 
Spring提供了三种可能来简化XML。随着国内用户水平的提高，这些基本的简化技巧大家都已掌握，从Spring 1.2开始又有了小小的改进，<a href="http://blog.arendsen.net/index.php/2005/07/09/spring-xml-way-simpler-since-12/">&lt;Spring 
XML: way simpler since 1.2&gt;</a>作了集大成的归纳<br></font></p>

<p><font size="2"><strong>1.1.autowire="byName" /"byType"</strong></font></p>

<p><font size="2">&nbsp;&nbsp;&nbsp;&nbsp; 假设Controller有一个属性名为customerDAO，Spring就会在配置文件里查找有没有名字为CustomerDAO的bean, 
自动为Controller注入。<br>&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;如果bean有两个属性，一个想默认注入，一个想自定义，只要设定了autowire，然后显式的声明
那个想自定义的，就可以达到要求。这就应了需求，在需要特别配置的时候就提供配置，否则给我一个默认注入可以了。<br><br>&nbsp;&nbsp;&nbsp;&nbsp; 
还有一个更懒的地方，在最最根部的&lt;beans&gt;节点写一句default-autovwrie="byName"，可以让所有bean 
都默认autowrie。<br>&nbsp;&nbsp;&nbsp; 不过Rod认为开发期可以这样，但Production 
Server上不应该使用Autowire，大家自己来决定了。</font></p>

<p><font size="2"><strong>1.2.&lt;bean&gt;节点之间抽象公共定义和 Inner Bean</strong></font></p>

<p><font size="2">&nbsp;&nbsp;&nbsp; 这太方便懒人了，想不到两个独立的XML节点都可以玩继承和派生，子节点拥有父节点的全部属性。<br>&nbsp;&nbsp;&nbsp; 最好用的地方就是那个Transtion 
Proxy的定义。先定义一个又长又冗的父类，然后用子类去继承它。<br>&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp; 另外，还有一个Inner 
Bean的机制，可以把DAO写成Proxy的内部类。为什么要写成内部类?为了让Proxy冒名顶替它去让Controller Autowire。(详见后面的示例) 
</font></p>

<p><font size="2"><strong>1.3. 宽松的配置, To XML or Not to XML&nbsp;<br></strong>&nbsp;&nbsp;&nbsp; 
据说Spring比Struts的配置宽松了很多，这就给人把东西从配置文件中撤回原码中的机会。<br>&nbsp;&nbsp;&nbsp; 不赞成什么都往配置文件里晒，造成了Rich 
Information的配置文件，修改或者查看的时候，要同时打开配置文件和原码才能清楚一切。 <br>&nbsp;&nbsp;&nbsp; 
而我希望配置文件就集中做一些整体的配置，还有框架必须的、无需管理的冗余代码。而一些细节的变化不大的配置和逻辑，就尽量别往里塞了。因此，Success/Fail 
View 的配置，不建议放在里面。 <br></font></p>

<p><font size="2"><strong>2.简化后的配置文件</strong></font></p>
<font size="2"><strong></strong></font>
<p><font size="2">1.Controller只剩下一句</font></p>

<div style="border: 0.5pt solid windowtext; padding: 4px 5.4pt; background: rgb(230, 230, 230) none repeat scroll 0% 50%; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; width: 98%;">
<div><font size="2"><span style="color: rgb(0, 0, 0);">&nbsp;</span><span style="color: rgb(0, 0, 255);">&lt;</span><span style="color: rgb(128, 0, 0);">bean&nbsp;</span><span style="color: rgb(255, 0, 0);">id</span><span style="color: rgb(0, 0, 255);">="saleOrderController"</span><span style="color: rgb(255, 0, 0);">&nbsp;class</span><span style="color: rgb(0, 0, 255);">="com.itorgan.web.SaleOrderController"</span><span style="color: rgb(255, 0, 0);">&nbsp;</span><span style="color: rgb(0, 0, 255);">/&gt;</span></font></div></div>

<p><font size="2">2.DAO只剩下5行 </font></p>

<div style="border: 0.5pt solid windowtext; padding: 4px 5.4pt; background: rgb(230, 230, 230) none repeat scroll 0% 50%; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; width: 98%;">
<div><font size="2"><span style="color: rgb(0, 0, 255);">&lt;</span><span style="color: rgb(128, 0, 0);">bean&nbsp;</span><span style="color: rgb(255, 0, 0);">id</span><span style="color: rgb(0, 0, 255);">="saleOrderDAO"</span><span style="color: rgb(255, 0, 0);">&nbsp;parent</span><span style="color: rgb(0, 0, 255);">="baseDAOService"</span><span style="color: rgb(255, 0, 0);">&nbsp;</span><span style="color: rgb(0, 0, 255);">&gt;</span><span style="color: rgb(0, 0, 0);"><br>&nbsp; </span><span style="color: rgb(0, 0, 255);">&lt;</span><span style="color: rgb(128, 0, 0);">property&nbsp;</span><span style="color: rgb(255, 0, 0);">name</span><span style="color: rgb(0, 0, 255);">="target"</span><span style="color: rgb(0, 0, 255);">&gt;</span><span style="color: rgb(0, 0, 0);"><br>&nbsp;&nbsp;&nbsp; </span><span style="color: rgb(0, 0, 255);">&lt;</span><span style="color: rgb(128, 0, 0);">bean&nbsp;</span><span style="color: rgb(255, 0, 0);">class</span><span style="color: rgb(0, 0, 255);">="com.itorgan.lherp.dao.impl.SaleOrderDAOImpl"</span><span style="color: rgb(255, 0, 0);">&nbsp;</span><span style="color: rgb(0, 0, 255);">/&gt;</span><span style="color: rgb(0, 0, 0);">&nbsp;<br>&nbsp; </span><span style="color: rgb(0, 0, 255);">&lt;/</span><span style="color: rgb(128, 0, 0);">property</span><span style="color: rgb(0, 0, 255);">&gt;</span><span style="color: rgb(0, 0, 0);"><br></span><span style="color: rgb(0, 0, 255);">&lt;/</span><span style="color: rgb(128, 0, 0);">bean</span><span style="color: rgb(0, 0, 255);">&gt;</span></font></div></div>

<div><font size="2"><b>3.Spring 
1.2后xml语法简化<br><br>&nbsp;</b>最主要的简化是把属性值和引用bean从<strong>子节点</strong>变回了<strong>属性值</strong>，对不喜欢autowire的兄弟比较有用。<br>&nbsp;当然，如果value要CDATA的时候还是要用子节点。<br></font></div>

<div><font size="2">1.属性值<br><br></font>
<div style="border: 0.5pt solid windowtext; padding: 4px 5.4pt; background: rgb(230, 230, 230) none repeat scroll 0% 50%; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; width: 98%;">
<div><font size="2"><span style="color: rgb(0, 0, 255);">&nbsp; &lt;</span><span style="color: rgb(128, 0, 0);">property&nbsp;</span><span style="color: rgb(255, 0, 0);">name</span><span style="color: rgb(0, 0, 255);">="foo"</span><span style="color: rgb(0, 0, 255);">&gt;</span><span style="color: rgb(0, 0, 0);"><br>&nbsp;&nbsp;&nbsp;&nbsp; </span><span style="color: rgb(0, 0, 255);">&lt;</span><span style="color: rgb(128, 0, 0);">value</span><span style="color: rgb(0, 0, 255);">&gt;</span><span style="color: rgb(0, 0, 0);">fooValue</span><span style="color: rgb(0, 0, 255);">&lt;/</span><span style="color: rgb(128, 0, 0);">value</span><span style="color: rgb(0, 0, 255);">&gt;<br></span><span style="color: rgb(0, 0, 255);">&nbsp; 
&lt;/</span><span style="color: rgb(128, 0, 0);">property</span><span style="color: rgb(0, 0, 255);">&gt;<br></span>&nbsp; 简化为<br><span style="color: rgb(0, 0, 255);">&nbsp; 
&lt;</span><span style="color: rgb(128, 0, 0);">property&nbsp;</span><span style="color: rgb(255, 0, 0);">name</span><span style="color: rgb(0, 0, 255);">="foo"</span><span style="color: rgb(255, 0, 0);">&nbsp;value</span><span style="color: rgb(0, 0, 255);">="fooValue"</span><span style="color: rgb(0, 0, 255);">/&gt;</span><span style="color: rgb(0, 0, 0);"></span></font></div></div><font size="2"><br>2.引用 
bean<br></font>
<div style="border: 0.5pt solid windowtext; padding: 4px 5.4pt; background: rgb(230, 230, 230) none repeat scroll 0% 50%; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; width: 98%;">
<div><font size="2"><span style="color: rgb(0, 0, 255);">&lt;</span><span style="color: rgb(128, 0, 0);">property&nbsp;</span><span style="color: rgb(255, 0, 0);">name</span><span style="color: rgb(0, 0, 255);">="foo"</span><span style="color: rgb(0, 0, 255);">&gt;</span><span style="color: rgb(0, 0, 0);"><br>&nbsp;&nbsp; </span><span style="color: rgb(0, 0, 255);">&lt;</span><span style="color: rgb(128, 0, 0);">ref&nbsp;</span><span style="color: rgb(255, 0, 0);">bean</span><span style="color: rgb(0, 0, 255);">="fooBean"</span><span style="color: rgb(0, 0, 255);">&gt;</span><span style="color: rgb(0, 0, 0);"><br></span><span style="color: rgb(0, 0, 255);">&lt;/</span><span style="color: rgb(128, 0, 0);">property</span><span style="color: rgb(0, 0, 255);">&gt;</span><span style="color: rgb(0, 0, 0);"><br>简化为<br></span><span style="color: rgb(0, 0, 255);">&lt;</span><span style="color: rgb(128, 0, 0);">property&nbsp;</span><span style="color: rgb(255, 0, 0);">name</span><span style="color: rgb(0, 0, 255);">="foo"</span><span style="color: rgb(255, 0, 0);">&nbsp;value</span><span style="color: rgb(0, 0, 255);">="fooBean"</span><span style="color: rgb(0, 0, 255);">/&gt;</span></font></div></div><font size="2"><br><br>3. 
list如果只有一个值时，可以直接用value表示</font></div>

<div><font size="2">&nbsp; 
</font><div style="border: 0.5pt solid windowtext; padding: 4px 5.4pt; background: rgb(230, 230, 230) none repeat scroll 0% 50%; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; width: 98%;">
<div><font size="2"><span style="color: rgb(0, 0, 255);">&lt;</span><span style="color: rgb(128, 0, 0);">property&nbsp;</span><span style="color: rgb(255, 0, 0);">name</span><span style="color: rgb(0, 0, 255);">="myFriendList"</span><span style="color: rgb(0, 0, 255);">&gt;</span><span style="color: rgb(0, 0, 0);"><br>&nbsp; </span><span style="color: rgb(0, 0, 255);">&lt;</span><span style="color: rgb(128, 0, 0);">list</span><span style="color: rgb(0, 0, 255);">&gt;</span><span style="color: rgb(0, 0, 0);"><br>&nbsp;&nbsp;&nbsp;&nbsp; </span><span style="color: rgb(0, 0, 255);">&lt;</span><span style="color: rgb(128, 0, 0);">value</span><span style="color: rgb(0, 0, 255);">&gt;</span><span style="color: rgb(0, 0, 0);">wuyu</span><span style="color: rgb(0, 0, 255);">&lt;/</span><span style="color: rgb(128, 0, 0);">value</span><span style="color: rgb(0, 0, 255);">&gt;</span><span style="color: rgb(0, 0, 0);"><br>&nbsp; </span><span style="color: rgb(0, 0, 255);">&lt;/</span><span style="color: rgb(128, 0, 0);">list</span><span style="color: rgb(0, 0, 255);">&gt;</span><span style="color: rgb(0, 0, 0);"><br></span><span style="color: rgb(0, 0, 255);">&lt;/</span><span style="color: rgb(128, 0, 0);">property</span><span style="color: rgb(0, 0, 255);">&gt;</span><span style="color: rgb(0, 0, 0);"><br>简化为<br></span><span style="color: rgb(0, 0, 255);">&lt;</span><span style="color: rgb(128, 0, 0);">property&nbsp;</span><span style="color: rgb(255, 0, 0);">name</span><span style="color: rgb(0, 0, 255);">="myFriendList"</span><span style="color: rgb(255, 0, 0);">&nbsp;value</span><span style="color: rgb(0, 0, 255);">="wuyu"</span><span style="color: rgb(0, 0, 255);">/&gt;</span><span style="color: rgb(0, 0, 0);"><br><br>
</span></font></div></div><font size="2">&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;因为只有一个值时，List就退化为Object类型嘛，多体贴。<br><br></font></div>

<div><font size="2"><strong></strong></font></div>

<div><font size="2"><b>4.使用Spring自带的DTD使编辑器Smart.</b></font></div>

<p><font size="2">&nbsp;&nbsp;&nbsp; 
如果没有用Eclipse的Spring插件，那至少也要使用spring自带的dtd使XML编辑器smart一些，能够自动为你生成属性,判断节点/属性名称有没有拼错等。<br><br><strong>5.还有更变态的简化配置方法</strong><br>&nbsp;&nbsp; 
&nbsp;比如autoproxy，不过我觉得更简化就不可控了，所以没有采用。</font></p>

<h1 class="block_title"><font size="2">II-Model层简化</font></h1><font size="2">


&nbsp;&nbsp;&nbsp;&nbsp;因为Spring自带的sample离我们的实际项目很远，所以官方一点的model层模式展现就靠Appfuse了。<br>&nbsp;&nbsp;&nbsp;&nbsp;但Appfuse的model层总共有一个DAO接口、一个DAOImpl类、一个Service接口、一个ServiceImpl类、一个DataObject.....大概只有受惯了虐待的人才会欣然接受吧。<br>&nbsp;&nbsp;&nbsp; 
另外，Domain-Driven逢初一、十五也会被拿出来讨论一遍。<br>&nbsp;&nbsp;&nbsp;
其实无论什么模式，都不过是一种人为的划分、抽象和封装。只要在团队里理解一致，自我感觉优雅就行了。在我的Model层里，DO和Manager一生一
旦包演全场，VO作为纯数据载体，而Manager类放置商业方法，用getHibernateTemplate()直接访问数据库，不强制基于接口编
程......<br><br>&nbsp;&nbsp;&nbsp; 
<strong>1.DataObject类<br>&nbsp;&nbsp;&nbsp;&nbsp; </strong>好听点也可以叫Domain 
Object。Domain Driven&nbsp; Development虽然诱人，但因为Java下的ORM框架都是基于Data Mapper模式的，没有Ruby On 
Rails中那种Active Recorder的模式。所以，还是压下了这个欲望，Data 
Object纯粹作一个数据载体，而把数据库访问与商业逻辑操作统一放到Manager类中。<br><br>&nbsp;&nbsp;&nbsp; <strong>2.Manager类</strong><br>&nbsp;&nbsp;&nbsp;&nbsp;我的Manager类是Appfuse中DAO类与Service类的结合体，因为：<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<strong>2.1 
不想使用纯DAO</strong><br>&nbsp;&nbsp;&nbsp;&nbsp;
以往的DAO是为了透明不同数据库间的差异，而现在Hibernate已经做的很好。所以目前纯DAO的更大作用是为了将来可以切换到别的ORM方案比如
iBatis，但一个Pragmaic的程序员显然不会无聊到为了这个机会不大的理由，现在就去做一个纯DAO层，项目又不是Appfuse那样为了
demo各种ORM方案而存在。<br><br>&nbsp;&nbsp;&nbsp; 
<strong>2.2 也不使用纯的薄Service层<br></strong>&nbsp;&nbsp;&nbsp; 
在JPetStore里有一个很薄的Service层，Fascade了一堆DAO类，把这些DAO类的所有方法都僵硬的重复了一遍。而我认为Fascade的意义在二：<br>&nbsp;&nbsp;&nbsp;&nbsp;一是Controller调用Manager甲的时候，总会伴随着调用Manager乙的某些方法。使用Fascade可以避免Controller零散的调用一堆Manager类。<br>&nbsp;&nbsp;&nbsp;&nbsp;二是一个商业过程里可能需要同时调用Manager甲乙丙丁的方法。&nbsp;<br><br>&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;这些时候，Fascade都是合理的。但我讨厌类膨胀，所以我宁愿在甲乙丙丁中挑一个来充当
Fascade的角色。有耦合的问题吗？对一个不是死搬书的Designer来说，组件边界之内的类之间的耦合并不是耦合。<br><br>&nbsp;&nbsp;&nbsp; 
</font><font size="2"><strong>3.去除不必要的基于接口编程</strong><br>&nbsp;&nbsp;&nbsp; 
众所周知，Spring是提倡基于接口编程的。<br>&nbsp;&nbsp;&nbsp;
但有些Manager类，比如SaleOrderManager
，只有5%的机会再有另一个Impl实现。95%时间里这两兄弟站一起，就像C++里的.h和.cpp，徒增维护的繁琐(经常要同步两个文件的函数声
明)，和代码浏览跳转时的不便(比如从Controler类跟踪到Service类时，只能跳转到接口类的相应函数，还要再按一次复杂的热键才跳转到实现
类)<br>&nbsp;&nbsp;&nbsp; 
连Martin 
Flower都说，强制每个类都分离接口和实现是过犹不及。只在有多个独立实现，或者需要消除对实现类的依赖时，才需要分离接口。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<strong>3.1 
DAO被强制用接口的原因</strong><br>&nbsp;&nbsp;&nbsp; Spring 
IOC本身是不会强制基于接口的，但DAO类一般要使用Spring的声明式事务机制，而声明式的事务机制是使用Spring AOP来实现的。Spring 
AOP的实现机制包括动态代理和Cgilib2，其中Spring 
AOP默认使用的Java动态代理是必须基于接口，所以就要求基于接口了。<br>&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp; <strong>3.2 
解决方法</strong><br>&nbsp;&nbsp;&nbsp; 那就让Spring 
AOP改用CGLib2，生成目标类的子类吧，我们只要指定使用声明式事务的FactoryBean使用CGLib的方式来实现AOP，就可以不基于接口编程了。<br>&nbsp;&nbsp;&nbsp; 
指定的方式为<strong>设置proxyTargetClass为true</strong>。如下：<br></font>
<div style="border: 0.5pt solid windowtext; padding: 4px 5.4pt; background: rgb(230, 230, 230) none repeat scroll 0% 50%; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; width: 98%;">
<div><font size="2"><span style="color: rgb(0, 0, 255);">
<div><span style="color: rgb(0, 0, 255);">&lt;</span><span style="color: rgb(128, 0, 0);">bean&nbsp;</span><span style="color: rgb(255, 0, 0);">class</span><span style="color: rgb(0, 0, 255);">="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"</span><span style="color: rgb(255, 0, 0);"><br>id</span><span style="color: rgb(0, 0, 255);">="baseService"</span><span style="color: rgb(255, 0, 0);">&nbsp;&nbsp;&nbsp;abstract</span><span style="color: rgb(0, 0, 255);">="true"</span><span style="color: rgb(0, 0, 255);">&gt;</span><span style="color: rgb(0, 0, 0);"><br>&nbsp; </span><span style="color: rgb(0, 0, 255);">&lt;</span><span style="color: rgb(128, 0, 0);">property&nbsp;</span><span style="color: rgb(255, 0, 0);">name</span><span style="color: rgb(0, 0, 255);">="transactionManager"</span><span style="color: rgb(255, 0, 0);">&nbsp;ref</span><span style="color: rgb(0, 0, 255);">="transactionManager"</span><span style="color: rgb(0, 0, 255);">/&gt;</span><span style="color: rgb(0, 0, 0);"><br>&nbsp; </span><span style="color: rgb(0, 0, 255);">&lt;</span><span style="color: rgb(128, 0, 0);">property&nbsp;</span><span style="color: rgb(255, 0, 0);">name</span><span style="color: rgb(0, 0, 255);">="proxyTargetClass"</span><span style="color: rgb(255, 0, 0);">&nbsp;value</span><span style="color: rgb(0, 0, 255);">="true"</span><span style="color: rgb(0, 0, 255);">/&gt;</span><span style="color: rgb(0, 0, 0);"><br><br></span><span style="color: rgb(0, 0, 255);">&lt;/</span><span style="color: rgb(128, 0, 0);">bean</span><span style="color: rgb(0, 0, 255);">&gt;</span></div></span></font></div></div><font size="2"><br>&nbsp;&nbsp;&nbsp;&nbsp; 又因为这些Service 
Bean都是单例，效率应该不受影响。<br><br>&nbsp;&nbsp;&nbsp; <strong>4.总结<br></strong>&nbsp;&nbsp;&nbsp;
对比Appfuse里面的5个类，我的Model层里只有VO作为纯数据载体，Manager类放商业方法。有人说这样太简单了，但一个应用，要划成几个
JSP，一个Controller，一个Manager，一个VO，对我来说已经足够复杂，再要往上架墙叠屋，恕不奉陪，起码在我的项目范围里不需要。
(但有很多项目是需要的，神佑世人）<br><br></font>

<h1 class="block_title"><font size="2">III-Controller层简化</font></h1>

<div class="post">
<div class="postcontent"><font size="2">&nbsp;&nbsp;&nbsp; Struts与Webwork的扇子请跳过本篇。<br><br>&nbsp;&nbsp;&nbsp; 
MVC不就是把M、V、C分开么？至唯物朴素的做法是两个JSP一个负责View，一个负责Controller，再加一个负责Model的Java 
Bean，已经可以工作得很好，那时候一切都很简单。<br>&nbsp;&nbsp;&nbsp; 而现在为了一些不是本质的功能，冒出这么多非标准的Web框架，实在让人一阵郁闷。像Ruby On 
Rails那样简捷开发，可用可不用，而且没有太多的限制需要学习的，比如<strong>Webwork</strong>这型还可以考虑。但像Struts那样越用框架越麻烦，或者像Tapestry那样有严重自闭倾向，额上凿着"高手专用玩具"的，用在团队里就是不负责任的行为了。<br><br></font>
<div><font size="2">&nbsp;&nbsp;&nbsp;&nbsp;so，<strong>我的MVC方案是使用Spring 
MVC的Controller接口，写最普通的JavaBean作为Controller</strong>，本质就和当年拿JSP作Controller差不多，但拥有了Spring 
IOC的特性。</font></div>
<div><font size="2">&nbsp;&nbsp;&nbsp;&nbsp;之所以用这么消极的选择标准，是因为觉得这一代MVC框架离重回RAD时代的标准还很远，注定了只是一段短暂的，过渡的技术，不值得投资太多精力和团队学习成本。</font></div>
<div><font size="2"><br><strong>1. 原理</strong></font> 
<div><font size="2">&nbsp;&nbsp;&nbsp;&nbsp; Spring MVC按植物分类学属于Martin Flower〈企业应用模式〉里的静态配置型Front 
Controler，使用DispatchServlet截获所有*.do的请求，按照xml文件的配置，调用对应的Command对象的handleRequest(request,response)函数，同时进行依赖对象的注入。<br>&nbsp;&nbsp;&nbsp;&nbsp; 
我们的Controller层，就是实现handleRequest(request,response)函数的普通JavaBean。</font></div>
</div>
<div><font size="2"><strong><br>2. 优势<br>&nbsp;&nbsp;&nbsp;&nbsp;</strong> Spring MVC与struts相比的优势:</font></div>
<div><font size="2"><strong>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</strong>一是它的Controller有着从松到紧的类层次结构，用户可以选择实现只有一个HandleRequest()函数的接口，也可以使用它有很多回调函数的SimpleFormController类。</font></div>
<div><font size="2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;二是不需要Form 
Bean，也不需要Tapestry那所谓面向对象的页面对象，对于深怕类膨胀，改一个东西要动N个地方的人最适合不过。</font></div>
<div><font size="2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;三是不需要强XML配置文件，宣告式编程是好的，但如果强制成框架，什么都要在xml里面宣告，写的时候繁琐，看的时候也要代码配置两边看才能明白就比较麻烦了。</font></div>
<div><font size="2">&nbsp;</font></div>
<div><font size="2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;那Webwork呢?没有实战过，不过因为对MVC框架所求就不多，单用Spring 
MVC的Controller已经可以满足需求，就不多搞一套Webwork来给团队设坎，还有给日后维护，spring,ww2之间的版本升级添麻烦了。真有什么需要添加的，Spring 
MVC源代码量很少，很容易掌控和扩展。<br>&nbsp; </font></div>
<div><font size="2"><b>3.化简</b></font></div>
<div><font size="2"><strong>3.1. 直接implement Controller，实现handleRequest()函数</strong></font></div>
<div><font size="2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;首先，simple form controller非我所好，一点都不simple。所以有时我会直接implement 
Controller接口。这个接口的唯一函数是供Front 
Controller调用的handleRequest(request,response)。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 
如果需要application对象，比如想用application.getRealPath()时，就要extends 
webApplicationObjectSupport。<br><br><strong>3.2.每个Controler负责一组相关的action</strong></font></div>
<div><font size="2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
我是坚决支持一个Controler负责多个action的，一个Controler一个action就像一个function一个类一样无聊。所以我用
最传统的方式，用URL参数如msg="insert"把一组相关action交给一个Controler控制。ROR与制作中的Groovy On
Rails都是这种模式，Spring也有MultiActionController支持。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 
以上三者都是把URL参数直接反射为Controller的函数，而<a href="http://mc4j.org/confluence/display/stripes/Home">Stripes</a>的设计可用annotation标注url 
action到响应函数的映射。</font></div>
<div><font size="2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 
<strong>3.3.xml宣告式编程的取舍</strong>&nbsp;</font></div>
<div><font size="2">&nbsp;&nbsp;&nbsp;&nbsp;我的取舍很简单，反正Spring没有任何强制，我只在可能需要不重新编译而改变某些东西的时候，才把东西放在xml里动态注入。jsp路径之类的就统统收回到controller里面定义.</font></div>
<div><font size="2">&nbsp;</font></div>
<div><font size="2"><strong>3.4.Data Binder</strong></font></div>
<div><font size="2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Data
Binder是Controller的必有环节，对于Spring提供的DataBinder，照理完全可用，唯一不爽是对象如果有内嵌对象，如订单对象
里面包含了Customer对象，Spring需要你先自行创建了Customer对象并把它赋给了Order对象，才可能实现
order.customer.customer_no这样的绑定。我偷懒，又拿Jakarta BeanUtils出来自己做了一个Binder。<br><br>3.<strong>5.提取基类</strong></font></div>
<div><font size="2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 
最后还是忍不住提取了一个基类，负责MultiAction和其他一些简便的方法。Sprnig的MultiActionController做得太死，规定所有函数的第1,2个参数必须是request和response，不懂动态的，温柔的进行参数注入。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp; 
&nbsp;经过化简再化简，已经是很简单一个Java Bean ，任谁都可以轻松上手，即使某年某月技术的大潮把现在所有MVC框架都淹没了，也不至于没人识得维护。<br><br></font>

<h1 class="block_title"><font size="2">IV-View层简化</font></h1>

<div class="post">
<div class="postcontent"><font size="2">&nbsp;&nbsp;&nbsp; 
人生像个舞台，请良家少女离开。<br>&nbsp;&nbsp;&nbsp;&nbsp;同样的，Freemarker和Velocity爱好者请跳过本篇。与弃用webwork而单用Spring MVC 
Controller接口的理由一样，<a href="http://www.freemarker.org/">Freemarker</a>本来是一样好东西，还跨界支持jsp&nbsp;的taglib，但为了它的非标准化，用户数量与IDE的缺乏，在View层我们还是使用了<strong>保守但人人会用，IDE友好的JSP2.0 
配合JSTL。<br><br>&nbsp;&nbsp;&nbsp; </strong>对于B/S结构的企业应用软件来说，基本的页面不外两种，一种是填Form的，一种是DataGrid 
数据列表管理的，再配合一些css, js, ajax的效果，就是View层要关注的东西了。<br><br><strong>1. JSP 
2.0的EL代替&lt;c:out&gt;</strong><br>JSP2.0可以直接把EL写在html部分，而不必动用&lt;c:out&gt;节点后，老实说，JSP2.0+JSTL达到的页面效果，已不比Velocity相差多少了。 

</font><div style="border: 0.5pt solid windowtext; padding: 4px 5.4pt; background: rgb(230, 230, 230) none repeat scroll 0% 50%; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; width: 98%;">
<div><font size="2"><span style="color: rgb(0, 0, 255);">&lt;</span><span style="color: rgb(128, 0, 0);">p</span><span style="color: rgb(0, 0, 255);">&gt;</span><span style="color: rgb(0, 0, 0);">{goods.name}</span><span style="color: rgb(0, 0, 255);">&lt;/</span><span style="color: rgb(128, 0, 0);">p</span><span style="color: rgb(0, 0, 255);">&gt;</span><span style="color: rgb(0, 0, 0);">&nbsp;<br>代替<br></span><span style="color: rgb(0, 0, 255);">&lt;</span><span style="color: rgb(128, 0, 0);">p</span><span style="color: rgb(0, 0, 255);">&gt;&lt;</span><span style="color: rgb(128, 0, 0);">c:out&nbsp;</span><span style="color: rgb(255, 0, 0);">value</span><span style="color: rgb(0, 0, 255);">="{goods.name}"</span><span style="color: rgb(0, 0, 255);">/&gt;&lt;/</span><span style="color: rgb(128, 0, 0);">p</span><span style="color: rgb(0, 0, 255);">&gt;</span></font></div></div>
<p><font size="2">(除了EL里面不能调用goods的函数，sun那帮老顽固始终坚持JSTL只能用于数据显示，不能进行数据操作，所以不能调用bean的get/set外的方法)<br><br>&nbsp;<strong>2. 
最懒的form 数据绑定</strong></font> </p>
<p><font size="2">&nbsp;&nbsp;&nbsp; Spring少得可怜的几个tag基本上是鸡肋，完全可以不要。 而Spring开发中的那些Simple Form 
tag又还没有发布。Spring的Tag主要用来把VO的值绑到input框上。但是，和Struts一样，需要逐个Input框绑定，而且语法极度冗长，遇到select框还要自己进行处理.....典型的Spring 
Sample页面让人一阵头晕. </font></p>
<p><font size="2">&nbsp;&nbsp;&nbsp; 而<a href="http://jodd.sourceforge.net/doc/forms.html">jodd的form 
tag</a>给了我们懒人一个懒得多的方法，只要在&lt;form&gt;两头用&lt;jodd:form 
bean="myVO"&gt;&lt;/jodd:form&gt;包住，里面的所有input框，select框，checkBox...统统自动被绑定了，这么简单的事情，真不明白struts,spring为什么不用，为了不必要的灵活性么? 
<br></font></p>
<div style="border: 0.5pt solid windowtext; padding: 4px 5.4pt; background: rgb(230, 230, 230) none repeat scroll 0% 50%; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; width: 98%;">
<div><font size="2"><span style="color: rgb(0, 0, 255);">&lt;</span><span style="color: rgb(128, 0, 0);">form</span><span style="color: rgb(0, 0, 255);">&gt;</span><span style="color: rgb(0, 0, 0);"><br></span><span style="color: rgb(0, 0, 255);">&lt;</span><span style="color: rgb(128, 0, 0);">jodd:form&nbsp;</span><span style="color: rgb(255, 0, 0);">bean</span><span style="color: rgb(0, 0, 255);">="human"</span><span style="color: rgb(0, 0, 255);">&gt;</span><span style="color: rgb(0, 0, 0);"><br></span><span style="color: rgb(0, 0, 255);">&lt;</span><span style="color: rgb(128, 0, 0);">input&nbsp;</span><span style="color: rgb(255, 0, 0);">type</span><span style="color: rgb(0, 0, 255);">="text"</span><span style="color: rgb(255, 0, 0);">&nbsp;name</span><span style="color: rgb(0, 0, 255);">="name"</span><span style="color: rgb(0, 0, 255);">&gt;</span><span style="color: rgb(0, 0, 0);"><br></span><span style="color: rgb(0, 0, 255);">&lt;</span><span style="color: rgb(128, 0, 0);">input&nbsp;</span><span style="color: rgb(255, 0, 0);">type</span><span style="color: rgb(0, 0, 255);">="radiobox"</span><span style="color: rgb(255, 0, 0);">&nbsp;name</span><span style="color: rgb(0, 0, 255);">="sex"</span><span style="color: rgb(255, 0, 0);">&nbsp;value</span><span style="color: rgb(0, 0, 255);">="man"</span><span style="color: rgb(0, 0, 255);">&gt;</span><span style="color: rgb(0, 0, 0);"><br></span><span style="color: rgb(0, 0, 255);">&lt;</span><span style="color: rgb(128, 0, 0);">select&nbsp;</span><span style="color: rgb(255, 0, 0);">name</span><span style="color: rgb(0, 0, 255);">="age"</span><span style="color: rgb(0, 0, 255);">&gt;</span><span style="color: rgb(0, 0, 0);"><br>&nbsp;&nbsp;</span><span style="color: rgb(0, 0, 255);">&lt;</span><span style="color: rgb(128, 0, 0);">option&nbsp;</span><span style="color: rgb(255, 0, 0);">value</span><span style="color: rgb(0, 0, 255);">="20"</span><span style="color: rgb(0, 0, 255);">&gt;</span><span style="color: rgb(0, 0, 0);">20</span><span style="color: rgb(0, 0, 255);">&lt;/</span><span style="color: rgb(128, 0, 0);">option</span><span style="color: rgb(0, 0, 255);">&gt;</span><span style="color: rgb(0, 0, 0);"><br>&nbsp;&nbsp;</span><span style="color: rgb(0, 0, 255);">&lt;</span><span style="color: rgb(128, 0, 0);">option&nbsp;</span><span style="color: rgb(255, 0, 0);">value</span><span style="color: rgb(0, 0, 255);">="30"</span><span style="color: rgb(0, 0, 255);">&gt;</span><span style="color: rgb(0, 0, 0);">30</span><span style="color: rgb(0, 0, 255);">&lt;/</span><span style="color: rgb(128, 0, 0);">option</span><span style="color: rgb(0, 0, 255);">&gt;</span><span style="color: rgb(0, 0, 0);"><br></span><span style="color: rgb(0, 0, 255);">&lt;/</span><span style="color: rgb(128, 0, 0);">select</span><span style="color: rgb(0, 0, 255);">&gt;</span><span style="color: rgb(0, 0, 0);"><br></span><span style="color: rgb(0, 0, 255);">&lt;/</span><span style="color: rgb(128, 0, 0);">jodd:form</span><span style="color: rgb(0, 0, 255);">&gt;</span><span style="color: rgb(0, 0, 0);"><br></span><span style="color: rgb(0, 0, 255);">&lt;/</span><span style="color: rgb(128, 0, 0);">form</span><span style="color: rgb(0, 0, 255);">&gt;</span><span style="color: rgb(0, 0, 0);">&nbsp;<br></span></font></div></div>
<p><font size="2"><br></font>
</p><font size="2">不过，jodd有个致命弱点是不能绑定内嵌对象的值。比如Order(订单)对象里有个Customer(顾客)对象，jodd就不能像 
struts,spring一样用如下语法绑定: <br></font>

<div style="border: 0.5pt solid windowtext; padding: 4px 5.4pt; background: rgb(230, 230, 230) none repeat scroll 0% 50%; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; width: 98%;">
<div><font size="2"><span style="color: rgb(0, 0, 255);">&lt;</span><span style="color: rgb(128, 0, 0);">input&nbsp;</span><span style="color: rgb(255, 0, 0);">name</span><span style="color: rgb(0, 0, 255);">="customer.customerNo"</span><span style="color: rgb(0, 0, 255);">&gt;</span></font></div></div>
<p><font size="2">这是因为它的beanUtils比Jakata Common弱，用了一个错误的思路的缘故。 动用beanUtils修改一下就可以了，<a href="../../Files/calvin/form_tag.rar">修改后的源码可以在这里下载</a>。 
</font></p><p><font size="2"><strong>3. DataGrid数据列表</strong></font></p>
<p><font size="2">DisplayTag和ValueList都属于这种形式的Tag Library。但最近出现的<a href="http://www.extremecomponents.org/">Extreme 
Table</a>是真正的killer，他本身功能强大不说，而且从一开始就想着如何让别人进行扩展重载，比如Extend 
Attributes机制就是DisplayTag这样的千人一面者不会预留。<br><br><br><strong>4.css, javascript, 
ajax</strong><br>天下纷扰，没有什么特别想讲想推荐的，爱谁谁吧。</font></p></div></div>
</div></div></div><img src ="http://www.blogjava.net/cmd/aggbug/34020.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/cmd/" target="_blank">静夜思</a> 2006-03-07 11:38 <a href="http://www.blogjava.net/cmd/articles/34020.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Spring XML配置十二个最佳实践</title><link>http://www.blogjava.net/cmd/articles/34018.html</link><dc:creator>静夜思</dc:creator><author>静夜思</author><pubDate>Tue, 07 Mar 2006 03:37:00 GMT</pubDate><guid>http://www.blogjava.net/cmd/articles/34018.html</guid><wfw:comment>http://www.blogjava.net/cmd/comments/34018.html</wfw:comment><comments>http://www.blogjava.net/cmd/articles/34018.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/cmd/comments/commentRss/34018.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/cmd/services/trackbacks/34018.html</trackback:ping><description><![CDATA[<div class="center">
		<h4><font size="2">摘要:</font></h4><font size="2">
		在这篇文章里，对于Spring XML的配置，我将向你展示12种比较好的实践。其中的一些实践不仅是好的实践，更是必要的实践。除此以外，还有其他因素，例如领域模型的设计，都能影响XML的配置，但是这篇文章重点研究XML配置的易读性和易管理性。
	</font></div>
	<div class="right">
		<div class="help">
			<h4><font size="2">文章工具</font></h4>
			
			
			<font size="2"><a href="http://matrix.org.cn/favorite.shtml;jsessionid=6C924E98D62B1974971AD89FFC4C907E?type=article&amp;title=Spring%2BXML%25E9%2585%258D%25E7%25BD%25AE%25E5%258D%2581%25E4%25BA%258C%25E4%25B8%25AA%25E6%259C%2580%25E4%25BD%25B3%25E5%25AE%259E%25E8%25B7%25B5&amp;url=resource%2Farticle%2F44%2F44236_Spring%2BXML%2BConfigurations.html">收藏</a><br><a href="http://matrix.org.cn/resource/article/44/44236_Spring+XML+Configurations.html#avote">投票评分</a><br><a href="http://matrix.org.cn/resource/article/44/44236_Spring+XML+Configurations.html#areview">发表评论</a><br><a href="http://matrix.org.cn/resource/article/44/44236_Spring+XML+Configurations.html#" onclick="copyLink('Spring XML配置十二个最佳实践');">复制链接</a><br></font>
			
			
			
		</div>
	</div>
	<!-- end of summary line --><font size="2">


Spring是一个强大的JAVA应用框架，广泛地应用于JAVA的应用程序。为Plain Old Java
Objects（POJOs）提供企业级服务。Spring利用依赖注入机制来简化工作，同时提高易测性。Spring
beans及依赖，以及beans类所需的服务都在配置文件中进行了详细的说明，这个配置文件是典型的XML格式。但是它既冗长又不实用。对于需要定义大
量Spring beans的大工程来说，我们难以阅读和管理它。<br><br><span style="color: Red;">版权声明：任何获得Matrix授权的网站，转载时请务必保留以下作者信息和链接</span><br>作者:Jason;Li;<a href="http://www.matrix.org.cn/user.shtml?username=evenbetter" target="_new">evenbetter</a>(作者的blog:<a href="http://blog.matrix.org.cn/page/evenbetter" target="_new">http://blog.matrix.org.cn/page/evenbetter</a>)<br>原文:<a href="http://www.onjava.com/pub/a/onjava/2006/01/25/spring-xml-configuration-best-practices.html" target="_new">http://www.onjava.com/pub/a/onjava/2006/01/25/spring-xml-configuration-best-practices.html</a><br>译文:<a href="http://www.matrix.org.cn/resource/article/44/44236_Spring+XML+Configurations.html" target="_new">http://www.matrix.org.cn/resource/article/44/44236_Spring+XML+Configurations.html</a><br>关键字:Spring;XML;Configurations<br><br>在这篇文章里，对于Spring XML的配置，我将向你展示12种比较好的实践。其中的一些实践不仅是好的实践，更是必要的实践。除此以外，还有其他因素，例如领域模型的设计，都能影响XML的配置，但是这篇文章重点研究XML配置的易读性和易管理性。<br><br><b>1。不要使用autowiring</b><br><br>Spring
可以通过类的自省来自动绑定其依赖部分，使得你不必明确指明bean的属性和构造器。Bean的属性可以通过属性名称或类型匹配来实现自动绑定。构造器通
过类型匹配来实现自动绑定。你甚至可以指定自动检测自动绑定模式，它可以引导Spring选择一种适当的运行机制。先来看看下面的一个例子：<br></font><pre class="overflow"><font size="2">&nbsp;&nbsp;&nbsp;&nbsp;&lt;bean id="orderService"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;class="com.lizjason.spring.OrderService"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;autowire="byName"/&gt;</font></pre><font size="2"><br><br>OrderService
类的属性名在容器中用于匹配bean实例。自动绑定可以潜在地节省一些打字和减少一些混乱。但是在现实世界的工程里你不应该使用这种方式，这是因为它牺牲
了配置的清晰性和可维护性。许多指南和介绍中大量吹捧自动绑定是Spring的一种极好的特征而没有提到这一特性所带来的牺牲。依我的观点，这就像
Spring中的object－pooling，它更像是一种为了占据更多市场的商业特征。它对于XML配置文件的小巧化是一个好办法，但实际上也增加了
复杂程度，尤其当你运行有大量类声明的工程时。虽然Spring允许你混合自动绑定和手动绑定，但是这个矛盾会使XML配置更加晦涩难懂。<br><br><b>2.使用通俗的命名</b><br><br>这
个方式对于Java编码也一样适用。在工程中使用清晰的、描述性的、协调的通俗名称对于开发者理解XML配置是十分有益的。例如对于bean
ID，你可以根据通俗的Java类名来命名它。对于例子中OrderServiceDAO的bean
ID命名为orderServiceDAO。对于大的工程，你可以在bean ID前面加上包名作为前缀。<br><br><b>3. 使用简洁的形式</b><br><br>简洁形式避免了冗长，是因为它从子元素中将属性值和参考写到属性中。例如下面的例子：<br></font><pre class="overflow"><font size="2">&nbsp;&nbsp;&nbsp;&nbsp;&lt;bean id="orderService"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;class="com.lizjason.spring.OrderService"&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;property name="companyName"&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;value&gt;lizjason&lt;/value&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;/property&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;constructor-arg&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;ref bean="orderDAO"&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;/constructor-arg&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&lt;/bean&gt;</font></pre><font size="2"><br><br>可以使用简洁形式将上述代码重写为：<br></font><pre class="overflow"><font size="2">&nbsp;&nbsp;&nbsp;&nbsp;&lt;bean id="orderService"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;class="com.lizjason.spring.OrderService"&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;property name="companyName"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;value="lizjason"/&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;constructor-arg ref="orderDAO"/&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&lt;/bean&gt;</font></pre><font size="2"><br><br>简洁形式功能在1.2版本中可以使用。对于&lt;ref local="..."&gt;没有简洁形式。<br>简洁形式不但可以节约你的打字，而且可以使XML配置文件清晰。它最引人注目的是当在一个配置文件中有大量定义的类时可以提高易读性。<br><br><b>4. 对于构造器参数匹配，类型名比序号好。</b><br><br>当一个构造器含有一个以上的同种类型的参数，或者属性值的标签已经被占用时，Spring允许你使用从0计数的序号来解决这些会带来混淆的问题。例如：<br></font><pre class="overflow"><font size="2">&nbsp;&nbsp;&nbsp;&nbsp;&lt;bean id="billingService"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;class="com.lizjason.spring.BillingService"&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;constructor-arg index="0" value="lizjason"/&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;constructor-arg index="1" value="100"/&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&lt;/bean&gt;</font></pre><font size="2"><br><br>像下面这样，利用类型属性来编写会更好一些：<br></font><pre class="overflow"><font size="2">&nbsp;&nbsp;&nbsp;&nbsp;&lt;bean id="billingService"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;class="com.lizjason.spring.BillingService"&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;constructor-arg type="java.lang.String"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;value="lizjason"/&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;constructor-arg type="int" value="100"/&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&lt;/bean&gt;</font></pre><font size="2"><br><br>使用索引可以稍稍减少一些冗长，但是和使用类型属性相比，它还是有容易发生错误的倾向和难于阅读的缺点。你应该只在构造器参数不明确的时候，才使用索引这一方法。<br><br><b>5. 尽可能重用已定义过的bean</b><br><br>Spring
提供一种类似继承一样的机制来减少配置信息的复制并简化XML配置。定义一个子类可以从它父类那里继承配置信息，而父类实质上作为子类的一个模板。这就是
大工程中所谓的重用。你所需要做的就是在父类bean中设置abstract=true，然后在子bean注明它自己的父类bean。例如：<br></font><pre class="overflow"><font size="2">&nbsp;&nbsp;&nbsp;&nbsp;&lt;bean id="abstractService" abstract="true"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;class="com.lizjason.spring.AbstractService"&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;property name="companyName"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;value="lizjason"/&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&lt;/bean&gt;<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&lt;bean id="shippingService"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;parent="abstractService"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;class="com.lizjason.spring.ShippingService"&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;property name="shippedBy" value="lizjason"/&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&lt;/bean&gt;</font></pre><font size="2"><br><br><br>ShippingService类从abstractService类那里继承companyName属性的值——lizjason。如果你没有为一个bean指明类或factory方法，那么这个bean便是抽象的。<br><br><b>6. 尽量使用ApplicationContext来装配定义的bean</b><br><br>像在Ant脚本中的引用一样，Spring的引用对于装配模块化的bean来说是很有用的。例如：<br></font><pre class="overflow"><font size="2">&nbsp;&nbsp;&nbsp;&nbsp;&lt;beans&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;import resource="billingServices.xml"/&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;import resource="shippingServices.xml"/&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;bean id="orderService"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;class="com.lizjason.spring.OrderService"/&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&lt;beans&gt;</font></pre><font size="2"><br><br>相
对于使用import在XML配置中来预装配，通过ApplicationContext来配置这些beans，显得更加灵活。利用
ApplicationContext也使得XML配置易于管理。你可以像下面的例子那样在ApplictionContext构造器里布置bean：<br></font><pre class="overflow"><font size="2">&nbsp;&nbsp;&nbsp;&nbsp;String[] serviceResources =<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{"orderServices.xml",<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"billingServices.xml",<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"shippingServices.xml"};<br>&nbsp;&nbsp;&nbsp;&nbsp;ApplicationContext orderServiceContext = new<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ClassPathXmlApplicationContext(serviceResources);</font></pre><font size="2"><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br><b>7. 利用id作为bean的标识符</b><br><br>你
可以指定一个id或名称来作为bean的标识符。虽然使用id不会提高易读性，但是它可以让XML
parser对bean的引用有效方面进行更好的验证。如果由于XML
IDREF的限制而不能使用某个id，你可以利用names来作为bean的标识符。XML
IDREF的限制是id必须以字母开头（或者在XML规范中定义的标点符号），后面接着字母，数字，连字号，下划线，冒号等。实际上，遇到XML
IDREF限制的问题是很少见的。<br><br><b>8. 在开发阶段使用依赖检验</b><br><br>你可以在bean中给依赖检验的属性设置值，而不采用原先默认的空值，属性设置例如simple，object或all，以便容器进行依赖检验。当bean的全部的属性（或某类属性）需要被明确设置或自动绑定时，依赖检验便显得很有用。<br></font><pre class="overflow"><font size="2">&nbsp;&nbsp;&nbsp;&nbsp;&lt;bean id="orderService"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;class="com.lizjason.spring.OrderService"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;dependency-check="objects"&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;property name="companyName"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;value="lizjason"/&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;constructor-arg ref="orderDAO"/&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&lt;/bean&gt;</font></pre><font size="2"><br><br>在这个例子里，容器确保为orderService bean设置的属性不是primitives 或者 collections。为所有的bean设置默认依赖检测也是可以的，但是我们很少这样做，是因为有些bean的属性根本就不必设置。<br><br><b>9. 为每个配置文件加上一个header comment</b><br><br>最好使用descriptive id和名称来代替在XML配置文件中的注释。此外，加上一个配置文件header也很有用处，它可以概述文件中所定义的bean。你可以选择将描述内容加入description标签中。例如：<br></font><pre class="overflow"><font size="2">&nbsp;&nbsp;&nbsp;&nbsp;&lt;beans&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;description&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;This file defines billing service<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;related beans and it depends on<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;baseServices.xml,which provides<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;service bean templates...<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;/description&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;...<br>&nbsp;&nbsp;&nbsp;&nbsp;&lt;/beans&gt;</font></pre><font size="2"><br>使用description标签的一个好处是可以容易地利用工具从标签中选取出description（的内容）。<br><br><b>10. 对于任何变化，要与队友积极交流</b><br>当你重构Java代码时，你需要随时更新配置文件并且通知队友。XML配置文件也是代码，它们是应用程序的至关重要的部分，但是它们难于阅读和维护。大部分时间你既要阅读XML配置文件又要阅读运行中的Java代码。<br><br><b>11. Setter injection优于constructor injection</b><br><br>Spring提供3种类型的依赖注入： constructor injection,setter injection, 和method injection。我们一般只用前两种类型。<br></font><pre class="overflow"><font size="2">&nbsp;&nbsp;&nbsp;&nbsp;&lt;bean id="orderService"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;class="com.lizjason.spring.OrderService"&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;constructor-arg ref="orderDAO"/&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&lt;/bean&gt;<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&lt;bean id="billingService"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;class="com.lizjason.spring.BillingService"&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;property name="billingDAO"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ref="billingDAO"&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&lt;/bean&gt;</font></pre><font size="2"><br><br>这
个例子中，orderService类使用的是constructor injection，而BillingService类使用的是setter
injection。constructor injection可以确保bean不会在一个非法状态下被创建，但是setter
injection更加灵活并且更易管理，尤其当类存在很多属性并且其中一些是可选的情况下。<br><br><b>12. 不要滥用依赖注入</b><br><br>作
为最后一点，Spring
ApplicationContext可以替你创建Java对象，但是并不是所有的Java对象都通过依赖注入来创建的。例如，全局的对象不应该通过
ApplicationContext来创建。Spring是一个很棒的框架，但是，就易读性和易管理性而言，当定义大量bean的时候，基于XML的配
置问题就会突出。过度的依赖注入会使XML配置变得复杂而且臃肿。记住！使用强大的IDE时，例如Eclipse和IntelliJ，与XML文件相比，
Java代码更加易读，易维护，易管理。<br><br><b>总结</b><br>对于Spring的配置，XML是很优秀的方式。但当定义大量
bean时，基于XML配置会变得冗长，笨拙。Spring提供了丰富的配置选项。适当地利用其中的选项可以使XML配置清晰，但是，有些选项，例如
autowiring（自动绑定），往往会降低易读性和易维护性。文章中所列举的实例，可以帮助你创建出清晰易读的XML配置文件。<br><br><b>关于作者：</b><br>Jason Zhicheng Li是Object Computing, Inc. in St. Louis, MO（公司）一名资深的软件工程师。<br><br><b>资源：</b><br><a href="http://www.matrix.org.cn/topic.shtml?forumId=26" target="_new"><span style="color: Green;">Spring论坛</span></a>：<a href="http://www.matrix.org.cn/topic.shtml?forumId=26" target="_new">http://www.matrix.org.cn/topic.shtml?forumId=26</a></font><img src ="http://www.blogjava.net/cmd/aggbug/34018.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/cmd/" target="_blank">静夜思</a> 2006-03-07 11:37 <a href="http://www.blogjava.net/cmd/articles/34018.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>HttpClient入门</title><link>http://www.blogjava.net/cmd/articles/33062.html</link><dc:creator>静夜思</dc:creator><author>静夜思</author><pubDate>Wed, 01 Mar 2006 11:43:00 GMT</pubDate><guid>http://www.blogjava.net/cmd/articles/33062.html</guid><wfw:comment>http://www.blogjava.net/cmd/comments/33062.html</wfw:comment><comments>http://www.blogjava.net/cmd/articles/33062.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/cmd/comments/commentRss/33062.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/cmd/services/trackbacks/33062.html</trackback:ping><description><![CDATA[<blockquote><font size="2">HttpClient 是 Apache Jakarta Common
下的子项目，可以用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包，并且它支持 HTTP
协议最新的版本和建议。本文首先介绍 HTTPClient，然后根据作者实际工作经验给出了一些常见问题的解决方法。</font></blockquote>
<p><font size="2"><a name="IDADCG0"><span class="atitle"><font face="Arial">HttpClient简介</font></span></a></font></p>
<p><font size="2">HTTP 协议可能是现在 Internet 上使用得最多、最重要的协议了，越来越多的 Java 应用程序需要直接通过 HTTP
协议来访问网络资源。虽然在 JDK 的 java.net 包中已经提供了访问 HTTP 协议的基本功能，但是对于大部分应用程序来说，JDK
库本身提供的功能还不够丰富和灵活。HttpClient 是 Apache Jakarta Common
下的子项目，用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包，并且它支持 HTTP
协议最新的版本和建议。HttpClient 已经应用在很多的项目中，比如 Apache Jakarta 上很著名的另外两个开源项目
Cactus 和 HTMLUnit 都使用了 HttpClient，更多使用 HttpClient 的应用可以参见<a href="http://wiki.apache.org/jakarta-httpclient/HttpClientPowered"><font color="#5c81a7">http://wiki.apache.org/jakarta-httpclient/HttpClientPowered</font></a>。HttpClient 项目非常活跃，使用的人还是非常多的。目前 HttpClient 版本是在 2005.10.11 发布的 3.0 RC4 。</font></p><font size="2"><br></font>
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tbody>
<tr>
<td><font size="2"><img alt="" src="http://www.crackj2ee.com/Article/UploadFiles/200511/20051125151629941.gif" height="1" width="100%"></font></td></tr></tbody></table>
<table class="no-print" align="right" cellpadding="0" cellspacing="0">
<tbody>
<tr align="right">
<td>
<table border="0" cellpadding="0" cellspacing="0">
<tbody>
<tr>
<td valign="center"><font size="2"><br></font></td>
<td align="right" valign="top"><font size="2"><br></font></td></tr></tbody></table></td></tr></tbody></table><font size="2"><br><br></font>
<p><font size="2"><a name="IDAOCG0"><span class="atitle"><font face="Arial">HttpClient 功能介绍</font></span></a></font></p>
<p><font size="2">以下列出的是 HttpClient 提供的主要的功能，要知道更多详细的功能可以参见 HttpClient 的主页。</font></p>
<ul><li><font size="2">实现了所有 HTTP 的方法（GET,POST,PUT,HEAD 等） 
</font></li><li><font size="2">支持自动转向 
</font></li><li><font size="2">支持 HTTPS 协议 
</font></li><li><font size="2">支持代理服务器等</font></li></ul>
<p><font size="2">下面将逐一介绍怎样使用这些功能。首先，我们必须安装好 HttpClient。</font></p>
<ul><li><font size="2">HttpClient 可以在<a href="http://jakarta.apache.org/commons/httpclient/downloads.html"><font color="#5c81a7">http://jakarta.apache.org/commons/httpclient/downloads.html</font></a>下载 
</font></li><li><font size="2">HttpClient 用到了 Apache Jakarta common 下的子项目 logging，你可以从这个地址<a href="http://jakarta.apache.org/site/downloads/downloads_commons-logging.cgi"><font color="#5c81a7">http://jakarta.apache.org/site/downloads/downloads_commons-logging.cgi</font></a>下载到 common logging，从下载后的压缩包中取出 commons-logging.jar 加到 CLASSPATH 中 
</font></li><li><font size="2">HttpClient 用到了 Apache Jakarta common 下的子项目 codec，你可以从这个地址<a href="http://jakarta.apache.org/site/downloads/downloads_commons-codec"><font color="#5c81a7">http://jakarta.apache.org/site/downloads/downloads_commons-codec</font></a>.cgi 下载到最新的 common codec，从下载后的压缩包中取出 commons-codec-1.x.jar 加到 CLASSPATH 中</font></li></ul><font size="2"><br></font>
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tbody>
<tr>
<td><font size="2"><img alt="" src="http://www.crackj2ee.com/Article/UploadFiles/200511/20051125151629941.gif" height="1" width="100%"></font></td></tr></tbody></table>
<table class="no-print" align="right" cellpadding="0" cellspacing="0">
<tbody>
<tr align="right">
<td>
<table border="0" cellpadding="0" cellspacing="0">
<tbody>
<tr>
<td valign="center"><font size="2"><br></font></td>
<td align="right" valign="top"><font size="2"><br></font></td></tr></tbody></table></td></tr></tbody></table><font size="2"><br><br></font>
<p><font size="2"><a name="IDANDG0"><span class="atitle"><font face="Arial">HttpClient 基本功能的使用</font></span></a></font></p>
<p><font size="2"><a name="IDASDG0"><span class="smalltitle"><strong><font face="Arial">GET 方法</font></strong></span></a></font></p>
<p><font size="2">使用 HttpClient 需要以下 6 个步骤：</font></p>
<p><font size="2">1. 创建 HttpClient 的实例</font></p>
<p><font size="2">2. 创建某种连接方法的实例，在这里是 GetMethod。在 GetMethod 的构造函数中传入待连接的地址</font></p>
<p><font size="2">3. 调用第一步中创建好的实例的 execute 方法来执行第二步中创建好的 method 实例</font></p>
<p><font size="2">4. 读 response</font></p>
<p><font size="2">5. 释放连接。无论执行方法是否成功，都必须释放连接</font></p>
<p><font size="2">6. 对得到后的内容进行处理</font></p>
<p><font size="2">根据以上步骤，我们来编写用GET方法来取得某网页内容的代码。</font></p>
<ul><li><font size="2">大部分情况下 HttpClient 默认的构造函数已经足够使用。 
</font><table bgcolor="#eeeeee" border="1" cellpadding="5" cellspacing="0" width="100%">
<tbody>
<tr>
<td><pre><font size="2"><code class="section"><br><font face="Lucida Console">HttpClient httpClient = new HttpClient();<br></font></code></font></pre></td></tr></tbody></table><font size="2"><br></font>
</li><li><font size="2">创建GET方法的实例。在GET方法的构造函数中传入待连接的地址即可。用GetMethod将会自动处理转发过程，如果想要把自动处理转发过程去掉的话，可以调用方法setFollowRedirects(false)。 
</font><table bgcolor="#eeeeee" border="1" cellpadding="5" cellspacing="0" width="100%">
<tbody>
<tr>
<td><pre><font size="2"><code class="section"><br><font face="Lucida Console">GetMethod getMethod = new GetMethod("http://www.ibm.com/");<br></font></code></font></pre></td></tr></tbody></table><font size="2"><br></font>
</li><li><font size="2">调用实例httpClient的executeMethod方法来执行getMethod。由于是执行在网络上的程序，在运行
executeMethod方法的时候，需要处理两个异常，分别是HttpException和IOException。引起第一种异常的原因主要可能是
在构造getMethod的时候传入的协议不对，比如不小心将"http"写成"htp"，或者服务器端返回的内容不正常等，并且该异常发生是不可恢复
的；第二种异常一般是由于网络原因引起的异常，对于这种异常
（IOException），HttpClient会根据你指定的恢复策略自动试着重新执行executeMethod方法。HttpClient的恢复
策略可以自定义（通过实现接口HttpMethodRetryHandler来实现）。通过httpClient的方法setParameter设置你实
现的恢复策略，本文中使用的是系统提供的默认恢复策略，该策略在碰到第二类异常的时候将自动重试3次。executeMethod返回值是一个整数，表示
了执行该方法后服务器返回的状态码，该状态码能表示出该方法执行是否成功、需要认证或者页面发生了跳转（默认状态下GetMethod的实例是自动处理跳
转的）等。 </font><table bgcolor="#eeeeee" border="1" cellpadding="5" cellspacing="0" width="100%">
<tbody>
<tr>
<td><pre><font size="2"><code class="section"><br><font face="Lucida Console">//设置成了默认的恢复策略，在发生异常时候将自动重试3次，在这里你也可以设置成自定义的恢复策略<br>getMethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, <br>    		new DefaultHttpMethodRetryHandler()); <br>//执行getMethod<br>int statusCode = client.executeMethod(getMethod);<br>if (statusCode != HttpStatus.SC_OK) {<br>  System.err.println("Method failed: " + getMethod.getStatusLine());<br>}<br></font></code></font></pre></td></tr></tbody></table><font size="2"><br></font>
</li><li><font size="2">在返回的状态码正确后，即可取得内容。取得目标地址的内容有三种方法：第一种，getResponseBody，该方法返回的是目标的
二进制的byte流；第二种，getResponseBodyAsString，这个方法返回的是String类型，值得注意的是该方法返回的
String的编码是根据系统默认的编码方式，所以返回的String值可能编码类型有误，在本文的"字符编码"部分中将对此做详细介绍；第三种，
getResponseBodyAsStream，这个方法对于目标地址中有大量数据需要传输是最佳的。在这里我们使用了最简单的
getResponseBody方法。 </font><table bgcolor="#eeeeee" border="1" cellpadding="5" cellspacing="0" width="100%">
<tbody>
<tr>
<td><pre><font size="2"><code class="section"><br><font face="Lucida Console">byte[] responseBody = method.getResponseBody();<br></font></code></font></pre></td></tr></tbody></table><font size="2"><br></font>
</li><li><font size="2">释放连接。无论执行方法是否成功，都必须释放连接。 
</font><table bgcolor="#eeeeee" border="1" cellpadding="5" cellspacing="0" width="100%">
<tbody>
<tr>
<td><pre><font size="2"><code class="section"><br><font face="Lucida Console">method.releaseConnection();<br></font></code></font></pre></td></tr></tbody></table><font size="2"><br></font>
</li><li><font size="2">处理内容。在这一步中根据你的需要处理内容，在例子中只是简单的将内容打印到控制台。 
</font><table bgcolor="#eeeeee" border="1" cellpadding="5" cellspacing="0" width="100%">
<tbody>
<tr>
<td><pre><font size="2"><code class="section"><br><font face="Lucida Console">System.out.println(new String(responseBody));<br></font></code></font></pre></td></tr></tbody></table><font size="2"><br></font></li></ul>
<p><font size="2">下面是程序的完整代码，这些代码也可在附件中的test.GetSample中找到。</font></p><font size="2"><br><a name="IDA5EG0"></a><br></font>
<table bgcolor="#eeeeee" border="1" cellpadding="5" cellspacing="0" width="100%">
<tbody>
<tr>
<td><pre><font size="2"><code class="section"><br><font face="Lucida Console">package test;<br>import java.io.IOException;<br>import org.apache.commons.httpclient.*;<br>import org.apache.commons.httpclient.methods.GetMethod;<br>import org.apache.commons.httpclient.params.HttpMethodParams;<br><br>public class GetSample{<br>  public static void main(String[] args) {<br>  //构造HttpClient的实例<br>  HttpClient httpClient = new HttpClient();<br>  //创建GET方法的实例<br>  GetMethod getMethod = new GetMethod("http://www.ibm.com");<br>  //使用系统提供的默认的恢复策略<br>  getMethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,<br>    new DefaultHttpMethodRetryHandler());<br>  try {<br>   //执行getMethod<br>   int statusCode = httpClient.executeMethod(getMethod);<br>   if (statusCode != HttpStatus.SC_OK) {<br>    System.err.println("Method failed: "<br>      + getMethod.getStatusLine());<br>   }<br>   //读取内容 <br>   byte[] responseBody = getMethod.getResponseBody();<br>   //处理内容<br>   System.out.println(new String(responseBody));<br>  } catch (HttpException e) {<br>   //发生致命的异常，可能是协议不对或者返回的内容有问题<br>   System.out.println("Please check your provided http address!");<br>   e.printStackTrace();<br>  } catch (IOException e) {<br>   //发生网络异常<br>   e.printStackTrace();<br>  } finally {<br>   //释放连接<br>   getMethod.releaseConnection();<br>  }<br> }<br>}<br></font></code></font></pre></td></tr></tbody></table><font size="2"><br></font>
<p><font size="2"><a name="IDAGFG0"><span class="smalltitle"><strong><font face="Arial">POST方法</font></strong></span></a></font></p>
<p><font size="2">根据RFC2616，对POST的解释如下：POST方法用来向目的服务器发出请求，要求它接受被附在请求后的实体，并把它当作请求队列（Request-Line）中请求URI所指定资源的附加新子项。POST被设计成用统一的方法实现下列功能：</font></p>
<ul><li><font size="2">对现有资源的注释（Annotation of existing resources） 
</font></li><li><font size="2">向电子公告栏、新闻组，邮件列表或类似讨论组发送消息 
</font></li><li><font size="2">提交数据块，如将表单的结果提交给数据处理过程 
</font></li><li><font size="2">通过附加操作来扩展数据库</font></li></ul>
<p><font size="2">调用HttpClient中的PostMethod与GetMethod类似，除了设置PostMethod的实例与GetMethod有些不同之
外，剩下的步骤都差不多。在下面的例子中，省去了与GetMethod相同的步骤，只说明与上面不同的地方，并以登录清华大学BBS为例子进行说明。</font></p>
<ul><li><font size="2">构造PostMethod之前的步骤都相同，与GetMethod一样，构造PostMethod也需要一个URI参数，在本例中，登录的地址是
http://www.newsmth.net/bbslogin2.php。在创建了PostMethod的实例之后，需要给method实例填充表单
的值，在BBS的登录表单中需要有两个域，第一个是用户名（域名叫id），第二个是密码（域名叫passwd）。表单中的域用类
NameValuePair来表示，该类的构造函数第一个参数是域名，第二参数是该域的值；将表单所有的值设置到PostMethod中用方法
setRequestBody。另外由于BBS登录成功后会转向另外一个页面，但是HttpClient对于要求接受后继服务的请求，比如POST和
PUT，不支持自动转发，因此需要自己对页面转向做处理。具体的页面转向处理请参见下面的"自动转向"部分。代码如下： </font><table bgcolor="#eeeeee" border="1" cellpadding="5" cellspacing="0" width="100%">
<tbody>
<tr>
<td><pre><font size="2"><code class="section"><br><font face="Lucida Console">String url = "http://www.newsmth.net/bbslogin2.php";<br>PostMethod postMethod = new PostMethod(url);<br>// 填入各个表单域的值<br>NameValuePair[] data = { new NameValuePair("id", "youUserName"),				<br>new NameValuePair("passwd", "yourPwd") };<br>// 将表单的值放入postMethod中<br>postMethod.setRequestBody(data);<br>// 执行postMethod<br>int statusCode = httpClient.executeMethod(postMethod);<br>// HttpClient对于要求接受后继服务的请求，象POST和PUT等不能自动处理转发<br>// 301或者302<br>if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY || <br>statusCode == HttpStatus.SC_MOVED_TEMPORARILY) {<br>    // 从头中取出转向的地址<br>    Header locationHeader = postMethod.getResponseHeader("location");<br>    String location = null;<br>    if (locationHeader != null) {<br>     location = locationHeader.getValue();<br>     System.out.println("The page was redirected to:" + location);<br>    } else {<br>     System.err.println("Location field value is null.");<br>    }<br>    return;<br>}<br></font></code></font></pre></td></tr></tbody></table><font size="2"><br></font></li></ul>
<p><font size="2">完整的程序代码请参见附件中的test.PostSample</font></p><font size="2"><br></font>
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tbody>
<tr>
<td><font size="2"><img alt="" src="http://www.crackj2ee.com/Article/UploadFiles/200511/20051125151629941.gif" height="1" width="100%"></font></td></tr></tbody></table>
<table class="no-print" align="right" cellpadding="0" cellspacing="0">
<tbody>
<tr align="right">
<td>
<table border="0" cellpadding="0" cellspacing="0">
<tbody>
<tr>
<td valign="center"><font size="2"><br></font></td>
<td align="right" valign="top"><font size="2"><br></font></td></tr></tbody></table></td></tr></tbody></table><font size="2"><br><br></font>
<p><font size="2"><a name="IDAZFG0"><span class="atitle"><font face="Arial">使用HttpClient过程中常见的一些问题</font></span></a></font></p>
<p><font size="2">下面介绍在使用HttpClient过程中常见的一些问题。</font></p>
<p><font size="2"><a name="IDA5FG0"><span class="smalltitle"><strong><font face="Arial">字符编码</font></strong></span></a></font></p>
<p><font size="2">某目标页的编码可能出现在两个地方，第一个地方是服务器返回的http头中，另外一个地方是得到的html/xml页面中。</font></p>
<ul><li><font size="2">在http头的Content-Type字段可能会包含字符编码信息。例如可能返回的头会包含这样子的信息：Content-Type:
text/html;
charset=UTF-8。这个头信息表明该页的编码是UTF-8，但是服务器返回的头信息未必与内容能匹配上。比如对于一些双字节语言国家，可能服务
器返回的编码类型是UTF-8，但真正的内容却不是UTF-8编码的，因此需要在另外的地方去得到页面的编码信息；但是如果服务器返回的编码不是UTF-
8，而是具体的一些编码，比如gb2312等，那服务器返回的可能是正确的编码信息。通过method对象的getResponseCharSet()方
法就可以得到http头中的编码信息。 </font></li><li><font size="2">对于象xml或者html这样的文件，允许作者在页面中直接指定编码类型。比如在html中会有&lt;meta
http-equiv="Content-Type" content="text/html;
charset=gb2312"/&gt;这样的标签；或者在xml中会有&lt;?xml version="1.0"
encoding="gb2312"?&gt;这样的标签，在这些情况下，可能与http头中返回的编码信息冲突，需要用户自己判断到底那种编码类型应该
是真正的编码。</font></li></ul>
<p><font size="2"><a name="IDAIGG0"><span class="smalltitle"><strong><font face="Arial">自动转向</font></strong></span></a></font></p>
<p><font size="2">根据RFC2616中对自动转向的定义，主要有两种：301和302。301表示永久的移走（Moved
Permanently），当返回的是301，则表示请求的资源已经被移到一个固定的新地方，任何向该地址发起请求都会被转到新的地址上。302表示暂时
的转向，比如在服务器端的servlet程序调用了sendRedirect方法，则在客户端就会得到一个302的代码，这时服务器返回的头信息中
location的值就是sendRedirect转向的目标地址。</font></p>
<p><font size="2">HttpClient支持自动转向处理，但是象POST和PUT方式这种要求接受后继服务的请求方式，暂时不支持自动转向，因此如果碰到POST方
式提交后返回的是301或者302的话需要自己处理。就像刚才在POSTMethod中举的例子：如果想进入登录BBS后的页面，必须重新发起登录的请
求，请求的地址可以在头字段location中得到。不过需要注意的是，有时候location返回的可能是相对路径，因此需要对location返回的
值做一些处理才可以发起向新地址的请求。</font></p>
<p><font size="2">另外除了在头中包含的信息可能使页面发生重定向外，在页面中也有可能会发生页面的重定向。引起页面自动转发的标签是：&lt;meta
http-equiv="refresh" content="5;
url=http://www.ibm.com/us"&gt;。如果你想在程序中也处理这种情况的话得自己分析页面来实现转向。需要注意的是，在上面那
个标签中url的值也可以是一个相对地址，如果是这样的话，需要对它做一些处理后才可以转发。</font></p><p><b><font color="#000000" face="Courier New" size="2">处理页面重定向</font></b>
</p><p><font face="Courier New" size="2">在JSP/Servlet编程中
response.sendRedirect方法就是使用HTTP协议中的重定向机制。它与JSP中的&lt;jsp:forward
…&gt;的区别在于后者是在服务器中实现页面的跳转，也就是说应用容器加载了所要跳转的页面的内容并返回给客户端；而前者是返回一个状态码，这些状态码
的可能值见下表，然后客户端读取需要跳转到的页面的URL并重新加载新的页面。就是这样一个过程，所以我们编程的时候就要通过
HttpMethod.getStatusCode()方法判断返回值是否为下表中的某个值来判断是否需要跳转。如果已经确认需要进行页面跳转了，那么可
以通过读取HTTP头中的location属性来获取新的地址。</font></p>
<p><font face="Courier New" size="2">状态码<br>&nbsp;对应HttpServletResponse的常量<br>&nbsp;详细描述<br>&nbsp;<br>301<br>&nbsp;SC_MOVED_PERMANENTLY<br>&nbsp;页面已经永久移到另外一个新地址<br>&nbsp;<br>302<br>&nbsp;SC_MOVED_TEMPORARILY<br>&nbsp;页面暂时移动到另外一个新的地址<br>&nbsp;<br>303<br>&nbsp;SC_SEE_OTHER<br>&nbsp;客户端请求的地址必须通过另外的URL来访问<br>&nbsp;<br>307<br>&nbsp;SC_TEMPORARY_REDIRECT<br>&nbsp;同SC_MOVED_TEMPORARILY<br>&nbsp;</font></p><font face="Courier New" size="2">
</font><p><font face="Courier New" size="2"><br>下面的代码片段演示如何处理页面的重定向</font></p>
<p><font face="Courier New" size="2">client.executeMethod(post);</font></p>
<p><font face="Courier New" size="2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println(post.getStatusLine().toString()); </font></p>
<p><font face="Courier New" size="2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; post.releaseConnection();</font></p>
<p><font face="Courier New" size="2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </font></p>
<p><font face="Courier New" size="2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //检查是否重定向</font></p>
<p><font face="Courier New" size="2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int statuscode = post.getStatusCode();</font></p>
<p><font face="Courier New" size="2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if ((statuscode == HttpStatus.SC_MOVED_TEMPORARILY) ||</font></p>
<p><font face="Courier New" size="2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (statuscode == HttpStatus.SC_MOVED_PERMANENTLY) ||</font></p>
<p><font face="Courier New" size="2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (statuscode == HttpStatus.SC_SEE_OTHER) ||</font></p>
<p><font face="Courier New" size="2">(statuscode == HttpStatus.SC_TEMPORARY_REDIRECT)) {</font></p>
<p><font face="Courier New" size="2">//读取新的URL地址</font></p>
<p><font face="Courier New" size="2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Header header = post.getResponseHeader("location");</font></p>
<p><font face="Courier New" size="2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (header != null) {</font></p>
<p><font face="Courier New" size="2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; String newuri = header.getValue();</font></p>
<p><font face="Courier New" size="2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if ((newuri == null) || (newuri.equals("")))</font></p>
<p><font face="Courier New" size="2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; newuri = "/"; </font></p>
<p><font face="Courier New" size="2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; GetMethod redirect = new GetMethod(newuri);</font></p>
<p><font face="Courier New" size="2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; client.executeMethod(redirect);</font></p>
<p><font face="Courier New" size="2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("Redirect:"+ redirect.getStatusLine().toString()); </font></p>
<p><font face="Courier New" size="2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; redirect.releaseConnection();</font></p>
<p><font face="Courier New" size="2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } else </font></p>
<p><font face="Courier New" size="2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("Invalid redirect");</font></p>
<p><font face="Courier New" size="2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New" size="2">我们可以自行编写两个JSP页面，其中一个页面用response.sendRedirect方法重定向到另外一个页面用来测试上面的例子。</font></p><p><font size="2"><br></font></p>
<p><font size="2"><a name="IDAQGG0"><span class="smalltitle"><strong><font face="Arial">处理HTTPS协议</font></strong></span></a></font></p>
<p><font size="2">HttpClient提供了对SSL的支持，在使用SSL之前必须安装JSSE。在Sun提供的1.4以后的版本中，JSSE已经集成到JDK中，
如果你使用的是JDK1.4以前的版本则必须安装JSSE。JSSE不同的厂家有不同的实现。下面介绍怎么使用HttpClient来打开Https连
接。这里有两种方法可以打开https连接，第一种就是得到服务器颁发的证书，然后导入到本地的keystore中；另外一种办法就是通过扩展
HttpClient的类来实现自动接受证书。</font></p>
<p><font size="2">方法1，取得证书，并导入本地的keystore：</font></p>
<ul><li><font size="2">安装JSSE
（如果你使用的JDK版本是1.4或者1.4以上就可以跳过这一步）。本文以IBM的JSSE为例子说明。先到IBM网站上下载JSSE的安装包。然后解
压开之后将ibmjsse.jar包拷贝到&lt;java-home&gt;\lib\ext\目录下。 </font></li><li><font size="2">取得并且导入证书。证书可以通过IE来获得： 
</font><p><font size="2">1． 用IE打开需要连接的https网址，会弹出如下对话框：</font></p><font size="2"><br><a name="IDA3GG0"></a><br><img alt="" src="http://www-128.ibm.com/developerworks/cn/opensource/os-httpclient/images/image001.png" border="0" height="301" width="384"><br></font>
<p><font size="2">2． 单击"View Certificate"，在弹出的对话框中选择"Details"，然后再单击"Copy to File"，根据提供的向导生成待访问网页的证书文件</font></p><font size="2"><br><a name="IDALHG0"></a><br><img alt="" src="http://www-128.ibm.com/developerworks/cn/opensource/os-httpclient/images/image003.png" border="0" height="476" width="409"><br></font>
<p><font size="2">3． 向导第一步，欢迎界面，直接单击"Next"，</font></p><font size="2"><br><a name="IDAZHG0"></a><br><img alt="" src="http://www-128.ibm.com/developerworks/cn/opensource/os-httpclient/images/image005.png" border="0" height="386" width="503"><br></font>
<p><font size="2">4． 向导第二步，选择导出的文件格式，默认，单击"Next"，</font></p><font size="2"><br><a name="IDAKAO0"></a><br><img alt="" src="http://www-128.ibm.com/developerworks/cn/opensource/os-httpclient/images/image007.png" border="0" height="386" width="503"><br></font>
<p><font size="2">5． 向导第三步，输入导出的文件名，输入后，单击"Next"，</font></p><font size="2"><br><a name="IDAYAO0"></a><br><img alt="" src="http://www-128.ibm.com/developerworks/cn/opensource/os-httpclient/images/image009.png" border="0" height="454" width="506"><br></font>
<p><font size="2">6． 向导第四步，单击"Finish"，完成向导</font></p><font size="2"><br><a name="IDAGBO0"></a><br><img alt="" src="http://www-128.ibm.com/developerworks/cn/opensource/os-httpclient/images/image011.png" border="0" height="386" width="503"><br></font>
<p><font size="2">7． 最后弹出一个对话框，显示导出成功</font></p><font size="2"><br><a name="IDAUBO0"></a><br><img alt="" src="http://www-128.ibm.com/developerworks/cn/opensource/os-httpclient/images/image013.png" border="0" height="100" width="183"><br></font>
</li><li>
<p><font size="2">用keytool工具把刚才导出的证书倒入本地keystore。Keytool命令在&lt;java-home&gt;\bin\下，打开命令行窗口，并到&lt;java-home&gt;\lib\security\目录下，运行下面的命令：</font></p>
<table bgcolor="#eeeeee" border="1" cellpadding="5" cellspacing="0" width="100%">
<tbody>
<tr>
<td><pre><font size="2"><code class="section"><br><font face="Lucida Console">keytool -import -noprompt -keystore cacerts -storepass changeit -alias yourEntry1 -file your.cer<br></font></code></font></pre></td></tr></tbody></table><font size="2"><br></font>
<p><font size="2">其中参数alias后跟的值是当前证书在keystore中的唯一标识符，但是大小写不区分；参数file后跟的是刚才通过IE导出的证书所在的路径和文件名；如果你想删除刚才导入到keystore的证书，可以用命令：</font></p>
<table bgcolor="#eeeeee" border="1" cellpadding="5" cellspacing="0" width="100%">
<tbody>
<tr>
<td><pre><font size="2"><code class="section"><br><font face="Lucida Console">keytool -delete -keystore cacerts -storepass changeit -alias yourEntry1<br></font></code></font></pre></td></tr></tbody></table><font size="2"><br></font>
</li><li><font size="2">写程序访问https地址。如果想测试是否能连上https，只需要稍改一下GetSample例子，把请求的目标变成一个https地址。 
</font><table bgcolor="#eeeeee" border="1" cellpadding="5" cellspacing="0" width="100%">
<tbody>
<tr>
<td><pre><font size="2"><code class="section"><br><font face="Lucida Console">GetMethod getMethod = new GetMethod("https://www.yourdomain.com");<br></font></code></font></pre></td></tr></tbody></table><font size="2"><br></font>
<p><font size="2">运行该程序可能出现的问题：</font></p>
<p><font size="2">1. 抛出异常java.net.SocketException: Algorithm SSL not available。出现这个异常可能是因为没有加JSSEProvider，如果用的是IBM的JSSE Provider，在程序中加入这样的一行：</font></p>
<table bgcolor="#eeeeee" border="1" cellpadding="5" cellspacing="0" width="100%">
<tbody>
<tr>
<td><pre><font size="2"><code class="section"><br><font face="Lucida Console"> if(Security.getProvider("com.ibm.jsse.IBMJSSEProvider") == null)<br> Security.addProvider(new IBMJSSEProvider());<br> </font></code></font></pre></td></tr></tbody></table><font size="2"><br></font>
<p><font size="2">或者也可以打开&lt;java-home&gt;\lib\security\java.security，在行</font></p>
<table bgcolor="#eeeeee" border="1" cellpadding="5" cellspacing="0" width="100%">
<tbody>
<tr>
<td><pre><font size="2"><code class="section"><br><font face="Lucida Console">security.provider.1=sun.security.provider.Sun<br>security.provider.2=com.ibm.crypto.provider.IBMJCE<br></font></code></font></pre></td></tr></tbody></table><font size="2"><br></font>
<p><font size="2">后面加入security.provider.3=com.ibm.jsse.IBMJSSEProvider</font></p>
<p><font size="2">2. 抛出异常java.net.SocketException: SSL implementation not available。出现这个异常可能是你没有把ibmjsse.jar拷贝到&lt;java-home&gt;\lib\ext\目录下。</font></p>
<p><font size="2">3. 抛出异常javax.net.ssl.SSLHandshakeException: unknown
certificate。出现这个异常表明你的JSSE应该已经安装正确，但是可能因为你没有把证书导入到当前运行JRE的keystore中，请按照前
面介绍的步骤来导入你的证书。</font></p></li></ul>
<p><font size="2">方法２，扩展HttpClient类实现自动接受证书</font></p>
<p><font size="2">因为这种方法自动接收所有证书，因此存在一定的安全问题，所以在使用这种方法前请仔细考虑您的系统的安全需求。具体的步骤如下：</font></p>
<ul><li><font size="2">提供一个自定义的socket
factory（test.MySecureProtocolSocketFactory）。这个自定义的类必须实现接口
org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory，在实现接口
的类中调用自定义的X509TrustManager(test.MyX509TrustManager)，这两个类可以在随本文带的附件中得到 </font></li><li><font size="2">创建一个org.apache.commons.httpclient.protocol.Protocol的实例，指定协议名称和默认的端口号 
</font><table bgcolor="#eeeeee" border="1" cellpadding="5" cellspacing="0" width="100%">
<tbody>
<tr>
<td><pre><font size="2"><code class="section"><br><font face="Lucida Console">Protocol myhttps = new Protocol("https", new MySecureProtocolSocketFactory (), 443);<br></font></code></font></pre></td></tr></tbody></table><font size="2"><br></font>
</li><li><font size="2">注册刚才创建的https协议对象 
</font><table bgcolor="#eeeeee" border="1" cellpadding="5" cellspacing="0" width="100%">
<tbody>
<tr>
<td><pre><font size="2"><code class="section"><br><font face="Lucida Console">Protocol.registerProtocol("https ", myhttps);<br></font></code></font></pre></td></tr></tbody></table><font size="2"><br></font>
</li><li><font size="2">然后按照普通编程方式打开https的目标地址，代码请参见test.NoCertificationHttpsGetSample</font></li></ul>
<p><font size="2"><a name="IDAJDO0"><span class="smalltitle"><strong><font face="Arial">处理代理服务器</font></strong></span></a></font></p>
<p><font size="2">HttpClient中使用代理服务器非常简单，调用HttpClient中setProxy方法就可以，方法的第一个参数是代理服务器地址，第二个参数是端口号。另外HttpClient也支持SOCKS代理。</font></p><font size="2"><br><a name="IDAPDO0"></a><br></font>
<table bgcolor="#eeeeee" border="1" cellpadding="5" cellspacing="0" width="100%">
<tbody>
<tr>
<td><pre><font size="2"><code class="section"><br><font face="Lucida Console">httpClient.getHostConfiguration().setProxy(hostName,port);<br></font></code></font></pre></td></tr></tbody></table><font size="2"><br><br></font>
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tbody>
<tr>
<td><font face="Lucida Console" size="2"><img alt="" src="http://www.crackj2ee.com/Article/UploadFiles/200511/20051125151629941.gif" height="1" width="100%"></font></td></tr></tbody></table>
<table class="no-print" align="right" cellpadding="0" cellspacing="0">
<tbody>
<tr align="right">
<td>
<table border="0" cellpadding="0" cellspacing="0">
<tbody>
<tr>
<td valign="center"><font face="Lucida Console" size="2"><br></font></td>
<td align="right" valign="top"><font size="2"><br></font></td></tr></tbody></table></td></tr></tbody></table><font size="2"><br><br></font>
<p><font size="2"><a name="IDAWDO0"><span class="atitle"><font face="Arial">结论</font></span></a></font></p>
<p><font size="2">从上面的介绍中，可以知道HttpClient对http协议支持非常好，使用起来很简单，版本更新快，功能也很强大，具有足够的灵活性和扩展性。对于想在Java应用中直接访问http资源的编程人员来说，HttpClient是一个不可多得的好工具。</font></p><font size="2"><br></font>
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tbody>
<tr>
<td><font size="2"><img alt="" src="http://www.crackj2ee.com/Article/UploadFiles/200511/20051125151629941.gif" height="1" width="100%"></font></td></tr></tbody></table>
<table class="no-print" align="right" cellpadding="0" cellspacing="0">
<tbody>
<tr align="right">
<td>
<table border="0" cellpadding="0" cellspacing="0">
<tbody>
<tr>
<td valign="center"><font size="2"><br></font></td>
<td align="right" valign="top"><font size="2"><br></font></td></tr></tbody></table></td></tr></tbody></table><font size="2"><br><br></font>
<p><font size="2"><a name="resources"><span class="atitle"><font face="Arial">参考资料 </font></span></a></font></p>
<ul><li><font size="2">本文样码下载：<a href="http://www-128.ibm.com/developerworks/cn/opensource/os-httpclient/src.zip"><font color="#5c81a7">src.zip</font></a></font> 
</li><li><font size="2">Commons logging包含了各种各样的日志API的实现，读者可以通过站点http://jakarta.apache.org/commons/logging/得到详细的内容<br><br></font>
</li><li><font size="2">Commons codec包含了一些一般的解码/编码算法。包含了语音编码、十六进制、Base64和URL编码等，通过http://jakarta.apache.org/commons/codec/可以得到详细的内容<br><br></font>
</li><li><font size="2">rfc2616是关于HTTP/1.1的文档，可以在http://www.faqs.org/rfcs/rfc2616.html上
得到详细的内容，另外rfc1945是关于HTTP/1.0的文档，通过http://www.faqs.org/rfcs/rfc1945.html可
以得到详细内容<br><br></font>
</li><li><font size="2">SSL――SSL 是由 Netscape Communications Corporation 于 1994 年开发的，而
TLS V1.0 是由 Internet Engineering Task Force（IETF）定义的标准，它基于 SSL
V3.0，并且在使用的加密算法上与其有些许的不同。例如，SSL 使用 Message Authentication
Code（MAC）算法来生成完整性校验值，而 TLS 应用密钥的 Hashing for Message Authentication
Code（HMAC）算法。<br><br></font>
</li><li><font size="2">IBM JSSE提供了SSL（Secure Sockets Layer）和TLS（Transport Layer
Security）的java实现，在http://www-
03.ibm.com/servers/eserver/zseries/software/java/jsse.html中可以得到详细的信息<br><br></font>
</li><li><font size="2">Keytool是一个管理密钥和证书的工具。关于它详细的使用信息可以在http://www.doc.ic.ac.uk/csg/java/1.3.1docs/tooldocs/solaris/keytool.html上得到<br><br></font>
</li><li><font size="2">HTTPClient的主页是http://jakarta.apache.org/commons/httpclient/，你可以在这里得到关于HttpClient更加详细的信息<br></font></li></ul><font size="2"><br></font>
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tbody>
<tr>
<td><font size="2"><img alt="" src="http://www.crackj2ee.com/Article/UploadFiles/200511/20051125151629941.gif" height="1" width="100%"></font></td></tr></tbody></table>
<table class="no-print" align="right" cellpadding="0" cellspacing="0">
<tbody>
<tr align="right">
<td>
<table border="0" cellpadding="0" cellspacing="0">
<tbody>
<tr>
<td valign="center"><font size="2"><br></font></td>
<td align="right" valign="top"><font size="2"><br></font></td></tr></tbody></table></td></tr></tbody></table><font size="2"><br><br></font>
<p><font size="2"><a name="author"><span class="atitle"><font face="Arial">作者简介</font></span></a></font></p>
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tbody>
<tr>
<td colspan="2"><font face="Arial" size="2"><img alt="" src="http://www.crackj2ee.com/Article/UploadFiles/200511/20051125151632968.gif" height="5" width="100%"></font></td></tr>
<tr align="left" valign="top">
<td>
<font size="2"><br></font></td>
<td>
<p><font size="2">金发华是一名工作在 IBM CSDL 的软件工程师。他喜欢钻研各种新的技术，在 Java 网络开发和 Web 开发方面颇有经验。</font></p></td></tr></tbody></table><font size="2"><br></font>


<table border="0" cellpadding="0" cellspacing="0" width="100%"><tbody><tr>
<td colspan="2"><font size="2"><img alt="" src="http://www.crackj2ee.com/Article/UploadFiles/200511/20051125151632968.gif" height="5" width="100%"></font></td></tr>
<tr align="left" valign="top">
<td>
<font size="2"><br></font></td>
<td>
<p><font size="2">陈樟洪是一位 IBM CSDL 的软件工程师，目前从事企业电子商务应用的开发。</font></p></td></tr></tbody></table><img src ="http://www.blogjava.net/cmd/aggbug/33062.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/cmd/" target="_blank">静夜思</a> 2006-03-01 19:43 <a href="http://www.blogjava.net/cmd/articles/33062.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>在Spring中集成Hibernate事务</title><link>http://www.blogjava.net/cmd/articles/32251.html</link><dc:creator>静夜思</dc:creator><author>静夜思</author><pubDate>Fri, 24 Feb 2006 02:24:00 GMT</pubDate><guid>http://www.blogjava.net/cmd/articles/32251.html</guid><wfw:comment>http://www.blogjava.net/cmd/comments/32251.html</wfw:comment><comments>http://www.blogjava.net/cmd/articles/32251.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/cmd/comments/commentRss/32251.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/cmd/services/trackbacks/32251.html</trackback:ping><description><![CDATA[<P><A class=f1><FONT size=2>本文主要探讨怎么用Spring来装配组件及其事务管理。在J2EE工程里连接到一个简单的数据库并不是什么难题，但是如果要综合组装企业类的组件就变得复杂了。一个简单的组件有一个或多个数据库支撑，所以，我们说到整合两个或多个的组件时，我们希望能够维持跨组件的许多数据库的运作的原子性。</FONT></A></P>
<P><A class=f1><FONT size=2>　　J2EE提供了这些组件的容器，可以保证处理的原子性和独立性。在没有J2EE的情况下我们可以用Spring。 Spring基于IoC模式(即反转模式)，不仅可以配置组件服务，还可以配置相应的方法。为了更好的实现本文的目的，我们使用Hibernate来做相应的后台开发。</FONT></A></P>
<P><A class=f1><FONT size=2>　　<STRONG>装配组件事务</STRONG></FONT></A></P>
<P><A class=f1><FONT size=2>　　假设在组件库里，我们已经有一个审核组件(audit component)，里面有可以被客户端调用的方法。接着，当我们想要构建一个处理订单的体系，我们发现设计需要的OrderListManager组件服务同样需要审核组件服务。OrderListManager创建和管理订单，每一个服务都含有自己的事务属性。当这时调用审核组件，就可以把 OrderListManager的处理内容传给它。也许将来新的业务服务(business service)同样需要审核组件，那这时它调用的事务内容已经不一样了。在网络上的结果就是，虽然审核的功能保持不变，但是可以和别的事件功能组合在一起，用这些方法属性来提供不同的运行时的处理参数。</FONT></A></P>
<P><A class=f1><FONT size=2>　　在图1中有两个分开的调用流程。在流程1里，如果客户端含有一个TX内容，OrderListManager 要由一个新的TX开始或者参与其中，取决于客户端在不在TX里以及OrderListManager方法指定的TX属性。这在它调用 AuditManager方法的时候仍然适用。</FONT></A></P>
<P><A class=f1><FONT size=2><IMG src="http://searchwebservices.techtarget.com.cn/imagelist/05/09/3g3z21580340.jpg" border=0></FONT></A></P>
<P><A class=f1><FONT size=2>　　图1. 装配组件事务</FONT></A></P>
<P><A class=f1><FONT size=2>　　</FONT></A><A class=bluekey href="http://www.yesky.com/key/4201/169201.html" target=_blank><FONT size=2>EJB</FONT></A><FONT size=2>体系通过装配者声明正确的事务属性来获得这种适应性。我们不是在探讨是否声明事务管理，因为这会使运行时的事务参数代码发生改变。几乎所有的J2EE工程提供了分布的事务管理来配合提交协议例如X/Open XA specification。</FONT></P>
<P><FONT size=2>　　现在的问题是我们能不能不用EJB来获得相同的功能?Spring是其中一种解决方案。来看一下Spring如何处理这样的问题:</FONT></P>
<P><FONT size=2>　　<STRONG>用Spring来管理事务</STRONG></FONT></P>
<P><FONT size=2>　　我们将看到的是一个轻量级的事务机制，实际上，它可以管理组件层的事务集成。Spring就是如此。它的优点是我们可以不用捆绑在J2EE的服务例如JNDI数据库。最棒的是如果我们想把这个事务机制与已经存在的J2EE框架组合在一起，没有任何问题，就好像我们找到了杠杆中完美的支撑点一样。</FONT></P>
<P><FONT size=2>　　Spring的另一个机制是使用了AOP框架。这个框架使用了一个可以使用AOP的Spring bean factory。在Spring特定的配置文件applicationContext.xml里通过特定的组件层的事件来指定。</FONT></P>
<P><FONT style="BACKGROUND-COLOR: rgb(221,221,221)" face=Verdana size=2>&lt;beans&gt;</FONT></P>
<P><FONT style="BACKGROUND-COLOR: rgb(221,221,221)" face=Verdana size=2>&lt;!-- other code goes here... --&gt;</FONT></P>
<P><FONT style="BACKGROUND-COLOR: rgb(221,221,221)" face=Verdana size=2>&lt;bean id="orderListManager"<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; class="org.springframework.transaction<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .interceptor.TransactionProxyFactoryBean"&gt;<BR>&lt;property name="transactionManager"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;ref local="transactionManager1"/&gt;<BR>&lt;/property&gt;<BR>&lt;property name="target"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;ref local="orderListManagerTarget"/&gt;<BR>&lt;/property&gt;<BR>&lt;property name="transactionAttributes"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;props&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;prop key="getAllOrderList"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PROPAGATION_REQUIRED<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/prop&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;prop key="getOrderList"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PROPAGATION_REQUIRED<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/prop&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;prop key="createOrderList"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PROPAGATION_REQUIRED<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/prop&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;prop key="addLineItem"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PROPAGATION_REQUIRED,<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -com.example.exception.FacadeException<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/prop&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;prop key="getAllLineItems"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PROPAGATION_REQUIRED,readOnly<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/prop&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;prop key="queryNumberOfLineItems"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PROPAGATION_REQUIRED,readOnly<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/prop&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/props&gt;<BR>&lt;/property&gt;<BR>&lt;/bean&gt;</FONT></P>
<P><FONT style="BACKGROUND-COLOR: rgb(221,221,221)" face=Verdana size=2>&lt;/beans&gt;</FONT></P>
<P><FONT size=2>　　一旦我们在服务层指定了事务属性，它们就被一个继承org.springframework.transaction.PlatformTransactionManager 接口的类截获. 这个接口如下:</FONT></P>
<P><FONT style="BACKGROUND-COLOR: rgb(221,221,221)" face=Verdana size=2>public interface PlatformTransactionManager{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; TransactionStatus getTransaction<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (TransactionDefinition definition);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; void commit(TransactionStatus status);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; void rollback(TransactionStatus status);<BR>}</FONT></P>
<P><FONT size=2>　　<STRONG>Hibernate事务管理</STRONG></FONT></P>
<P><FONT size=2>　　一旦我们决定了使用Hibernate作为ORM工具,我们下一步要做的就是用Hibernate特定的事务管理实例来配置。</FONT></P>
<P><FONT style="BACKGROUND-COLOR: rgb(221,221,221)" face=Verdana size=2>&lt;beans&gt;</FONT></P>
<P><FONT style="BACKGROUND-COLOR: rgb(221,221,221)" face=Verdana size=2>&lt;!-- other code goes here... --&gt;</FONT></P>
<P><FONT style="BACKGROUND-COLOR: rgb(221,221,221)" face=Verdana size=2>&lt;bean id="transactionManager1"<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; class="org.springframework.orm.hibernate.<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; HibernateTransactionManager"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;property name="sessionFactory"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;ref local="sessionFactory1"/&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/property&gt;<BR>&lt;/bean&gt;</FONT></P>
<P><FONT style="BACKGROUND-COLOR: rgb(221,221,221)" face=Verdana size=2>&lt;/beans&gt;</FONT></P>
<P><FONT size=2>　　我们来看看什么是“装配组件事务”，你也许注意到了那个OrderListManager 特有的TX属性，那个服务层的组件。我们的工程的主要的东西在表2的BDOM里:</FONT></P>
<P><FONT size=2><IMG src="http://searchwebservices.techtarget.com.cn/imagelist/05/09/865z06r9r74u.jpg" border=0></FONT></P>
<P><FONT size=2>　　图 2. 业务领域对象模型 (BDOM)</FONT></P>
<P><FONT size=2>　　为了用实例说明，我们来列出工程里的非功能需求(NFR):</FONT></P>
<P><FONT face=Verdana size=2>　　---事务在数据库appfuse1里保存。<BR>　　---审核时要登入到另一个数据库appfuse2里,出于安全的考虑，数据库有防火墙保护。<BR>　　---事务组件可以重用。<BR>　　---所有访问事件必须经过在事务服务层的审核。</FONT></P>
<P><FONT size=2>　　出于以上的考虑，我们决定了OrderListManager 服务将委托任何审核记录来调用已有的AuditManager 组件.这产生了表3这样更细致的结构:</FONT></P>
<P><FONT size=2><IMG src="http://searchwebservices.techtarget.com.cn/imagelist/05/09/h5x0011eo11o.jpg" border=0></FONT></P>
<P><FONT size=2>　　图 3.<FONT color=#0000ff> <U>组件服务</U></FONT></FONT><A class=bluekey href="http://www.yesky.com/key/2834/172834.html" target=_blank><FONT color=#0000ff size=2>结构设计</FONT></A></P>
<P><FONT size=2>　　值得注意的是，由于我们的NFR，我们要映射OrderListManager相关的事物到appfuse1 数据库里去,而审核相关的到appfuse2。这样，任何审核的时候 OrderListManager 组件都会调用AuditManager 组件。我们认为OrderListManager 组件里的所有方法都要执行, 因为我们通过服务来创建次序和具体项目。那么AuditManager 组件里的服务呢? 因为它做的是审核的动作,我们关心的是为系统里所有的事务记录审核情况。这样的需求是,“即使事务事件失败了，我们也要记录登录的审核情况”。 AuditManager 组件同样要有自己的事件，因为它同样与自己的数据库有关联。如下所示:</FONT></P>
<P><FONT style="BACKGROUND-COLOR: rgb(221,221,221)" face=Verdana size=2>&lt;beans&gt;</FONT></P>
<P><FONT style="BACKGROUND-COLOR: rgb(221,221,221)" face=Verdana size=2>&lt;!—其他代码在这里--&gt;<BR>&lt;bean id="auditManager"<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; class="org.springframework.transaction.<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; interceptor.TransactionProxyFactoryBean"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;property name="transactionManager"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;ref local="transactionManager2"/&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/property&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;property name="target"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;ref local="auditManagerTarget"/&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/property&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;property name="transactionAttributes"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;props&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;prop key="log"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PROPAGATION_REQUIRES_NEW<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/prop&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/props&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/property&gt;<BR>&lt;/bean&gt;</FONT></P>
<P><FONT style="BACKGROUND-COLOR: rgb(221,221,221)" face=Verdana size=2>&lt;/beans&gt;</FONT></P>
<P><FONT size=2>　　我们现在把注意力放到这两个事务createOrderList 和 addLineItem中来，作为我们的试验。同时注意我们并没有要求最好的设计——你可能注意到了 addLineItem 方法抛出了 FacadeException, 而 createOrderList 没有。在</FONT><A class=bluekey href="http://www.yesky.com/key/1157/171157.html" target=_blank><FONT size=2>产品设计</FONT></A><FONT size=2>中，你也许希望每一个方法都处理异常。</FONT></P>
<P><FONT style="BACKGROUND-COLOR: rgb(221,221,221)" face=Verdana size=2>public class OrderListManagerImpl<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; implements OrderListManager{</FONT></P>
<P><FONT style="BACKGROUND-COLOR: rgb(221,221,221)" face=Verdana size=2>private AuditManager auditManager;</FONT></P>
<P><FONT style="BACKGROUND-COLOR: rgb(221,221,221)" face=Verdana size=2>public Long createOrderList<BR>&nbsp;(OrderList orderList){<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Long orderId = orderListDAO.<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; createOrderList(orderList);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; auditManager.log(new AuditObject<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (ORDER + orderId, CREATE));<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return orderId;<BR>}</FONT></P>
<P><FONT style="BACKGROUND-COLOR: rgb(221,221,221)" face=Verdana size=2>public void addLineItem<BR>&nbsp;(Long orderId, LineItem lineItem)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; throws FacadeException{</FONT></P>
<P><FONT style="BACKGROUND-COLOR: rgb(221,221,221)" face=Verdana size=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Long lineItemId = orderListDAO.<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; addLineItem(orderId, lineItem);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; auditManager.log(new AuditObject<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (LINE_ITEM + lineItemId, CREATE));<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int numberOfLineItems = orderListDAO.<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; queryNumberOfLineItems(orderId);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if(numberOfLineItems &gt; 2){<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; log("Added LineItem " + lineItemId +<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; " to Order " + orderId + ";<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; But rolling back *** !");<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; throw new FacadeException("Make a new<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Order for this line item");<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; log("Added LineItem " + lineItemId +<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; " to Order " + orderId + ".");<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<BR>}</FONT></P>
<P><FONT style="BACKGROUND-COLOR: rgb(221,221,221)" face=Verdana size=2>//其他代码在这里</FONT></P>
<P><FONT style="BACKGROUND-COLOR: rgb(221,221,221)" face=Verdana size=2>}</FONT></P>
<P><FONT size=2>　　要创建一个这个试验的异常，我们已经介绍了其他事务规则规定一个特定的次序不能在同一行里包含两个项目。我们应该注意到 createOrderList 和 addLineItem调用了auditManager.log() 方法。你应该也注意到了上面方法中的事务属性。</FONT></P>
<P><FONT style="BACKGROUND-COLOR: rgb(221,221,221)" face=Verdana size=2>&lt;bean id="orderListManager"<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; class="org.springframework.transaction.<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; interceptor.TransactionProxyFactoryBean"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;property name="transactionAttributes"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;props&gt;<BR>&lt;prop key="createOrderList"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PROPAGATION_REQUIRED<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/prop&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;prop key="addLineItem"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PROPAGATION_REQUIRED,-com.<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; example.exception.FacadeException<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/prop&gt;<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/props&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/property&gt;<BR>&lt;/bean&gt;</FONT></P>
<P><FONT style="BACKGROUND-COLOR: rgb(221,221,221)" face=Verdana size=2>&lt;bean id="auditManager" class="org.<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; springframework.transaction.interceptor.<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; TransactionProxyFactoryBean"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;property name="transactionAttributes"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;props&gt;<BR>&lt;prop key="log"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PROPAGATION_REQUIRES_NEW<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/prop&gt;<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/props&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/property&gt;<BR>&lt;/bean&gt;</FONT></P>
<P><FONT size=2>　　PROPAGATION_REQUIRED 和 TX_REQUIRED相同，而PROPAGATION_REQUIRES_NEW 和在EJB里的 TX_REQUIRES_NEW 相同。如果我们想让方法一直在事务里运行，可以用PROPAGATION_REQUIRED。这时，如果有一个TX已经运行了，bean的方法就会加入到 TX里，或者Spring的TX管理器给你新建一个。如果我们想一旦方法被调用，就创建一个新的事务实例，我们可以用 PROPAGATION_REQUIRES_NEW 属性。 </FONT></P>
<P><FONT size=2>　　我们同样要让addLineItem 一直都在抛出FacadeException异常时回滚事务。在我们有异常的情况下，使得我们可以很好的控制TX结束达到了另一个级别。前缀-符号表示回滚TX,而+ 符号表示提交TX。</FONT></P>
<P><FONT size=2>　　接下来的问题是为什么我们给log方法设置一个PROPAGATION_REQUIRES_NEW呢?这是因为我们要做的是无论主函数怎么运行，我们都要为所有订单的创建和项目的添加记录审核情况。就是说即使在运行createOrderList 和 addLineItem 的时候抛出了异常也要记录。这仅在我们开始一个新的TX并调用log的时候起作用。这就是为什么要给它设置的原因:</FONT></P>
<P><FONT size=2>　　如果调用auditManager.log(new AuditObject(LINE_ITEM +</FONT></P>
<P><FONT size=2>　　lineItemId, CREATE));成功了, auditManager.log() 将在新的TX里发生，这样auditManager.log() 成功的话就会被提交 (前提是不抛出异常)。</FONT></P>
<P><FONT size=2>　　<STRONG>设置试验工程的环境</STRONG></FONT></P>
<P><FONT size=2>　　为了运行这个工程，我们来按Spring Live 这本书的流程进行:</FONT></P>
<P><FONT size=2>　　1. 下载并安装以下组件，注意版本，不然会引起一系列的问题</FONT></P>
<P><FONT face=Verdana size=2>　　o JDK 1_5_0_01 或更高<BR>　　o Apache Tomcat 5.5.7<BR>　　o Apache Ant 1.6.2<BR>　　o Equinox 1.2</FONT></P>
<P><FONT size=2>　　2. 配置系统环境变量:</FONT></P>
<P><FONT face=Verdana size=2>　　o JAVA_HOME<BR>　　o CATALINA_HOME<BR>　　o ANT_HOME</FONT></P>
<P><FONT size=2>　　3. 把下列目录添加到你的PATH变量中去或者用绝对路径来运行你的程序:</FONT></P>
<P><FONT face=Verdana size=2>　　o JAVA_HOME\bin<BR>　　o CATALINA_HOME\bin<BR>　　o ANT_HOME\bin</FONT></P>
<P><FONT size=2>　　4. 要配置Tomcat, 打开 $CATALINA_HOME/conf/tomcat-users.xml 确保里面有下面这段文字，如果没有就手动添加进去:</FONT></P>
<P><FONT size=2><FONT style="BACKGROUND-COLOR: rgb(221,221,221)"><FONT face=Verdana>&lt;role rolename="manager"/&gt;&lt;user username="admin" password="admin" roles="manager"/&gt;</FONT>　</FONT>　<ROLE rolename="manager"><USER username="admin" roles="manager" password="admin"></USER></ROLE></FONT></P>
<P><FONT size=2>　　5. 要创建基于Struts，Spring, 和 Hibernate的web工程,我们要用Equinox来构建一个空白的框架，这将包含上面提到的文件结构，所有需要用到的jar文件，还有ant构建脚本。把Equinox解压到一个文件夹中，将创建一个equinox文件夹。到equinox文件夹里去，输入命令ANT_HOME\bin\ant new -Dapp.name=myusers。这样就会创建一个和equinox结构一样的文件夹myusers 。具体内容如下:</FONT></P>
<P><FONT size=2><IMG src="http://searchwebservices.techtarget.com.cn/imagelist/05/09/si9phtgxpebu.jpg" border=0></FONT></P>
<P><FONT size=2>　　图 4. Equinox myusers 工程文件夹</FONT></P>
<P><FONT size=2>　　6. 删掉myusers\web\WEB-INF目录下所有的xml文件。</FONT></P>
<P><FONT size=2>　　7. 复制 equinox\extras\struts\web\WEB-INF\lib\struts*.jar 到 myusers\web\WEB-INF\lib,这样这个工程就可以用struts了。</FONT></P>
<P><FONT size=2>　　8. 用本文最后的资源里的代码, 解压myusersextra.zip 到相应位置。 把目录下的所有内容拷贝到myusers目录下。</FONT></P>
<P><FONT size=2>　　9. 打开命令行转到myusers目录下。输入CATALINA_HOME\bin\</FONT><A class=bluekey href="http://www.yesky.com/key/2538/207538.html" target=_blank><FONT size=2>startup</FONT></A><FONT size=2> 从myusers 目录启动Tomcat可以保证数据库在myusers 文件夹里创建,这样可以避免在运行build.xml里定义的任务发生错误。</FONT></P>
<P><FONT size=2>　　10. 再次打开命令行转到myusers。执行 Execute ANT_HOME\bin\ant install。这样将创建整个工程并把它部署到Tomcat里。这时我们可以看到myusers 里多了一个 db 目录，里面存放数据库 appfuse1 和 appfuse2。</FONT></P>
<P><FONT size=2>　　11. 打开浏览器确定myusers 工程部署在http://localhost:8080/myusers/</FONT></P>
<P><FONT size=2>　　12. 要重建工程，执行ANT_HOME\bin\ant remove,用CATALINA_HOME\bin\shutdown关掉Tomcat.并在CATALINA_HOME\webapps里删掉 myusers 文件夹。然后用CATALINA_HOME\bin\startup 重启Tomcat然后用ANT_HOME\bin\ant install重构工程。</FONT></P>
<P><FONT size=2>　　<STRONG>执行工程</STRONG></FONT></P>
<P><FONT size=2>　　如果要测试，OrderListManagerTest，在myusers\test\com\example\service 目录下可以运行作为JUnit测试。要运行的话，在构建工程时加入以下代码:</FONT></P>
<P><FONT style="BACKGROUND-COLOR: rgb(221,221,221)" size=2>CATALINA_HOME\bin\ant test -Dtestcase=OrderListManager</FONT></P>
<P><FONT size=2>　　测试工程分为两个主要部分:第一个部分创建两行项目的一个排列，并把这两个链接到排列中。如下所示，可以成功运行:</FONT></P>
<P><FONT style="BACKGROUND-COLOR: rgb(221,221,221)" face=Verdana size=2>OrderList orderList1 = new OrderList();<BR>Long orderId1 = orderListManager.<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; createOrderList(orderList1);<BR>log("Created OrderList with id '"<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; + orderId1 + "'...");<BR>orderListManager.addLineItem(orderId1,lineItem1);<BR>orderListManager.addLineItem(orderId1,lineItem2);</FONT></P>
<P><FONT size=2>　　另一个执行类似的事件，但是这时我们添加三行到排列中去，将产生一个异常</FONT></P>
<P><FONT face=Verdana size=2>OrderList orderList2 = new OrderList();<BR>Long orderId2 = orderListManager.<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; createOrderList(orderList2);<BR>log("Created OrderList with id '"<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; + orderId2 + "'...");<BR>orderListManager.addLineItem(orderId2,lineItem3);<BR>orderListManager.addLineItem(orderId2,lineItem4);<BR>//这里将抛出异常…………但是仍然要执行下去<BR>try{<BR>&nbsp; orderListManager.addLineItem<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (orderId2,lineItem5);<BR>}<BR>catch(FacadeException facadeException){<BR>&nbsp; log("ERROR : " + facadeException.getMessage());<BR>}</FONT></P>
<P><FONT size=2>　　输出窗口如图5所示:</FONT></P>
<P><FONT size=2><IMG src="http://searchwebservices.techtarget.com.cn/imagelist/05/09/3p92k67d4hle.jpg" border=0></FONT></P>
<P><FONT size=2>　　图 5. 客户端输出</FONT></P>
<P><FONT size=2>　　我们创建了Order1,添加了两个ID是1和2的项目到里面去。然后创建Order2, 试图添加3个项目，前两个(ID是3和4)成功了，如图5所示添加ID为5的项目时抛出了异常。然后，事务回滚，数据库里没有ID为5的项目。执行以下代码从图6和图7可以看出:</FONT></P>
<P><FONT size=2>　　CATALINA_HOME\bin\ant browse1</FONT></P>
<P><FONT size=2><IMG src="http://searchwebservices.techtarget.com.cn/imagelist/05/09/5k11l585moa9.jpg" border=0></FONT></P>
<P><FONT size=2>　　图 6. appfuse1 数据库里的排列</FONT></P>
<P><FONT size=2>&nbsp;<IMG src="http://searchwebservices.techtarget.com.cn/imagelist/05/09/b0z7dh1k821r.jpg" border=0></FONT></P>
<P><FONT size=2>　　图 7. appfuse1里的项目</FONT></P>
<P><FONT size=2>　　接下来，试验中可以看出次序和项目存在appfuse1 里，而审核部分在appfuse2里. OrderListManager 同时访问两个数据库。打开 appfuse2 数据库，看审核记录的细节:</FONT></P>
<P><FONT size=2>　　CATALINA_HOME\bin\ant browse2</FONT></P>
<P><FONT size=2><IMG src="http://searchwebservices.techtarget.com.cn/imagelist/05/09/av994m6028zf.jpg" border=0></FONT></P>
<P><FONT size=2>　　图 8. appfuse2数据库里的审核记录, 包括失败的TX</FONT></P>
<P><FONT size=2>　　表8最后一列尤其值得注意，RESOURCE这一栏上显示这一行对应着LineItem5。 但是当我们回过来</FONT><A class=bluekey href="http://www.yesky.com/key/599/190599.html" target=_blank><FONT size=2>看图</FONT></A><FONT size=2>7，却发现并没有这种对应。这是个错误吗?事实上，没有问题，图7里没有的那行其实是这篇文章的精华所在，让我们来看看是怎么回事。</FONT></P>
<P><FONT size=2>　　首先addLineItem() 方法有 PROPAGATION_REQUIRED 属性而 log() 方法有PROPAGATION_REQUIRES_NEW。进而， addLineItem() 在内部调用log() 方法。所以我们往第二个排列里添加第三个表项时，发生了异常 (由于我们的事务规则)，就将这个创建过程和链接都回滚了。但是，因为已经调用了log()，而log()有 PROPAGATION_REQUIRES_NEW TX 属性,回滚了addLineItem() 不会回滚 log(), 因为 log() 是在一个新的TX里。</FONT></P>
<P><FONT size=2>　　让我们现在改变一下log()的TX属性。把PROPAGATION_REQUIRES_NEW 替换成PROPAGATION_SUPPORTS。ROPAGATION_SUPPORTS 属性允许方法在客户端的TX里运行，如果客户端有TX，否则就不用TX。你需要重建工程让这些变化自动被刷新。请按照设置工程环境的第12步。</FONT></P>
<P><FONT size=2>　　重新开始的话，我们会发现有一点不同。这次，我们在往排列2添加第三项时依然有异常。发生回滚。这时方法调用了log()方法。由于它有着 PROPAGATION_SUPPORTSTX属性， log() 将在同一个addLineItem() 方法环境下调用。由于 addLineItem() 回滚，log() 也回滚了，没有留下审核记录。所以在图9里没有这项失败的记录!</FONT></P>
<P><FONT size=2>　　Figure 9. appfuse2数据库的审核记录，没有失败的TX</FONT></P>
<P><FONT size=2>　　我们所改变的仅仅是TX属性，如下所示:</FONT></P>
<P><FONT style="BACKGROUND-COLOR: rgb(221,221,221)" face=Verdana size=2>&lt;bean id="auditManager"<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; class="org.springframework.transaction.<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; interceptor.TransactionProxyFactoryBean"&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;property name="transactionAttributes"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;props&gt;<BR>&lt;!-- prop key="log"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PROPAGATION_REQUIRES_NEW<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/prop --&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;prop key="log"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PROPAGATION_SUPPORTS<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/prop&gt;<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/props&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;/property&gt;<BR>&lt;/bean&gt;</FONT></P>
<P><FONT size=2>　　这是生成实例管理的效果，自从我们接触EJB以来就开始寻找杠杆的最佳位置。我们需要一个高端的应用服务器 来管理我们的our EJB组件。现在我们不用EJB服务器就达到了一样的结果，用Spring。</FONT></P>
<P><FONT size=2>　　这篇文章介绍了J2EE里十分强大的组合之一:Spring 和 Hibernate。通过两者的有机结合，我们现在多了对Container-Managed Persistence (CMP), Container-Managed Relationships (CMR), 和生成实例管理的新选择。虽然Spring不能完全代替EJB，但是它提供了很多功能，例如一般Java程序的实例生成，使得用户可以在大部分工程中和 EJB搭配使用。</FONT></P>
<P><FONT size=2>　　我们不是要为了寻找EJB的代替品，而是对于现在的问题得出一个最理想的解决方案。我们仍然要寻找Spring 和 Hibernate组合的更多优点，这就留给我们的读者去探索了。</FONT></P><img src ="http://www.blogjava.net/cmd/aggbug/32251.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/cmd/" target="_blank">静夜思</a> 2006-02-24 10:24 <a href="http://www.blogjava.net/cmd/articles/32251.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>OpenLDAP学习笔记</title><link>http://www.blogjava.net/cmd/articles/31754.html</link><dc:creator>静夜思</dc:creator><author>静夜思</author><pubDate>Tue, 21 Feb 2006 02:56:00 GMT</pubDate><guid>http://www.blogjava.net/cmd/articles/31754.html</guid><wfw:comment>http://www.blogjava.net/cmd/comments/31754.html</wfw:comment><comments>http://www.blogjava.net/cmd/articles/31754.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/cmd/comments/commentRss/31754.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/cmd/services/trackbacks/31754.html</trackback:ping><description><![CDATA[<div><div class="abstract"><p class="title"><font size="2"><b>Abstract</b></font></p><p><font size="2">LDAP
(轻量级目录服务访问协议，Lightweight Directory Access
Protocol)基于X.500标准，支持TCP/IP，使用简单方便。现在越来越多的网络应用系统都支持LDAP。OpenLDAP是LDAP的一种
开源实现，本笔记基于OpenLDAP2.1.29。</font></p></div></div><hr><div class="toc"><p><font size="2"><b>Table of Contents</b></font></p><dl><dt><font size="2"><span class="chapter"><a href="http://www.ringkee.com/jims/technic_folder/linux/open?page=openldap.htm#id2809654">1. 目录服务简介</a></span></font></dt><dd><dl><dt><font size="2"><span class="sect1"><a href="http://www.ringkee.com/jims/technic_folder/linux/open?page=openldap.htm#id2809734">1.1. X.500和LDAP</a></span></font></dt><dt><font size="2"><span class="sect1"><a href="http://www.ringkee.com/jims/technic_folder/linux/open?page=openldap.htm#id2810057">1.2. LDAP产品</a></span></font></dt></dl></dd><dt><font size="2"><span class="chapter"><a href="http://www.ringkee.com/jims/technic_folder/linux/open?page=openldap.htm#id2810083">2. OpenLDAP安装笔记</a></span></font></dt><dd><dl><dt><font size="2"><span class="sect1"><a href="http://www.ringkee.com/jims/technic_folder/linux/open?page=openldap.htm#id2810091">2.1. 源码安装</a></span></font></dt><dt><font size="2"><span class="sect1"><a href="http://www.ringkee.com/jims/technic_folder/linux/open?page=openldap.htm#id2810479">2.2. 数据录入</a></span></font></dt><dd><dl><dt><font size="2"><span class="sect2"><a href="http://www.ringkee.com/jims/technic_folder/linux/open?page=openldap.htm#id2810506">2.2.1. 手动录入方法</a></span></font></dt><dt><font size="2"><span class="sect2"><a href="http://www.ringkee.com/jims/technic_folder/linux/open?page=openldap.htm#id2810743">2.2.2. 文件方式</a></span></font></dt><dt><font size="2"><span class="sect2"><a href="http://www.ringkee.com/jims/technic_folder/linux/open?page=openldap.htm#id2810843">2.2.3. 脚本方式</a></span></font></dt></dl></dd><dt><font size="2"><span class="sect1"><a href="http://www.ringkee.com/jims/technic_folder/linux/open?page=openldap.htm#id2810869">2.3. 常用命令介绍</a></span></font></dt><dt><font size="2"><span class="sect1"><a href="http://www.ringkee.com/jims/technic_folder/linux/open?page=openldap.htm#id2808358">2.4. 启用sasl验证</a></span></font></dt><dt><font size="2"><span class="sect1"><a href="http://www.ringkee.com/jims/technic_folder/linux/open?page=openldap.htm#id2808424">2.5. 配置服务器复制</a></span></font></dt></dl></dd><dt><font size="2"><span class="chapter"><a href="http://www.ringkee.com/jims/technic_folder/linux/open?page=openldap.htm#id2808612">3. 管理工具</a></span></font></dt><dd><dl><dt><font size="2"><span class="sect1"><a href="http://www.ringkee.com/jims/technic_folder/linux/open?page=openldap.htm#id2808628">3.1. phpldapadmin</a></span></font></dt><dt><font size="2"><span class="sect1"><a href="http://www.ringkee.com/jims/technic_folder/linux/open?page=openldap.htm#id2808639">3.2. gq</a></span></font></dt><dt><font size="2"><span class="sect1"><a href="http://www.ringkee.com/jims/technic_folder/linux/open?page=openldap.htm#id2808650">3.3. CPU</a></span></font></dt><dt><font size="2"><span class="sect1"><a href="http://www.ringkee.com/jims/technic_folder/linux/open?page=openldap.htm#id2808661">3.4. JXplore</a></span></font></dt></dl></dd><dt><font size="2"><span class="chapter"><a href="http://www.ringkee.com/jims/technic_folder/linux/open?page=openldap.htm#id2808673">4. HowTo</a></span></font></dt><dd><dl><dt><font size="2"><span class="sect1"><a href="http://www.ringkee.com/jims/technic_folder/linux/open?page=openldap.htm#id2808681">4.1. 禁止整个服务器的匿名访问</a></span></font></dt></dl></dd></dl></div><div class="chapter" lang="en"><div class="titlepage"><div><div><h2 class="title"><font size="2"><a name="id2809654"></a>Chapter&nbsp;1.&nbsp;目录服务简介</font></h2></div></div></div><div class="toc"><p><font size="2"><b>Table of Contents</b></font></p><dl><dt><font size="2"><span class="sect1"><a href="http://www.ringkee.com/jims/technic_folder/linux/open?page=openldap.htm#id2809734">1.1. X.500和LDAP</a></span></font></dt><dt><font size="2"><span class="sect1"><a href="http://www.ringkee.com/jims/technic_folder/linux/open?page=openldap.htm#id2810057">1.2. LDAP产品</a></span></font></dt></dl></div><p><font size="2">目
录是一个为查询、浏览和搜索而优化的专业分布式数据库，它成树状结构组织数据，就好象Linux/Unix系统中的文件目录一样。目录数据库和关系数据库
不同，它有优异的读性能，但写性能差，并且没有事务处理、回滚等复杂功能，不适于存储修改频繁的数据。所以目录天生是用来查询的，就好象它的名字一样。目
录服务是由目录数据库和一套访问协议组成的系统。类似以下的信息适合储存在目录中：</font></p><div class="itemizedlist"><ul type="disc"><li><p><font size="2">企业员工和企业客户之类人员信息；</font></p></li><li><p><font size="2">公用证书和安全密钥；</font></p></li><li><p><font size="2">邮件地址、网址、IP等电脑信息；</font></p></li><li><p><font size="2">电脑配置信息。</font></p></li><li><p><font size="2">...</font></p></li></ul></div><div class="sect1" lang="en"><div class="titlepage"><div><div><h2 class="title" style="clear: both;"><font size="2"><a name="id2809734"></a>1.1.&nbsp;X.500和LDAP</font></h2></div></div></div><p><font size="2">现在国际上的目录服务标准有两个，一个是较早的X.500标准，一个是较新的LDAP标准。</font></p><div class="itemizedlist"><ul type="disc"><li><p><font size="2">X.500是一个协议族，由一系列的概念和协议组成，包括：</font></p><div class="itemizedlist"><ul type="circle"><li><p><font size="2">X.501是模型定义，定义目录服务的基本模型和概念；</font></p></li><li><p><font size="2">X.509是认证框架，定义如何处理目录服务中客户和服务器认证；</font></p></li><li><p><font size="2">X.511是抽象服务定义，定义X.500提供的功能性服务；</font></p></li><li><p><font size="2">X.518是分布式操作过程定义，定义如何跨平台处理目录服务；</font></p></li><li><p><font size="2">X.519
是协议规范，定义了X.500协议，包括DAP(Directory Access Protocol，目录访问协议)、DSP(Directory
System Protocol，目录系统协议)、DOP(Directory Operator
Protocol，目录操作绑定协议)、DISP(Directory Information Shadowing
Protocol，目录信息阴影协议 )；</font></p></li><li><p><font size="2">X.520定义属性类型要求；</font></p></li><li><p><font size="2">X.521定义对象类型；</font></p></li><li><p><font size="2">X.525定义如果在目录服务器间复制内容。</font></p></li></ul></div><p><font size="2">X.500标准中定义了很多内容，包括：</font></p><div class="itemizedlist"><ul type="circle"><li><p><font size="2">定义了信息模型，确定目录中信息的格式和字符集，如何在项中表示目录信息(定义对象类、属性等模式)；</font></p></li><li><p><font size="2">定义命名空间，确定对信息进行的组织和引用，如何组织和命名项-目录信息树DIT和层次命名模型；</font></p></li><li><p><font size="2">定义功能模型，确定可以在信息上执行的操作；</font></p></li><li><p><font size="2">定义认证框架，保证目录中信息的安全，如何实现目录中信息的授权保护-访问控制模型；</font></p></li><li><p><font size="2">定义分布操作模型，确定数据如何分布和如何对分布数据执行操作，如何将全局目录树划分为管理域，以便管理。</font></p></li><li><p><font size="2">定义客户端与服务器之间的通信的各种协议。</font></p></li></ul></div><p><font size="2">由于X.500较复杂，且需严格遵照OSI七层协议模型。造成应用开发较困难。所以开发了LDAP，以便在INTERNET上使用。</font></p></li><li><p><font size="2">LDAP协议于1993年获批准，产生LDAPv1版，1997年发布最新的LDAPv3版，该版本是LDAP协议发展的一个里程碑，它作为X.500的简化版提供了很多自有的特性，使LDAP功能更为完备，具有更强大的生命力。</font></p><p><font size="2">LDAP也是一个协议族，包含以下内容：</font></p><div class="itemizedlist"><ul type="circle"><li><p><font size="2">RFC 2251--LDAPv3核心协议，定义了LDAPv3协议的基本模型和基本操作；</font></p></li><li><p><font size="2">RFC 2252--定义LDAPv3基本数据模式(Schema)(包括语法、匹配规则、属性类型和对象类)以及标准的系统数据模式；</font></p></li><li><p><font size="2">RFC 2253--定义LDAPv3中的分辩名(DN)表达式；</font></p></li><li><p><font size="2">RFC 2254--定义了LDAPv3中的过滤表达式；</font></p></li><li><p><font size="2">RFC 2255--定义LDAPv3统一资源地址的格式；</font></p></li><li><p><font size="2">RFC 2256--定义LDAPv3中使用X.500的Schema列表；</font></p></li><li><p><font size="2">RFC 2829--定义了LDAPv3中的认证方式；</font></p></li><li><p><font size="2">RFC 2830--定义了如何通过扩展使用TLS服务；</font></p></li><li><p><font size="2">RFC 1823--定义了C的LDAP客户端API开发接口；</font></p></li><li><p><font size="2">RFC 2847--定义了LDAP数据导入、导出文件接口LDIF。</font></p></li></ul></div><p><font size="2">这些协议定义了LDAP的内容，包括：</font></p><div class="itemizedlist"><ul type="circle"><li><p><font size="2">定义了一个信息模型，确定了LDAP目录中信息的格式和字符集，如何表示目录信息(定义对象类、属性、匹配规则和语法等模式)；</font></p></li><li><p><font size="2">定义了命名空间，确定信息的组织方式--目录树DIT，以DN和RDN为基础的命名方式，以及LDAP信息的Internet表示方式；</font></p></li><li><p><font size="2">定义了功能模型，确定在可以在信息上执行的操作及API。</font></p></li><li><p><font size="2">定义了安全框架，保证目录中信息的安全，定义匿名、用户名/密码、SASL等多种认证方式，以及与TLS结合的通讯保护框架；</font></p></li><li><p><font size="2">定义分布式操作模型，基于指引方式的分布式操作框架；</font></p></li><li><p><font size="2">定义了LDAP扩展框架。</font></p></li></ul></div></li></ul></div></div><div class="sect1" lang="en"><div class="titlepage"><div><div><h2 class="title" style="clear: both;"><font size="2"><a name="id2810057"></a>1.2.&nbsp;LDAP产品</font></h2></div></div></div><p><font size="2">现
在市场上有关LDAP的产品已有很多，各大软件公司都在他们的产品中集成了LDAP服务，如Microsoft的ActiveDirectory、
Lotus的Domino
Directory、IBM的WebSphere中也集成了LDAP服务。LDAP的开源实现是OpenLDAP，它比商业产品一点也不差，而且源码开
发。</font></p></div></div><div class="chapter" lang="en"><div class="titlepage"><div><div><h2 class="title"><font size="2"><a name="id2810083"></a>Chapter&nbsp;2.&nbsp;OpenLDAP安装笔记</font></h2></div></div></div><div class="toc"><p><font size="2"><b>Table of Contents</b></font></p><dl><dt><font size="2"><span class="sect1"><a href="http://www.ringkee.com/jims/technic_folder/linux/open?page=openldap.htm#id2810091">2.1. 源码安装</a></span></font></dt><dt><font size="2"><span class="sect1"><a href="http://www.ringkee.com/jims/technic_folder/linux/open?page=openldap.htm#id2810479">2.2. 数据录入</a></span></font></dt><dd><dl><dt><font size="2"><span class="sect2"><a href="http://www.ringkee.com/jims/technic_folder/linux/open?page=openldap.htm#id2810506">2.2.1. 手动录入方法</a></span></font></dt><dt><font size="2"><span class="sect2"><a href="http://www.ringkee.com/jims/technic_folder/linux/open?page=openldap.htm#id2810743">2.2.2. 文件方式</a></span></font></dt><dt><font size="2"><span class="sect2"><a href="http://www.ringkee.com/jims/technic_folder/linux/open?page=openldap.htm#id2810843">2.2.3. 脚本方式</a></span></font></dt></dl></dd><dt><font size="2"><span class="sect1"><a href="http://www.ringkee.com/jims/technic_folder/linux/open?page=openldap.htm#id2810869">2.3. 常用命令介绍</a></span></font></dt><dt><font size="2"><span class="sect1"><a href="http://www.ringkee.com/jims/technic_folder/linux/open?page=openldap.htm#id2808358">2.4. 启用sasl验证</a></span></font></dt><dt><font size="2"><span class="sect1"><a href="http://www.ringkee.com/jims/technic_folder/linux/open?page=openldap.htm#id2808424">2.5. 配置服务器复制</a></span></font></dt></dl></div><div class="sect1" lang="en"><div class="titlepage"><div><div><h2 class="title" style="clear: both;"><font size="2"><a name="id2810091"></a>2.1.&nbsp;源码安装</font></h2></div></div></div><p><font size="2">我的安装方法是以源码编译的方式进行的，以root用户进行安装。安装所需软件如下：</font></p><div class="itemizedlist"><ul type="disc"><li><p><font size="2"><a href="http://www.openldap.org/" target="_top">openldap-2.1.29</a></font></p></li><li><p><font size="2"><a href="http://www.sleepycat.com/" target="_top">Berkeley DB 4.2.52</a></font></p></li></ul></div><p><font size="2">具体的安装步骤如下：</font></p><div class="orderedlist"><ol type="1"><li><p><font size="2">由于openldap需要Berkeley DB来存放数据，所以需先安装Berkeley DB 4.2.52，可到它的网站下载，网址见上面。运行下面的命令解压：</font></p><pre class="screen"><font size="2"># tar -zxvf db-4.2.52.tar.gz<br></font></pre><p><font size="2">解完压后，会生成一个db-4.2.52目录,进行该目录下的build_unix目录。执行以下命令进行配置安装。</font></p><pre class="screen"><font size="2"># ../dist/configure<br># make<br># make install<br></font></pre><p><font size="2">也是按linux源码安装的三步曲完成，没有什么好说的了。该软件默认是安装在/usr/local/BerkeleyDB.4.2目
录下。安装完成后，要把/usr/local/BerkeleyDB.4.2/lib的库路径加到/etc/ld.so.conf文件内，添加完成后执行
一次ldconfig，使配置文件生效。这样编译openldap时才能找到相应的库文件。这样资料库就安装完成了，接下来可以安装openldap了。
ld.so.conf是什么东西？它就是系统动态链接库的配置文件。此文件内,存放着可被LINUX共享的动态链接库所在目录的名字(系统目录/lib,
/usr/lib除外)，各个目录名间以空白字符(空格，换行等)或冒号或逗号分隔。一般的LINUX发行版中，此文件均含一个共享目录
/usr/X11R6/lib，为X window窗口系统的动态链接库所在的目录。
ldconfig是它的管理命令，具体操作方法可查询man手册，这里就不细讲了。</font></p></li><li><p><font size="2">到openldap官方网站下载最新
的稳定版源码，并解压。查看INSTALLT
和README文档，这个很重要，因为安装方法和一些注意事项都在里面有介绍。认真弄明白文档内容能节省你不少的安装调试时间。这也是开源软件的一个特
点，给用户提供了最大的灵活性和可配置性。但也增加了系统安装配置的难度，需要有相关的文档配置说明和指导。在官方网站上还有详细的帮助文件，在整个系统
配置中需要经常查询。</font></p><pre class="screen"><font size="2"># tar -zxvf openldap-stable-20040329.tgz<br></font></pre><p><font size="2">解压完成后，会生成一个openldap-2.1.29目录。进行该目录，执行以下命令进行配置安装。</font></p><pre class="screen"><font size="2"># env CPPFLAGS="-I/usr/local/BerkeleyDB.4.2/include" <br>LDFLAGS="-L/usr/local/BerkeleyDB.4.2/lib" ./configure --prefix=/usr/local/openldap <br>--enable-ldbm <br></font></pre><p><font size="2">注意以上配置语句，要设置资料库的include和lib路径，否则在配置到资料库相关内容时会提示Berkeley DB版本不兼容，并中断配置。如果没有--enable-ldbm选项，在make test时会提示ldbm找不到。为了减少出错，还是加上为好。</font></p><pre class="screen"><font size="2">#make depens<br>#make<br>#make test<br></font></pre><p><font size="2">在make test阶段要花费较长时间进行测试，好像有16项吧。你可以放松一下，上上网，聊聊天，听听歌，呵呵，开玩笑了，这个时间应该是最紧张的了。成与不成就看这下的了，如果没问题就可安装了。</font></p><pre class="screen"><font size="2">#make install<br></font></pre><p><font size="2">通过配置命令可以看出，我们把openldap安装到/usr/local/openldap目录下。建议以源码安装的软件都放到独立的目录下，不要放到软件默认的目录。好处是方便管理和控制，所有文件在统一的目录下，卸载软件只要删除整个目录就可以了。</font></p></li><li><p><font size="2">安
装完相关软件后就可以着手配置了。Berkeley DB资料库没什么好配置的。主要是配置openldap
服务。配置文件在软件的安装目录的etc/openldap下，有四个文件，主要的是slapd.conf and
ldap.conf，其它两个是backup文件。首先，我们先来配置slapd.conf文档。系统默认的slapd.conf文件如下：</font></p><pre class="screen"><font size="2"># $OpenLDAP: pkg/ldap/servers/slapd/slapd.conf,v 1.23.2.8 2003/05/24 23:19:14 kurt Exp $<br># <br># See slapd.conf(5) for details on configuration options.<br># This file should NOT be world readable. <br># <br>include      /usr/local/openldap/etc/openldap/schema/core.schema  <br>#设置schema配置文档包含<br> <br># Define global ACLs to disable default read access.<br> <br># Do not enable referrals until AFTER you have a working directory<br># service AND an understanding of referrals.<br>#referral       ldap://root.openldap.org<br> <br>pidfile         /usr/local/openldap/var/slapd.pid <br>#设置pid和args文档位置<br>argsfile        /usr/local/openldap/var/slapd.args<br> <br># Load dynamic backend modules:<br># modulepath    /usr/local/openldap/libexec/openldap<br># moduleload    back_bdb.la <br># moduleload    back_ldap.la<br># moduleload    back_ldbm.la <br># moduleload    back_passwd.la<br># moduleload    back_shell.la<br> <br># Sample security restrictions <br>#       Require integrity protection (prevent hijacking)<br>#       Require 112-bit (3DES or better) encryption for updates<br>#       Require 63-bit encryption for simple bind<br># security ssf=1 update_ssf=112 simple_bind=64<br> <br># Sample access control policy: <br>#       Root DSE: allow anyone to read it<br>#       Subschema (sub)entry DSE: allow anyone to read it<br>#       Other DSEs: <br>#       Subschema (sub)entry DSE: allow anyone to read it<br>#       Other DSEs:<br>#               Allow self write access<br>#               Allow authenticated users read access<br>#               Allow anonymous users to authenticate<br>#       Directives needed to implement policy:<br># access to dn.base="" by * read<br># access to dn.base="cn=Subschema" by * read<br># access to *<br>#       by self write<br>#       by users read<br>#       by anonymous auth<br>#<br># if no access controls are present, the default policy is:<br>#       Allow read by all<br>#<br># rootdn can always write!<br> <br>#######################################################################<br># ldbm database definitions<br>#######################################################################<br> <br>database        bdb                                <br>#设置使用的资料库，也可用lbdm。<br>suffix          "dc=my-domain,dc=com"              <br>#设置目录后缀<br>rootdn          "cn=Manager,dc=my-domain,dc=com"   <br>#设置目录管理员<br># Cleartext passwords, especially for the rootdn, should<br># be avoid.  See slappasswd(8) and slapd.conf(5) for details.<br># Use of strong authentication encouraged.<br>rootpw          secret                             <br>#设置管理密码，这里用了明文的“secret”密码。这样设置不安全，需使用加密的密码，下面会讲到如何设置加密密码。<br># The database directory MUST exist prior to running slapd AND <br># should only be accessible by the slapd and slap tools.<br># Mode 700 recommended.<br>directory       /usr/local/openldap/var/openldap-data  <br>#设置资料库路径<br># Indices to maintain<br>index   objectClass     eq                        <br>#设置目录项索引<br></font></pre><p><font size="2">要服务器正常动作，要修改一些始初参数和设置，修改后的配置文档如下：</font></p><pre class="screen"><font size="2"># $OpenLDAP: pkg/ldap/servers/slapd/slapd.conf,v 1.23.2.8 2003/05/24 23:19:14 kurt Exp $<br>#<br># See slapd.conf(5) for details on configuration options.<br># This file should NOT be world readable.<br>#<br>#为了有效使用目录服务，包含相关的文件。注意，在包含文件时是要按一定顺序的，因为<br>#文件里的属性存在依赖关系。如果顺序不对，服务器启动不了，文档间的依赖关系在文档<br>#中都有说明，请仔细查看一下。如果懒得看也可以按我的顺序。<br>include         /usr/local/openldap/etc/openldap/schema/core.schema<br>include         /usr/local/openldap/etc/openldap/schema/corba.schema<br>include         /usr/local/openldap/etc/openldap/schema/cosine.schema<br>include         /usr/local/openldap/etc/openldap/schema/inetorgperson.schema    <br>include         /usr/local/openldap/etc/openldap/schema/misc.schema             <br>include         /usr/local/openldap/etc/openldap/schema/openldap.schema<br>include         /usr/local/openldap/etc/openldap/schema/nis.schema<br>include         /usr/local/openldap/etc/openldap/schema/samba.schema<br># Define global ACLs to disable default read access.<br> <br># Do not enable referrals until AFTER you have a working directory<br># service AND an understanding of referrals.<br>#referral       ldap://root.openldap.org<br> <br>pidfile         /usr/local/openldap/var/slapd.pid<br>argsfile        /usr/local/openldap/var/slapd.args<br> <br>loglevel 1                       <br>#增加了日志功能，需修改syslog配置文件，在文件中增加一项：local4.* /var/log/ldap.log日志级别定义可查相官方网站的文档。<br>#1级记录的信息很多，可用于调试。<br># Load dynamic backend modules:<br># modulepath    /usr/local/openldap/libexec/openldap<br># moduleload    back_bdb.la<br># moduleload    back_ldap.la<br># moduleload    back_ldbm.la<br># moduleload    back_passwd.la<br># moduleload    back_shell.la<br> <br># Sample security restrictions<br>#       Require integrity protection (prevent hijacking)<br>#       Require 112-bit (3DES or better) encryption for updates<br>#       Require 63-bit encryption for simple bind<br># security ssf=1 update_ssf=112 simple_bind=64<br> <br># Sample access control policy:<br>#       Root DSE: allow anyone to read it<br>#       Subschema (sub)entry DSE: allow anyone to read it<br>#       Other DSEs:<br>#               Allow self write access<br>#               Allow authenticated users read access<br>#               Allow anonymous users to authenticate<br>#       Directives needed to implement policy:<br># access to dn.base="" by * read<br># access to dn.base="cn=Subschema" by * read<br># access to *<br>#       by self write<br>#       by users read<br>#       by anonymous auth<br>#<br># if no access controls are present, the default policy is:<br>#       Allow read by all<br>#<br># rootdn can always write!<br> <br>#######################################################################<br># ldbm database definitions<br>#######################################################################<br> <br>database        bdb<br>suffix          "dc=it,dc=com"<br>#改成你自已的目录后缀，<br>rootdn          "cn=root,dc=it,dc=com"<br>#设置root为管理员，与linux的root没有什么关系。<br># Cleartext passwords, especially for the rootdn, should<br># be avoid.  See slappasswd(8) and slapd.conf(5) for details.<br># Use of strong authentication encouraged.<br>rootpw          {MD5}mjkiuPt0wXhpxxkdiOOO+0000000AKq0by<br>#设置root密码，用MD5加密。密码串用slappasswd -h {MD5}指令生成<br># The database directory MUST exist prior to running slapd AND <br># should only be accessible by the slapd and slap tools.<br># Mode 700 recommended.<br>directory       /usr/local/openldap/var/openldap-data    <br># Indices to maintain<br>index   objectClass     eq<br>#这里可根据你的需要设置相关索引，以加快查询速度。具体内容可查询官方网站管理手册。<br> <br>#ACL configure以下内容定义访问控制<br>access to  attr=userPassworduserPassword<br>#只能由自已修改，有效验证用户查询。<br>        by self write<br>        by anonymous auth<br>access to attr=mail<br>        by dn="cn=root,dc=it,dc=tigerhead" writemail<br>#只能由自已修改，有效验证用户查询。<br>        by self write<br>        by anonymous auth<br>access to dn=".*,dc=it,dc=tigerhead"<br>#允许所有人查询没受控制访问限制的信息。<br>        by self write<br>        by * read<br></font></pre><p><font size="2">到现在为止，服务器基本就配置完成了，可以启动了，服务器程序是位于安装目录的libexec下的slapd程序。注意，不是
sldap哦。ok，到现在为止，服务器基本就配置完成了，可以启动了，服务器程序是位于安装目录的libexec下的slapd程序。注意，不是
sldap哦。启动服务器执行以下命令：</font></p><pre class="screen"><font size="2"># ./slapd<br></font></pre><p><font size="2">如果没有提示什么出错信息，直接返回shell状态，就说明服务器正常启动了，你可以查询日志或用ps -aux查看。或用以下命令查询服务器:</font></p><pre class="screen"><font size="2">ldapsearch -x -b '' -s base '(objectclass=*)' namingContexts<br></font></pre><p><font size="2">如果命令执行成功，返回一些信息，则说明服务器正常运行了。如果启动不成功，它会提示一些出错信息，多数是slapd.conf配置出错。回头仔细核查一下配置文档。</font></p></li><li><p><font size="2">客户端配置文档是ldap.conf。该文档相当简单，其实不用配置也能正常操作。</font></p><pre class="screen"><font size="2"># $OpenLDAP: pkg/ldap/libraries/libldap/ldap.conf,v 1.9 2000/09/04 19:57:01 kurt Exp $<br>#<br># LDAP Defaults<br>#<br> <br># See ldap.conf(5) for details<br># This file should be world readable but not world writable.<br> <br>BASE    dc=it, dc=com设置目录起点<br>#URI    ldap://ldap.example.com ldap://ldap-master.example.com:666<br> <br>#SIZELIMIT      12<br>#TIMELIMIT      15<br>#DEREF          never<br></font></pre></li></ol></div></div><div class="sect1" lang="en"><div class="titlepage"><div><div><h2 class="title" style="clear: both;"><font size="2"><a name="id2810479"></a>2.2.&nbsp;数据录入</font></h2></div></div></div><p><font size="2">服
务器正常运作后，就可以录入信息了。信息的录入有三种方法，一种是手工录入，一种是.ldif文件格式录入，一种是脚本自动录入。我们先从最基础的手工录
入方式开始介绍，了解录入信息的格式。明白了手工录入的格式，其它两种方式都很容易明白。信息录入用到ldapadd这个程序。可在安装目录的bin目录
下找到。</font></p><div class="sect2" lang="en"><div class="titlepage"><div><div><h3 class="title"><font size="2"><a name="id2810506"></a>2.2.1.&nbsp;手动录入方法</font></h3></div></div></div><div class="itemizedlist"><ul type="disc"><li><p><font size="2">第一步是要建立DN：</font></p><pre class="screen"><font size="2"># ldapadd -x -D 'cn=root,dc=it,dc=com' -W<br>dn: dc=it,dc=com<br>objectClass: dcObject<br>objectClass: organization<br>dc: it<br>o: Corporation<br>description: d Corporation<br>注意：如果你用复制/粘贴功能把以上内容拷贝过去，一定要注意每行后面不要有空格，建议还是手工输入，按Ctrl+d存盘。<br></font></pre></li><li><p><font size="2">第二步是建立RDN:</font></p><pre class="screen"><font size="2"># ldapadd -x -D 'cn=root,dc=it,dc=com' -W        <br>#-x表示用简单验证，-D表示指定目录，-W表示弹出密码输入提示<br></font></pre><p><font size="2">输入密码，这里的密码是cn=root,dc=it,dc=com的密码，不是操作系统中root用户的密码。验证通过后就可输入以下内容：</font></p><pre class="screen"><font size="2">dn: uid=qq,dc=it,dc=com<br>objectClass: person<br>objectClass: organizationalPerson<br>objectClass: inetOrgPerson<br>uid: qq<br>cn: qq<br>sn: qq<br>telephoneNumber: 138888888<br>description: openldap test<br>telexNumber: tex-8888888<br>street: my street<br>postOfficeBox: postofficebox<br>displayName: qqdisplay<br>homePhone: home1111111<br>mobile: mobile99999<br>mail:qq@qq.com<br></font></pre><p><font size="2">输入完所有信息后，按Ctrl+d结束存盘。如果出现出错信息，请查一下对象类和属性的对应关系有没有错或输入失误。初学者就容易出错
的地方是对象类和属性的对应关系没有处理好。对象类和属性是在schema文档中定义的。它们之间的关系是这样的，对象类中有些属性是必选的，有些属性是
可选的。录入信息的属性必须在对象类中有定义才能用。</font></p></li></ul></div><p><font size="2">输入以下命令可查询到刚才输入的信息。</font></p><pre class="screen"><font size="2"># ldapsearch -x -b 'dc=it,dc=com'            <br>-b选项是设置目录起点，如果设置了客户端的BASE配置参数，该项可不用。<br></font></pre><p><font size="2">如果按以上配置文件设置了acl，用上面的查询命令是查询不到受保护的内容的。如上面的userPassword and mail。要查询到这些受限内容，需要通过验证才可以：</font></p><pre class="screen"><font size="2"># ldapsearch -x -LLL -h it.com -b 'dc=it,dc=com' -D 'uid=qq,dc=it,dc=com' -W 'uid=qq'<br>接着提示输入密码。输入userPassword的密码回车，所有信息就都出来了。<br></font></pre></div><div class="sect2" lang="en"><div class="titlepage"><div><div><h3 class="title"><font size="2"><a name="id2810743"></a>2.2.2.&nbsp;文件方式</font></h3></div></div></div><p><font size="2">.ldif文件方式也就是把以上手工输入的内容先写入一个.ldif文件中，然后，用ldapadd命令的-f参数导入。</font></p><pre class="screen"><font size="2"># ldapadd -x -D "cn=root,dc=it,dc=com" -W -f test.ldif<br></font></pre><p><font size="2">一个完整的global.ldif文件例子：</font></p><pre class="screen"><font size="2">dn: dc=info, dc=net<br>objectClass: top<br>objectClass: organization<br>o: info.net<br><br>dn: ou=People, dc=info, dc=net<br>objectClass: top<br>objectClass: organizationalUnit<br>ou: People<br>description: User Info<br><br>dn: cn=Admin, dc=info, dc=net<br>objectClass: top<br>objectClass: person<br>objectClass: organizationalPerson<br>cn: Admin<br>sn: Admin<br>userPassword: Admin<br>description: Administrator for info.net<br><br>dn: id=1, ou=people, dc=info, dc=net<br>objectclass: top<br>objectclass: InfoPerson<br>id: 1<br>username: 张三<br>tel:021-63138990<br>card_id:ABC001<br></font></pre><p><font size="2">我们也可用slapadd命令来导入数据。该命令可以导入包含一些系统信息的ldif文件，如：</font></p><pre class="screen"><font size="2">dn: dc=it,dc=com<br>objectClass: top<br>objectClass: dcObject<br>objectClass: organization<br>dc: it<br>structuralObjectClass: organization<br>entryUUID: d97b06da-d77e-1028-9866-d4ec7ac00d12<br>creatorsName: cn=anonymous            #系统信息<br>createTimestamp: 20041201005115Z      #系统信息<br>o:: 5bm/5bee5biC6JmO5aS055S15rGg6ZuG5Zui5pyJ6ZmQ5YWs5Y+4<br>userPassword:: e01ENX14TXBDT0tDNUk0SU56RkNhYjNXRW13PT0=<br>entryCSN: 2004120603:50:08Z#0x0001#0#0000     #系统信息<br>modifiersName: cn=admin,dc=it,dc=com          #系统信息<br>modifyTimestamp: 20041206035008Z              #系统信息<br></font></pre><div class="note" style="margin-left: 0.5in; margin-right: 0.5in;"><table summary="Note" border="0"><tbody><tr><td rowspan="2" align="center" valign="top" width="25"><font size="2"><img alt="[Note]" src="http://www.ringkee.com/jims/technic_folder/linux/images/note.png"></font></td><th align="left"><font size="2"><br></font></th></tr><tr><td colspan="2" align="left" valign="top"><font size="2">再次提醒，注意每行后面不要留有空格。</font></td></tr></tbody></table></div></div><div class="sect2" lang="en"><div class="titlepage"><div><div><h3 class="title"><font size="2"><a name="id2810843"></a>2.2.3.&nbsp;脚本方式</font></h3></div></div></div><p><font size="2">脚本录入方式需要自已编写脚本，或到网上下载。有一个用PHP写的LDAP管理工具不错，叫phpLDAPadmin。可以到以下网址下载：<a href="http://phpldapadmin.sourceforge.net/" target="_top">http://phpldapadmin.sourceforge.net</a>。安装方法也很简单，只要解压出来，拷贝到apache的web目录下，按说明配置一下设定文档,就ok了。</font></p></div></div><div class="sect1" lang="en"><div class="titlepage"><div><div><h2 class="title" style="clear: both;"><font size="2"><a name="id2810869"></a>2.3.&nbsp;常用命令介绍</font></h2></div></div></div><p><font size="2">接着为大家介绍一下几个常用的ldap命令，如果你用了phpLDAPadmin程序，其实它已经有一个很好的图形介面帮你完成这些命令了。但了解一下还是对你还是很有益的，因为命令方法才是最根本的。</font></p><div class="itemizedlist"><ul type="disc"><li><p><font size="2">删除命令ldapdelete</font></p><pre class="screen"><font size="2"># ldapdelete -x -D 'cn=root,dc=it,dc=com' -W 'uid=qq1,dc=it,dc=com'<br></font></pre></li><li><p><font size="2">重新索引ldap数据库命令slapindex</font></p><pre class="screen"><font size="2"># slapindex -f slapd.conf<br></font></pre></li><li><p><font size="2">设置使用者密码，当然了，你的用户需要有userPassword项了。</font></p><pre class="screen"><font size="2">#ldappasswd -x -D "cn=root,dc=it,dc=com" -W "uid=qq1,dc=it,dc=com" -S <br>New password: <br>Re-enter new password: <br>Enter bind password: <br>Result: Success (0)<br></font></pre><div class="note" style="margin-left: 0.5in; margin-right: 0.5in;"><table summary="Note" border="0"><tbody><tr><td rowspan="2" align="center" valign="top" width="25"><font size="2"><img alt="[Note]" src="http://www.ringkee.com/jims/technic_folder/linux/images/note.png"></font></td><th align="left"><font size="2"><br></font></th></tr><tr><td colspan="2" align="left" valign="top"><font size="2">"Enter bind password" 是 "cn=root,dc=it,dc=com"管理员的密码。</font></td></tr></tbody></table></div></li><li><p><font size="2">管理员密码更改 </font></p><pre class="screen"><font size="2">#slappasswd <br>New password <br>Re-enter new password <br>{SSHA}83DJ4KVwqlk1uh9k2uDb8+NT1U4RgkEs <br></font></pre><p><font size="2">接下再拷贝到 path/to/sldap.conf 的 rootpw 即可,重启使用配置文件生效</font></p></li><li><p><font size="2">通过ldapmodify修改目录内容</font></p><pre class="screen"><font size="2"># ldapmodify -x -D "cn=root,dc=it,dc=com" -W -f modify.ldif<br></font></pre><p><font size="2">通过ldif文件修改ldap数据，ldif文件格式如下：</font></p><pre class="screen"><font size="2">dn: cn=qq,dc=it,dc=com<br>changetype: modify<br>replace: mail<br>mail: modme@example.com<br>-<br>add: title<br>title: Grand Poobah<br>-<br>add: jpegPhoto<br>jpegPhoto:&lt; file:///tmp/modme.jpeg<br>-<br>delete: description<br>-<br></font></pre></li></ul></div></div><div class="sect1" lang="en"><div class="titlepage"><div><div><h2 class="title" style="clear: both;"><font size="2"><a name="id2808358"></a>2.4.&nbsp;启用sasl验证</font></h2></div></div></div><p><font size="2">前提是你在系统中安装了sasl认证库，并在编译openldap时支持它，默认就支持了。到<a href="http://asg.web.cmu.edu/cyrus" target="_top">http://asg.web.cmu.edu/cyrus</a>下载。安装方法见我写的sendmail安装笔记。安装好之后，需要在sasl中建立相应的帐号，用以下命令可完成。</font></p><pre class="screen"><font size="2"># saslpasswd2 -c test<br></font></pre><p><font size="2">接着配置slapd.conf文件，加入以下内容。</font></p><pre class="screen"><font size="2">sasl-regexp<br>        uid=(.*),cn=.*,cn=auth<br>        uid=$1,dc=it,dc=com<br></font></pre><p><font size="2">重启服务器使配置文件生效。这个配置是最大权限的配置，如果要细化请查阅相关文档。用以下命令测试。</font></p><pre class="screen"><font size="2"># ldapsearch -U qq  -b 'uid=qq,dc=it,dc=com' -D 'dc=it,dc=com' -Y DIGEST-MD5<br></font></pre><p><font size="2">采用digest-md5验证,提示密码，输入saslpasswd2的密码。</font></p></div><div class="sect1" lang="en"><div class="titlepage"><div><div><h2 class="title" style="clear: both;"><font size="2"><a name="id2808424"></a>2.5.&nbsp;配置服务器复制</font></h2></div></div></div><p><font size="2">在
一些关键的应用场合，我们需设置多个ldap服务器实例，且数据要保持同步。当一台服务器出现故障或被黑客攻击时，我们就能继续保持应用的正常运行。通过
DNS的轮流查询功能，还能实现服务器的负载均衡，提高响应速度。在openldap中有一个slurpd进程，利用slurpd进程可帮助我们实现多台
ldap服务器数据的同步功能。下面简单介绍一下主、从ldap服务器的配置。</font></p><p><font size="2">slurpd运行在主服务器上，它能把主服务器上的变化通过
LDAP协议传送到从服务器上。从服务器上的变化不能传送到主服务器上，也就是说是单向同步的。主从服务器的版本最好一样，以减少兼容性问题。主从服务器
的安装方法是一样的，关键是配置文件有所不同。我的操作系统是debian sarge，在确保主从服务器能正常运行的前提下进行以下配置：</font></p><div class="itemizedlist"><ul type="disc"><li><p><font size="2">首先，把主从服务器关闭。通过以下三步操作静态同步主从服务器上的数据：</font></p><div class="orderedlist"><ol type="1"><li><p><font size="2">把主服务器上/var/lib/ldap目录下的所有数据库文件全部拷贝到从服务器的同目录中，覆盖原有文件。</font></p></li><li><p><font size="2">把主服务器上的/etc/ldap/schema目录下的所有schema文件拷贝到从服务器的同目录中，覆盖原有文件。</font></p></li><li><p><font size="2">把主服务器上/etc/ldap/slapd.conf文件拷贝到从服务器的同目录中，覆盖原有文件。</font></p></li></ol></div></li><li><p><font size="2">配置主服务器上的slapd.conf文件，取消replogfile指令前的注释符号，取消后的结果如下：</font></p><pre class="screen"><font size="2"># Where to store the replica logs for database #1<br>replogfile      /var/lib/ldap/replog<br></font></pre><p><font size="2">增加replica指令，如：</font></p><pre class="screen"><font size="2">#replace config<br>replica uri=ldap://192.168.6.195:389     #指定从服务器主机名和端口号<br>        binddn="cn=admin,dc=com"         #指定需同步的DN的管理员<br>        bindmethod=simple credentials=1  #指定验证方式和需同步的DN的管理员密码<br></font></pre></li><li><p><font size="2">配置从服务器上的slapd.conf文件，增加updatedn指令，如：</font></p><pre class="screen"><font size="2">updatedn "cn=admin,dc=com"          #与主服务器的binddn对应<br></font></pre><p><font size="2">在从服务器的配置文件中，不要包含replica和replogfile指令。</font></p></li><li><p><font size="2">先启动主服务器的slapd和slurpd，再启动从服务器的slapd。</font></p></li></ul></div><p><font size="2">配置完成后，我们可测试一下，在主服务器上修改一个目录项，在从服务器上可查看目录项的数据已同步。</font></p></div></div><div class="chapter" lang="en"><div class="titlepage"><div><div><h2 class="title"><font size="2"><a name="id2808612"></a>Chapter&nbsp;3.&nbsp;管理工具</font></h2></div></div></div><div class="toc"><p><font size="2"><b>Table of Contents</b></font></p><dl><dt><font size="2"><span class="sect1"><a href="http://www.ringkee.com/jims/technic_folder/linux/open?page=openldap.htm#id2808628">3.1. phpldapadmin</a></span></font></dt><dt><font size="2"><span class="sect1"><a href="http://www.ringkee.com/jims/technic_folder/linux/open?page=openldap.htm#id2808639">3.2. gq</a></span></font></dt><dt><font size="2"><span class="sect1"><a href="http://www.ringkee.com/jims/technic_folder/linux/open?page=openldap.htm#id2808650">3.3. CPU</a></span></font></dt><dt><font size="2"><span class="sect1"><a href="http://www.ringkee.com/jims/technic_folder/linux/open?page=openldap.htm#id2808661">3.4. JXplore</a></span></font></dt></dl></div><p><font size="2">开源的目录服务管理工具有很多，包括phpldapadmin，gq，CPU，JXplore等。这些工具可帮助我们方便维护目录服务器上的数据。这些工具各有优缺点，下面简要介绍一下，详细的内容可参考相关的官方网站。</font></p><div class="sect1" lang="en"><div class="titlepage"><div><div><h2 class="title" style="clear: both;"><font size="2"><a name="id2808628"></a>3.1.&nbsp;phpldapadmin</font></h2></div></div></div></div><div class="sect1" lang="en"><div class="titlepage"><div><div><h2 class="title" style="clear: both;"><font size="2"><a name="id2808639"></a>3.2.&nbsp;gq</font></h2></div></div></div></div><div class="sect1" lang="en"><div class="titlepage"><div><div><h2 class="title" style="clear: both;"><font size="2"><a name="id2808650"></a>3.3.&nbsp;CPU</font></h2></div></div></div></div><div class="sect1" lang="en"><div class="titlepage"><div><div><h2 class="title" style="clear: both;"><font size="2"><a name="id2808661"></a>3.4.&nbsp;JXplore</font></h2></div></div></div></div></div><div class="chapter" lang="en"><div class="titlepage"><div><div><h2 class="title"><font size="2"><a name="id2808673"></a>Chapter&nbsp;4.&nbsp;HowTo</font></h2></div></div></div><div class="toc"><p><font size="2"><b>Table of Contents</b></font></p><dl><dt><font size="2"><span class="sect1"><a href="http://www.ringkee.com/jims/technic_folder/linux/open?page=openldap.htm#id2808681">4.1. 禁止整个服务器的匿名访问</a></span></font></dt></dl></div><div class="sect1" lang="en"><div class="titlepage"><div><div><h2 class="title" style="clear: both;"><font size="2"><a name="id2808681"></a>4.1.&nbsp;禁止整个服务器的匿名访问</font></h2></div></div></div><p><font size="2">在slapd.conf配置文件中加入disallow bind_anon即可。</font></p></div></div><img src ="http://www.blogjava.net/cmd/aggbug/31754.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/cmd/" target="_blank">静夜思</a> 2006-02-21 10:56 <a href="http://www.blogjava.net/cmd/articles/31754.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>OpenLDAP快速指南</title><link>http://www.blogjava.net/cmd/articles/31753.html</link><dc:creator>静夜思</dc:creator><author>静夜思</author><pubDate>Tue, 21 Feb 2006 02:55:00 GMT</pubDate><guid>http://www.blogjava.net/cmd/articles/31753.html</guid><wfw:comment>http://www.blogjava.net/cmd/comments/31753.html</wfw:comment><comments>http://www.blogjava.net/cmd/articles/31753.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/cmd/comments/commentRss/31753.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/cmd/services/trackbacks/31753.html</trackback:ping><description><![CDATA[<font size="2">LDAP简介
<br>LDAP（轻量级目录访问协议，Lightweight Directory Access Protocol)是实现提供被称为目录服务的信息服务。
<br>目录服务是一种特殊的数据库系统，其专门针对读取，浏览和搜索操作进行了特定的优化。目录一般用来包含描
<br>述性的，基于属性的信息并支持精细复杂的过滤能力。目录一般不支持通用数据库针对大量更新操作操作需要的
<br>复杂的事务管理或回卷策略。而目录服务的更新则一般都非常简单。这种目录可以存储包括个人信息、web链结、
<br>jpeg图像等各种信息。为了访问存储在目录中的信息，就需要使用运行在TCP/IP之上的访问协议—LDAP。
<br><br>LDAP目录中的信息是是按照树型结构组织，具体信息存储在条目(entry)的数据结构中。条目相当于关系数据库中
<br>表的记录；条目是具有区别名DN（Distinguished Name）的属性（Attribute），DN是用来引用条目的，DN相当于
<br>关系数据库表中的关键字（Primary Key）。属性由类型（Type）和一个或多个值（Values）组成，相当于关系数
<br>据库中的字段（Field）由字段名和数据类型组成，只是为了方便检索的需要，LDAP中的Type可以有多个Value，
<br>而不是关系数据库中为降低数据的冗余性要求实现的各个域必须是不相关的。LDAP中条目的组织一般按照地理位置
<br>和组织关系进行组织，非常的直观。LDAP把数据存放在文件中，为提高效率可以使用基于索引的文件数据库，而不
<br>是关系数据库。类型的一个例子就是mail，其值将是一个电子邮件地址。
<br><br><br><br>LDAP的信息是以树型结构存储的，在树根一般定义国家(c=CN)或域名(dc=com)，在其下则往往定义一个或多个组织
<br>(organization)(o=Acme)或组织单元(organizational units) (ou=People)。一个组织单元可能包含诸如所有雇员、
<br>大楼内的所有打印机等信息。此外，LDAP支持对条目能够和必须支持哪些属性进行控制，这是有一个特殊的称为对
<br>象类别(objectClass)的属性来实现的。该属性的值决定了该条目必须遵循的一些规则，其规定了该条目能够及至少
<br>应该包含哪些属性。例如：inetorgPerson对象类需要支持sn(surname)和cn(common name)属性，但也可以包含可选
<br>的如邮件，电话号码等属性。
<br><br>目录设计
<br>设计目录结构是LDAP最重要的方面之一。下面我们将通过一个简单的例子来说明如何设计合理的目录结构。该例子将
<br>通过Netscape地址薄来访文。假设有一个位于美国US(c=US)而且跨越多个州的名为Acme(o=Acme)的公司。Acme希望为
<br>所有的雇员实现一个小型的地址薄服务器。 
<br>我们从一个简单的组织DN开始：　
<br><br>	dn: o=Acme, c=US
<br>Acme所有的组织分类和属性将存储在该DN之下，这个DN在该存储在该服务器的目录是唯一的。Acme希望将其雇员的信
<br>息分为两类：管理者(ou=Managers)和普通雇员(ou=Employees),这种分类产生的相对区别名(RDN,relative distinguished
<br> names。表示相对于顶点DN)就shi ： 
<br><br>	dn: ou=Managers, o=Acme, c=US
<br>	dn: ou=Employees, o=Acme, c=US
<br>在下面我们将会看到分层结构的组成：顶点是US的Acme，下面是管理者组织单元和雇员组织单元。因此包括Managers
<br>和Employees的DN组成为： 
<br><br>	dn: cn=Jason H. Smith, ou=Managers, o=Acme, c=US
<br>	dn: cn=Ray D. Jones, ou=Employees, o=Acme, c=US
<br>	dn: cn=Eric S. Woods, ou=Employees, o=Acme, c=US
<br>为了引用Jason H. Smith的通用名(common name )条目，LDAP将采用cn=Jason H. Smith的RDN。然后将前面的父条目
<br>结合在一起就形成如下的树型结构： 
<br><br>   cn=Jason H. Smith
<br>	+ ou=Managers
<br>		+ o=Acme
<br>			+ c=US
<br>				-&gt; cn=Jason H. Smith, ou=Managers, o=Acme, c=US
<br><br>现在已经定义好了目录结构，下一步就需要导入目录信息数据。目录信息数据将被存放在LDIF文件中，其是导入目录
<br>信息数据的默认存放文件。用户可以方便的编写Perl脚本来从例如/etc/passwd、NIS等系统文件中自动创建LDIF文件。
<br><br>下面的实例保存目录信息数据为testdate.ldif文件，该文件的格式说明将可以在man ldif中得到。
<br><br>在添加任何组织单元以前，必须首先定义Acme DN：　 
<br><br>dn: o=Acme, c=US
<br>objectClass: organization
<br>这里o属性是必须的 
<br><br>o: Acme
<br>下面是管理组单元的DN，在添加任何管理者信息以前，必须先定义该条目。 
<br><br>dn: ou=Managers, o=Acme, c=US
<br>objectClass: organizationalUnit
<br>这里ou属性是必须的。 
<br><br>ou: Managers
<br>第一个管理者DN： 
<br><br>dn: cn=Jason H. Smith, ou=Managers, o=Acme, c=US
<br>objectClass: inetOrgPerson
<br>cn和sn都是必须的属性： 
<br><br>cn: Jason H. Smith
<br>sn: Smith
<br>但是还可以定义一些可选的属性：
<br><br>telephoneNumber: 111-222-9999
<br>mail: headhauncho@acme.com
<br>localityName: Houston
<br><br>可以定义另外一个组织单元： 
<br><br>dn: ou=Employees, o=Acme, c=US
<br>objectClass: organizationalUnit
<br>ou: Employees
<br>并添加雇员信息如下： 
<br><br>dn: cn=Ray D. Jones, ou=Employees, o=Acme, c=US
<br>objectClass: inetOrgPerson
<br>cn: Ray D. Jones
<br>sn: Jones
<br>telephoneNumber: 444-555-6767
<br>mail: jonesrd@acme.com
<br>localityName: Houston
<br><br>dn: cn=Eric S. Woods, ou=Employees, o=Acme, c=US
<br>objectClass: inetOrgPerson
<br>cn: Eric S. Woods
<br>sn: Woods
<br>telephoneNumber: 444-555-6768
<br>mail: woodses@acme.com
<br>localityName: Houston
<br>安装配置
<br>下一步需要设置OpenLDAP来接受刚才定义的目录结构的导入及提供访问Netscape中的地址薄。在OpenLDAP邮件列
<br>表中一个常见的问题是“我如何使Netscape地址薄来使用我的LDAP服务器？”保存地址薄信息是LDAP常见的一个
<br>应用方面，这是因为它具有快速的查询和读取功能。而且OpenLDAP支持例如SSL/TLS等会话加密和目录服务器复制
<br>等功能，这样就可以实现一个非常好的开发源码解决方案。
<br><br>下面的讨论都是基于openldap-2.0.7，其支持LDAP v2和LDAP v3。LDAP v3相对于LDAP v2最重要的是添加了对传输
<br>层安全(TLS,Transport Layer Security)的支持及增加了认证方法。OpenLDAP有两种安装方式：源代码方式和打包
<br>的deb/rpm模式。可以从http://www.openldap.org/下载源代码方式或者从http://rpmfind.net/及光盘上得到RPM包
<br>方式。源代码方式安装过程如下：
<br><br>[root@radiusd src]# ar -xzvf openldap-2.0.7.tgz 
<br><br>[root@radiusd src]# cd openldap-2.0.7
<br><br>[root@radiusd openldap-2.0.7]# ./configure --prefix=/usr/local 
<br><br>这里指示openldap被安装在/usr/local目录下，当这并不是必须的。 
<br><br>[root@radiusd openldap-2.0.7]# make depend;make 
<br><br>在安装结束以前进行测试： 
<br><br>[root@radiusd openldap-2.0.7]# make test 
<br><br>[root@radiusd openldap-2.0.7]# make install 
<br><br>若出现任何编译错误，应该到OpenLDAP邮件列表去寻求帮助。你也许需要在PATH环境变量中添加如下路径：
<br>/usr/local/libexec, /usr/local/bin及/usr/local/sbin。 
<br><br>PRM包方式的安装实例如下：
<br><br>rpm －ivh openldap-2.0.7-14-i386.rpm
<br><br>rpm －ivh openldap-devel-2.0.7-14-i386.rpm
<br><br>下来需要编辑slapd.conf文件，其是slapd守护进程的配置文件。slapd进程负责响应客户应用访问目录服务请求。配置
<br>文件存放/usr/local/etc/openldap。
<br><br>为了能使用Netscape地址薄属性，需要添加一些额外的"模式"配置信息。在slapd.conf文件的开头处添加如下include
<br>内容，但是根据安装路径的不同，模式目录路径可能也不大一样。
<br><br>include		/usr/local/etc/openldap/schema/cosine.schema 
<br>include		/usr/local/etc/openldap/schema/inetorgperson.schema 
<br>在slapd.conf的定义的suffix和rootdn行修改为能反应你需要的DN： 
<br><br>suffix	"o=Acme, c=US"
<br>rootdn	"cn=root, o=Acme, c=US"
<br>这里cn=root条目是我们的管理DN，其不受任何访问控制或限制。其默认是cn=Manager，但是我希望root访问。在
<br>slapd.conf文件的末端添加如下内容，实现给Netsacpe进行目录过滤和搜索操作的读权限。所有没有授权的访问目
<br>录服务的请求都被作为匿名用户对待。下面的DN条目被格式化处理，也就是所有的空格被去掉，并且其值被逗号隔开。
<br>在访问控制，必须格式化条目否则将不能工作。 
<br><br>access to dn=".*,o=Acme,c=US"
<br>	by anonymous		read
<br>对目录的访问许可以进行精细的调节以适应各种需求。OpenLDAP 2.0管理指南有非常好的配置访问许可的文档说明。
<br>这里为了测试目的，这样的访问控制级别是足够了。 
<br><br>下面我们就将启动slapd服务器。若系统的ldap是通过RPM/DEB格式进行安装的，根据使用的Linux发布版本不同，启动
<br>脚本可能是/etc/rc.d/init.d/ldap或/etc/init.d/ldap。当然也可以手工启动来进行测试。
<br><br>slapd &amp; 
<br><br>下面测试看slapd是否在运行 
<br><br>ps -ef | grep -i slapd | grep -v grep
<br>root     15479        1  0 10:42 ?        00:00:00 slapd
<br>root     15483 15479  0 10:42 ?        00:00:00 slapd
<br>root     15484 15483  0 10:42 ?        00:00:00 slapd
<br>root     15491 15483  0 10:43 ?        00:00:00 slapd
<br>root     15492 15483  0 10:43 ?        00:00:00 slapd
<br><br>下面测试ldap的默认端口389是否被监听： 
<br><br>netstat -an | grep 389
<br>tcp	0	0 0.0.0.0:389	0.0.0.0:*	LISTEN
<br><br>到这里为止，一切看上去都很正常，下面将导入ldap信息数据到数据库中： 
<br><br>ldapadd -D "cn=root, o=Acme, c=US" -W -v -f testdata.ldif 
<br><br>我们使用-D参数和无限制的cn来捆绑目录，这样允许写信息到目录中。-W参数导致服务器需要密码才能访问。缺省的
<br>密码是在slapd.conf文件中的rootpw来设定的，默认是secre。使用该默认密码是非常危险的，因此在测试完毕以后，
<br>应该改变该密码。记得使用-v参数来进行详细输出以判断是否及如何修正出现的错误。
<br><br>测试　
<br>当数据导入结束，下一步就需要配置客户端来进行测试。Netscape地址薄支持很多目录属性，在下面的资源部分将包
<br>含Netscape地址薄API标准链结地址。下面的简单的测试实例，将使用如下属性：cn,sn,mail.telephoneNumber和
<br>localityName。地址薄中的Nickname条目是通过属性xmozillanickname来支持的，其在任何“模式”中都不是默认地
<br>被支持而需要对“模式”进行修改。本文将不设计如何修改“模式”方面。 
<br>打开Netscape的地址薄，选择File-&gt;New Directory，输入LDAP服务器的信息：
<br><br>Description: Acme Address Book
<br>LDAP Server: the IP/hostname address of your LDAP server
<br>Server Root: o=Acme, c=US 
<br><br>端口号和其他信息不需要修改。而且由于链结将以匿名用户身份进行，因此不需要设置用户名和密码。
<br><br>选择OK按钮，然后在左边的目录栏选中"Acme Address Book"，最后在"Show names containing"框中输入一个查询，
<br>例如Smith然后回车。你将可以看到返回了一行数据。
<br><br>若希望对每个组织单元得到独立的列表输出，你可以在Netsacpe中创建另外一个新的目录条目：
<br><br>Description: Acme Managers
<br>LDAP Server: the IP/hostname address of your LDAP server
<br>Server Root: ou=Managers, o=Acme, c=US 
<br><br>这将导致仅仅在Acem目录中搜索Nanagers组织单元，也就是实现了一定的过滤。当然可以对Employees进行同样的限制。 
<br><br>错误处理
<br>可能会在测试中遇到如下问题： 
<br>若目录服务不能返回数据，则编辑slapd.conf file并添加"Loglevel 1"。将导致slapd服务进程记录所有的信息到
<br>syslog LOCAL4。同样需要编辑 /etc/syslog.conf文件来将这些信息定向到一个单独的文件来便于调试。检查该
<br>log文件以确保slapd服务器启动正常没有任何错误信息。这同样会详细记录每个请求服务的信息。 
<br>确保PATH环境包括所有的ldap命令的路径。 
<br>若导入数据失败，仔细察看文件LDIF文件格式。更高一级的条目必须首先出现，从你的目录数顶端开始，直到叶子节点。 
<br>需要有root身份来启动slapd，除非改变slapd到超过1024以上的端口。 
<br>检查slapd.conf文件格式，若你的访问控制列表没有被格式化，则可能导致链结服务器失败。 
<br>使用Netscape地址薄来访问LDAP是掌握使用LDAP概念一个非常好的方法。下面是一些和LDAP相关的一些链结资源，
<br>包括一些使用LDAP认证一些常见服务的方法如：系统登录及Samba等。 
<br><br>资源
<br>http://www.openldap.org/ - OpenLDAP Web Site
<br>http://www.openldap.org/doc/admin/ - OpenLDAP 2.0 Administrators Guide
<br>http://www.hklc.com/ldapschema/ - LDAP Schema Browser
<br>http://www.padl.com/pam_ldap.html - Pam-LDAP Authentication Module (they also have some Perl migration scripts)
<br>http://perl-ldap.sourceforge.net/ - Perl LDAP modules
<br>http://www.unav.es/cti/ldap-smb-howto.html - Samba-PDC LDAP Howto
<br>http://developer.netscape.com/docs/manuals/communicator/addrapi.htm - Netscape Address Book API Specification
</font>


























































<img src ="http://www.blogjava.net/cmd/aggbug/31753.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/cmd/" target="_blank">静夜思</a> 2006-02-21 10:55 <a href="http://www.blogjava.net/cmd/articles/31753.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>[导入]APPFUSE wiki中文文档问题解决了</title><link>http://www.blogjava.net/cmd/articles/31766.html</link><dc:creator>静夜思</dc:creator><author>静夜思</author><pubDate>Wed, 30 Nov 2005 23:31:00 GMT</pubDate><guid>http://www.blogjava.net/cmd/articles/31766.html</guid><wfw:comment>http://www.blogjava.net/cmd/comments/31766.html</wfw:comment><comments>http://www.blogjava.net/cmd/articles/31766.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/cmd/comments/commentRss/31766.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/cmd/services/trackbacks/31766.html</trackback:ping><description><![CDATA[<p>原来<a href="http://raibledesigns.com/wiki/Wiki.jsp?page=AppFuse">appfuse wiki</a>上的中文文档编辑有问题，保存以后重新编辑发现所有的字符都回变成unicode，后来连显示都有问题了，凡是使用了inner link的地方使用了中文就会在页面上直接显示Unicode，但是在我自己机器上测试JSPWiki一切正常。前天把自己的配置文件提交到appfuse的 <a href="http://issues.appfuse.org/browse/APF">Issue Tracker</a> 。今天Matt说明是encoding配置有问题，原来是没有使用UTF - 8，所以又重新把自己翻译的文档整理好了。现在可以正常浏览了。不过 Rock Sun翻译的我没有原文无法修改，Matt 联系他作相应的更新，以后大家应该可以自由的在Appfuse Wiki上面编辑中文了。:)</p><img src="http://blog.donews.com/skyhero/aggbug/643368.aspx" height="1" width="1"><br>文章来源:<a href="http://blog.donews.com/skyhero/archive/2005/11/30/643368.aspx">http://blog.donews.com/skyhero/archive/2005/11/30/643368.aspx</a><img src ="http://www.blogjava.net/cmd/aggbug/31766.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/cmd/" target="_blank">静夜思</a> 2005-12-01 07:31 <a href="http://www.blogjava.net/cmd/articles/31766.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>[导入]无法通过Test-canoo的问题解决方案</title><link>http://www.blogjava.net/cmd/articles/31767.html</link><dc:creator>静夜思</dc:creator><author>静夜思</author><pubDate>Thu, 17 Nov 2005 17:30:00 GMT</pubDate><guid>http://www.blogjava.net/cmd/articles/31767.html</guid><wfw:comment>http://www.blogjava.net/cmd/comments/31767.html</wfw:comment><comments>http://www.blogjava.net/cmd/articles/31767.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/cmd/comments/commentRss/31767.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/cmd/services/trackbacks/31767.html</trackback:ping><description><![CDATA[在appfuse 1.8.2安装后，运行ant test-canoo或者ant test-jsp会发现无法通过测试。本文说明如何解决这个问题。 <img src="http://blog.donews.com/skyhero/aggbug/630206.aspx" height="1" width="1"><br>文章来源:<a href="http://blog.donews.com/skyhero/archive/2005/11/17/630206.aspx">http://blog.donews.com/skyhero/archive/2005/11/17/630206.aspx</a><img src ="http://www.blogjava.net/cmd/aggbug/31767.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/cmd/" target="_blank">静夜思</a> 2005-11-18 01:30 <a href="http://www.blogjava.net/cmd/articles/31767.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>[导入]Appfuse开发指南</title><link>http://www.blogjava.net/cmd/articles/31769.html</link><dc:creator>静夜思</dc:creator><author>静夜思</author><pubDate>Sun, 19 Jun 2005 23:18:00 GMT</pubDate><guid>http://www.blogjava.net/cmd/articles/31769.html</guid><wfw:comment>http://www.blogjava.net/cmd/comments/31769.html</wfw:comment><comments>http://www.blogjava.net/cmd/articles/31769.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/cmd/comments/commentRss/31769.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/cmd/services/trackbacks/31769.html</trackback:ping><description><![CDATA[<img src="http://blog.donews.com/skyhero/aggbug/437105.aspx" height="1" width="1"><br>文章来源:<a href="http://blog.donews.com/skyhero/archive/2005/06/19/437105.aspx">http://blog.donews.com/skyhero/archive/2005/06/19/437105.aspx</a><img src ="http://www.blogjava.net/cmd/aggbug/31769.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/cmd/" target="_blank">静夜思</a> 2005-06-20 07:18 <a href="http://www.blogjava.net/cmd/articles/31769.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>[导入]AppFuse快速起步指南</title><link>http://www.blogjava.net/cmd/articles/31770.html</link><dc:creator>静夜思</dc:creator><author>静夜思</author><pubDate>Sun, 19 Jun 2005 23:15:00 GMT</pubDate><guid>http://www.blogjava.net/cmd/articles/31770.html</guid><wfw:comment>http://www.blogjava.net/cmd/comments/31770.html</wfw:comment><comments>http://www.blogjava.net/cmd/articles/31770.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/cmd/comments/commentRss/31770.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/cmd/services/trackbacks/31770.html</trackback:ping><description><![CDATA[<img src ="http://blog.donews.com/skyhero/aggbug/437099.aspx" width = "1" height = "1" /><br>文章来源:<a href='http://blog.donews.com/skyhero/archive/2005/06/19/437099.aspx'>http://blog.donews.com/skyhero/archive/2005/06/19/437099.aspx</a><img src ="http://www.blogjava.net/cmd/aggbug/31770.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/cmd/" target="_blank">静夜思</a> 2005-06-20 07:15 <a href="http://www.blogjava.net/cmd/articles/31770.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>[导入]appfuse快速开发应用程序指导手册</title><link>http://www.blogjava.net/cmd/articles/31771.html</link><dc:creator>静夜思</dc:creator><author>静夜思</author><pubDate>Thu, 16 Jun 2005 21:31:00 GMT</pubDate><guid>http://www.blogjava.net/cmd/articles/31771.html</guid><wfw:comment>http://www.blogjava.net/cmd/comments/31771.html</wfw:comment><comments>http://www.blogjava.net/cmd/articles/31771.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/cmd/comments/commentRss/31771.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/cmd/services/trackbacks/31771.html</trackback:ping><description><![CDATA[本文提供如何利用appfuse快速建立一个简单应用程序的简明操作手册。<img src ="http://blog.donews.com/skyhero/aggbug/433493.aspx" width = "1" height = "1" /><br>文章来源:<a href='http://blog.donews.com/skyhero/archive/2005/06/16/433493.aspx'>http://blog.donews.com/skyhero/archive/2005/06/16/433493.aspx</a><img src ="http://www.blogjava.net/cmd/aggbug/31771.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/cmd/" target="_blank">静夜思</a> 2005-06-17 05:31 <a href="http://www.blogjava.net/cmd/articles/31771.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>[导入]appfuse使用Taperstry框架（一）——创建Tapestry框架页面</title><link>http://www.blogjava.net/cmd/articles/31772.html</link><dc:creator>静夜思</dc:creator><author>静夜思</author><pubDate>Mon, 24 Jan 2005 22:04:00 GMT</pubDate><guid>http://www.blogjava.net/cmd/articles/31772.html</guid><wfw:comment>http://www.blogjava.net/cmd/comments/31772.html</wfw:comment><comments>http://www.blogjava.net/cmd/articles/31772.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/cmd/comments/commentRss/31772.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/cmd/services/trackbacks/31772.html</trackback:ping><description><![CDATA[<img src ="http://blog.donews.com/skyhero/aggbug/255120.aspx" width = "1" height = "1" /><br>文章来源:<a href='http://blog.donews.com/skyhero/archive/2005/01/24/255120.aspx'>http://blog.donews.com/skyhero/archive/2005/01/24/255120.aspx</a><img src ="http://www.blogjava.net/cmd/aggbug/31772.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/cmd/" target="_blank">静夜思</a> 2005-01-25 06:04 <a href="http://www.blogjava.net/cmd/articles/31772.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>[导入]Appfuse开发实践（五）—— 增加校验和列表页面 </title><link>http://www.blogjava.net/cmd/articles/31773.html</link><dc:creator>静夜思</dc:creator><author>静夜思</author><pubDate>Thu, 30 Dec 2004 09:08:00 GMT</pubDate><guid>http://www.blogjava.net/cmd/articles/31773.html</guid><wfw:comment>http://www.blogjava.net/cmd/comments/31773.html</wfw:comment><comments>http://www.blogjava.net/cmd/articles/31773.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/cmd/comments/commentRss/31773.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/cmd/services/trackbacks/31773.html</trackback:ping><description><![CDATA[<img src ="http://blog.donews.com/skyhero/aggbug/220109.aspx" width = "1" height = "1" /><br>文章来源:<a href='http://blog.donews.com/skyhero/archive/2004/12/30/220109.aspx'>http://blog.donews.com/skyhero/archive/2004/12/30/220109.aspx</a><img src ="http://www.blogjava.net/cmd/aggbug/31773.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/cmd/" target="_blank">静夜思</a> 2004-12-30 17:08 <a href="http://www.blogjava.net/cmd/articles/31773.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>[导入]Appfuse开发实践（四）—— 创建Webwork 框架的 Actions和JSP</title><link>http://www.blogjava.net/cmd/articles/31774.html</link><dc:creator>静夜思</dc:creator><author>静夜思</author><pubDate>Wed, 22 Dec 2004 21:44:00 GMT</pubDate><guid>http://www.blogjava.net/cmd/articles/31774.html</guid><wfw:comment>http://www.blogjava.net/cmd/comments/31774.html</wfw:comment><comments>http://www.blogjava.net/cmd/articles/31774.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/cmd/comments/commentRss/31774.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/cmd/services/trackbacks/31774.html</trackback:ping><description><![CDATA[<img src ="http://blog.donews.com/skyhero/aggbug/211354.aspx" width = "1" height = "1" /><br>文章来源:<a href='http://blog.donews.com/skyhero/archive/2004/12/22/211354.aspx'>http://blog.donews.com/skyhero/archive/2004/12/22/211354.aspx</a><img src ="http://www.blogjava.net/cmd/aggbug/31774.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/cmd/" target="_blank">静夜思</a> 2004-12-23 05:44 <a href="http://www.blogjava.net/cmd/articles/31774.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>[导入]Appfuse开发实践（二）——创建DAO对象</title><link>http://www.blogjava.net/cmd/articles/31776.html</link><dc:creator>静夜思</dc:creator><author>静夜思</author><pubDate>Mon, 20 Dec 2004 22:06:00 GMT</pubDate><guid>http://www.blogjava.net/cmd/articles/31776.html</guid><wfw:comment>http://www.blogjava.net/cmd/comments/31776.html</wfw:comment><comments>http://www.blogjava.net/cmd/articles/31776.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/cmd/comments/commentRss/31776.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/cmd/services/trackbacks/31776.html</trackback:ping><description><![CDATA[<img src="http://blog.donews.com/skyhero/aggbug/209107.aspx" height="1" width="1"><br>文章来源:<a href="http://blog.donews.com/skyhero/archive/2004/12/20/209107.aspx">http://blog.donews.com/skyhero/archive/2004/12/20/209107.aspx</a><img src ="http://www.blogjava.net/cmd/aggbug/31776.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/cmd/" target="_blank">静夜思</a> 2004-12-21 06:06 <a href="http://www.blogjava.net/cmd/articles/31776.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>[导入]Appfuse里面带的Ant任务列表</title><link>http://www.blogjava.net/cmd/articles/31777.html</link><dc:creator>静夜思</dc:creator><author>静夜思</author><pubDate>Sun, 19 Dec 2004 21:48:00 GMT</pubDate><guid>http://www.blogjava.net/cmd/articles/31777.html</guid><wfw:comment>http://www.blogjava.net/cmd/comments/31777.html</wfw:comment><comments>http://www.blogjava.net/cmd/articles/31777.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/cmd/comments/commentRss/31777.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/cmd/services/trackbacks/31777.html</trackback:ping><description><![CDATA[Appfuse自带的Ant任务详细说明。<img src ="http://blog.donews.com/skyhero/aggbug/208014.aspx" width = "1" height = "1" /><br>文章来源:<a href='http://blog.donews.com/skyhero/archive/2004/12/19/208014.aspx'>http://blog.donews.com/skyhero/archive/2004/12/19/208014.aspx</a><img src ="http://www.blogjava.net/cmd/aggbug/31777.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/cmd/" target="_blank">静夜思</a> 2004-12-20 05:48 <a href="http://www.blogjava.net/cmd/articles/31777.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>[导入]Appfuse实践(一)——配置安装</title><link>http://www.blogjava.net/cmd/articles/31778.html</link><dc:creator>静夜思</dc:creator><author>静夜思</author><pubDate>Fri, 17 Dec 2004 13:54:00 GMT</pubDate><guid>http://www.blogjava.net/cmd/articles/31778.html</guid><wfw:comment>http://www.blogjava.net/cmd/comments/31778.html</wfw:comment><comments>http://www.blogjava.net/cmd/articles/31778.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/cmd/comments/commentRss/31778.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/cmd/services/trackbacks/31778.html</trackback:ping><description><![CDATA[<img src ="http://blog.donews.com/skyhero/aggbug/205662.aspx" width = "1" height = "1" /><br>文章来源:<a href='http://blog.donews.com/skyhero/archive/2004/12/17/205662.aspx'>http://blog.donews.com/skyhero/archive/2004/12/17/205662.aspx</a><img src ="http://www.blogjava.net/cmd/aggbug/31778.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/cmd/" target="_blank">静夜思</a> 2004-12-17 21:54 <a href="http://www.blogjava.net/cmd/articles/31778.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>[导入]Appfuse简介</title><link>http://www.blogjava.net/cmd/articles/31779.html</link><dc:creator>静夜思</dc:creator><author>静夜思</author><pubDate>Fri, 17 Dec 2004 13:53:00 GMT</pubDate><guid>http://www.blogjava.net/cmd/articles/31779.html</guid><wfw:comment>http://www.blogjava.net/cmd/comments/31779.html</wfw:comment><comments>http://www.blogjava.net/cmd/articles/31779.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/cmd/comments/commentRss/31779.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/cmd/services/trackbacks/31779.html</trackback:ping><description><![CDATA[<img src ="http://blog.donews.com/skyhero/aggbug/205661.aspx" width = "1" height = "1" /><br>文章来源:<a href='http://blog.donews.com/skyhero/archive/2004/12/17/205661.aspx'>http://blog.donews.com/skyhero/archive/2004/12/17/205661.aspx</a><img src ="http://www.blogjava.net/cmd/aggbug/31779.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/cmd/" target="_blank">静夜思</a> 2004-12-17 21:53 <a href="http://www.blogjava.net/cmd/articles/31779.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>