凌华 さんのプロフィール张凌华フォトブログリストその他 ツール ヘルプ

ブログ


12月6日

Frails诞生,JSF果然应该这样走的

曾几何时 , 就觉得JSF应该像EJB3那样使用annotations的方式,而抛弃掉那繁冗的像懒婆娘的裹脚那样臭长的xml配置文件 。 如今,终于也有人意识到了这点 ,并且付诸了实现 。(可惜不是官方付诸实现) 。 那就是Frails  。

你可以在Souceforge找到这个项目 https://sourceforge.net/projects/frails 。目前Frails的最新版本是2.0。
Frails的特性如下:

◆Frails 让开发者使用约定(习惯)和注解(annotations)来省略在 faces-config.xml 文件中 managed-bean 和 navigation-rule的配置。
◆Frails 提供一个更简单的方法来使用 Faces API。
◆Frails 重定向非 faces 请求为一个 faces 请求,这样可以避免因为URL输入的疏忽导致的异常。
◆Frails 提供一些预定义的 Managed Bean 基类让开发者扩展 。
◆Frails 可以让开发者在 Managed Bean 中使用注解(Annotation)来支持AOP功能,如 函数拦截(method interception)。
◆Frails 提供 Managed Bean 属性的injection 和 outjection
◆Frails 中的Hibernate GenericDao 对象提供了基本的 CRUD 函数 和 其他一些有用(常用)的数据访问函数。
◆Frails 还提供了一些有用的注解(annotations)来简化JSF的开发。
◆Frails 完全支持 jsf 1.1 和 jsf 1.2 规范,并且可以自动的探测你使用的版本。
◆Frails 提供了对action Method 和 Listener 函数的增强,在这些函数中现在你可以传递参数了。

是不是很令人惊喜!

不需要配置faces-config.xml
再也不需要在faces-config.xml里一个个的写导航规则,注册ManageBean了。在Frails框架下注册一个ManageBean只需要在Bean的Class文件前加上@DefMbean这样的Annotation就可以了。而导航会根据一定的导航规则实现。比如导航去index.jsp就可以直接返回"index"就可以了,当然,还有更灵活的方式。

轻松实现GET

URL里加上参数-值对能够轻松的解析出来了,甚至不需要写方法。在一个String属性前加上@Param(name=paramName)后,这个属性就自动获得来自URL里paramName的值。如:

@Param(name
=
"
paraName
"
)
String paVa;

URL是./xxx.jsp?paraName=Yes 那么paVa的值就是Yes。
而在一个无返回方法前加上@PreRender后,这个方法将回在页面渲染前调用,提前获取一些相关的信息。如:

@PreRender
public
void
preRender()
{   
//
do something for rendering the page
}

当然了,Frails提供了让方法在各个声明周期得到调用的Annotations,不仅仅是预渲染。

从烦琐的验证中解脱
Frails使得你能在申明一个属性的时候就能快速简单的加上一些限制条件,成为这个属性的验证器。如下:

@Prop
@ValidateNumber(required
=

true
, max
=

"
33
"
, id
=

"
indexForm:id
"
, message
=

"
error.id.invalid
"
)

int
id;

这样就使得在setID的时候根据需要进行验证,而不必再写validate方法。

使用简单、功能强大的JSFHelper
例如,如果一个对象要放入HttpSession。使用标准的 Faces API,代码如下:

FacesContext.getCurrentInstance().getExternalContext()
.getSessionMap().put(name, val);

如果使用 JSFHelper,代码是如此的简单:

JSFHelper.putIntoSession(name, val);

当然,JSFHelper还有很多其他的功能。如导航功能,能将页面之间的数据最大化独立开,之间的联系只需要规定有什么属性就可以了。(以后会详细介绍:Frails下网站快速开发模式)

还有很多特性,帮助你快速开发JSF。当你真正体验过Frails后,你就会发现JSF一统天下的时代的来临!

11月27日

关于jsf 生命周期

在目前的规范中,jsf还是很不完善的,这也就导致了为什么jsf还是不能成为目前的主流框架。先不去谈论这些弊端,还是先看看一下jsf具体是如何运作的。


对于jsf规范,个人觉得和其他框架相比,最大的区别,可能在于jsf划分了web 请求的生命周期。like ejb一样,web 请求也是有生命周期的。虽然,在其他的框架中,也可以看到相关的生命周期,但还是没有jsf划分的清晰。也许,这也是jsf的一大特色。


对于生命周期的执行,所有的操作都归结到Lifecycle这个接口。接口包括了两个主要的方法:
    public abstract void execute(FacesContext context) throws FacesException;和
    public abstract void render(FacesContext context) throws FacesException;
   前者是用来执行各个生命周期的阶段,也就是除了render之外的其他五个阶段,而且是按照相应的顺序执行。而render,是执行最后一个阶段,展示页面。可能有人不太理解,为什么不把两个方法合并成一个方法,刚开始,我也是这么认为。既然已经定义了相应的Phase,何必要把最后的render过程分离出来。看了sun 的RI实现类,发现在render之前需要进行context.getResponseComplete()判断,可能规范中,认为render是必须要执行的阶段,其他的阶段可以跳过,所以分离了相应的方法,同时在执行前,为了避免重复输出,需要对render过程进行特殊的处理.
   规范中定义了6个阶段,从下面的流程图中可以看到。
lct
简单介绍一下每个阶段的工作:
RESTORE_VIEW:查找原有的view ,恢复原有的状态,如果没有,则调用ViewHandler.createView,如果为post操作,则按照顺序执行各个阶段。
否则执行RENDER_RESPONSE阶段。
APPLY_REQUEST_VALUES:读取客户端参数,处理各个组件的processDecodes方法,内部调用decode方法,由Renderer执行decode方法
PROCESS_VALIDATIONS:执行组件的processValidators方法,对于UIInput执行validate方法,用于绑定值,调用convert,和validate
UPDATE_MODEL_VALUES:执行组件的processUpdates方法,对于UIViewRoot,执行broadcastEvents和notifyPhaseListeners
所有的UIInput,执行updateModel方法。
INVOKE_APPLICATION:调用UIViewRoot.processApplication方法。这一过程主要读取相应的action配置,如果存在action,则调用action,也就是调用应用逻辑。在执行完相应的逻辑后,查询action是否返回值,如果有,由navigationHandler去读取下一个view id。
RENDER_RESPONSE:展示view,调用ViewHandler.renderView,展示view。
每个阶段定义定义的都比较清晰,有一点需要注意的是,在处理请求时,并不一定会执行每个阶段,可能其中会直接跳到最后的render response阶段。举例来说,如果validator时,存在错误信息,那么就会直接到render response阶段,而下一个阶段不会执行。

揭开J2EE集群的面纱[转]

