凌华's profile张凌华PhotosBlogListsMore Tools Help

Blog


    November 18

    xccxcxv

    xcxccxcxxcc

    March 14

    改博客地址了

    我改博客地址了,新地址为:

    http://www.lhzhang.org

    December 06

    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一统天下的时代的来临!

    November 30

    通货膨胀严重威胁稳定

    中国社会科学院世界经济与政治研究所所长余永定为英国《金融时报》中文网撰写

    (2007年10月9日英国《金融时报》首席经济评论员马丁•沃尔夫发表了题目为《通货膨胀不是对稳定的重大威胁》的评论。作为马丁•沃尔夫专栏的特邀评论员,余永定对他的观点进行了点评。马丁•沃尔夫的文章已译成中文,见FT中文网站。下面是余永定的评论:)

    可以说,通货膨胀对中国的经济稳定暂时还没有构成严重威胁。但是,中国政府有足够的理由担心通货膨胀将会造成的严重威胁。

    首先,2007年中国经济的增长将超过11%。直到前不久,政府和经济学界普遍接受的看法是,中国的潜在增长率在8%至9%之间。在中国的十一五规划中,经济增长目标被隐含地确定为8%以下。在过去,中国GDP只要保持连续两年10%以上增长,都会因此在四至五个季度以后引发严重的通货膨胀。在目前的经济周期中,中国经济已经保持了连续四年10%以上的增速,且通货膨胀率被控制在3%以下,这已经是一个奇迹了。


    或许,中国的劳动生产率在最近几年取得了长足的进步。但是,由于固定资产投资约25%的增速,中国的投资率已经超过45%。加上地方政府主导的遍布全国的城市建设运动,以及在这一过程中对能源和原材料极端浪费,很难令人相信中国的生产率(据全要素生产率和/或资本产出比计算得出)已经大幅攀升到如此高的程度,以致中国的潜在经济增长率已从8%增至11%,因而能够保持目前的增长势头而不导致严重的通货膨胀。诚然,中国的内需低于其潜在的供给,但当我们在讨论需求过剩和通货膨胀时,相对的概念应是总需求(内需加外需),而不仅仅是内需。从这点出发,可以认为,中国存在需求过剩并且过剩的需求正在增加,其结果必然是通胀压力的上升。

    第二,近期的食品价格上涨并不能完全归咎于一次性的、孤立的、外部的供给冲击。实际上,从饲料到化肥,所有农产品生产所需的投入要素价格都有所上涨,而这至少可以部分归因于需求拉动。举例来说,由于房地产的发展,中国的耕地面积迅速萎缩,几乎达到了政府规定的保护下限。尽管政府能在一段时间内通过行政手段遏制食品价格上涨,但由于许多导致食品价格上涨的因素不会自行消失,通货膨胀也不会自行消失。

    第三,通胀预期在百姓中已经形成。据最近一项由中国央行进行的调查显示,公众认为通货膨胀会进一步恶化。人们已开始调整自己的行为:他们取出自己的存款转而购买股票和房地产,他们要求进一步提高工资和薪金。因此,即使通货膨胀在现阶段对稳定不构成威胁,日益严重的通胀预期却能如此。

    第四,近年来,工资和薪金已经达到两位数的增长,并且持续加快。但这种增长趋势并没有反映在统计数字上。因为与正规的收入相比,“灰色收入”在中国也许更为重要。我不知道怎样才能让生产力快速上升以抵消迅速增加的劳动力成本从而不会推高产品价格。

    第五,价格扭曲仍然广泛蔓延。中国的能源价格是世界上最低的;税收对采矿和开采活动极为慷慨;几乎不需为污染付费;在许多地方,外国直接投资(FDI)用地的租金非常便宜。在一定程度上,低通货膨胀率是以低效率和资源配置不当为代价实现的。如果政府不放弃进一步价格改革的计划,那么很多重要产品的价格上涨将无法避免,这又可能加剧通胀和通胀预期。

    第六,中国的货币供应量在很长一段时间持续快速增长,远高于GDP的增长速度。目前,尽管中国央行采取货币紧缩政策,但M2的增长速度在8月份仍高于18%。银行贷款增速也非常高。也就是说,中国的金融环境状况(financial condition)仍然相当宽松,有利于通货膨胀。

    第七,2006年末以来,中国的股票价格已经增加了一倍多,股票市值从2005年不足GDP的一半上升到目前超过GDP总额。财富效应的迹象已无处不在。

    总之,目前,通货膨胀在中国恶化的所有必要条件都已具备。令人惊讶的不是中国的通货膨胀会恶化,而是通货膨胀率竟仍如此之低。因此,中国政府必须对通货膨胀予以警觉,将其看作是社会稳定的威胁。

    马丁是对的,相比快速增长的潜在和实际产量,中国国内需求的增长依然不足,而不是过热。在2006年,净出口对中国的经济增长的贡献率约为3%至4%。除此以外,中国的国内需求或许无法支持经济9%的增长。在2007年,中国的净出口量将超过3000亿美元。如果中国不能保持这一出口势头,中国经济的增长速度可能会由于产量过剩而显著下降。这意味着,尽管此刻中国经济过热,但过热的经济会很快转化为通货紧缩。这是因为高投资率和高固定资产投资增长率是中国经济增长的特点。所以目前过度投资导致的经济过热能够在将来引发由生产能力过剩造成的通货紧缩。

    2002年至2004年,中国经历了一段时期的投资热。其结果是在2005年初,经济显现出过剩的迹象。不过,由于第二轮投资热的快速膨胀,预期的经济放缓没有出现。从某种意义上讲,中国实际上是通过创造更多的生产能力来吸收过剩的产能。问题是,这种做法是不可持续的。中国自2005年以来激增的净出口额也有部分是产能过剩的结果。

    在我看来,中国同时面临经济过热和产能过剩:当下的经济过热与未来的产能过剩。但不管怎么说,目前的通货膨胀已经对经济稳定形成巨大威胁。首先,它引发通货膨胀预期,从而在成本推动和需求拉动互动的恶性循环中维持通胀。其次,通货膨胀和资产泡沫将会相互强化,并诱发严重的金融不稳定。我认为,当下中国经济形势最危险的特点就是通货膨胀与资产泡沫之间的共生关系。

    马丁在他的文章中没有提到中国的资产泡沫,他或许故意回避讨论这一问题。中国股票的平均市盈率在10月14日达到60。中国的两个证券交易所的资本总额已经在不到两年的时间内增加了一倍多,资本总额与GDP的比率已超过100%。毫无疑问,中国的资产泡沫非常严重。然而,股票市场仍然被不断涌入的流动资本所淹没。

    在过去的几年中,流动性主要来自中国央行银行对外汇市场的干预。此举是为了在面对日益庞大的经常帐户及资本帐户盈余时,能够控制人民币升值的速度。为了保持物价稳定、遏制资产泡沫,中国央行已经采取了大规模的对冲措施以吸干过多的流动性。

    收回流动性的政策已经给商业银行带来了严重的问题,因为它们必须购买越来越多的低收益央行票据,并且必须将更高比例的现金存入央行。尽管如此,这一行动基本上成功地收回了过多的流动资金,使得基础货币和M2的增长率与央行确定的目标大致相符。

    然而,尽管对冲流动性相对成功,但中国的金融系统仍充斥着过多的流动资金。否则的话,资产价格不会飙升;通货膨胀应被驯服,投资增长率应该下降。那么,过多的流动资金从哪来?答案就在于流动性过剩不仅是一个货币供给的问题,也是一个货币需求问题。在特殊情况下,资金需求也可以成为流动性过剩的推动力。即使货币供应量保持不变,流动性过剩也能由货币需求的减少而出现。

    两大根本原因导致了货币需求的急剧减少。首先,居民持有储蓄存款的偏好开始减弱。资本市场的发育给普通储蓄者提供了分散其资产的机会,很多人现在已经拥有股票、债券和固定资产。2004年至2006年的股市改革,伴随着外国资本的流入,共同点燃了提升股价热气球的火焰。不论其来源,通过股票市场实现较高回报会鼓励居民把自己的存款从银行移至证交所。股票价格的上升,又会进一步加剧上述“存款搬家”过程。其次,即使其他因素未变,由于利率低于物价上涨速度(实际利息率为负),居民通过增加储蓄存款进行储蓄的意愿也会降低。2006年四季度以来开始恶化的通货膨胀正在为“存款搬家”火上浇油。总之,股票价格的上涨和通货膨胀加剧了家庭储蓄的转移(亦既减少了公众的货币需求)。

    资产价格飞涨和通货膨胀恶化肯定是由流动性过剩所引发。在过去相当一段时间里,流动性过剩主要是双顺差导致外汇储备急剧增加(这又是因为央行必须不断干预外汇市场以维持人民币汇率稳定),而央行又未能充分冲销的结果。但在最近一段时间,流动性过剩的罪魁祸首是大幅下降的货币需求。在此种情况下,即使中国央行完全对冲了由外汇储备增加产生的过剩流动性,并将M0和M2的增长调整到历史最佳状态,货币供应量仍将超过需求量,流动性过剩仍然会继续存在。(对方因货币需求减少而产生的流动性过剩,需要采取升息、增加资产供应等政策)。

    从历史上看,中国货币供应量的涨幅远远超过国内生产总值的增长,并且储蓄存款量巨大。中国的M2与GDP的比例高达160%以上,或许是全世界最高。就38万亿元存款相对于总额8万亿元的流通股而言,储蓄存款由银行转入证交所推高股价的潜力巨大。不断涌入到股票和房地产市场的投资阻碍了对资产的理性估值。股票价格上涨潜力远远没有耗尽。一方面,盛宴可能会持续很多年,直到最愚蠢的买家购买了最昂贵的最后一张股票,股市才会开始崩盘。另一方面,中国目前的(资金推动型的)股价上涨是一个正反馈的过程。换言之,这是一个高度不稳定的的过程。因此,市场可能随时崩溃。这需要政府迅速采取措施以冷却资本市场,而且越快越好。然而,由于种种限制中国政府还不愿或不能这样做。

    股市泡沫、通货膨胀、离谱的房价、投资的过度以及巨额的贸易盈余,这些都是对稳定的巨大威胁。中国的收支平衡正处在风口浪尖。作为最坏的一种可能性,如果股市泡沫破裂,房市崩盘,劳动力成本持续快速上涨,以及全球对中国产品的需求出于各种原因而下降,那么中国的经济将会怎样?分析出所有的可能性并制定出综合的应对策略对经济学家来说确实是很大的挑战。

    未经英国《金融时报》书面许可,对于英国《金融时报》拥有版权和/或其他知识产权的任何内容,任何人不得复制、转载、摘编或在非FT中文网(或:英国《金融时报》中文网)所属的服务器上做镜像或以其他任何方式进行使用。已经英国《金融时报》授权使用作品的,应在授权范围内使用。

    November 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和你的应用。选择合适的第三方软件和框架时确保它们可以被集群。然后设计合适的架构以得到集群带来的实际利益。

    Google Android会成为手机领域的微软Windows吗?

    Google gPhone手机的传言已经沸沸扬扬好几个月了,然而就像Google其他产品那样出人意料,当Android轰轰烈烈推出的时候,原来并非手机产品,而是手机操作系统。Google对无线互联网市场垂涎已久,这已经是尽人皆知的事情。在公众场合,无论是Google全球CEO艾里克施密特博士,还是在中国媒体面前的李开复博士,都毫不掩饰Google对于无线互联网市场的向往。Android的推出就像Google在无线互联网市场亮出的一把利剑,已经是司马昭之心,路人皆知。

    Google Android动了谁的奶酪?是Apple,还是微软?Apple刚刚发布了划时代的iPhone手机,而微软已经在手机操作系统领域耕耘了很多年。大家可能忽略了一个简单的事实:Google全球CEO艾里克施密特是Apple公司的董事会成员,Google Android实际上也避开了和iPhone的竞争关系。

    互联网时代奇迹般崛起的Google,已经成为微软的心腹大患。然而不论GoogleOS的谣言传得多么活灵活现,业界多么意淫Google直接挑战微软Windows操作系统,然而Google从来都是按兵不动。Google看得很清楚,桌面操作系统时代快要结束了,现在是掌上操作系统时代登场了,谁能够先一步占领消费者的手掌,谁才是真正的赢家。这一次,Google终于亮剑了,亮出来的绝对是一把无坚不摧的利剑 - Android。

    Android对于Google未来的无线互联网战略为什么那样重要?Android比其他手机操作系统有什么更牛的地方?

    一、Android是开源的

    开源社区对于软件行业的推动力已经没有人可以否认了,纵观整个手机操作系统产品,也只有Android的开源力度是如此之大,之强。

    二、Android不单纯只是操作系统
    android-system
    Android不只是一个操作系统而已,它包括了:

    1、经过Google剪裁和调优的Linux Kernel,对于掌上设备的硬件提供了优秀的支持。Google在Linux方面的应用能力不容置疑,Google公司所有的几十万台服务器全部都是自己修改过的Linux操作系统。

    2、经过Google修改的Java虚拟机Dalvik,请注意这个虚拟机并不是Sun的Hotspot,而是基于Apache Harmony虚拟机版本进行改良而来,能够提供比Hotspot高得多的执行性能。有了Java虚拟机,大部分Java核心类库都已经可以直接运行。

    3、大量立即可用的类库和应用软件,例如浏览器WebKit,数据库SQLite,让你可用轻易开发出来媲美桌面应用复杂度的手机软件。

    4、Google已经开发好的大量现成的应用软件,同时可以直接使用Google很多的在线服务。

    5、Google提供了基于Eclipse的完整开发环境,模拟器,文档,帮助,示例,当然,还有悬赏1000万美元的花红。

    三、围绕Android形成了一个移动手机联盟,主要的手机厂商几乎全部在列,对于已经形成的一个庞大的产业联盟的推动力来说,影响力是非常惊人的。

    事实上,通过Android战略,Google已经开始抢占未来互联网领域的制高点。对于我们程序员来说,有几个非常值得关心的问题:

    一、Android是用Java来开发应用的
    对于Java程序员,没有比这更令人开心的事情了。的确是这样,打开你的Eclipse,安装上插件,你现在就可以利用你所有的Java编程经验开发Android应用,而这项应用将在未来几年之后可以运行在绝大部分智能手机之上。Java屹立不倒

    二、Java ME前景如何?
    事实上,Android是在继续JavaME未竟的事业。JavaME提供了统一的编程平台,但是JavaME不能调用操作系统资源,也没有提供诸多的应用工具,最终JavaME处在一个非常尴尬的位置上。而Android往下直达操作系统内核,往上直通现成的应用软件,例如联系人,日历,地图,浏览器,Android就是手机应用的未来。

    三、我应该现在开始学习Android吗?
    如果你已经是一个熟练的Java程序员,那么你唯一需要做的就是熟悉一下Android类库而已,

    无线互联网已经成为未来时代争夺的制高点,Apple iPhone上市,紧接着Google Android一出,你会发现很多传统的无线互联网技术,例如Java ME,WAP,都将成为过眼云烟,而站在未来时代最前沿的是Google和Apple两个身影。

    November 26

    Ruby on Rails 和 J2EE:两者能否共存?

    两个 Web 应用程序框架的比较


    developerWorks

    文档选项


    将此页作为电子邮件发送

    将此页作为电子邮件发送

    级别: 初级

    Aaron Rustad (arustad@gmail.com), 技术架构师, Anassina, Inc.

    2005 年 8 月 11 日

    Ruby on Rails 是一个相对较新的 Web 应用程序框架,构建在 Ruby 语言之上。它被宣传为现有企业框架的一个替代,而它的目标,简而言之,就是让生活,至少是 Web 开发方面的生活,变得更轻松。在本文中,Aaron Rustad 将对 Rails 和传统的 J2EE 框架在架构上的一些关键特性进行比较。

    Ruby on Rails 是一个 Web 应用程序框架,它的目标是为应用程序开发提供一条易行道。实际上,框架的支持者们声称 Ruby on Rails 开发人员的生产率最多是使用传统 J2EE 框架的 10 倍。(请阅读“Rolling with Ruby on Rails”一文,以获得关于这一声明的更多内容;请参阅 参考资料)。虽然这句话造成了 Rails 和 J2EE 社区相当大的争议,但争论中却很少谈及如何比较 Rails 和 J2EE 架构。本文将使用企业应用程序中常见的开源工具,对 Rails 框架和典型的 J2EE 实现进行比较。

    什么是 Ruby on Rails?

    要想找到用一句话描述 Rails 的简单说明,只需查看项目的 主页 即可:

    Rails 是一个用 Ruby 编写的全栈的(full-stack)、开源的 Web 框架,可以使用它来轻松编写实际的应用程序,所需的代码也要比大多数框架花在处理 XML 上的代码少。

    虽然我不能保证框架确实会提供它所承诺的轻松快乐,但是上面这句话确实很好地总结了 Rails 的品质。全栈包括:Web 服务器、处理 HTTP 请求和响应的框架,以及方便地把数据持久存储到关系数据库的框架。Rails 通过消除复杂的 XML 配置文件,使用 Ruby 语言的动态性质,帮助把静态类型语言中常见的许多重复代码减少到最少,努力使开发工作变得更容易。

    Rails 和典型的 J2EE Web 堆栈

    图 1 比较了 Rails 堆栈和典型的 J2EE Web 堆栈(包括 Tomcat servlet 容器、Struts Web 应用程序框架和 Hibernate 持久性框架)。

    图 1. Rails 和 J2EE 堆栈的比较
    stack

    可以看到,Rails 堆栈和构成普通的基于 J2EE 的 Web 应用程序的组件之间的基本区别很小。两者都有用来执行应用程序代码的容器;都有帮助分离应用程序的模型、视图和控件的 MVC 框架;以及持久存储数据的机制。

    MVC 框架

    模型-视图-控制器(MVC)是应用时间相当长、应用面相当广的一个设计模式。它源自 Smalltalk;如今,几乎所有的 GUI 框架,包括 Web 和胖客户端,都以该框架为基础。MVC 有三个部分:模型,负责业务逻辑,包括应用程序状态和将在这个状态上执行的动作;视图,用来渲染和向用户呈现模型(在 Web 应用程序中,视图一般渲染为 HTML);控制器,定义应用程序的行为。有关 MVC 模式的详细解释,请参阅 参考资料

    前端控制器

    Struts 的 ActionServlet 和 Rails 的 DispatchServlet 都是前端控制器模式的例子;所以,它们提供了相同的功能。它们接受 HTTP 请求,解析 URL,把请求的处理转发给适当的动作。在 Struts 中,动作是扩展自 Action 的类;对于 Rails,动作是扩展自 ActionController 的类。两个前端控制器之间的主要区别是它们如何决定处理具体请求的动作。

    使用 Struts,开发人员需要把特定请求的映射外部化到 XML 配置文件中的 Action 类。当首次装入 ActionServlet 时,它将解析这个文件,并准备接受请求。根据约定,以 .do 结束的请求被重定向到 ActionServlet,由 ActionServlet 分派到适当的 Action图 2 的 XML 是一个典型的映射。它告诉 ActionServlet 把叫作 deleteOrder.do 的请求转发到 controllers.order.DeleteOrderAction 作进一步处理。

    Rails 采用了不同的方式。它没有依赖配置文件把请求映射到某一个动作,而是根据请求的 URL 发现适当的动作。从图 2 可以看到,URL http://localhost/order/delete/4 告诉 Rails 调用 OrderController 实例上的 delete 方法,并将 4 作为可用的实例变量。Rails 足够聪明,知道 /order 将映射到文件 order_controller.rb 中定义的一个控制器类。如果在控制器中定义了 find 方法,那么只要用 find 替代 URL 中的 delete,就可以调用这个方法。

    图 2. Rails 和 Struts 中的 URL 映射
    mappings

    动作和模型

    在 Rails 和 Struts 中,动作用来充当前端控制器和模型之间的桥梁。开发人员提供动作的现实,从而提供特定于应用程序的请求处理。前端控制器负责接受请求,并把请求传递到特定动作。图 3 演示了 Rails 和 Struts 基本的动作层次结构。

    图 3. Rails 和 Struts 的动作层次结构
    actions

    动作是模型还是控制器?

    ActionActionController 从技术上讲是 MVC 模式的控制器的一部分,因为它们对客户发起的事件进行响应。但是,在小型应用程序中,开发人员通常在这些类中对域或业务逻辑进行编码,所以在这些情况下,也可以把它们看作是模型的一部分。最佳实践建议:应当把域逻辑从控制器中抽象出来,放置在它自己的特定于域的类中。

    Struts 要求开发人员扩展 Action 并覆盖 execute(),以处理请求。通常,每个 Action 类都提供了非常具体的工作单元。图 3 演示了三个特定动作:SaveOrderActionDeleteOrderActionListOrdersAction。前端控制器将调用 execute() 方法,传递给它许多有用的对象,其中包括 HTTP 请求和响应对象。ActionForm 是一个类,它可以方便地向视图来回传输并验证与表单有关的输入,ActionMapping 包含映射的配置信息,就像 图 2 的 XML 所描述的那样。

    execute() 方法返回 ActionForward 对象,Struts 用这个对象来确定对请求继续进行处理的组件。一般来说,这个组件是一个 JSP 页面,但是 ActionForward 也能指向其他动作。开发人员必须清楚,Struts 创建的是 Action 的单一实例,并允许多个线程调用它的 execute()。这使请求处理变得更快,因为框架处理每个请求时不用频繁地创建新的 Action 实例。但是因为可以在多个线程之间共享单一对象,所以必须遵守适当的线程注意事项,因为其他线程可能会破坏在这个动作中保持状态的实例变量。

    在 Rails 中,必须扩展 ActionController::Base,让模型参与到请求处理中。Rails 没有将 ActionController 的实例池化;相反,它为每个请求创建新的实例。虽然这对性能可能有负面影响,但是它可以让开发变得更容易。开发人员不需要关注 Struts 中存在的线程问题,因此,会话、请求、标题和参数都可以作为 ActionController 的实例成员来进行访问。ActionController 还是一个将特定域逻辑的所有处理组合在一起的合理场所。Struts 的 Action 类是细粒度的,它提供了非常具体的工作单元,而 Rails ActionController 则是粗粒度的,它将具体的工作单元模拟为一些方法。

    清单 1清单 2 分别演示了典型的 Struts 动作和典型的 Rails 动作

    表 1 提供了对两种方法的逻辑流程的比较,并演示了清单 1 和清单 2 的特定行中发生的事情。研究 DeleteOrderActionexecute() 方法和 OrderControllerdelete 方法,可以看出它们基本上是相同的。

    表 1. execute() 和 delete 方法比较

    步骤
    Struts
    Rails

    框架调用动作
    行 03: execute()
    行 07: delete

    从请求中检索到的 ID
    行 06-07:从请求对象中取出
    行 08:从所有参数的实例哈希中取出

    从数据库删除订单记录
    行 09、14-24:调用 delete() 方法,用 Hibernate 删除记录
    行 09:用 ActiveRecord 删除记录

    重定向到列出剩余订单
    行 11:ActionMapping 对象查找将要转发处理的下一个组件。图 2 中的 XML 映射显示,success 将映射到 /listOrders,这是另一个 Action,负责查找剩余订单,并以 JSP 的形式呈现它们
    行 10:用将调用的下一动作的哈希来调用 redirect_to 方法;在这种情况下,它只是调用同一控制器的 list 方法



    回页首

    持久性框架

    持久性框架 用来在应用程序层和数据库之间来回移动数据。Hibernate 和 Rails 的持久性框架可以归类为对象/关系映射(ORM)工具,这意味着它们接受数据的对象视图,并将该视图映射到关系数据库内的表中。使用两种框架的目的都是为了减少与关系数据库有关的开发时间。但是,图 4 演示了两者在设计和配置上的一些根本区别。

    图 4. Active Record 和 Hibernate 持久性框架的比较
    persistence

    Hibernate

    Hibernate 基于 Data Mapper 模式,在这种模式中,特定的映射器类 Session 负责在数据库中持久存储和检索数据。Hibernate 可以持久存储任何 Java 对象,只要这个对象符合 JavaBean 规范。XML 映射文件描述了如何将类映射到数据库中具体的表,并描述了类与其他类的关系。

    清单 3 显示了 Hibernate 映射文件的一个实例。class 标签把 Order 对象映射到 ORDERS 表,还有许多子标签用于描述其属性、ID 订单名称,以及同 models.Item 的一对多关系。清单 4 显示了 Order 类本身。

    清单 3. Order.hbm.xml

    ...
    01 <hibernate-mapping>
    02    <class name="models.Order" table="ORDERS"
    03        dynamic-update="true" dynamic-insert="false"
    04        discriminator-value="null">
    05
    06 	<id name="id" column="id" type="java.lang.Long" 
    07             unsaved-value="null">
    08             <generator class="identity"/>
    09         </id>
    10 
    11         <set name="items" lazy="false" inverse="false"
    12            cascade="none" sort="unsorted">
    13             <key column="id"/>
    14             <one-to-many class="models.Item"/>
    15         </set>
    16 
    17         <property name="name" type="java.lang.String"
    18             update="true" insert="true"
    19             access="property" column="name"/>
    20     </class>
    21 </hibernate-mapping>
    

    清单 4. Order.java

    01 public class Order {
    02    private Set items;
    03     private String name;
    04     private Long id;
    05 
    06     public Long getId() { return id;}
    07 
    08     public void setId(Long id) { this.id = id;}
    09 	
    10     public Set getItems() { return items;}
    11 	
    12     public void setItems(Set items) { this.items = items; }
    13 	
    14     public String getName() { return name; }
    15 
    16     public void setName(String name) { this.name = name; }
    17 }
    

    Active Record

    反射和元编程

    Wikipedia 中(请参阅 参考资料)简要地把 反射 定义为“程序在运行的时候检查和修改其高级结构的能力”。在那里,还将 元编程 定义为“编写那些能够编写和操作其他其他程序(或它们自己),将其他程序作为自己的数据的程序,或者编写那些完成其他程序在运行时所做的部分工作的程序。”

    以下代码将实现反射:

    01 obj = "some_string"
    02 if obj.respond_to?('length'):
    03   puts "obj length = #{obj.length}" 
    03 end
    >> obj length = 5
    

    这个代码将实现元编程:

    01 class SomeClass
    02 end
    03 newMethod = %q{def msg() puts "A message!" end}
    04 SomeClass.class_eval(newMethod)
    05 aClass = SomeClass.new
    06 aClass.msg
    >> A message!
    

    Rails 的 ORM 框架叫作 Active Record,它基于同名的设计模式。Martin Fowler 将 Active Record 描述为“包装数据库表或视图中数据行的对象,封装数据库访问,在数据上添加域逻辑”。在 Rails 中,每个域对象都将扩展提供 CRUD 操作的 ActiveRecord::Base

    与 Hibernate 一样,Active Record 不需要映射文件;实际上,使用 Active Record 的开发人员不需要对 getter 或 setter、甚至类的属性进行编码。通过一些漂亮的词汇分析,Active Record 能够判断出,Order 类将映射到数据库中的 ORDERS 表。使用 Ruby 反射和元编程的组合,表的列可以变成对象的属性。访问器和调整器也添加了进来。

    清单 5 显示了 Order 类的完成后的代码。在 Order 类体中有一行代码定义了它与 Item 对象的关系。has_many 是一个静态方法调用,符号 :items 是它的参数。ActiveRecord 用 :items 发现 Item 域对象,然后将这个 Item 对象映射回数据库中的 ITEMS 表。

    清单 5. order.rb

    01 class Order < ActiveRecord::Base
    02	has_many :items
    03 end
    

    清单 5 那样编码的 Order 类在运行时提供了一些类和实例方法。表 2 提供了可在 Order 上使用的操作和属性的部分列表:

    表 2. 在 Order 上可用的属性和操作

    类方法
    实例方法
    属性

    • find(*args)
    • find_by_sql(sql)
    • exists?(id)
    • create(attributes)
    • update(id, attributes)
    • update_all(updates, conditions
    • delete(id)
    • delete_all(conditions)
    • ...
    • add_items
    • build_to_items
    • create_in_items
    • find_all_in_items
    • find_in_items
    • has_items?
    • items
    • items=
    • items_count
    • remove_items
    • id
    • name



    回页首

    结束语

    虽然 Ruby on Rails 是一个非常新、令人兴奋的框架,并且在 Web 社区中已经引起了人们相当的兴趣,但是它的核心架构仍然遵循在 J2EE 中发现的基本模式。开发把两个框架分开的 Web 应用程序是一种合理的方法。Rails 更喜欢清楚的代码而不是配置文件,而 Ruby 语言的动态性质在运行时生成了大部分管道 代码。大多数 Rails 框架都是作为独立项目创建的,而且应用程序开发能够从一组同类组件受益。相比之下,典型的 J2EE 堆栈倾向于构建在通常独立开发的最好的组件之上,常常用 XML 进行配置并将组件组合在一起。

    那么,是否应该考虑对下一个 Web 应用程序使用 Rails 呢?嗯,为什么不呢?它是编写得很好的组件堆栈,它们彼此之间工作得很好,并且基于行业接受的企业模式。Ruby 语言支持快速开发,并通过生产大多数应用程序管道来添加到框架。熟悉 Java 世界中的 MVC 和 ORM 框架的人们在用 Rails 表达自己的思想时没有任何困难。

    与 J2EE 一起分发会不会有利于 Rails?绝对不要。J2EE 是一个已经设置好的标准,有许多固定的实现,而且,最重要的是,它是一个经过验证的技术。我建议您下载一份 Rails 的副本,并开始自己钻研它。许多可用的教程都是介绍性的,这些教程可以让您立即开始使用 Rails。再次声明,我并不能保证您会通过使用 Rails 得到快乐,但是我敢打赌您会感到满意。

    使用 Ruby on Rails 快速开发 Web 应用程序

    基于 Ruby 的框架用于快速开发,使用的是模型-视图-控制器模式


    developerWorks

    文档选项


    将此页作为电子邮件发送

    将此页作为电子邮件发送

    级别: 初级

    David Mertz, Ph.D. (mertz@gnosis.cx), 开发人员, Gnosis Software

    2005 年 7 月 04 日

    虽然还是测试版本,但 Ruby on Rails 已经成为进行 Web 应用程序开发的一个新途径。Rails 的成功之处在于能够自动化大部分常见类型的 Web 应用程序的创建,而且在您希望添加定制或者有特殊要求时,并不会受到限制。不仅如此,同那些只能完成 Web 应用程序单方面需求的自由软件(Free Software)库相比,Rails 包含了非常完整的一套工具。

    Ruby on Rails 正在令整个 Web 开发领域受到震憾。让我们首先了解底层的技术:

    • Ruby 是一门免费的、简单的、直观的、可扩展的、可移植的、解释的脚本语言,用于快速而简单的面向对象编程。类似于 Perl,它支持处理文本文件和执行系统管理任务的很多特性。
    • Rails 是用 Ruby 编写的一款完整的、开放源代码的 Web 框架,目的是使用更简单而且更少的代码编写实际使用的应用程序。

    作为一个完整的框架,这意味着 Rails 中的所有的层都是为协同工作而构造的,所以您不必自己再重复,可以完全只使用一门单一的语言。在 Rails 中,所有内容(从模板到控制流再到业务逻辑)都是用 Ruby 编写的。Rails 支持基于配置文件和注释的反射(reflection)和运行时扩展。

    本文详细介绍了 Rails 的组成部分,并介绍了它的工作原理。

    Rails 介绍

    关于 Rails,首先需要理解的是它的模型/视图/控制器(model/view/controller,MVC)架构。虽然这种技术不是 Rails 所特有的 —— 甚至不是 Web 应用程序所特有的(相对于其他程序),但是 Rails 具有非常清晰而专一的 MVC 思维方式。如果您并不使用 MVC 方法,那么 Rails 的用处将大为降低(与遵循其模式的情况相比)。

    模型

    Rails 应用程序的模型部分主要是它所使用的底层数据库。实际上,在很多情形中 Rails 应用程序正是以一种受管理的方式对关系型数据库管理系统(RDBMS)中的数据执行操作的一个途径。

    ActiveRecord 类是 Rails 的一个核心组成部分,它将关系型表映射为 Ruby 对象,使其成为控制器可以操作并能在视图中显示的数据。Rails 应用程序特别倾向于使用广为应用的 MySQL 数据库,不过也有与很多其他 RDBMS 的绑定,比如 IBM? DB2?。

    如果您愿意,您可以添加 Ruby 代码来在应用程序模型中执行额外的验证,加强数据关联,或者触发其他操作。应用程序的 app/models/ 目录中的 Ruby 文件能够调用 ActiveRecord 的多种验证方法。不过,您也可以将模型代码留作一个存根,而只是依赖保存数据的 RDBMS 的约束。例如,我在这个示例中所开发的应用程序只包含这个骨架模型代码(至少在开始时是):

    清单 1. 骨架模型 app/models/contact.rb

    				
    class Contact < ActiveRecord::Base
    end
    

    控制器

    控制器以其抽象形式执行应用程序的逻辑。也就是说,应用程序的 app/controllers/ 目录中的 Ruby 脚本能把模型数据导入为变量,保存回去,或对其进行修改和处理。不过,控制器不关心用户如何适当地显示或者输入数据。在通常的 MVC 模型中,这可以让用户能够以多种方式与同一控制器进行交互:本地 GUI, Web 界面,以及视力较弱的人使用的语音界面都可以与相同的控制器进行交互。

    不过,Rails 不像那样非常通用;相反,它仅局限于在 Web 页中提供和收集数据。虽然如此,但是您可以修改那些 Web 页的布局 —— 颜色、字体、表格、样式表单,等等 —— 与控制器代码无关。

    视图

    Rails 视图是我们编写 Ruby 代码的地方。Rails 包含有一门用于 .rhtml 的非常好的模板语言,它将纯粹的 HTML 与嵌入的 Ruby 代码组合起来。 Rails 应用程序界面的最表层外观通常是由 CSS 样式表单控制的。.rhtml 格式是一种增强的 HTML。实际上,一个简单的 HTML 文件本身也是一个合法的 RHTML 模板,不过,不应该忽略 RHTML 为您提供的脚本控制。

    RHTML 是真正的模板格式 —— 不仅是在 HTML 中嵌入代码的方式 —— 这是一种更为有效的方法。如果您熟悉 PHP,那么可以考虑 PHP 本身与 Smarty 模板之间的对照。也就是说,嵌入的脚本只是将代码与未被解释的 HTML 混合在一起;当需要向客户机输出某些内容时,代码部分仍要负责执行 print 语句。

    与之不同的是,模板引擎向 HTML 添加了一组定制的标签,让您能够将条件、循环以及其他逻辑作为增强的 HTML 标记的一部分来表示。



    回页首

    生成代码

    Rails 所提供的工具主要是一组代码生成器。相对于那些强迫我使用严格的工作空间和 IDE 的开发环境,我更喜欢这种方法。 Rails 不会妨碍您,但是却会为您省去大部分手工编程的工作 —— 或者,通过提供“可自由获得的”初步(first-pass)支架(scaffolding),至少帮助您轻松将需要手工编码的工作分为多个部分。

    支架 概念是 Rails 中的核心概念。非常简单的应用程序可能完全不用编码,让 Rails 在运行时动态地生成客户机 HTML 页面。第一遍生成代码时创建的只是粗略的支架;接下来您可以生成更详细的能够定制的控制器、视图和模型。不过在开始时不需要生成太多。

    Rails 对其文件的组织是固定的而且非常普通的,不过这种组织相对严格。如果您试图强行使用其他文件和代码组织方式,那么您可能得付出努力去修改 Rails 环境。再者说,我找不到不使用 Rails 所提供的组织方式的理由;在大部分情况下,它“fits your brain”(Ruby 的支持者喜欢这样讲)。例如,如果您从头开始设计一个框架(至少如果您以“Ruby 方式”思考),那么这些目录名称及其组织可能与您的选择非常接近。



    回页首

    构建一个简单的应用程序

    在 Ruby on Rails Web 站点上有一些教程,可以完整地引导您创建一个简单的 Rails 应用程序(见 参考资料)。这里的示例程序与之类似,因为正确开始构建 Rails 应用程序的方式是确定的。由于此介绍的长度相对较短,所以我 极力 推荐那些较长的教程中的一篇,以使得您能够打好更为全面的基础。

    示例应用程序是一个基本的通讯录。它演示了创建应用程序的一般步骤:

    1. 生成模型(在此步骤中创建 MySQL 数据库和表)。
    2. 生成应用程序(包括生成基本代码和目录)。
    3. 启动 Rails(并配置数据库的访问)。
    4. 创建一些内容(包括生成支架模型和控制器,并告知控制器去使用那个支架)。

    我们将详细研究每一个步骤。

    生成 AddressBook 模型

    对于任何应用程序,您需要做的第一件事情是为它创建一个存放数据的数据库。技术上这个步骤不必最先进行,不过需要在早期完成;应该在编写任何应用程序代码(甚至是自动生成的代码)之前创建数据库,这应该是显然的。所以,让我们在 MySQL 数据库中创建一个数据库,并在此数据库中创建第一张表。(阅读其他文档以了解如何安装运行 MySQL 或其他 RDBMS。)

    我们假定 MySQL 已经安装并且可用。

    清单 2. 创建 MySQL 数据库和表

    [~/Sites]$ cat AddressBook.sql
    CREATE DATABASE IF NOT EXISTS AddressBook;
    USE AddressBook;
    CREATE TABLE IF NOT EXISTS contacts (
      id smallint(5) unsigned NOT NULL auto_increment,
      name varchar(30) NOT NULL default '',
      created_on timestamp(14) NOT NULL,
      updated_on timestamp(14) NOT NULL,
      PRIMARY KEY (id),
      UNIQUE KEY name_key (name)
    ) TYPE=MyISAM COMMENT='List of Contacts';
    [~/Sites]$ cat AddressBook.sql | mysql
    

    在这第一张表中有些地方需要注意。最重要的是每一张表都必须拥有一个 id 列,列名称就是 id。Rails 使用主键列 id 来完成各种记录保持和引用任务。域 created_onupdated_on 是不需要的,不过,如果您使用了它们,那么 Rails 会自动地“在后台”维护它们;在大部分情况下使用这些时间戳没有什么不好。所以,您还要添加的“真正” 数据就只是通讯录内容的名称。

    另一个稍微古怪的方面是,Rails 为不同的内容使用单数和复数的名称。根据上下文,各种条目会被重命名为单数或复数形式。表的名称应该使用复数格式。我没有使用不规则复数单词的经验;datumdata 等单词可能会令 Rails 出现问题。

    生成 AddressBook 应用程序

    既然已经拥有了一个能够交互的数据库,就可以创建 AddressBook 应用程序了。第一个步骤是简单地运行 rails 来生成基本目录和支架代码:

    清单 3. 生成基本代码和目录

    [~/Sites]$ rails AddressBook
    create
    create  app/apis
    create  app/controllers
    create  app/helpers
    create  app/models
    create  app/views/layouts
    create  config/environments
    create  components
    [...]
    create  public/images
    create  public/javascripts
    create  public/stylesheets
    create  script
    [...]
    create  README
    create  script/generate
    create  script/server
    [...]
    

    我删减了运行 rails 的输出;所忽略了那些行只是提醒您已经创建的各种文件和目录。在您的系统上试运行它,浏览生成的所有文件。我已经在代码中显示了一些最重要的文件和目录。

    运行 Rails

    创建了 AddressBook/ 目录和所需要的子目录后,您需要执行一次惟一的初始配置。首先,通过修改 YAML 配置文件来设置数据库,如下:

    清单 4. 配置数据库访问

    [~/Sites]$ cd AddressBook
    [~/Sites/AddressBook]$ head -6 config/database.yml # after editing
    development:
      adapter: mysql
      database: AddressBook
      host: localhost
      username: some_user
      password: password_if_needed
    

    最后,您需要提供数据。Rails 附带了它自己的单一功能的 Web 服务器,即 WEBrick,非常适用于我们的试验。您可能也会遵循 Ruby on Rails Web 站点上的说明来配置 Apache 或者其他服务器,以通过 FCGI(或者普通的 CGI,但是普通的 CGI 将会较慢)向 Rails 应用程序提供服务。

    清单 5. 启动 WEBrick 服务器

    [~/Sites/AddressBook]$ ruby script/server -d
    => Rails application started on http://0.0.0.0:3000
    [2005-03-21 17:57:38] INFO  WEBrick 1.3.1
    [2005-03-21 17:57:38] INFO  ruby 1.8.2 (2004-12-25) [powerpc-darwin7.8.0]
    

    创建一些内容

    要在 WEBrick 端口上看到一个欢迎页面,先前的步骤就足够了。例如,在我的本地系统中,现在可以访问 http://gnosis-powerbook.local:3000/。不过,为了操作定制数据库,需要生成稍微多一些代码。可以使用脚本 generate 来完成此任务,这个脚本创建在 AddressBook/ 应用程序目录中:

    清单 6. 支架模型和控制器代码的生成

    [~/Sites/AddressBook]$ ruby script/generate model contact
          exists  app/models/
          exists  test/unit/
          exists  test/fixtures/
          create  app/models/contact.rb
          create  test/unit/contact_test.rb
          create  test/fixtures/contacts.yml
    [~/Sites/AddressBook]$ ruby script/generate controller contact
          exists  app/controllers/
          exists  app/helpers/
          create  app/views/contact
          exists  test/functional/
          create  app/controllers/contact_controller.rb
          create  test/functional/contact_controller_test.rb
          create  app/helpers/contact_helper.rb
    

    注意,在相应的表名中,这里应该使用单数的 contact,而不是复数的 contacts

    现在需要编辑一个或多个生成的文件(只需稍加编辑)来让控制器去使用支架:

    清单 7. 告知控制器去使用支架

    [~/Sites/AddressBook]$ cat app/controllers/contact_controller.rb
    class ContactController < ApplicationController
      model :contact
      scaffold :contact
    end
    

    现在可以通过类似于 http://rails.server/contact/ 的 URL (在我的测试用例中是 http://gnosis-powerbook.local:3000/contact/)来查看和修改数据库的内容。输入一些数据后,它看起来如图 1 和图 2 所示:

    图 1. 列出联系人
    rails-contact1

    图 2. 编辑联系人
    rails-edit1



    回页首

    创建可定制的内容

    前面的代码创建了一个查看和修改数据库的功能完全的界面,不过,所有格式化、显示以及业务逻辑(比如本来就有的)都由 Rails 动态完成,没有任何重大修改。为了创建一些更为定制的内容,需要生成更多一些代码。现在我们所需要的是让 Rails 显式地写出它在运行时隐式地生成的所有支架,以使得我们能够修改它。

    图 8. 显式控制器和视图代码生成

    [~/Sites/AddressBook]$ ruby script/generate scaffold Contact
      dependency  model
          [...]
          create  app/views/contacts
          exists  test/functional/
          create  app/controllers/contacts_controller.rb
          create  test/functional/contacts_controller_test.rb
          create  app/helpers/contacts_helper.rb
          create  app/views/layouts/contacts.rhtml
          create  public/stylesheets/scaffold.css
          create  app/views/contacts/list.rhtml
          create  app/views/contacts/show.rhtml
          create  app/views/contacts/new.rhtml
          create  app/views/contacts/edit.rhtml
    

    现在有了更多一些要做的,所以尝试去修改一些内容。(注意此代码已经重新使用了复数格式 contacts,我不清楚其原因;现在我们需要接受它。)尝试在 CSS 中修改一些颜色和字体:

    清单 9. 配置层叠样式表单

    [~/Sites/AddressBook]$ head -8 public/stylesheets/scaffold.css
    body { background-color: #ffe; color: #338; }
    body, p, ol, ul, td {
      font-family: verdana, arial, helvetica, sans-serif;
      font-size:   13px;
    }
    td { border: 1px solid; }
    a { color: #eef; background-color: #446; }
    a:hover { color: #fff; background-color:#000; }
    

    您已经拥有了这段代码,那么 contacts_controller.rb 做什么?就其操作而言,它比前面的代码中所出现的 contact_controller.rb 更为显式且可配置。控制器类似如下:

    清单 10. 控制器 app/controllers/contacts_controller.rb

    class ContactsController < ApplicationController
      def list
        @contacts = Contact.find_all
      end
      def show
        @contact = Contact.find(@params['id'])
      end
      def create
        @contact = Contact.new(@params['contact'])
        if @contact.save
          flash['notice'] = 'Contact was successfully created.'
          redirect_to :action => 'list'
        else
          render_action 'new'
        end
      end
    

    如前所述,控制器的主要任务是将数据导入到变量之中。对象 Contact 是模型所提供的 ActiveRecord 对象-关系映射。变量 @contacts 或者 @contact 是它们的适当方法中所给出的数据。通过 URL 可以访问那些方法本身,比如 http://rails.server/contacts/show/2 (这一个方法显示出 id 为“2”的联系人)。

    此示例中的控制器最终连接到了视图,即 RHTML 文件,它们使用的是控制器导入到变量中的数据值。例如,这里是 list 视图的一部分:

    清单 11. 列出视图 app/views/contacts/list.rhtml

    [...]
    <% for contact in @contacts %>
      <tr>
      <% for column in Contact.content_columns %>
        <td><%=h contact.send(column.name) %></td>
      <% end %>
        <td><%= link_to 'Show', :action => 'show', :id => contact.id %></td>
        <td><%= link_to 'Edit', :action => 'edit', :id => contact.id %></td>
        <td><%= link_to 'Destroy', :action => 'destroy', :id => contact.id %></td>
      </tr>
    <% end %>
    [...]
    

    方法 ContactsController.list 导入变量 @contacts,RHTML 中的流控制标签从数组中取出单个的记录。



    回页首

    修改模型

    初始的模型只包含联系人的名字。不幸的是,本文中我已经没有余地扩展这个模型以使其包含实际的联系人数据,比如电话号码、地址、电子邮件等等。通常,那些数据应该存放在一张子表中,子表的外部关键字关联到表 contacts。Rails 模型会使用类似这样的定制代码来指明关联:

    清单 12. 定制代码 app\models\phone.rb

    class Phone < ActiveRecord::Base
      belongs_to :contact
    end
    

    在结束之前,让我们来对数据模型稍加修改,以查看它如何影响应用程序。首先,添加一列:

    清单 13. 向模型添加 first_met 数据

    $ cat add-contact-date.sql
    USE AddressBook;
    ALTER TABLE contacts ADD first_met date;
    $ cat add-contact-date.sql | mysql
    

    既然已经修改了底层的模型,http://rails.server/contact/ —— 支架的后台版本 —— 就会直接调整过来,不需要您做什么。控制器和视图是完全自动基于模型的。不过,在 http://rails.server/contacts/ 上应用程序版本使用了我们手工编写的文件,并不是那样自动化的。

    list 视图将 Contact.content_columns 作为模板循环的一部分,能够自动查找 所有 的列,不管它们是什么。不过,edit 等其他视图已经被生成了,需要添加新的数据域。例如:

    清单 14. 编辑视图 app/views/contacts/edit.rhtml

    <h1>Editing contact</h1>
    <%= error_messages_for 'contact' %>
    <%= start_form_tag :action => 'update' %>
    <%= hidden_field 'contact', 'id' %>
    <p><label for="contact_name">Name</label><br/>
      <%= text_field 'contact', 'name'  %></p>
    <p><label for="first_met">Known Since</label><br/>
      <%= date_select "contact", "first_met", :use_month_numbers => false %></p>
    <input type="submit" value="Update" />
    <%= end_form_tag %>
    <%= link_to 'Show', :action => 'show', :id => @contact.id %> |
    <%= link_to 'Back', :action => 'list' %>
    

    那么您手工修改的应用程序看起来如何了呢?与默认的区别不太大,不过在图 3 和 4 中可以看到修改已经生效了:

    图 3. 列出联系人,修改后
    rails-contact2

    图 4. 编辑联系人,修改后
    rails-edit2



    回页首

    结束语

    Rails 为您提供了开发灵活的 Web 应用程序的一种极其快速的途径;本篇介绍只是肤浅地涉及了如何使用 Rails。完整的框架包含很多实用的类和方法,能够完成基于 Web 的应用程序使用最多的操作。

    Rails 的最大价值在于它孕育了一个成体系的“Rails 思维方式”,因为您所需要的所有支持代码令它变得完整。相对于只是给出要使用的原始材料的其他工具包和框架而言,这是一个巨大的优势。Rails 开发为您提供了将半成形(half-formed)的想法实现为功能完全的 Web 应用程序的一条坦途。

    November 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、对日软件开发初级程序员

    November 24

    Java集合对象的比较

    Java 集合对象的区分和比较

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

    November 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操作清除掉

    November 21

    我们的.Net培训进入第一个项目练手阶段了(实验)

    一周时间完成如下课题:

     

    一,简要:

    用Asp.net2.0  开发一套有用户管理的建议博客系统 。

    二,要求:

    1,使用三层架构
    2,充分融会“Asp.net开发即组件开发”这句话
    3,使用Theme , Masterpage .
    4,充分体会“一类一接口”这句话
    5,文章发布模块的输入框最好使用FCKEdit等第三方输入容器 。
    6,支持多用户的博客发布及管理

    三,功能模块:

    前台:
        用户注册,登录
        文章发布,修改和删除

    后台:
        用户管理(增删改查)
        文章类别管理
        文章发布以及管理

    四,可参考原型

    http://spaces.live.com/

    blog1  blog2

    五,页面描述

    前台:

        Default.aspx (首页)
        BlogList.aspx (列表页)
        BlogDetail.aspx (具体博客页面)

    后台:

        自行设计页面

    六,提示

    如何来区分用户?
    可以考虑二级目录或者Request参数来区分 。

    七,周期
    每日三小时 , 一周完成

    八,指导讲师:林立峰 ,张凌华

     

    实验场景 (场地简陋,不过我们也不计较这个了):

    DSC00141  DSC00142

    DSC00145  DSC00146

    (四个角落缩影)

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

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

     

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

     

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

     

                                                          Sundy . 2007.11.21

    November 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

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

    November 15

    Windows Server 2008 将与 Visual Studio 2008 以及 SQL Server 2008 共同发布

    itpro_rc 

    微软公司刚刚在全球伙伴大会 (Worldwide Partner Conference) 上郑重宣布,Windows Server 2008 将与 Visual Studio 2008 和 SQL Server 2008 于 2008 年 2 月 27 日在洛杉矶共同发布。作为微软公司历史上最重要的企业平台发布,这一发布将为接下来的几百个世界性活动所构成的微软“发布浪潮”正式拉开序幕。了解更多

     

     

    发布倒计时

    2008 全球发布浪潮
    103
    Days
    Windows Server 2008, Microsoft Visual Studio 2008, Microsoft SQL Server 2008

    将此小工具添加到您的 Vista 桌面

    Windows Server 2008 内置的 Web 和虚拟化技术,可助您增强服务器基础结构的可靠性和灵活性。新的虚拟化工具、Web 资源和增强的安全性可助您节省时间、降低成本,并且向您提供了一个动态而优化的数据中心平台。强大的新工具,如 IIS7、Windows Server Manager 和 Windows PowerShell,能够使您加强对服务器的控制,并可助您简化 Web、配置和管理任务。先进的安全性和可靠性增强功能,如 Network Access Protection 和 Read-Only Domain Controller,可助您加强服务器操作系统安全并保护服务器环境,确保您拥有坚实的业务基础。

     

    产品亮点

    针对 Web 而建

    Internet Information Services 7.0 是一个强大的应用程序和 Web 服务平台,可助您简化 Web 服务器管理,这个模块化的平台提供了简化的、基于任务的管理界面,更好的跨站点控制,增强的安全功能,以及集成的 Web 服务运行状态管理。

    Internet Information Server (IIS) 7 和 .NET Framework 3.0 提供了一个综合性平台,用于建立连接用户与用户、用户与数据之间的应用程序,以使他们能够可视化、共享和处理信息。

    虚拟化

    Windows Server 2008 的虚拟化技术,可助您在一个服务器上虚拟化多种操作系统,如Windows、Linux 等等。服务器操作系统内置的虚拟化技术和更加简单灵活的授权策略,可助您获得前所未有的易用性优势并降低成本。

    Windows Server 2008 可助您灵活地创建敏捷、动态的数据中心,以满足您不断变化的业务需求。

    借助Terminal Services Gateway 和 Terminal Services RemoteApp ,您可以轻松进行远程访问并与本地桌面应用程序进行集成,还可实现在无需 VPN 的情况下,安全无缝地部署应用程序。

    安全性

    Windows Server 2008 是迄今为止最可靠的 Windows Server,它加强了操作系统安全性并进行了突破安全创新,包括 Network Access Protection、Federated Rights Management、Read-Only Domain Controller,可为您的网络、数据和业务提供最高水平的安全保护。

    Windows Server 2008 可帮助您保护服务器、网络、数据和用户帐户安全,以免发生故障或遭到入侵。

    Network Access Protection 能够帮助您隔离不符合组织安全策略的计算机,并提供网络限制、更正和实时符合性检查功能。

    Federated Rights Management Services 提供了一个综合性信息保护平台,可对敏感数据提供持续性保护,同时帮助降低风险并保证符合性。

    Read-Only Domain Controller 可支持部署 Active Directory Domain Services,同时限制整个 Active Directory 数据库的复制,以便更好地防止服务器的信息泄露或被窃取。

    坚实的业务基础

    Windows Server 2008 是迄今为止最灵活、最稳定的 Windows Server 操作系统,借助其新技术和新功能,如 Server Core、PowerShell、Windows Deployment Services 和加强的网络和群集技术,为您提供了性能最全面、最可靠的 Windows 平台,可以满足您所有的业务负载和应用程序要求。

    Server Manager 可以加速服务器的安装和配置,并能通过统一的管理控制台,简化进行中的服务器角色管理。

    Windows PowerShell 是一个全新的命令行 Shell, 提供130 多种工具,以及集成的脚本语言,帮助管理员实现例行系统管理任务自动化,尤其是针对跨多个服务器的任务自动化。

    Server Core 是一个全新的安装选项,仅包含必要的组件和子系统,而没有图形用户界面,以提供一个具有高可用性,且较少需要进行更新和服务的服务器。

    谷歌发布“Android”的软件开发工具包 采用Java编写应用程序

    【日经BP社报道】

    Android的结构

      美国谷歌及手机平台促进团体美国开放手机联盟(Open Handset Alliance)(参阅本站报道1本站报道2)于2007年11月13日发布了OSA推广的软件平台——“Android”的软件开发工具包(SDK)试用版。该工具包可通过OSA的网站下载(Android网站)。谷歌发布的Android SDK可分别在Windows、OS X、Linux系统上使用。综合开发环境利用了开源的“Eclipse”。
      公开的SDK为“初试版”,并非正式版。该公司网站登载的Android的架构如图1所示。Linux 2.6内核上集成了谷歌开发的本地数据库群“Libraries”和应用软件运行环境“Android Runtime”。均在本地(native)环境下运行。Libraries中包括:能够嵌入应用软件中使用的网页浏览器引擎,标准C语言库“libc”,用于处理视频、图片和音频的媒体库(美国PacketVideo的“OpenCORE”库),以及数据库管理系统“SQLite”。
      Android的应用软件编写语言为Java,“提供了Java语言核心库的几乎所有功能”(Android的说明书)。Android的应用软件可以利用名为“Dalvik Virtual Machine”的虚拟机运行。Android平台中包括的电子邮件客户端、SMS、日历、地图信息、网页浏览器需要在Dalvik VM上运行。因此,二进制文件与通常的Java程序不同,采用了自主的“Dalvik execution format(.dex)”。
      为了简化应用软件的开发,Android提供了Application Framework。以该框架为基础编写程序能够减轻开发的负荷。配备了名为“Views”的用户界面部分以及名为“Content Providers”的应用联动机构和名为“Resource Manager”的程序资源管理机构。
      现阶段,通话机构支持GSM。数据通信机构支持蓝牙、EDGE、3G、无线LAN。从此看出,Android并不单纯面向手机,还考虑了向其他产品的发展。另外还积极吸收了开源软件,比如说,Android的浏览器引擎采用了“WebKit”。该引擎也得到了美国苹果为“iPhone”配备的“Safari”和美国诺基亚S60软件平台用“Web Browser for S60”的采用。除此之外,数据库管理软件SQLite也为开源软件。(记者:Phil Keys,北乡 达郎)
    ■日文原文
    【速報】Google社が「Android」のSDKを公開,Javaでアプリケーションを記述

    冬天到了,换个主题,加点儿温度

    今日终于感受到冬季的寒冷 。 坐在电脑上敲着赶工的代码 ,感觉手脚都有些不听使唤了 。 可惜我这个小区的供暖日期延后了,因此还要把这3000大洋的供暖费用揣在怀里 ,过几日才能贡献出去 。

    给博客换个皮肤吧,暖色调的,以免把耳朵冻掉 !

    November 09

    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 组件不需要更多了。但是如果想创建稍微复杂一些的组件,针对更复杂的使用场景时该怎么办?请继续往下看。