对于理解J2EE集群技术不错的文章,虽然是Sun的技术人员撰写的,基本观点还算客观,内容深浅恰当,非常适合刚刚接触集群的朋友阅读,故此大胆翻译过来,放在这里和大家共享,错误难免,欢迎指正。
原链接
Uncover the hood of J2EE Clustering
1 前言
越来越多的关键任务和大型应用正运行在J2EE平台上,象银行之类的应用要求很高的可用性(HA),大型系统比如google和Yahoo则要求更好的伸缩性。今天高可用性和伸缩性的重要性对于互联世界日益增长,最著名的证明是1999年eBay中断了22小时的服务,原因是超过230万次的拍卖,最终导致 eBay股票下跌了9.2个百分点。
J2EE集群是最常用的技术,用来提供高可用性和伸缩性的容错服务。但由于缺乏J2EE规范的支持,J2EE提供厂商实现的集群尽不相同,引起许多J2EE架构师和开发者的麻烦,比如下面:
1. 为什么附带集群能力的商业J2EE服务器如此昂贵?(比不带集群能力的贵10倍)
2. 为什么我在单机上创建的应用无法运行在集群模式?
3. 为什么我的应用在集群时运行缓慢但不集群时却很快速?
4. 为什么我的集群应用不能与其他提供商的服务器通信?
2 基本术语
讨论不同实现之前,理解集群技术的概念是很有意义的,我希望不仅能给你提供关于J2EE集群产品基本的设计理念和概念,还可以概括性的描绘不同的集群实现,使它们更容易被理解。
2.1 伸缩性(Scalability)
大型系统很难预测终端用户的数量与行为,伸缩性是指系统可以支持用户的快速增长。提高服务器同时处理并发会话的最直觉的方式就是增加服务器资源(内存, CPU,或硬盘),集群是解决伸缩性的另一种可选方式。它允许一组服务器分担处理繁重的任务,而逻辑上就象一台服务器一样。
2.2 高可用性(High Availability)
提高伸缩性的单服务器解决方案(添加内存和CPU)是并不强壮的办法,因为单点失效原因。关键任务应用不能容忍服务中断哪怕一分钟。它要求任何时候都可以合理地可预期的响应时间访问这些服务,集群可以通过提供额外的服务器使其在一台服务器实效时提供服务,从而提高可用性。
2.3 负载均衡(Load balancing)
负载均衡是集群技术之后的一个关键技术,通过分发请求到不同的服务器来提高可用性和更好的性能。负载均衡器可以是一个Servlet或插件(例如a linux box using ipchains),除分发请求之外,负载均衡器应负责其他一些重要的任务,例如“会话黏附”,使得某个用户会话始终在一台服务器上存活,还有“心跳检测”,防止分发请求到失效的服务器。有时候负载均衡器也参与到“失效转移”处理。
2.4 容错(Fault Tolerance)
高可用性数据不必是严格正确数据.在J2EE集群中,当一个服务器实例失效时,服务仍然可用,因为新的请求可由其他冗余的服务器实例处理。但如果请求正在处理当中时服务器实例失效,则不能能得到正确的数据。然而容错服务则总是保证严格正确的行为。
2.5 失效转移(Failover)
失效转移是另一项使得集群实现容错的关键技术。通过选择集群中另一个节点,原始节点失效时处理将继续下去。失效转移可以显式地编码也可以由低层平台自动执行。
2.6 等幂方法(Idempotent methods)
可以用相同的参数重复调用的方法,并且总是得到相同的结果。这些方法不应该影响系统状态,可以被重复地调用而不必担心改变系统。例如, “getUsername()” 是等幂方法,而“deleteFile()” 就不是等幂方法。等幂是HTTP会话和EJB失效转移的重要概念。
3 J2EE集群是什么?
一个幼稚的问题,不是吗?但我仍然用一些话语和图表来回答它。通常J2EE集群技术包括“负载均衡”和“失效转移”。
image
图1 负载均衡
如图1所示,有很多客户端对象并发发送请求到目标对象。负载均衡器则位于调用者和被调用者之间,分发请求到冗余的拥有同样功能的对象上。用此方法可以实现高可用性和高性能。
image
图2:失效转移
如图2所示,失效转移与负载均衡不同。有时候,客户端对象对目标对象发起连续的方法请求。如果目标对象在请求之间失效,失效转移系统应该检测到并将后续请求转向导另一个可用的对象,容错也可用此途径实现。
如果想了解更多J2EE集群,你应该问更多的基本问题,例如:“什么类型的对象可以被集群?”,“在我的J2EE代码中什么地方出现负载均衡和失效转移?”,对于理解J2EE集群的原理,这些都是非常好的问题。实际上,不是所有对象可以被集群,也不是任何地方都可以负载均衡和失效转移!看看下列代码:
image
图3:代码例子
当“instance1” 失效时,“Class A”的“business()”方法会负载均衡和失效转移到另一个B实例吗?不,不会。对于负载均衡和失效转移,必须要在调用者和被调用者之间存在一个拦截器,用来分发或者转向方法调用到不同的对象上。A和B的实例对象运行在同一个JVM上,并紧密耦合。很难在方法调用之间添加分发逻辑。
所以,什么类型的对象可以被集群?---只有发布在分布式结构的对象
所以,什么地方会发生负载均衡和失效转移呢?--只有在调用一个分布式对象的方法时
image
图4:分布式对象
在分布式环境,如图4所示,调用者和被调用者被分离为不同边界的运行容器,边界是JVM,进程或机器。
当客户端调用目标对象时,在目标对象容器内执行功能。客户端和目标对象通过标准的网络协议通信。利用这些特性添加机制到方法调用路由中,以实现负载均衡和失效转移。
如图4所示,浏览器可以通过HTTP协议调用远程JSP对象。JSP在Web服务器上执行,浏览器不关心执行过程,它只需要结果。在这样的场景中,有些东西可以位于浏览器和web服务器之间用来实现负载均衡和失效转移功能。在J2EE中,分布式技术包括:JSP(Servlet),JDBC ,EJB,JNDI,JMS,Webservices和其他。当分布式方法被调用时可以发生负载均衡和失效转移。下面我们会讨论细节。
4 web层集群实现
web层集群是J2EE集群中最重要和基础的功能。web层集群技术包括:Web负载均衡和HTTPSession失效转移。
4.1 web负载均衡
J2EE提供商有很多方法实现web负载均衡,基本的,在浏览器和web服务器之间放置负载均衡器。
image
图5:web负载均衡
负载均衡器可以是硬件产品如F5 负载均衡器,也可以是另一个带有负载均衡插件的web服务器。简单的带有ipchains的linux box也可以执行负载均衡的功能。无论何种技术,负载均衡器通常有以下特性:
*实现负载均衡算法
当客户端请求达到时,负载均衡器决定如何分发请求到后端服务器实例。通常的算法包括Round-Robin,(指的是使组中所有成员都能均等地以某种合理的顺序被选择到的一种安排方式, 通常是从列表的头至尾,而后周而复始),随机和基于权重。负载均衡器试图使每个服务器实例完成相同的工作量,但以上算法都不能真正达到理想的等量,因为他们只是基于分发到某个服务器实例请求的数量来计算。一些聪明的负载均衡器实现特别的算法,他们会测试每个服务器的工作负载来决定分发请求。
*心跳检测
当一些服务器实例失效时,负载均衡器应该检测到失效,并不再分发请求到失效的服务器上。负载均衡器也需要监视服务器恢复状况,并给它重新分发请求。
*会话粘连
几乎每个web应用都有会话状态,使得记住用户是否登陆或购物车变得非常简单。因为HTTP协议本身没有状态,会话状态需要存在某个地方,并和浏览会话关联起来,可以在下一次请求时很容易地被检索到。当负载均衡时,最好选择分发相同的浏览器会话请求到同一台服务器实例上,否则应用工作会不正常。
因为会话状态存储在某个web服务器实例的内存中,“会话粘连”对于负载均衡非常重要。但,如果一台服务器实例因为某些原因失效(比如断电),那么该服务器上多有的会话状态就丢失了。负载均衡器检测到了该失效,不再向其分发请求。但存储在失效服务器上的会话的那些请求将丢失会话信息,这种情况会引起错误,因此会话失效转移到来了!
4.2 HTTPSession失效转移
几乎所有的流行的J2EE提供商都在他们的集群产品中实现了HTTPSession失效转移,确保所有的客户请求可以不因为一些服务器实例的失效而丢失任何会话状态。如图6所示,当浏览器访问一个有状态的web应用(第1,2步),此应用可以在内存中创建会话对象存储信息以备后用。同时,向浏览器送回HTTPSession ID,用来标记该会话对象(第3步)。浏览器以cookie的方式存储该ID,并会在下次发起请求时送回web服务器。为了支持会话失效转移,会话对象将在某时某处将它自己备份(第4步)。负载均衡器可以检测失效(第5,6步),分发请求到其他服务器实例(第7步)。因为会话对象已经被备份,新的web服务器实例可以恢复该会话并正确地处理请求。
image
图6:HTTPSession失效转移
为了实现以上功能,注意下列问题:
*全局HTTPSession ID
如上所述,HTTPSession ID用来标记某个服务器实例内存中的会话对象。在J2EE中,HTTPSession ID依赖于JVM实例。每个JVM实例可以控制多个web应用,每个应用又可以控制许多不同用户的HTTPSession,HTTPSession ID是存取当前JVM实例中相关会话对象的键。在会话失效转移实现中,要求不同的JVM实例不应该产生两个同样的HTTPSession ID,因为失效时,在一个JVM的会话可能在另一个JVM中备份和恢复。所以,应该建立全局的HTTPSession ID机制。
*如何备份会话状态
如何备份会话状态是一个使得J2EE服务器与众不同的关键因素。不同的提供商实现不同。
*备份频率和粒度
HTTPSession状态备份有性能消耗,包括CPU周期,网络带宽和写磁盘和数据库的IO消耗。备份频率和粒度严重影响集群的性能。
4.3 数据库持久化方案
几乎所有的J2EE集群产品允许你通过JDBC接口使用关系数据库备份会话状态。如图7所示,该方案只是简单地让服务器实例序列化会话内容,并在适当的时候写入数据库。当失效转移发生时,另一个可用的服务器实例负责失效的服务器,并从数据库中恢复会话状态。对象序列化是关键点,使得内存会话对象数据持久化和可移动。更多对象序列化信息请参考
http://java.sun.com/j2se/1.5.0/docs/guide/serialization/index.html
image
图7:备份会话状态到数据库
象数据库事务一样代价昂贵,该方案的主要缺点是有限的伸缩性:当存储大量会话中的对象时。许多使用数据库会话持久化的应用服务器鼓吹HTTPSession 的最小限度的使用,但其实他们还限制了你的应用架构和设计,特别是如果你使用HTTPSession存储缓冲用户数据。
数据库方案也有一些优点.
*实现简单。江会话备份处理和请求处理分离,使得集群易于管理并比较健壮。
*会话可以失效转移到任何其它机器上,因为使用共享数据库。
*会话数据可以在整个集群失效后仍然生存。
4.4 内存复制方案
由于性能问题,一些J2EE服务器(Tomcat,JBoss,WebLogic和WebSphere)提供另一种实现:内存复制。
image
图8:内存复制
基于内存的会话持久化存储会话信息,该方案由于性能好非常流行。和数据库方案比较,在原始服务器和备份服务器之间直接网络通信是非常轻量级的。请注意该方案,数据库方案中的“恢复”阶段是不需要的。请求到来时,所有的会话数据已经存在于备份服务器的内存中了。
”JavaGroups”是目前JBoss和Tomcat集群的通讯层,JavaGroups 是一个实现群通讯和管理的工具包。它提供的核心特性是“Group membership protocols”和“message multicast”更多信息请参考http://www.jgroups.org/javagroupsnew/docs/index.html
4.4.1 Tomcat方案:多机复制
内存复制存在很多变体方案。一中办法是跨集群中的多节点复制会话状态。Tomcat5以此法实现内存复制。
image
图9:多机复制
如图9所示,当一个服务器实例的会话状态改变时,它将向集群中所有服务器备份它的数据。如果其中一个实例失效,负载均衡器可选择其它任何可用的服务器实例作为其备份服务器。但该方案在伸缩性上有缺陷。如果集群中有很多实例,网络通讯消耗就不可忽略了,这种情况将严重降低性能和网络交通,成为瓶颈问题。
4.4.2 Weblogic, Jboss and WebSphere的方案--配对复制
因为性能和伸缩性问题,Weblogic, JBoss and Websphere 都提供了另一种方法实现内存复制:每个服务器实例选择任一个备份实例存储会话信息,如图10。
按这种方式,每个服务器实例拥有它自己的配对备份服务器而不是所有其它服务器。该方案消除了伸缩性问题。
image
图10:配对复制
虽然该方案实现了会话失效转移的高性能和高伸缩性,仍然有下列限制:
*给负载均衡器带来了复杂性。当一台服务器实例失效,负载均衡器必须记住哪个实例是其配对备份服务器,缩小了负载均衡器 的选择范围,一些硬件均衡器不能在此结构中使用。
* 除了处理正常的请求,服务器同时负责复制。每个服务器实例,请求处理容量被减小,因为CPU周期需要完成复制职责。
*除了正常处理,许多内存被用来存储备份会话状态,即使没有失效转移发生,也增加了JVM垃圾收集的消耗。
*集群中的服务器实例组成复制配对。所以如果会话粘连失效的主服务器,负载均衡器能发送失效转移请求到备份服务器上。备份服务器将在进入的请求中遇到麻烦,引起性能问题。
为了克服以上限制,不同的提供商的变种产生了。Weblogic 为每个会话而不是服务器定义了备份配对。当一台服务器实例失效,失效服务器上的会话被分散到不同备份服务器上,并且被分散地装载。
4.4.4 IBM的方案--中央状态服务器
Websphere 另一个可用的内存复制选择是:备份会话信息到一个中央状态服务器,如图11。
image
图11:中央服务器复制
和数据库方案非常相似,不同的是一个专门的“会话备份服务器”代替了数据库服务器。该方案兼有数据库和内存复制的优点:
*分离的请求处理和会话备份,使得集群更强壮。
*所有的会话数据备份到专门的服务器上。bu需要服务器实例浪费内存区保存其它服务器的备份会话数据。
*会话可以失效转移到其它任何实例上。因为会话备份服务器是被集群中的所有节点共享的。所以,大多数负载均衡软件和硬件可以在此集群中使用。更重要的是,请求装载在失效时将均匀分散。
*因为应用服务器和会话备份服务器之间的socket通讯是轻量级的相对于重型的数据库联接。它比数据库方案有着更好的性能和伸缩性。
然而,由于“恢复”阶段覆盖失效服务器的会话数据,它的性能和直接或配对内存复制方案不一样,附加的会话备份服务器对管理员也增加了复杂性,性能瓶颈由备份服务器自己的性能所限制。
4.4.5 Sun的方案--特殊的数据库
image
Sun 自己的方案,本节略,大家感兴趣可以自己看原文。
4.4.6 其它失效转移实现
JRun 使用Jini,Tangosol 使用分布式缓冲。本节无实质内容,略。
5 JNDI集群实现
暂略。
6 EJB 集群实现
暂略。
7 关于J2EE集群的神话
7.1 失效转移可以完全避免错误。  ---否定
JBoss文档中,有一整章节警告“你真的需要HTTP会话复制吗?”,是的,有时候一个没有失效转移的高可用性方案也是可以接受的,并且便宜。更进一步说,失效转移并不像你想的那样有力。
到底失效转移给了你什么?你们中的一些可能认为它可以避免错误。您瞧,没有失效转移,会话数据在服务器失效时丢失了并引起错误;当会话失效转移,会话可以从备份中恢复,并且请求可以由另一个实例继续处理,客户端不知道该失效。这可能是真的,但它不是必要的条件。
当我定义“失效转移”时,我定义了失效转移发生的条件:“在方法调用之间”,意味着你可以有两个连续的到一个远程对象的方法,失效转移将发生在第一个方法成功完成之后和第二个方法请求发出之前。
那么,在方法调用处理中间,远程服务器失效了会发生什么事情?答案是:处理将停止,大多数案例中,客户端将看到错误信息,除非该方法是等幂的(参考前文)。如果是等幂方法,一些负载均衡器足够聪明,会在其它服务器上重试这些方法。
为什么等幂重要?因为当失效发生时,客户端从来不知道请求在哪里执行,方法被初始化或已经完成了?客户端从不确定它。如果方法不是等幂的,两次调用同样的方法将改变系统状态两次,系统将处于不一致状态。
您可能想把所有的方法放在一个事务中就变成等幂的了。毕竟,如果错误发生,事务将回滚,所有的事务状态没有改变。但是事实是,事务边界不能包括所有远程方法调用。如果事务提交,在返回客户端时网络崩溃,客户端不会知道服务器事务成功与否。
在应用中,将所有方法变成等幂是不可能的。所以,通过失效转移,你可以减少错误,但不能避免它们!以再现购物网站为例,假定每台服务器实例可以同时处理 100个在线用户的请求。当一台服务器失效,没有会话失效转移的方案将丢失所有那100个用户的会话数据并激怒他们;当拥有会话失效转移,只有20个用户的请求被失效的服务器在处理过程中,只有这些用户被错误激怒了。其它80个用户还处在思考时间(用户行为的间隔时间)或者方法调用之间。这些用户的会话被透明地失效转移了。所以,你应该考虑以下事项:
*激怒20个和100个用户的不同影响
*拥有失效转移和没有失效转移的成本
7.2 单机应用可以被透明地转换为集群结构 ---- 绝对不是!
虽然一些提供商声称他们的J2EE产品的适应性,不要相信他们!实际上,你应该在系统设计开始时就考虑集群,并影响到开发和测试的所有阶段。
7.2.1 Http会话
在集群环境,对HTTPSession的使用有着严格的限制正如我在前边提及的一样,你的应用服务器依赖不同的机制使用会话失效转移。最重要的限制是所有存储在HTTPSession里的对象必须是可序列化的,这一点限制了应用的结构和设计。一些设计模式和MVC框架使用HTTPSession存储不可序列化对象(如Servlet Context,Local EJB 接口,web services引用),这样的设计不能在集群中工作。其次,对象序列化和反序列化在性能上消耗很大特别是数据库方案。在这样的环境中,存储巨大或众多的会话对象,应该避免。如果你已经选择内存复制方案,小心HTTPSession的交叉引用属性的限制。另一主要的区别是,你需要在每次改变 HTTPSession属性的时候调用“setAttribute ()”方法。调用这些方法在单机应用中不是必须的。其目的是将改变的属性和未改变的属性分离开来,所以系统可以只备份必要的数据。
7.2.2 缓冲
几乎我经历过的每一个J2EE项目都使用缓冲提升性能,所有的流行服务器提供不同程度的缓冲以提升应用性能。但这些缓冲是单机应用的典型设计,只能在一个 JVM实例上工作。我们需要缓冲是因为创建这些新对象的代价是如此昂贵,所以我们维护一个对象池来重用对象实例。每个JVM实例都拥有自己的缓冲拷贝,它们也应该被同步,以在所有服务器实例上维持一致的状态。有时,这种同步导致性能恶劣还不如不要缓冲。
7.7.3 静态变量
一些设计模式,比如“Singleton”(单例) 使用静态变量来给其它对象共享状态。这种方案在单一的服务器上工作得很好,但在集群中失效了。集群中的每个实例都会在其JVM实例中维护它自己的静态变量拷贝,因此就破坏了该设计模式的共享机制。一个使用静态变量的例子是保持在线用户的总数。简单的办法就是将改数字存储到静态变量中,当用户登录或下线时增加或减少它。在单一服务器上该应用绝对运行得非常好,但在集群中就失效了。更可取的办法是将所有状态存储到数据库中去。
7.7.4 外部资源
虽然J2EE规范不推荐使用,外部I/O操作还是有很多的用途。例如一些应用使用文件系统存储用户上载的文件,或者创建动态的配置XML文件。在集群中,英勇服务器没法跨实例复制这些文件。为了在集群中工作,解决方案是如有可能,使用数据库替代外部文件。也可以选择SAN作为中央文件存储。(备注:SAN英文全称:Storage Area Network,即存储区域网络。它是一种通过光纤集线器、光纤路由器、光纤交换机等连接设备将磁盘阵列、磁带等存储设备与相关服务器连接起来的高速专用子网。)
7.7.5 特殊的服务
有一些特殊的服务只有在单机模式下才有意义。定时器服务就是一种,给予一个恒定的间隔有规律地发生。定时器服务通常用来自动执行管理任务,比如日志文件转储,系统数据备份,数据库consistence 检测和冗余数据清除。一些基于事件的服务也很难移植到集群环境中。初始化服务就是很好的例子。邮件通知服务也是如此。
这些服务由事件而非请求触发,应当只执行一次。这样的服务将使得集群的负载均衡和失效转移的意义降低。
一些产品对这样的服务早有准备,例如,JBoss使用“集群单一设施”协调所有实例,保证只执行这些服务一次或仅仅一次。基于你选择的产品平台,这些服务有可能成为移植到集群的障碍。
7.8 分布式结构比排列式结构更有弹性? -- 不一定!
J2EE技术,特别是EJB,因为分布式计算而产生。解耦业务功能,重用远程对象使得多层应用流行。但我们也不能把所有东西都分布式化吧。一些J2EE架构师认为将web层和EJB层紧密排列更好。
image
图20:分布式结构
如图20分布式结构,当请求到来时,负载均衡器将它们分发到不同的服务器上的web容器,如请求中包括EJB调用,web容器将重新分发EJB调用到布通的EJB容器。这样,请求被负载均衡和失效转移了两次。
一些人不看好分布式结构,他们指出:
*第二次负载均衡是不必要的,因为它不能使任务分配更均匀。每个服务器实例在同一个JVM实例中都拥有它自己的web容器和EJB容器。让EJB容器处理其它实例的web容器的请求,看不出比实例内部调用有更多的优点。
*第二次失效转移是不必要的,因为它并不增加可用性。多数提供商的产品都是在同一JVM实例中集成了web容器和EJB容器。如果EJB容器失效,在多数情况下,web容器此时也是失效的。
*性能降低了。设想应用中的一个方法将调用几个EJB,若对每个EJB进行负载均衡,最终应用实例跨越了很多的服务器实例运行。这些服务器对服务器的横跨通信是不必要的。还有,如果方法在一个事务中,事务边界将包括许多服务器实例,严重影响了性能。
在实际的运行状态,多数提供商(包括Sun JES, Weblogic and JBoss)优化了EJB负载均衡使得请求首先选择在同一个服务器中的EJB容器。用这种办法,只在web层进行负载均衡,后续的服务都在同一服务器上处理。这种结构称为排列式结构。技术上说,也是分布式结构的一种。
image
图2:排列式结构
一个有趣的问题是,因为大部分发布运行时最后都成为了排列式结构,为什么不直接使用本地接口代替远程接口,这将提升性能。当然可以这样。但请记住,使用本地接口,web组件和EJB就紧密耦合了,使得方法直接调用而非通过IIOP/RMI。负载均衡器和失效转移分发器没有机会拦截本地调用,“Web+ EJB”过程就被作为一个整体了。
不幸的是,集群中,在J2EE服务器上使用本地接口有很多限制。EJB是带有本地接口的本地对象,但他们不可序列化。限制就是本地引用不允许存储在HTTPSession中。一些产品,如Sun JES区别处理本地EJB,使他们可序列化也能存储在HTTPSession中。
另一个有趣的问题是:因为排列式结构很流行也有良好的性能,为什么还需要分布式结构?大多苏h情况,事出有因,有时分布式结构不可替代。
*EJB不仅仅给web容器使用,富客户端也是用户之一。
*EJB组件和web组件可能在不同的安全级别,需要物理地分隔开来。所以,防火墙可能用来保护运行EJB的更重要的机器。
*Web 层和EJB层的极端不对称也许是选择分布式结构的一个好理由,例如,一些EJB组件太复杂资源消耗巨大,那么它们只能在一些昂贵的大服务器上运行;另一方面,web组件(HTML,JSP,Servlet)非常简单可以在便宜的PC服务器上运行。在这种条件下,专门的web服务器接收客户端请求,提供静态数据(HTML和图片)和简单的web组件(JSP和Servlet)。大型服务器只用来进行复杂计算。
8 结论
集群和单机环境是不同的。J2EE提供商以不同的方式实现集群。为了构建一个大型的系统,在项目开始时,你就应该为J2EE集群做准备。选择合适的J2EE 产品是g和你的应用。选择合适的第三方软件和框架时确保它们可以被集群。然后设计合适的架构以得到集群带来的实际利益。

11月25日

IT业结构失衡 软件外包人才奇缺

来源:凤凰财经  日期: 2007-11-20

  目前,我国的互联网发展似乎进入了一个发展的平稳期。据了解,it业急需的人才主要集中在项目管理和研发方面的中、高级人才。不过职位虽然诱人,入行的门槛却不低,基本的"硬件"条件是:科班出身,至少具备5年以上相关工作经验。

  有关专家指出,我国的it队伍存在严重的结构失衡,既缺乏高级it人才,更缺乏技能型、应用型信息技术人才,和一大批能从事基础性工作的技术人员。目前,软件开发主要分两类,一类是通用软件开发,另一类是软件定制服务。未来国内it企业将需要大量的通用软件开发人才。同时,针对各行各业的软件定制服务,也将对软件人才提出更高的要求,当然最好是该行业的从业人员能够进入it业设计适合行业特征的软件。

  此外,对日软件外包人才也成为时下的紧缺人才。据悉,在过去的一年,软件外包进入到一个疯狂扩张的阶段,掌握日语的软件项目经理更是奇缺。虽然我国对日软件外包每年以50%以上的速度增长,但目前在中国进行的软件开发只占日本业务总量的1%以上,不到2%。现在对日软件外包企业之间的竞争很少,但真正的竞争就是在人才方面,人才能不能到你的企业中来是决定企业发展的核心因素。

  日前,专业人才相关机构发布了最新一期的it职场人气排行榜。据了解,"十大it职场人气企业"选自求职者投递简历最多的企业;而"十大it职场人气职位"则是统计自企业发布需求最多的it职位,透过榜单看职场,对于准备找工作、跳槽的人来说,这就是一个最及时的职场风向标。

  十大it职场人气企业

  1、大唐电信科技产业集团

  2、中国惠普有限公司

  3、京信通信系统(广州)有限公司

  4、方正集团

  5、三星(中国)投资有限公司

  6、中信大东宽带网络技术有限公司

  7、英顺达科技有限公司

  8、亚信科技(中国)有限公司

  9、中国长城计算机深圳股份有限公司

  10、朝华科技有限责任公司

  十大it职场人气职位

  1、区域销售经理

  2、软件工程师

  3、nc销售工程师(兼职)

  4、夜间数据处理文员(兼职)

  5、executivesecretary

  6、高级技术支持

  7、测试人员

  8、嵌入式软件工程师

  9、issc-webtechnologydeveloper

  10、对日软件开发初级程序员

11月24日

Java集合对象的比较

Java 集合对象的区分和比较

有序否
允许元素重复否
Collection
List
Set
AbstractSet
HashSet
TreeSet
是(用二叉树排序)
Map
AbstractMap
使用key-value来映射和存储数据,Key必须惟一,value可以重复
HashMap
TreeMap
是(用二叉树排序)

11月22日

Java Servlet中HttpSession , HttpSessionListener , HttpSessionBindingListener , HttpSessionAtributeListener的正确理解[转]

关于HttpSession的误解实在是太多了,本来是一个很简单的问题,怎会搞的如此的复杂呢?下面说说我的理解吧:

一个session就是一系列某用户和服务器间的通讯。服务器有能力分辨出不同的用户。一个session的建立是从一个用户向服务器发第一个请求开始,而以用户显式结束或session超时为结束。
其工作原理是这样的:
1.当一个用户向服务器发送第一个请求时,服务器为其建立一个session,并为此session创建一个标识号;
2.这个用户随后的所有请求都应包括这个标识号。服务器会校对这个标识号以判断请求属于哪个session。
这种机制不使用IP作为标识,是因为很多机器是通过代理服务器方式上网,没法区分每一台机器。
对于session标识号(sessionID),有两种方式实现:cookies和URL重写。

HttpSession的使用
我们来看看在API中对session是如何定义和操作的。
当需要为用户端建立一个session时,servlet容器就创建了一个HttpSession对象。其中存储了和本session相关的信息。所以,在一个servlet中有多少个不同用户连接,就会有多少个HttpSession对象。
使用的机理是:
1.从请求中提取HttpSession对象;
2.增加或删除HttpSession中的属性;
3.根据需要关闭HttpSession或使其失效。

在请求中有两个重载的方法用来获取HttpSession对象。
HttpSession getSession(boolean create)/getSession();作用是提取HttpSession对象,如果没有自动创建。

获取到HttpSession对象后,我们就需要使用HttpSession的某些方法去设置和更改某些参数了。如:
void setAttribute(String name, Object value);
Object getAttribute(String name);
void removeAttribute(String name);

在javax.servlet.http包里一共定义了四个session监听器接口和与之关联的两个session事件。分别是:
HttpSessionAttributeListener and HttpSessionBindingEvent;
HttpSessionBindingListener and HttpSessionBindingEvent;
HttpSessionListener and HttpSessionEvent;
HttpSessionActivationListener and HttpSessionEvent.

他们的继承关系是:
所有四个接口的父类是java.util.EventListener;
HttpSessionEvent扩展java.util.EventObject;
而HttpSessionBindingEvent又扩展了HttpSessionEvent。

以下分别详述:
HttpSessionAttributeListener
当session中的属性被添加,更改,删除时得到通知。这个接口上节讲过,主要看其它三个。

HttpSessionBindingListener
当一个实现了HttpSessionBindingListener的类被加入到HttpSession中(或从中移出)时,会产生HttpBindingEvent事件,而这些事件会被它本身接收到。
本接口定义了两个方法:
void valueBound(HttpSessionBindingEvent e);
void valueUnbound(HttpSessionBindingEvent e);
当多个实现了HttpSessionBindingListener的类被加入到HttpSession中时,各类的方法只对本类感兴趣,不会去理会其它类的加入。
即使是同一类的不同实例间,也是互不关心的(各扫门前雪)。

我们可以看到前两个接口都对HttpSessionBindingEvent事件做出反应,但机理不同。
HttpSessionAttributeListener是在web.xml中登记的,servlet容器仅创建一个实例,来为任何在session中增加属性的servlet服务。触发事件的对象是所有可以转换为Object的实例。
HttpSessionBindingListener不用在web.xml中登记,在每个servlet中用new创建实例,且仅对本实例向session中的加入(或移出)感兴趣。触发事件的对象仅仅是自己。

HttpSessionListener
对于session的创建和取消感兴趣。需要在web.xml中登记。
共有两个方法:
void sessionCreated(HttpSessionEvent se);
void sessionDestroyed(HttpSessionEvent se);
使用它我们可以容易的创建一个类来对session计数。

也许我们会简单的考虑使用sessionDestroyed方法来在session结束后做一些清理工作。但是,请注意,当这个方法被调用的时候,session已经结束了,你不能从中提取到任何信息了。因此,我们要另辟蹊径。

一种通常采用的方法是使用HttpSessionBindingListener接口。在session创建时增加一个属性,而在session结束前最后一件事将这个属性删除,这样就会触发valueUnbound方法,所有对session的清理工作可以在这个方法中实现。

HttpSessionActivationListener
当session在分布式环境中跨JVM时,实现该接口的对象得到通知。共两个方法:
void sessionDidActivate(HttpSessionEvent se);
void sessionWillPassivate(HttpSessionEvent se);

1、HTTP协议本身是“连接-请求-应答-关闭连接”模式的,是一种无状态协议(HTTP只是一个传输协议);

2、Cookie规范是为了给HTTP增加状态跟踪用的(如果要精确把握,建议仔细阅读一下相关的RFC),但不是唯一的手段;

3、所谓Session,指的是客户端和服务端之间的一段交互过程的状态信息(数据);这个状态如何界定,生命期有多长,这是应用本身的事情;

4、由于B/S计算模型中计算是在服务器端完成的,客户端只有简单的显示逻辑,所以,Session数据对客户端应该是透明的不可理解的并且应该受控于服务端;Session数据要么保存到服务端(HttpSession),要么在客户端和服务端之间传递(Cookie或url rewritting或Hidden input);

5、由于HTTP本身的无状态性,服务端无法知道客户端相继发来的请求是来自一个客户的,所以,当使用服务端HttpSession存储会话数据的时候客户端的每个请求都应该包含一个session的标识(sid, jsessionid 等等)来告诉服务端;
6、会话数据保存在服务端(如HttpSession)的好处是减少了HTTP请求的长度,提高了网络传输效率;客户端session信息存储则相反;

7、客户端Session存储只有一个办法:cookie(url rewritting和hidden input因为无法做到持久化,不算,只能作为交换session id的方式,即a method of session tracking),而服务端做法大致也是一个道理:容器有个session管理器(如tomcat的org.apache.catalina.session包里面的类),提供session的生命周期和持久化管理并提供访问session数据的api;

8、使用服务端还是客户端session存储要看应用的实际情况的。一般来说不要求用户注册登录的公共服务系统(如google)采用cookie做客户端session存储(如google的用户偏好设置),而有用户管理的系统则使用服务端存储。原因很显然:无需用户登录的系统唯一能够标识用户的就是用户的电脑,换一台机器就不知道谁是谁了,服务端session存储根本不管用;而有用户管理的系统则可以通过用户id来管理用户个人数据,从而提供任意复杂的个性化服务;

9、客户端和服务端的session存储在性能、安全性、跨站能力、编程方便性等方面都有一定的区别,而且优劣并非绝对(譬如TheServerSide号称不使用HttpSession,所以性能好,这很显然:一个具有上亿的访问用户的系统,要在服务端数据库中检索出用户的偏好信息显然是低效的,Session管理器不管用什么数据结构和算法都要耗费大量内存和CPU时间;而用cookie,则根本不用检索和维护session数据,服务器可以做成无状态的,当然高效);

10、所谓的“会话cookie”简单的说就是没有明确指明有效期的cookie,仅在浏览器当前进程生命期内有效,可以被后继的Set-Cookie操作清除掉

11月21日

最近一直没有心情上来发点儿文章,太忙了。今日终于有个阶段性的进展

最近一直没有发点儿文章,包括我的NDSL L键坏了,也一直没有好好的去理会它 。原因无他。因为最近项目中设计一个消息集群处理中心  。 发现了不少问题 。 在做这个设计的时候,发现JMS & MDB怎么都不能满足我的需求 。 似乎没有一个商业化的可行性架构 。 而且我查遍了网上的文章 , 看了5,6本中英文的书籍的相关部分 。 居然就没有人通过JMS &MDB商业化过。 都是些简单的Send , Receive 。 就好像他们都只是写一个HelloWorld用在自己的项目中似的  。

 

我真是表示怀疑 。 全国就没有人深入理解何接触这块儿么 ?其实主题很明确,就是在JSF (Web架构)中实现消息集群的调用 , 处理和呈现  。 但是确实有些难度。主要原因还是在于JMS 设计的还不够完善 。 与MSMQ Server差远了 。 不过最后通过我一个多礼拜的不断实验(大概实验了十几个Case),研究了两套方案来解决这样的问题并且可以支持商用 。 将来有时间,我会写出文章分享这些经验 。

 

天下文章一大抄 , 网上的文章大多都是道听途说,人云亦云 。 从开始做J2EE起,我就写了不少网站没有的原创经验了,也喜欢能为其它程序员的商用之路提供一些帮助!

 

                                                      Sundy . 2007.11.21

11月17日

WebSphere Application Server 处理有害消息的方法(转)

最近在开发中开发消息中心 。 遇到一些有害消息的处理 。 特此,摘抄一篇文章对有害消息处理的简单描述,也算是共享一些经验了。 虽然是针对WebSphere 的,但对于JBoss思路都是一致的。

引言

WebSphere Application Server V5.x 提供基于 Java™ Messaging Service(JMS)规范的异步消息传递支持。通过使用消息侦听器服务和嵌入式 JMS 服务器或外部消息提供程序(比如 WebSphere MQ),应用程序开发人员可以编写消息驱动 Bean(MDB),它们在 JMS 目的地(一个消息队列,或者一个主题)上侦听,并且在消息到达那个特定的目的地时被调用。如果消息侦听器服务将一个“有害”消息传递给 MDB 应用程序,则该应用程序可以选择拒绝此消息。此时,应用程序服务器怎样处理此消息?

本文假定您具备 JMS 基础知识。



回页首

什么是有害消息?

有害消息就是接收该消息的 MDB 应用程序无法处理的消息。该消息可能是已损坏的或是以非预期格式表示的消息。例如,假设您有一个处理 TextMessage 类型 JMS 消息的 MDB。如果消息侦听器服务传递了一个具有不同消息类型的消息,则该消息被认为是有害消息。



回页首

遇到有害消息

如果 MDB 发现一个有害消息,它可以做以下三件事之一:

  • 将消息回滚到它来自的队列。如果该 MDB 在一个事务中运行,并且确保该消息未丢失,则可以完成此操作。为此,MDB 必须在与它相关的消息驱动上下文中调用 setRollbackOnly() 方法。
  • 将消息移动到不同的队列。该方法对于 MDB 没有在一个事务中运行时特别有用,因为该方法能防止有害消息丢失。
  • 无需做任何事,丢弃该消息。这表示该消息永远消失。

请牢记 MDB 应用程序的职责是确定它是否收到一个有害消息。JMS 提供程序或消息侦听器服务无法检测一个消息是否已损坏或是否应用程序所期望的 JMS 消息类型。



回页首

回滚有害消息

如果在一个事务中运行,则 MDB 可以回滚有害消息。这将导致消息被返回到它来源的队列。JMS 服务器在这种情况下的缺省行为是关闭与 MDB 相关的侦听器端口,主要为防止可能出现的失控状态。

如果侦听器端口没有被关闭,则消息侦听器服务将检测消息到达的受监控的目的地,并且将再次把消息发送给 MDB。然而,该消息仍然是有害消息,所以 MDB 将第二次回滚该消息。

有害消息第一次被回滚时关闭侦听器端口,这会强制应用程序开发人员仔细考虑应用程序和 JMS 服务器应如何处理它们。



回页首

更改缺省行为

JMS 服务器的确切行为依赖于三个属性:

  • 侦听器端口属性 Maximum retries

    该属性定义了在侦听器端口被停止之前消息侦听器服务向 MDB 尝试发送消息的次数。该属性的缺省值是 0,这表示当第一次出现某个消息无法发送或应用程序拒绝了有害消息时,侦听器端口将自关闭。在 WebSphere 管理控制台中的 Listener Port Settings 面板中可以更改 Maximum retries 属性。

    图 1 显示了样本 MDB 应用程序使用的 SamplePtoPListenerPort 的 Listener Port Settings 面板。该端口将 Maximum retries 属性设置为 10。

    图 1. 侦听器端口设置
    figure01

  • JMS 消息属性 Redelivery count

    Redelivery count 属性说明消息已被尝试提交给应用程序的次数。在提交消息后,如果消息无法提交或应用程序拒绝该消息(例如,通过回滚事务),则该属性加 1。

  • JMS 目的地属性 Backout threshold

    作为 WebSphere Application Server 一部分安装的 JMS 服务器允许用户定义自己的 JMS 目的地。每个以这种方式定义的目的地的 Backout threshold 属性值都固定为 5。当一个消息传送失败的次数等于 Backout threshold 属性值,则 JMS 服务器将把消息移动到缺省队列 SYSTEM.DEAD.LETTER.QUEUE(此队列是无法传送消息的仓库,而不考虑失败原因)。

嵌入式 JMS 服务器首先把消息返回到它源自的目的地。此时,服务器将比较消息 Redelivery count 和为目的地而定义的 Backout threshold 属性值。

如果 Redelivery count 少于 Backout threshold,则把消息留在队列中。然而,如果 Redelivery count 等于 Backout threshold,则把消息移出队列并置于队列 SYSTEM.DEAD.LETTER.QUEUE 中。

何时设置侦听器端口的 Maximum retries 属性?如上所述,该属性指定了在侦听器端口被停止之前消息侦听器服务向 MDB 尝试发送一个消息的次数。该属性的缺省值是 0,这表示当第一次出现某个消息无法发送时,侦听器端口被关闭,并且消息被返回到队列。此时,该消息的 Redelivery count 属性值加 1。

当侦听器端口被重启,它将再次尝试传送消息到 MDB。如果传送失败或消息被再次回滚,则该消息的 Redelivery count 加 1(将该属性设置为 2),然后将关闭侦听器端口。

此行为将持续到消息传送失败或回滚五次,此时该消息被置于 SYSTEM.DEAD.LETTER.QUEUE。这样做的原因是该消息的 Redelivery count 等于队列的 Backout threshold。

如果 Maximum retries 属性值被设置为 6 或更多,则当侦听器端口无法传送一个消息到 MDB 时,它将永远不会自关闭。当侦听器端口第五次发送同一个消息失败时,嵌入式 JMS 服务器将把消息置于 SYSTEM.DEAD.LETTER.QUEUE,而不是消息的原始队列,这是因为 Redelivery count 等于该队列的 Backout threshold 值。



回页首

当 JMS 的提供程序是 WebSphere MQ

缺省地,WebSphere MQ 创建的队列将 Backout threshold 属性(在 WebSphere MQ 用术语 BOTHRESH 表示)设置为 0。因此,WebSphere MQ 的缺省行为是从不收回有害消息。这意味着什么?

当 MDB 将有害消息回滚时,该消息被返回到它源自的队列。因为该队列未定义 Backout threshold,有害消息将被留在那里。然而,因为它现在存在于队列中,消息侦听器服务会检测到它并重新发送回 MDB。但这是一个有害消息,所以 MDB 将拒绝该消息,导致它被回滚被返回队列!

这一系列事件将持续直到有害消息被回滚的次数等于侦听器端口的 Maximum retries 属性值。此时,侦听器端口将自关闭。防止这种事情发生的方法是将队列的 Backout threshold 属性值设置为大于 0 而小于侦听器端口的 Maximum retries 属性值。这将确保在侦听器关闭之前回滚并删除任何有害消息。

退回的消息去了哪里?

在使用 WebSphere MQ 时,您可以通过修改队列的 Backout Requeue Name 属性(在 WebSphere MQ 术语中是 BOQNAME)来指定退回消息的目的地。缺省未设置该属性,这表示任何已被回滚的次数等于队列 Backout threshold 属性值的消息将丢失。需要培养的好习惯是将此属性值设置为系统的死信(dead letter)队列(缺省死信队列是 SYSTEM.DEAD.LETTER.QUEUE),因为有害消息实际上是“死信”。

可以使用 WebSphere MQ 提供的命令程序来设置 Backout threshold 和 Backout Requeue Name 属性。

图 2 来自 WebSphere MQ Explorer 实用程序,运行于 Windows® 平台。在此,将称为 CSINPUT_QUEUE 的队列的 Backout threshold 属性值设置为 1,并把 Backout Requeue Name 属性设置为 SYSTEM.DEAD.LETTER.QUEUE。当有害消息第一次被 MDB 检测到并回滚,MDB 将把该消息从 CSINPUT_QUEUE 移至队列 SYSTEM.DEAD.LETTER.QUEUE。

图 2. WebSphere MQ 队列属性
figure02

当然,您应该周期性地监控死信队列以检查应用程序接收了多少有害消息。如果死信队列包含了大量的消息,您应该调查这些消息出现的原因。

11月9日

Web开发就是组件开发 , JSF复合组件开发实例

最近一直在研究JSF的应用 , 还买了一本书(JSF in Action 中文名:JSF实战),却发现“原来无耻的还是中国人”这个道理,原因在于几点:
1,翻译错误百出(这个还可以原谅,毕竟在中国让不会写程序的来翻译编程书籍比比皆是,我也可以麻木的接受了,反正我猜书的能力也不错)


2,命名书籍少翻译了300页内容 , 居然无耻的在封面上说赠送300页超值内容,请到http://****下载(事实上,那个地址根本就没有后三百页下载,哪怕给个英文版也好啊) 。 这就导致我看目录明明700多页的书籍,看到500页发现没有了。原来剩下几百页组件开发关键的内容都被砍掉了 ,这一砍,基本上就把JSF自定义组件开发内容砍掉了 。 这自定义组件开发内容一砍,在我看来 ,就是把整本书砍掉了 。 既然整本书都被砍掉了。那我买书的那几十块钱去哪儿了?找谁报销啊 。


3,好吧,等于我花钱买了个很牛×的封皮好了(多牛?封皮上红色醒目的标识“超值赠送近300页内容”,就很吹牛) 。 但是既然打算卖封皮,就实话实说嘛 , 为何要骗读者呢, 少了说多了, 错了说对了 。 这就是这些出版商和译者的哲学吧  。 让我想起中国楼市的销售商的手段 。我想,这本书的主编以前肯定是搞房产的 。

 

闲话休谈, 下面是我为了研究JSF组件开发在IBM官方网站看到的内容 , 很不错,权且当作那300页的补充吧 。

 

复合组件

在下一个示例中,我将介绍如何创建这样一个组件(和标记),它可以记住最后一个人离开的位置。Field 组件把多个组件的工作组合到一个组件中。复合组件是 JSF 组件开发的重点,会节约大量时间!

Field 组件把标签、文本输入和消息功能组合到一个组件。Field 的文本输入功能允许用户输入文本。如果有问题(例如输入不正确),它的标签功能会显示红色,还会显示星号(*)表示必需的字段。它的消息功能允许它在必要的时候写出出错消息。

Field 组件示例演示了以下内容:

  • UIInput 组件
  • 处理值绑定和组件属性
  • 解码来自请求参数的值
  • 处理出错消息

与 Label 组件不同,Field 组件使用独立渲染器。如果为一个基于 HTML 的应用程序开发组件,那么不要费力使用独立渲染器。这么做是额外的无用功。如果正在开发许多 JSF 组件,打算卖给客户,而针对的客户又不止一个,那么就需要独立的渲染器了。简而言之,渲染器适用于商业框架的开发人员,不适用于开发内部 Web 应用程序的应用程序开发人员。

了解代码

由于我已经介绍了创建组件、定义渲染器以及创建定制标记的基本步骤,所以这次我让代码自己说话,我只点出几个重要的细节。在清单 5 中,可以看到在典型的应用程序示例中如何使用 Field 标记的:

清单 5. Field 标记

				
<f:view>
  <h2>CD Form</h2>
      
  <h:form id="cdForm">
        
    <h:inputHidden id="rowIndex" value="#{CDManagerBean.rowIndex}" /> 
      
      	
        <arcmind:field id="title"
                           value="#{CDManagerBean.title}"  
                           label="Title:"
                           errorStyleClass="errorText"
                           required="true" /> <br />
		
        <arcmind:field id="artist"
                           value="#{CDManagerBean.artist}"  
                           label="Artist:"
                           errorStyleClass="errorText"
                           required="true" /> <br />
      	
        <arcmind:field id="price"
                           value="#{CDManagerBean.price}"  
                           label="CD Price:"
                           errorStyleClass="errorText"
                           required="true">
           <f:validateDoubleRange maximum="1000.0" minimum="1.0"/>
        </arcmind:field>

以上标记输出以下 HTML:

<label style="" class="errorText">Artist*</label>
<input type="text" id="cdForm:artist " 
       name=" cdForm:artist " />
Artist is blank, it must contain characters

图 5 显示了浏览器中这些内容可能显示的效果。

图 5. Field 组件
5

清单 6 显示了创建 Field 组件的代码。因为这个组件负责输入文本而不仅仅是输出它(像 Label 那样),所以要从继承 UIInput 开始,而不是从继承 UIOutput 开始。

清单 6. Field 继承 UIInput

				
package com.arcmind.jsfquickstart;
import javax.faces.component.UIInput;
import javax.faces.context.FacesContext;
/**
 * @author Richard Hightower
 *  
 */
public class FieldComponent extends UIInput {
	private String label;
    @Override
     public Object saveState(FacesContext context) {
        Object values[] = new Object[2];
        values[0] = super.saveState(context);
        values[1] = label;
        return ((Object) (values));
    }
    @Override
    public void restoreState(FacesContext context, Object state) {
        Object values[] = (Object[])state;
        super.restoreState(context, values[0]);
        label = (String)values[1];
    }
    
	public FieldComponent (){
		this.setRendererType("arcmind.Field");
	}
	/**
	 * @return Returns the label.
	 */
	public String getLabel() {
		return label;
	}
	/**
	 * @param label
	 *  The label to set.
	 */
	public void setLabel(String label) {
		this.label = label;
	}
	
	@Override
	public String getFamily() {
		return "arcmind.Field";
	}
	public boolean isError() {
		return !this.isValid();
	}
}

可以注意到,代表片段中遗漏了编码方法。这是因为编码和解码发生在独立的渲染器中。我稍后会介绍它。

值绑定和组件属性

虽然 Label 组件只有一个属性(JSP 属性),可是 Field 组件却有多个属性,即 labelerrorStyleerrorStyleClassvaluelabelvalue 属性位于 Field 组件的核心,而 errorStyleerrorStyleClass 是特定于 HTML 的。因为这些属性是特定于 HTML 的,所以不需要让它们作为 Field 组件的属性;相反,只是把它们作为组件属性进行传递,只有渲染器知道这些属性。

像使用 Label 组件时一样,需要用定制标记把 Field 组件绑定到 JSP,如清单 7 所示:

清单 7. 为 FieldComponent 创建定制标记

				
/*
 * Created on Jul 19, 2004
 *
 */
package com.arcmind.jsfquickstart;
import javax.faces.application.Application;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.el.ValueBinding;
import javax.faces.webapp.UIComponentTag;
/**
 * @author Richard Hightower
 *
 */
public class FieldTag extends UIComponentTag {
     private String label;
     private String errorStyleClass="";
     private String errorStyle="";
     private boolean required;
     private String value="";
     
     /**
      * @return Returns the label.
      */
     public String getLabel() {
          return label;
     }
     /**
      * @param label The label to set.
      */
     public void setLabel(String label) {
          this.label = label;
     }
     /**
      * @see javax.faces.webapp.UIComponentTag#setProperties
      * (javax.faces.component.UIComponent)
      */
     @Override
     protected void setProperties(UIComponent component) {
          /* You have to call the super class */
          super.setProperties(component);
          ((FieldComponent)component).setLabel(label);
          component.getAttributes().put("errorStyleClass",
            errorStyleClass);
          component.getAttributes().put("errorStyle",errorStyle);
          ((FieldComponent)component).setRequired(required);
     
     
         FacesContext context = FacesContext.getCurrentInstance();
         Application application = context.getApplication();
         ValueBinding binding = application.createValueBinding(value);
         component.setValueBinding("value", binding);
          
     }
     /**
      * @see javax.faces.webapp.UIComponentTag#getComponentType()
      */
     @Override
     public String getComponentType() {
          return "arcmind.Field";     
     }
     /**
      * @see javax.faces.webapp.UIComponentTag#getRendererType()
      */
     @Override
     public String getRendererType() {
          return "arcmind.Field";     
     }
     /**
      * @return Returns the errorStyleClass.
      */
     public String getErrorStyleClass() {
          return errorStyleClass;
     }
     /**
      * @param errorStyleClass The errorStyleClass to set.
      */
     public void setErrorStyleClass(String errorStyleClass) {
          this.errorStyleClass = errorStyleClass;
     }
     
     /**
      * @return Returns the errorStyle.
      */
     public String getErrorStyle() {
          return errorStyle;
     }
     /**
      * @param errorStyle The errorStyle to set.
      */
     public void setErrorStyle(String errorStyle) {
          this.errorStyle = errorStyle;
     }
     /**
      * @return Returns the required.
      */
     public boolean isRequired() {
          return required;
     }
     /**
      * @param required The required to set.
      */
     public void setRequired(boolean required) {
          this.required = required;
     }
     
     /**
      * @return Returns the value.
      */
     public String getValue() {
          return value;
     }
     /**
      * @param value The value to set.
      */
     public void setValue(String value) {
          this.value = value;
     }
}

从概念上说,在上面的代码和 Label 组件之间找不出太大区别。但是,在这个示例中,setProperties 方法有些不同:

protected void setProperties(UIComponent component) {
    /* You have to call the super class */
    super.setProperties(component);
    ((FieldComponent)component).setLabel(label);
    component.getAttributes().put("errorStyleClass", 
      errorStyleClass);
    component.getAttributes().put("errorStyle",errorStyle);
    ((FieldComponent)component).setRequired(required);

虽然 label 属性传递时的方式与前面的示例相同,但是 errorStyleClasserrorStyle 属性不是这样传递的。相反,它们被添加到 JSF 组件的属性映射 中。Renderer 类会使用属性映射去渲染类和样式属性。这个设置允许特定于 HTML 的代码从组件脱离。

这个修订后的 setProperties 方法实际的值绑定代码也有些不同,如下所示。

protected void setProperties(UIComponent component) {
      ...	
	
     FacesContext context = FacesContext.getCurrentInstance();
     Application application = context.getApplication();
     ValueBinding binding = application.createValueBinding(value);
     component.setValueBinding("value", binding);

这个代码允许 Field 组件的 value 属性绑定到后台 bean。出于示例的原因,我把 CDManagerBean 的 title 属性绑定到 Field 组件,像下面这样:value="#{CDManagerBean.title}。值绑定是用 Application 对象创建的。Application 对象是创建值绑定的工厂。这个组件拥有保存值绑定的特殊方法,即 setValueBinding;可以有不止一个值绑定。

独立渲染器

最后介绍渲染器,但并不是说它不重要。独立渲染器必须考虑的主要问题是解码(输入) 和编码(输出)。Field 组件做的编码比解码多得多,所以它的渲染器有许多编码方法,而只有一个解码方法。在清单 8 中,可以看到 Field 组件的渲染器:

清单 8. FieldRenderer 扩展自 Renderer

				
package com.arcmind.jsfquickstart;
import java.io.IOException;
import java.util.Iterator;
import java.util.Map;
import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.component.UIInput;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.convert.Converter;
import javax.faces.convert.ConverterException;
import javax.faces.el.ValueBinding;
import javax.faces.render.Renderer;
/**
 * @author Richard Hightower
 *
 */
public class FieldRenderer extends Renderer {
  @Override 
   public Object getConvertedValue(FacesContext facesContext, UIComponent component, 
     Object submittedValue) throws ConverterException {
        
    //Try to find out by value binding
    ValueBinding valueBinding = component.getValueBinding("value");
    if (valueBinding == null) return null;
    Class valueType = valueBinding.getType(facesContext);
    if (valueType == null) return null;
    if (String.class.equals(valueType)) return submittedValue;    
    if (Object.class.equals(valueType)) return submittedValue;    
    Converter converter = ((UIInput) component).getConverter();
    converter =  facesContext.getApplication().createConverter(valueType);
    if (converter != null ) {
        return converter.getAsObject(facesContext, component, (String) submittedValue);
    }else {
        return submittedValue; 
    }
		
    }
   @Override
    public void decode(FacesContext context, UIComponent component) {
        /* Grab the request map from the external context */
       Map requestMap = context.getExternalContext().getRequestParameterMap();
        /* Get client ID, use client ID to grab value from parameters */
       String clientId = component.getClientId(context);
       String value = (String) requestMap.get(clientId);
		
        FieldComponent fieldComponent = (FieldComponent)component;
          /* Set the submitted value */
        ((UIInput)component).setSubmittedValue(value);
    }
	
   @Override
    public void encodeBegin(FacesContext context, UIComponent component)
        throws IOException {
        FieldComponent fieldComponent = (FieldComponent) component;
        ResponseWriter writer = context.getResponseWriter();
        encodeLabel(writer,fieldComponent);
        encodeInput(writer,fieldComponent);
        encodeMessage(context, writer, fieldComponent);
        writer.flush();
    }
	
	
    private void encodeMessage(FacesContext context, ResponseWriter writer, 
      FieldComponent fieldComponent) throws IOException {
        Iterator iter = context.getMessages(fieldComponent.getClientId(context));
        while (iter.hasNext()){
           FacesMessage message = (FacesMessage) iter.next();
           writer.write(message.getDetail());
        }
    }
    private void encodeLabel(ResponseWriter writer, FieldComponent 
      fieldComponent) throws IOException{
        writer.startElement("label", fieldComponent);
        if (fieldComponent.isError()) {
            String errorStyleClass = (String) fieldComponent.getAttributes().get("errorStyleClass");
            String errorStyle = (String) fieldComponent.getAttributes().get("errorStyle");
            writer.writeAttribute("style", errorStyle, "style");
            writer.writeAttribute("class", errorStyleClass, "class");
        }
        writer.write("" + fieldComponent.getLabel());
        if (fieldComponent.isRequired()) {
            writer.write("*");
        }
       writer.endElement("label");
    }
	
    private void encodeInput(ResponseWriter writer, FieldComponent 
      fieldComponent) throws IOException{
        FacesContext currentInstance = FacesContext.getCurrentInstance();
        writer.startElement("input", fieldComponent);
        writer.writeAttribute("type", "text", "type");
        writer.writeAttribute("id", fieldComponent.getClientId(currentInstance), "id");
		writer.writeAttribute("name", fieldComponent.getClientId(currentInstance), "name");
        if(fieldComponent.getValue()!=null)
            writer.writeAttribute("value", fieldComponent.getValue().toString(), "value");
        writer.endElement("input");
    }
}

编码和解码

正如前面提到的,渲染器做的主要工作就是解码输入和编码输出。我先从解码开始,因为它是最容易的。 FieldRenderer 的 decode 方法如下所示:

@Override
public void decode(FacesContext context, UIComponent component) {
       /* Grab the request map from the external context */
     Map requestMap = context.getExternalContext().getRequestParameterMap();
       /* Get client ID, use client ID to grab value from parameters */
     String clientId = component.getClientId(context);
     String value = (String) requestMap.get(clientId);
		
     FieldComponent fieldComponent = (FieldComponent)component;
       /* Set the submitted value */
     ((UIInput)component).setSubmittedValue(value);
}

Label 组件不需要进行解码,因为它是一个 UIOutput 组件。Field 组件是一个 UIInput 组件,这意味着它接受输入,所以 必须 进行解码。decode 方法可以从会话、cookie、头、请求等处读取值。在大多数请问下,decode 方法只是像上面那样从请求参数读取值。Field 渲染器的 decode 方法从组件得到 clientId,以标识要查找的请求参数。给定组件容器的路径,clientId 被计算成为组件的全限定名称。而且,因为示例组件在表单中(是个容器),所以它的 clientid 应当是 nameOfForm:nameOfComponent 这样的,或者是示例中的 cdForm:artist、cdForm:price、cdForm:title。decode 方法的最后一步是把提交的值保存到组件(稍后会转换并验证它,请参阅 参考资料 获取更多关于验证和转换的内容)。

编码方法没什么惊讶的。它们与 Label 组件中看到的类似。第一个方法 encodeBegin,委托给三个帮助器方法 encodeLabelencodeInputencodeMessage,如下所示:

@Override
public void encodeBegin(FacesContext context, UIComponent component)
       throws IOException {
     FieldComponent fieldComponent = (FieldComponent) component;
     ResponseWriter writer = context.getResponseWriter();
     encodeLabel(writer,fieldComponent);
     encodeInput(writer,fieldComponent);
     encodeMessage(context, writer, fieldComponent);
     writer.flush();
}

encodeLabel 方法负责在出错的时候,把标签的颜色改成红色(或者在样式表中指定的其他什么颜色),并用星号 (*) 标出必需的字段,如下所示:

private void encodeLabel(ResponseWriter writer, FieldComponent fieldComponent) throws IOException{
     writer.startElement("label", fieldComponent);
     if (fieldComponent.isError()) {
          String errorStyleClass = (String) fieldComponent.getAttributes().get("errorStyleClass");
          String errorStyle = (String) fieldComponent.getAttributes().get("errorStyle");
          writer.writeAttribute("style", errorStyle, "style");
          writer.writeAttribute("class", errorStyleClass, "class");
     }
     writer.write("" + fieldComponent.getLabel());
     if (fieldComponent.isRequired()) {
          writer.write("*");
     }
     writer.endElement("label");
}

首先,encodeLabel 方法检查是否有错误,如果有就输出 errorStyleerrorStyleClass(更好的版本是只有在它们不为空的时候才输出 —— 但是我把它留给您做练习!)。然后帮助器方法会检查组件是不是必需的字段,如果是,就输出星号。encodeMessagesencodeInput 方法做的就是这件事,即输出出错消息并为 Field 组件生成 HTML 输入的文本字段。

注意,神秘方法!

您可能已经注意到,有一个方法我还没有介绍。这个方法就是这个类中的“黑马”方法。如果您阅读 Renderer(所有渲染器都要扩展的抽象类)的 javadoc,您可能会感觉到这样的方法是不需要的,现有的就足够了:这就是我最开始时想的。但是,您和我一样,都错了!

实际上,基类 Renderer 并不 自动调用 Renderer 子类的相关转换器 —— 即使 Renderer 的 javadoc 和 JSF 规范建议它这样做,它也没做。MyFaces 和 JSF RI 拥有为它们的渲染器执行这个魔术的类(特定于它们的实现),但是在核心 JSF API 中并没有涉及这项功能。

相反,需要使用方法 getConvertedValues 锁定相关的转换器并调用它。清单 9 显示的方法根据值绑定的类型找到正确的转换器:

清单 9. getConvertedValues 方法

				
@Override
 public Object getConvertedValue(FacesContext facesContext, 
   UIComponent component, Object submittedValue) throws ConverterException {
        
     //Try to find out by value binding
     ValueBinding valueBinding = component.getValueBinding("value");
     if (valueBinding == null) return null;
     Class valueType = valueBinding.getType(facesContext);
     if (valueType == null) return null;
     if (String.class.equals(valueType)) return submittedValue;    
     if (Object.class.equals(valueType)) return submittedValue;    
     Converter converter = ((UIInput) component).getConverter();
     converter =  facesContext.getApplication().createConverter(valueType);
     if (converter != null ) {
          return converter.getAsObject(facesContext, component, (String) submittedValue);
     }else {
          return submittedValue; 
     }
		
}

清单 9 的代码添加了 Render javadoc 和 JSF 规范都让您相信应当是自动执行的功能,而实际上并不是。另一方面,请注意如果没有 独立的 Renderer,就不需要 以上(getConvertedValues)方法。UIComponentBase 类(Field 组件的超类)在直接渲染器的情况下提供了这个功能。请接受我的建议,只在特别想尝试或者在编写商业框架的时候,才考虑采用渲染器。在其他情况下,它们不值得额外的付出。

如果想知道如何把组件和渲染器关联,那么只要看看图 6 即可。

图 6. 把渲染器映射到组件
6

定制标记有两个方法,分别返回组件类型和渲染器类型。这些方法用于查找配置在 faces-config.xml 中的正确的渲染器和组件。请注意(虽然图中没有)组件必须返回正确的 family 类型。

Web的开发就是组件的开发 ,JSF的组件开发实例

最近一直在研究JSF的应用 , 还买了一本书(JSF in Action 中文名:JSF实战),却发现“原来无耻的还是中国人”这个道理,原因在于几点:
1,翻译错误百出(这个还可以原谅,毕竟在中国让不会写程序的来翻译编程书籍比比皆是,我也可以麻木的接受了,反正我猜书的能力也不错)


2,命名书籍少翻译了300页内容 , 居然无耻的在封面上说赠送300页超值内容,请到http://****下载(事实上,那个地址根本就没有后三百页下载,哪怕给个英文版也好啊) 。 这就导致我看目录明明700多页的书籍,看到500页发现没有了。原来剩下几百页组件开发关键的内容都被砍掉了 ,这一砍,基本上就把JSF自定义组件开发内容砍掉了 。 这自定义组件开发内容一砍,在我看来 ,就是把整本书砍掉了 。 既然整本书都被砍掉了。那我买书的那几十块钱去哪儿了?找谁报销啊 。


3,好吧,等于我花钱买了个很牛×的封皮好了(多牛?封皮上红色醒目的标识“超值赠送近300页内容”,就很吹牛) 。 但是既然打算卖封皮,就实话实说嘛 , 为何要骗读者呢, 少了说多了, 错了说对了 。 这就是这些出版商和译者的哲学吧  。 让我想起中国楼市的销售商的手段 。我想,这本书的主编以前肯定是搞房产的 。

 

闲话休谈, 下面是我为了研究JSF组件开发在IBM官方网站看到的内容 , 很不错,权且当作那300页的补充吧 。

 

JSF 组件模型

JSF 组件模型与 AWT GUI 组件模型类似。它有事件和属性,就像 Swing 组件模型一样。它也有包含组件的容器,容器也是组件,也可以由其他容器包含。从理论上说,JSF 组件模型分离自 HTML 和 JSP。JSF 自带的标准组件集里面有 JSP 绑定,可以生成 HTML 渲染。

JSF 组件的示例包括日历输入组件和 HTML 富文本输入组件。您可能从来没时间去编写这样的组件,但是如果它们已经存在,那会如何呢?通过把常用功能变成商品,组件模型降低了向 Web 应用程序添加更多功能的门槛。

组件的功能通常围绕着两个动作:解码和编码数据。解码 是把进入的请求参数转换成组件的值的过程。编码 是把组件的当前值转换成对应的标记(也就是 HTML)的过程。

JSF 框架提供了两个选项用于编码和解码数据。使用直接实现 方式,组件自己实现解码和编码。使用委托实现 方式,组件委托渲染器进行编码和解码。如果选择委托实现,可以把组件与不同的渲染器关联,会在页面上以不同的方式渲染组件;例如多选列表框和一列复选框。

因此,JSF 组件由两部分构成:组件和渲染器。JSF 组件 类定义 UI 组件的状态和行为;渲染器 定义如何从请求读取组件、如何显示组件 —— 通常通过 HTML 渲染。渲染器把组件的值转换成适当的标记。事件排队和性能验证发生在组件内部。

在图 1 中可以看到数据编码和解码出现在 JSF 生命周期中的什么阶段(到现在,我希望您已经熟悉 JSF 生命周期了)。

图 1. JSF 生命周期和 JSF 组件
1

提示!

在许多情况下,可以在保持组件本身不变的情况下,通过改变渲染而简化开发过程。在这些情况下,可以编写定制渲染器而不是定制组件。

更多组件概念

所有 JSF 组件的基类是 UIComponent。在开发自己的组件时,需要继承 UIComponentBase,它扩展了 UIComponent 并提供了 UIComponent 中所有抽象方法的默认实现。

组件拥有双亲和标识符。每个组件都关联着一个组件类型,组件类型用于在 face 的上下文配置文件(faces-config.xml)中登记组件。可以用 JSF-EL (表达式语言)把 JSF 组件绑定到受管理的 bean 属性。可以把表达式关联到组件上的任何属性,这样就允许用 JSF-EL 设置组件的属性值。在创建使用 JSF-EL 绑定的组件属性时,需要创建值绑定表达式。在调用绑定属性的 getter 方法时,除非 setter 方法已经设置了值,否则 getter 方法必须用值绑定获得值。

组件可以作为 ValueHolderEditableValueHolderValueHolder 与一个或多个 ValidatorConverter 相关联;所以 JSF UI 组件也与 ValidatorConverter 关联(请参阅 参考资料 获得更多关于 JSF 验证和转换的内容。)

像表单字段组件这样的组件拥有一个 ValueBinding,它必须绑定到 JavaBean 的读写属性。组件可以调用 getParent 方法访问它们的双亲,也可以调用 getChildren 方法访问它们的子女。组件也可以有 facet 组件,facet 组件是当前组件的子组件,可以调用 getFacets 方法访问它,这个方法返回一个映射。Facets 是著名的子组件。

这里描述的许多组件的概念将会是接下来展示的示例的一部分,所以请记住它们!



回页首

JSF 样式的 Hello World!

我们用一个又好又容易的示例来开始 JSF 组件的开发:我将展示如何渲染 Label 标记(示例:<label>Form Test</label>)。

下面是我要采取的步骤:

  1. 扩展 UIComponent
    • 创建一个类,扩展 UIComponent
    • 保存组件状态
    • 用 faces-config.xml 登记组件
  2. 定义渲染器或者内联地实现它
    • 覆盖 encode
    • 覆盖 decode
    • 用 faces-config.xml 登记渲染器
  3. 创建定制标记,继承 UIComponentTag
    • 返回渲染器类型
    • 返回组件类型
    • 设置可能使用 JSF 表达式的属性

Label 示例将演示 JSF 组件开发的以下方面:

  • 创建组件
  • 直接实现渲染器
  • 编码输出
  • 把定制标记与组件关联

返回 图 1,可以看到在这个示例中会有两个生命周期属性在活动。它们是 Apply Request ValueRender Response

在图 2 中,可以看到在 JSP 中如何使用 Label 标记的(<label>Form Test</label>)。

图 2. 在 JSP 中使用 JSF 标记
2

第 1 步:扩展 UIComponent

第一步是创建一个组件,继承 UIOutput,后者是 UIComponent 的子类。 除了继承这个类之外,我还添加了组件将会显示的 label 属性,如清单 1 所示:

清单 1. 继承 UIComponent 并添加 label

				
import java.io.IOException;
import javax.faces.component.UIOutput;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
public class LabelComponent extends UIOutput{
	private String label;
	public String getLabel() {
		return label;
	}
	public void setLabel(String label) {
		this.label = label;
	}
...

接下来要做的是保存组件状态。JSF 通常通过会话、隐藏表单字段、cookies 等进行实际的存储和状态管理。(这通常是用户配置的设置)。要保存组件状态,需要覆盖组件的 saveStaterestoreState 方法,如清单 2 所示:

清单 2. 保存组件状态

				
    @Override
    public Object saveState(FacesContext context) {
        Object values[] = new Object[2];
        values[0] = super.saveState(context);
        values[1] = label;
        return ((Object) (values));
    }
    @Override
    public void restoreState(FacesContext context, Object state) {
        Object values[] = (Object[])state;
        super.restoreState(context, values[0]);
        label = (String)values[1];
    }
 

可以注意到,我使用的是 JDK 1.5。我对编译器进行了设置,所以我必须指定 override 注释,以便指明哪些方法要覆盖基类的方法。这样做可以更容易地标识出 JSF 的钩子在哪。

创建组件的最后一步是用 faces-config.xml 登记它,如下所示:

<faces-config>
   <component>
      <component-type>simple.Label</component-type>
      <component-class>
         arcmind.simple.LabelComponent
      </component-class>
   </component>
...

第 2 步:定义渲染器

下面要做的是内联地定义渲染器的功能。稍后我会介绍如何创建独立的渲染器。现在,先从编码 Label 组件的输出、显示 label 开始,如清单 3 所示:

清单 3. 编码组件的输出

				
public class LabelComponent extends UIOutput{
	...
	public void encodeBegin(FacesContext context) 
					throws IOException {
		ResponseWriter writer = 
			context.getResponseWriter();
		writer.startElement("label", this);
        	            writer.write(label);
        	            writer.endElement("label");
        	            writer.flush();
	}
	...
}

注意,响应写入器(javax.faces.context.ResponseWriter)可以容易地处理 HTML 这样的标记语言。清单 3 的代码输出 <label> 元素体内的 label 的值。

下面显示的 family 属性用来把 Label 组件与渲染器关联。虽然目前 Label 组件还不需要这个属性(因为还没有独立的渲染器),但是在这篇文章后面,在介绍如何创建独立渲染器的时候,会需要它。

public class LabelComponent extends UIOutput{
	...
	public String getFamily(){
		return "simple.Label";
	}
	...
}

插曲:研究 JSF-RI

如果正在使用来自 Sun Microsystems 的 JSF 参考实现(不是 MyFaces 实现),那么就不得不在组件创建代码中添加下面一段:

public void encodeEnd(FacesContext context) 
			throws IOException {
	return;
}
public void decode(FacesContext context) {
	return;
}

Sun 的 JSF RI 期望,在组件没有渲染器的时候,渲染器会发送一个空指针异常。MyFaces 实现不要求处理这个需求,但是在代码中包含以上方法依然是个好主意,这样组件既可以在 MyFaces 环境中工作也可以在 JSF RI 环境中工作了。

MyFaces 更好!

如果正在使用 Sun JSF RI 或其他替代品,那么请帮自己一个忙,转到 MyFaces。虽然 MyFaces 不总是 更好的实现,但是目前它是。它的错误消息要比 Sun JSF RI 的好,而这个框架相比之下更严格。

第 3 步:创建定制标记

JSF 组件不是天生绑定到 JSP 上的。要连接起 JSP 世界和 JSF 世界,需要能够返回组件类型的定制标记(然后在 faces-context 文件中登记)和渲染器,如图 3 所示。

图 3. 连接 JSF 和 JSP
3

注意,由于没有独立的渲染器,所以可以给 getRendererType() 返回 null 值。还请注意,必须已经把 label 属性的值从定制标记设置到组件上,如下所示:

[LabelTag.java]
public class LabelTag extends UIComponentTag {
…
protected void setProperties(UIComponent component) {
	/* you have to call the super class */
	super.setProperties(component);
	((LabelComponent)component).setLabel(label);
}

记住,Tag 设置从 JSP 到 Label 组件的绑定,如图 4 所示。

图 4. 绑定 JSF 和 JSP
4

现在要做的全部工作就是创建一个 TLD(标记库描述符)文件,以登记定制标记,如清单 4 所示:

清单 4. 登记定制标记

				
[arcmind.tld]
<taglib>
   <tlib-version>0.03</tlib-version>
   <jsp-version>1.2</jsp-version>
   <short-name>arcmind</short-name>
   <uri>http://arcmind.com/jsf/component/tags</uri>
   <description>ArcMind tags</description>
   
   <tag>
      <name>slabel</name>
      <tag-class>arcmind.simple.LabelTag</tag-class>
      <attribute> 
         <name>label</name> 
         <description>The value of the label</description>
      </attribute> 
   </tag>
...

一旦定义了 TLD 文件,就可以开始在 JSP 中使用标记了,如下面示例所示:

[test.jsp]
<%@ taglib prefix="arcmind" 
         uri="http://arcmind.com/jsf/component/tags" %>
            ...
	<arcmind:slabel label="Form Test"/>

现在就可以了 —— 开发一个简单的 JSP 组件不需要更多了。但是如果想创建稍微复杂一些的组件,针对更复杂的使用场景时该怎么办?请继续往下看。

11月8日

JBoss 下 EJB通过JNDI 远程调用小贴士 -备忘

一个小技巧 。 发现网上都没有人写到相关文章,都是人云亦云,道听途说 。 看来国内还没有几个正式的EJB3的项目 。我也算是走在前头了 。呵呵 。

××××××

1,JBoss启动的时候加上绑定IP的参数 :run -b IPAddress
2,修改JBossPath\server\default\deploy\jboss-web.deployer文件中配置的时候要设置成这样。address="${IPAddress}" 
切记 , ${} 不能丢 , 否则出错 。
3,JBoss不需要有些人所说的那样在Windows平台安装目录不能有空格 。

××××××

以上经验经过我的项目实测 。

11月5日

使用MyEclipse 自动生成EJB3 企业级项目代码的研究(五)-自定义我的ejb模版

因为我用的EJB项目 , 所以开始修改我的模版文件 。 主要用到 dao 目录下面的 :facadeif.vm ,daohome.vm  文件 。前者是生成本地和远程接口的 , 后者是生成Session Bean 的 。 我在接口中加入了如下属性和方法 :

#if ($generatePreciseFindBy)
#foreach($property in $pojo.getFindByIterator())
    public List findBy$pojo.getPropertyName($property)(Object $property.Name);
#end  
    public List findAdvance(String filter)  ;

    public List findAdvanceByPage(String filter ,Integer startRow , Integer maxRows)  ;

    public String orders = ""  ;

#end

接下来打开daohome.vm , 实现相应的方法

......
//Advance Search
    public $pojo.importType("java.util.List")<${declarationName}> findAdvance(String filter)
    {
    ${pojo.importType(${emHelperFQN})}.log("finding ${declarationName} instance with Advance, value: " + filter, Level.INFO, null);
    try
    {
        String queryString = "select model from ${declarationName} model where " + filter  ;
        if(this.orders != null && !this.orders.equals(""))
        {
            queryString += " order by "+this.orders  ;
        }
        return ${entityManager}.createQuery(queryString).getResultList();
    } catch (RuntimeException re)
    {
        ${pojo.importType(${emHelperFQN})}.log("find by property name failed", Level.SEVERE, re);
        throw re;
    }
    }

    public $pojo.importType("java.util.List")<${declarationName}> findAdvanceByPage(String filter ,Integer startRow , Integer maxRows)
    {
    ${pojo.importType(${emHelperFQN})}.log("finding ${declarationName} instance with AdvanceByPage, value: " + filter, Level.INFO, null);
    try
    {
        String queryString = "select model from ${declarationName} model where " + filter  ;

        //Set Page Rows ;
        if(maxRows>=0)
        {
            if(startRow ==0)
            {
                queryString = "select top " + maxRows + "  model from ${declarationName} model where " + filter  ;
            }
            else
            {
                queryString = "select top " + maxRows + "  model from ${declarationName} model where [PrimayKeyName] not in (select top "+ startRow +" model.[PrimaryKeyName] from model where "+ filter +" order by "+ this.orders+ ") where " + filter +" order by "+ this.orders  ;
            }
        }

        //Set Orders ;
        if(this.orders != null && !this.orders.equals(""))
        {
            queryString += " order by "+this.orders  ;
        }
        return ${entityManager}.createQuery(queryString).getResultList();
    } catch (RuntimeException re)
    {
        ${pojo.importType(${emHelperFQN})}.log("find by property name failed", Level.SEVERE, re);
        throw re;
    }
    }
......

这样,我自动生成的DAL层的EJB代码中包含了高级查询, 分页查询 , 排序等功能。这样一个完整的DAL层就实现了 。 当然,修改的不止这点儿,因为代码太长,就不列出来了。

说说弊端,这个工具有些方法是定死的,比如生成文件格式等 。因此自定义功能还是很有局限性。不够灵活 。 只能凑合着用 :)

就讲到这里,有兴趣的朋友可以联系我继续沟通。

使用MyEclipse 自动生成EJB3 企业级项目代码的研究(四)-koders

http://www.koders.com/

似乎能够开始定制模版了吧 。但是新的问题又来了 , 模版文件中有很多初始变量,不知道在哪儿定义的 。 比如:$pojo.getPackageDeclaration() , 能够得到当前项目的包引用 。 但pojo对象却不是在模版中定义的 。 很显然,不少对象都已经固化到Hibernate里面了 。 因此需要查询Hibernate框架代码才知道这些对象的含义 。

这里推荐大家一个好的查询源代码的站点:http://www.koders.com/ 。 在这里,我输入pojo.java,查询到如下结果:

http://www.koders.com/java/fid1A0681CB648F627E42E1B9917C3CFBA9C0AC7E1B.aspx

package org.hibernate.tool.hbm2x.pojo;

import java.util.Iterator;
import java.util.List;

import org.hibernate.mapping.Property;

/**
 * Wrapper class over PersistentClass used in hbm2java and hbm2doc tool
 * @author max
 * @author <a href="mailto:abhayani@jboss.org">Amit Bhayani</a>
 *
 */
public interface POJOClass extends ImportContext {

	/** 
	 * Returns "package packagename;" where packagename is either the declared packagename,
	 * or the one provide via meta attribute "generated-class".
	 * 
	 * Returns "// default package" if no package declarition available.
	 *  
	 * @param cm
	 * @param tool TODO
	 * @return
	 */
	public String getPackageDeclaration();
	
	public String getClassModifiers();

	public String getQualifiedDeclarationName();
	
	/**
	 * Returns the javadoc associated with the class.
	 * 
	 * @param fallback the default text if nothing else is found
	 * @param indent how many spaces should be added
	 * @return
	 */
	public String getClassJavaDoc(String fallback, int indent);
	
	/**
	 * 
	 * @return declaration type "interface" or "class"
	 */
	public String getDeclarationType();
	
	/**
	 * @return unqualified classname for this class (can be changed by meta attribute "generated-class")
	 */
	public String getDeclarationName();
	
	public String getImplementsDeclaration();
	public String getImplements();
	
	public String getExtendsDeclaration();
	public String getExtends();
	
	public String generateEquals(String thisName, String otherName, boolean useGenerics);
	
	public boolean isComponent();
	
	public String getExtraClassCode();
		
	public boolean needsEqualsHashCode();
	
	public boolean hasIdentifierProperty();
	
	public boolean needsAnnTableUniqueConstraints();	
	public String generateAnnColumnAnnotation(Property property);
	public String generateAnnIdGenerator();
	public String generateAnnTableUniqueConstraint();
	public Iterator getAllPropertiesIterator();

	public String getPackageName();
	public String getShortName();

	public Iterator getToStringPropertiesIterator();
	public Iterator getEqualsHashCodePropertiesIterator();
	
	public boolean needsToString();
	
	public String getFieldJavaDoc(Property property, int indent);
	public String getFieldDescription(Property property);

	public Object getDecoratedObject();

	public boolean isInterface();
	
	public boolean isSubclass();

	public List getPropertiesForFullConstructor();
	public List getPropertyClosureForFullConstructor();
	public List getPropertyClosureForSuperclassFullConstructor();
	
	public boolean needsMinimalConstructor();
	public List getPropertiesForMinimalConstructor();
	public List getPropertyClosureForMinimalConstructor();
	public List getPropertyClosureForSuperclassMinimalConstructor();
	
	public POJOClass getSuperClass();
	
	public String getJavaTypeName(Property p, boolean useGenerics);
	public String getFieldInitialization(Property p, boolean useGenerics);
	
	public Property getIdentifierProperty();
	
}

是不是一幕了然啊,这下知道这些对象的定义和方法执行结果了 。 同理,查处模版需要的其它的代码文件 。

待续......

使用MyEclipse 自动生成EJB3 企业级项目代码的研究(三)-Velocity

什么是Velocity?

官方网站

velocity是一个基于java的模板引擎。它允许任何人使用简单但是极为出色的模板语言来引用java中定义的对象。当velocity被用在了web开发中,根据mvc模型,网页设计人员可以和java程序员并行开发网站,这意味着,网页设计人员可以专注于创建页面的显示效果,而java程序员可以专注于编写复杂的代码。velocity将java代码从网页中分离出来,使得网站在其生命期中维护性更强,而且可以提供一个可行的选择到jsp或php。

velocity的能力远远超过了它在网页领域中的应用。利用velocity,可以使用模板生成sql语句,PostScripte,和xml。velocity可以被单独用作生成源代码或报告,或作为一个其他系统的集成组件。例如,velocity为web应用框架提供了模板服务,而turbine根据真正的mvc模型,同样也在表现层中推动着web应用。

我们来看看Velocity的基本语法吧,掌握了基本上可以看懂模版文件了。

一,变量定义

因为是一种弱类型语言,所以不用定义变量的类型,编译器根据赋值的类型自动进行判断,定义的关键字

为set,set前要加#,变量名前必须加$,变量名和值的组合要放在小括号中,不必用分号结束语句。例如:

#set($maxValue=5)

#set($name="Bob")

也可以定义数组,形式如下:

#set($arrayName=["element1","element2",...])

例如:#set($members=["mary","rose","Mr Black"])

二,注释

对单行注释,形式如下:

#set($name="mike") ##这是一个注释:定义名字为mike

对多行注释,形式如下:

#set($name="mike")

#*

这是一个多行注释:定义名字为mike,

如果定义为其他的,不符合用户习惯!

*#

三,流程控制

支持的流程控制有:#if...#else...#end(逻辑判断语句)和#foreach(... in ...)...#end(循环语句),可以嵌套使用。例如:if的例子

 <table>

    <tr>

      <td>

      #set($name="mary")

      #set($sex="female")

      #set($age=20)

      #set($coutry="America")

      #if($name=="mary")

        hello mary!<br>

      #end

      #if($sex=="male")

        You are a boy!<br>

      #else

        You are a girl!<br>

      #end

      #if($age<12)

        You are too young!<br>

      #elseif($age>12 && $age<18)

        You are not an adult!<br>

      #else

        You are an adult!<br>

      #end

      #if($country!="China")

        #if($country=="Amemica")

          You are from America!<br>

        #end

      #else

        #if($city=="Beijing")

          You are from Beijing!<br>

        #else

          You are not from Beijing!<br>

        #end

      #end

      </td>

    </tr>

 </table>

例如:foreach的例子

 <table>

    <tr>

      <td>

      #set($members=["mary","rose","mike"])

      #foreach($member in $members)

        $member<br>

      #end

      </td>

    </tr>

 </table>

另外,foreach循环不仅可以显示数组内容,也可以显示java的Vector、List、Iterator等对象的内容, 

其他学习网址:

http://www.javaworld.com/javaworld/jw-12-2001/jw-1228-velocity.html?page=3

http://www.openitpower.com/wenzhang/103/10656_1.html

 

Velocity  的完整语法因为太长了,我就不放在这里了,有兴趣的朋友可以到它的官方站点去学习。

待续......

使用MyEclipse 自动生成EJB3 企业级项目代码的研究(二)

接下来讲讲EJB Reverse-Engineering的自定义模版

老实说,发现可以自定义模版后我一阵子高兴 。 但很快就失落了。因为看不懂模版文件,看不懂又如何自定义呢 ,请你看看下面的模版文件代码:

$pojo.getPackageDeclaration()

$secondpassclassimports
#set($declarationName=$pojo.importType($pojo.getDeclarationName()))
/**
#if($ejb3)
 * Facade for entity $declarationName.
 * @see $pojo.getQualifiedDeclarationName()
 #else
 * Data access object (DAO) for domain model class $declarationName.
 * @see $pojo.getQualifiedDeclarationName()
 #end
 * @author MyEclipse Persistence Tools
 */
#if($ejb3)
@$pojo.importType("javax.ejb.Stateless")
#set($className="${declarationName}Facade")
#else
#set($className="${declarationName}DAO")
#end

public class $className #if ($daoExporter.getExtends())extends $pojo.importType($daoExporter.getExtends()) #end $daoExporter.getImplementsString($pojo.getDeclarationName()){
#if(!$ejb3 && !$jpa)
    private static final $pojo.importType("org.apache.commons.logging.Log") log = ${pojo.importType("org.apache.commons.logging.LogFactory")}.getLog(${className}.class);
#end

//orders filter
public String orders = ""  ;

#if ($generatePreciseFindBy)
    //property constants
#foreach($property in $pojo.getFindByIterator())
    public static final String $pojo.getConstantString($property.Name) = "$property.Name";
#end

#end
......

接着开始研究这到底是什么语法 。 查看了所有MyEclipse的帮助(英文)以及官方网站,都只说到如何用自定义模版,而没有说如何定制和开发自定义模版 。
不断的努力后,突然想起来Hibernate 参见了PowerDesigner的数据映射机制 ,也借用了他的模版规则  。 于是研究PowerDesigner , 发现语法惊人的相似 。但细看之下又不相同 , 比如:
#if
...
#end
在PD中就是
%if
...
%end

看来还是不对 。
(几个小时之后)

开始搜索扩展名 .vm , 发现了一个大名堂。原来它采用的是 Velocity

这下终于找到正主了。 下面的问题就容易了,研究Velocity 。那么什么是Velocity呢 ?

待续......

11月2日

使用MyEclipse 自动生成EJB3 企业级项目代码的研究(一)

最近一直在研究J2EE有什么好的MDA代码生成工具 。 花了不少时间研究了国外很流行的OptimalJ , 研究明白过后才发现它实在是不合适我的项目使用 。 最后在MyEclipse DBBrowser里面看到了有根据数据库结构生成 EnttiyBean 和 SessionBean的功能 。 但是却发现有不少缺陷,包括不能支持排序,不支持复杂查询 , 不支持根据外键查询,不支持分页等......

不过突然想起来Eclipse的模版功能很强劲 。 MyEclipse 程序员在做开发的时候没有理由不做自定义代码模版的功能开发的  。 就好像CodeSmith一样可以自动生成各种各样的代码 .于是就开始查找。 首先查找这个功能的实现是什么插件 ,终于找到了名字:是 Hibernate Reverse Engineering .

image

使用Hibernate Reverse Engineering最大的好处就是它可以自动生成POJO和DAO的源码,以及映射配置文件。我打算给每个Facade (DAO)类方法改成静态方法(改来玩玩,不推荐用静态方法).           首先得分析一下,生成的DAO是继承的BaseHibernateDAO,而BaseHibernateDAO是扩展的 IBaseHibernateDAO接口,在这个接口中,定义了getSession方法。要把数据库操作方法改成static,这个 getSession方法也必须是static,但是在接口中不能定义是static的方法,那么唯一的办法就是不管IBaseHibernateDAO 接口,在BaseHibernateDAO类中自己实现一个静态的getSession方法。
           BaseHibernateDAO有模板文件,但是没有必要改它,因为一个工程只用得到一个BaseHibernateDAO。我们要改的模板文件是用于 生成具体DAO类的。然而在安装目录下用hibernate关键字搜一搜,并没有发现哪个文件与我们的DAO有关,难道MyEclipse把生成规则写死 在代码里了吗?
           我不相信MyEclipse的开发人员那么糊涂,于是用templates关键字搜索了一下,发现了这个: myeclipse_templates_5.5GA.zip,打开一点,发现里面全部是有关hibernate的模板文件,另外有一个readme的说 明,关键部分如下:

  1. 2) Edit the velocity templates you wish to customize.  
  2. Note: POJO templates are present in the pojo folder  
  3.        DAO template in the dao folder  
  4.        HBM templates in the hbm folder  
  5. 3) Optional: Though not necessary, it is recommended that you delete the  
  6.     velocity templates you do not plan to customize. This will prevent future  
  7.     confusion over which templates are being processed by MyEclipse.  
  8. 4) Invoke the JPA/EJB3/Hibernate Reverse-Engineering wizard and select   
  9. "Use custom templates". Manually enter or click the browse button to select the   
  10.     myeclipse_templates_5.5GA folder.  

           上面的意思是:把解压之后的文件夹放在一个方便的地方(不需要放在原目录下,那样找起来很不方便),修改它,然后删掉其他你没有修改的文件,以免发生可能的冲突,最后,在Hibernate Reverse-Engineering的对话框中,钩选Use custom templates,输入你解压后的文件夹(myeclipse_templates_5.5GA)路径,就可以使用到自定义的模板了。

image
           照这个意思,我修改了DAO文件夹下的daohome.vm和facadeif.vm,在其中所有的public标识后加上了static(我也不知道哪 些是hibernatet真正用到的,反正我不用EJB和JPA)。然后照它的意思试 了一把,果然,DAO方法全变成了static。

Java中的HashMap , HashTable , Collection , Set , HashSet , List , ArrayList 等容器类的略表(转)

容器类可以大大提高编程效率和编程能力,在Java2中,所有的容器都由SUN公司的Joshua Bloch进行了重新设计,丰富了容器类库的功能。
  Java2容器类类库的用途是“保存对象”,它分为两类:
  Collection----一组独立的元素,通常这些元素都服从某种规则。List必须保持元素特定的顺序,而Set不能有重复元素。
  Map----一组成对的“键值对”对象,即其元素是成对的对象,最典型的应用就是数据字典,并且还有其它广泛的应用。另外,Map可以返回其所有键组成的Set和其所有值组成的Collection,或其键值对组成的Set,并且还可以像数组一样扩展多维Map,只要让Map中键值对的每个“值”是一个Map即可。
  1.迭代器
  迭代器是一种设计模式,它是一个对象,它可以遍历并选择序列中的对象,而开发人员不需要了解该序列的底层结构。迭代器通常被称为“轻量级”对象,因为创建它的代价小。
  Java中的Iterator功能比较简单,并且只能单向移动:
  (1) 使用方法iterator()要求容器返回一个Iterator。第一次调用Iterator的next()方法时,它返回序列的第一个元素。
  (2) 使用next()获得序列中的下一个元素。
  (3) 使用hasNext()检查序列中是否还有元素。
  (4) 使用remove()将迭代器新返回的元素删除。
  Iterator是Java迭代器最简单的实现,为List设计的ListIterator具有更多的功能,它可以从两个方向遍历List,也可以从List中插入和删除元素。
  2.List的功能方法
  List(interface): 次序是List最重要的特点;它确保维护元素特定的顺序。List为Collection添加了许多方法,使得能够向List中间插入与移除元素(只推荐LinkedList使用)。一个List可以生成ListIterator,使用它可以从两个方向遍历List,也可以从List中间插入和删除元素。

  ArrayList: 由数组实现的List。它允许对元素进行快速随机访问,但是向List中间插入与移除元素的速度很慢。ListIterator只应该用来由后向前遍历ArrayList,而不是用来插入和删除元素,因为这比LinkedList开销要大很多。
  LinkedList: 对顺序访问进行了优化,向List中间插入与删除得开销不大,随机访问则相对较慢(可用ArrayList代替)。它具有方法addFirst()、addLast()、getFirst()、getLast()、removeFirst()、removeLast(),这些方法(没有在任何接口或基类中定义过)使得LinkedList可以当作堆栈、队列和双向队列使用。
  3.Set的功能方法
  Set(interface): 存入Set的每个元素必须是唯一的,因为Set不保存重复元素。加入Set的Object必须定义equals()方法以确保对象的唯一性。Set与Collection有完全一样的接口。Set接口不保证维护元素的次序。
  HashSet: 为快速查找而设计的Set。存入HashSet的对象必须定义hashCode()。
  TreeSet: 保持次序的Set,底层为树结构。使用它可以从Set中提取有序的序列。
  LinkedHashSet: 具有HashSet的查询速度,且内部使用链表维护元素的顺序(插入的次序)。于是在使用迭代器遍历Set时,结果会按元素插入的次序显示。
  HashSet采用散列函数对元素进行排序,这是专门为快速查询而设计的;TreeSet采用红黑树的数据结构进行排序元素;LinkedHashSet内部使用散列以加快查询速度,同时使用链表维护元素的次序,使得看起来元素是以插入的顺序保存的。需要注意的是,生成自己的类时,Set需要维护元素的存储顺序,因此要实现Comparable接口并定义compareTo()方法。

11月1日

EJB3 通过 JNDI 远程调用(集群试)Bean 方法 (给ychan 布置的练手项目)

给ychan布置的任务,让他搞定在不同的机器上,不用的JVM调用远程的SessionBean .他反馈的完整结果如下: 目前网上类似文章都是抄的,而且都是抄错了。因此我才把这个发布出来:

1:新建一个EJB3.0的工程NameEjb(发布到机器为192.168.0.2的机器上)

开发出SessionBean:NameSession

package istg.ejb;

/*

* Author: YiChu Han

* Date: 2007-10-31

*/

import javax.ejb.Local;

import javax.ejb.Remote;

import javax.ejb.Stateless;

import javax.interceptor.Interceptors;

@Stateless

@Remote(NameSessionRemote.class)

@Local(NameSessionLocal.class)

@Interceptors(NameInter.class)

public class NameSession implements NameSessionLocal, NameSessionRemote {

public String getName(){

return "Zhong Guo";

}

public String getPassword(String value){

return value+":这是密码";

}

}

2:开发Remote接口

package istg.ejb;

/*

* Author: YiChu Han

* Date: 2007-10-31

*/

import javax.ejb.Remote;

@Remote

public interface NameSessionRemote {

public String getName();

public String getPassword(String value);

}

打包成NameEjb.jar并布署在Jboss服务器上

3:编写调用EJB的客户端(发布到机器为192.168.0.2的机器上)

新建一个WEB工程NameClient,Jboss_Home/lient目录下的Jar导入工程下

<%@ page contentType="text/html; charset=GB2312"%>

<%@ page import="

java.util.Properties

,javax.naming.Context

,javax.naming.InitialContext

,javax.rmi.PortableRemoteObject

,istg.ejb.NameSessionRemote

" %>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>

<body>

<%

try{

Properties props = new Properties();

props.setProperty("java.naming.factory.initial","org.jnp.interfaces.NamingContextFactory");

props.setProperty("java.naming.provider.url", "192.168.0.3:1099");

props.setProperty("java.naming.factory.url.pkgs", "org.jboss.naming");

InitialContext ctx = new InitialContext(props);

NameSessionRemote user=(NameSessionRemote)ctx.lookup("NameSession/remote");

String name=user.getName();

out.println("String Name=="+name);

out.println("<br/>");

String password=user.getPassword("Gong Chan Dang");

out.println("String Password=="+password);

}catch(Exception e){

e.printStackTrace();

}

%>

</body>

</html>

把工程打成NameClient.war,布署到Tomcat的服务器上。因为要不在同一个机器上所以要把Jbos_Home\ server\default\conf下的jboss-service.xml中的

<attribute name="Port">1099</attribute>

<!-- The bootstrap JNP server bind address. This also sets the default

RMI service bind address. Empty == all addresses

-->

<attribute name="BindAddress">${jboss.bind.address}</attribute>

<!-- The port of the RMI naming service, 0 == anonymous -->

<attribute name="RmiPort">1098</attribute>

绑定地址(绿色文字)改为192.168.0.3

10月31日

EJB3 中的 消息驱动Bean(Message Driven Bean)的经验及实例 (一)

J2EE 的开发还是那么不方便 。倒不觉得J2EE有什么难的 。 主要是非常不方便,很多的硬性规定,很多容易疏忽而出错的地方,很多的工具和文档需要你配置 。 所以有些小孩儿就觉得J2EE学习困难 ,觉得用J2EE才是牛人,才本事吧 。 其实是天下之大谬也 ! 程序不是这样写的 。

切入正题 , EJB3开发中的消息驱动Bean 还是比较有用 ,我们这里的EJB容易都用的JBoss。 这里我先说说写消息驱动Bean的步骤:

1,得到一个JNDI初始化上下文 。 示例代码:

Properties props = new Properties();
props.setProperty("java.naming.factory.initial", "org.jnp.interfaces.NamingContextFactory");
props.setProperty("java.naming.provider.url", "localhost:1099");
props.setProperty("java.naming.factory.url.pkgs", "org.jboss.naming");   

InitialContext ctx = new InitialContext(props) ;

2, 根据上下文来查找一个连接工厂TopicConnectFactory/ QueueConnectionFactory (有两种连接工厂,根据是topic/queue 来使用相应的类型);
例子对应代码:
QueueConnectionFactory factory = (QueueConnectionFactory) ctx.lookup("ConnectionFactory");


3,从连接工厂得到一个连接(Connect 有两种[TopicConnection/ QueueConnection]);
例子对应代码:conn = factory.createQueueConnection();
顺便说一下 , 分发的时候用TopicConnection ,定向的时候用QueueConnection


4, 通过连接来建立一个会话(Session);
例子对应代码:session = conn.createQueueSession(false, QueueSession.AUTO_ACKNOWLEDGE);
这句代码意思是:建立不需要事务的并且能自动接收消息收条的会话,在非事务Session 中,消息传递的方
式有三种:
Session.AUTO_ACKNOWLEDGE :当客户机调用的receive 方法成功返回,或当MessageListenser 成功处理
了消息,session 将会自动接收消息的收条。
Session.CLIENT_ACKNOWLEDGE :Session 对象依赖于应用程序对已收到的消息调用确认方法。一旦调用
该方法,会话将确认所有自上次确认后收到的消息。该方法允许应用程序通过一次调用接收、处理和确认一
批消息。
Session. DUPS_OK_ACKNOWLEDGE :一旦消息处理中返回了应用程序接收方法,Session 对象即确认消息
接收,允许重复确认。就资源利用情况而言,此模式最高效。


5, 查找目的地(Topic/ Queue);
例子对应代码:Destination destination = (Queue) ctx.lookup("queue/foshanshop");


6,根据会话以及目的地来建立消息制造者MessageProducer (扩展了QueueSender 和opicPublisher 这两个基本接口)
例子对应代码:
MessageProducer producer = session.createProducer(destination);
TextMessage msg = session.createTextMessage("Hello China");//发送文本
producer.send(msg);

 

待续......

10月26日

EJB 客户端调用服务器端(JBoss)的方式

当你的EJB3是deploy to jboss的情况下,ejb client调用EJB3分两种情况:

1) 发布在jbossweb app作为client来调用EJB

EJB和调用EJB的WEB 应用都发布在Jboss 集成环境下。在Jboss下发布WEB 应用,需要把WEB 应用打包成war 文件。另外在此环境下调用EJB 不需要把EJB 的接口类放入/WEB-INF/classes/目录中,否则在调用Stateful Bean 就会发生类型冲突,引发下面的例外

java.lang.ClassCastException: $Proxy84

org.apache.jsp.StatefulBeanTest_jsp._jspService(org.apache.jsp.StatefulBeanTest_jsp:55)

如果EJB和调用EJB的WEB 应用都发布在Jboss 集成环境下,那么EJB的Local或Remote接口都可以被调用

发布在Jboss 下的客户端不需要明确设置JNDI 访问的上下文环境,可以直接通过

InitialContext ctx = new InitialContext()

获得上下文环境,容器会自动赋给InitialContext 正确的环境,例如:

InitialContext ctx = new InitialContext();//客户端和jboss运行在同一个jvm,不需要传入props

HelloWorld helloworld = (HelloWorld) ctx.lookup("HelloWorldBean/remote");

如果硬给InitialContext 设置了访问属性,反而会带来不可移植的问题,因为你的应用有可能部署在weblogic 等应用服务器。(本教程考虑到部分同学可能需要在独立的J2se 中调用EJB,为了教学的方便,把访问属性都设上了,这样不管在jboss、j2se 或独立tomcat,都能获得正确的InitialContext)

2) 在单独的TomcatJ2SE(如junit test)中调用EJB

在正式的生产环境下,大部分调用EJB 的客户端可能是单独的Tomcat 或Resin。下面介绍如何在单独的Tomcat服务器中调用EJB。在单独的Tomcat 服务器中调用EJB 需要有以下步骤:

A) 把调用EJB 所依赖的Jar 包拷贝到tomcat 下的/shared/lib 目录或WEB 应用的WEB-INF/lib,所依赖的Jarjboss 安装目录的client目录下。你可以在eclipse里设置一个自定义的library “ejb3 library”,把这些jars都添加到该library里,然后在你的EJB project里把“ejb3 library” add to classpath

B) 把EJB 接口拷贝到应用的/WEB-INF/classes/目录下

C) 客户端访问EJB 时必须明确设置InitialContext 环境属性,代码如下:

Properties props = new Properties();

props.setProperty("java.naming.factory.initial", "org.jnp.interfaces.NamingContextFactory");

props.setProperty("java.naming.provider.url", "localhost:1099");

props.setProperty("java.naming.factory.url.pkgs", "org.jboss.naming");

InitialContext ctx = new InitialContext(props);//客户端和jboss运行在同一个jvm,不需要传入props

HelloWorld helloworld = (HelloWorld) ctx.lookup("HelloWorldBean/remote");

Tip: 除了上面通过硬编码设置环境属性的方式外,还可以在应用的classpath 下放置一个jndi.properties 文件

注意:在单独的tomcat和J2SE里不能调用EJB Local 接口,因为他与JBOSS 不在同一个VM 中。J2se中调用EJB3 同样需要把上述jar EJB 接口放置在应用的类路径下。