lbom

小江西

  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  18 随笔 :: 21 文章 :: 69 评论 :: 0 Trackbacks

#

    几天前,偶尔和邻居聊天,她说要去买顶蚊帐过夏,不由的也动了心。是啊,在小时候,那家不是用蚊帐来保证漫长夏的良好睡眠呢!现在随着科技发达,家家户户,特别是城市住户都已经将蚊帐扔掉而改用蚊香或蚊片了。
    于是等夫人下班回家,和其商量,却是死活不同意,理由如下:1)影响卧室美观;2)挤占空间;3)拆洗不便。。。。。。
    没办法,为了达到目的,我只得绞尽脑汁,想出各种理由,以期望能够说服夫人:
    1)身体牌:我对蚊香有过敏,因此,在夏天,我们是不能点蚊香的;再说,各种灭蚊产品其主要成份都有毒性,不管其含量多少,都对身体无益。
    2)经济牌:在重庆,一年有6个月的夏天,以每晚一片灭蚊片计算,一年下来,加上电热灭蚊器最少需要投入120元以上才能保证夏季无忧,三四年下来,就可以买个较好的蚊帐了;再说,灭蚊片用久后,还得防止蚊子产生的抗药性;但使用蚊帐却不需要用电,一年下来,其电费也能节约不少,还可防止可能因电热灭蚊器散热不良而导致的用电风险的发生;
    3)环保牌:在卧室支个蚊帐,即温馨又浪漫,还无灭蚊产品的各类化学合成的气味,最环保不过了;
    4)卫生牌:在床上支个蚊帐,将有效的减少灰尘降落,也免了蜘蛛等小虫在晚间不意间打扰我们的安眠,多好的一件事!
    。。。。。。
    啰啰嗦嗦,直说的口干舌燥,并许下一堆好处之后,终于换来老婆的点头。于是二话不说,拉上夫人,直奔商场,在东挑西比之后,买下一款合意的落地式蚊帐。
    至此,我的环保蚊帐计划就此实现!
 
    所以,回归和怀旧,并不都是倒退!
posted @ 2009-05-26 09:43 lbom 阅读(281) | 评论 (0)编辑 收藏

        在一周前,项目组碰到一个大问题:NTKO Office Activex控件在上传文件及提交页面信息时,其提交的页面元素输入中文值变成了无法识别的、也不属于已知编码中的任何一种编码格式的乱码;但在NTKO Office Activex控件包装项目组提供的的测试项目中,此问题并未出现,因此判断是项目兼容性所导致。
       在项目组功能开发员和控件包装组成员进行近一周的努力后,也未解决此问题。最后,此问题交由我来做最后分析和处理。
       经过三天时间对问题项目的分拆、组装、分析、测试后,终于找到问题所出,现将此过程进行记录,以备参考:
        1)以控件包装组测试项目为基准,建立项目级测试项目,并保证在此测试项目中不存在兼容性问题;
        2)检测web.xml:将问题项目的web.xml代替测试项目中的web.xml,未出现兼容性问题,从而排除因web.xml的差异而导致的兼容性;
        3)测试问题项目中的项目依赖:将问题项目的项目依赖关系复制至测试项目中,发现兼容性问题未出现,从而排除项目依赖导致的兼容性;
        4)检测支持包:将问题项目中的支持包(各jar)代替测试项目中的支持包,未出现兼容性问题,从而排除因支持包的差异导致的兼容性,也就排除了各servers,servlet,listener等导致的兼容性问题;
        5)检测js支撑:将问题项目中的所有相关js文件取出,代替测试项目中的相关js文件,未出现兼容性问题,从而排除因js支持文件的差异导致的兼容性;
        6)检测css支撑:将问题项目中的所有相关css文件取出,代替测试项目中的相关js文件,未出现兼容性问题,从而排除因css支持文件的差异导致的兼容性;
        7)检测tld,xml文件:将问题项目中的tld,xml文件取出,代替测试项目中的tld,xml文件,未出现兼容性问题,从而排除因tld,xml的差异导致的兼容性;
        8)至此,正常解决的兼容性措施都已用完,还是未找到问题所出!如何办?
        9)开始使用非正常手段进行排查:
            <1>对比检查.project和.classes未发现异常,从而排除基本项目配置导致的兼容性;
            <2>将问题项目的web项目设置文件(.settings)代替测试项目的web项目设置文件(.settings),问题出现了!继续排队分析,发现问题出现在文件org.eclipse.wst.common.component中,
问题项目的设置为:
                                    <?xml version="1.0" encoding="UTF-8"?>
                                    <project-modules id="moduleCoreId" project-version="1.5.0">
                                       <wb-module deploy-name="XXX_IC">
                                       <wb-resource deploy-path="/" source-path="/web"/>
                                       <wb-resource deploy-path="/WEB-INF/classes" source-path="/src"/>
                                       <wb-resource deploy-path="/WEB-INF/classes" source-path="/test"/>
                                       <property name="java-output-path" value="build/classes"/>
                                       <property name="context-root" value="XXX_IC"/>
                                   </wb-module>
                                   </project-modules>
测试项目设置为:
                                    <?xml version="1.0" encoding="UTF-8"?>
                                    <project-modules id="moduleCoreId" project-version="1.5.0">
                                       <wb-module deploy-name="test">
                                       <wb-resource deploy-path="/" source-path="/web"/>
                                       <wb-resource deploy-path="/WEB-INF/classes" source-path="/src"/>
                                       <wb-resource deploy-path="/WEB-INF/classes" source-path="/test"/>
                                       <property name="java-output-path" value="build/classes"/>
                                       <property name="context-root" value="test"/>
                                   </wb-module>
                                   </project-modules>
且无论如何修改"XXX_IC",都会导致兼容性出现,最后没办法,将下划线"_"去掉,奇迹出现了。
        原来NTKO Office Activex控件在提交数据时,是通过scoket模拟Http进行文件和页面元素的提供,如提交的页URL完整路径中包含了"_"等字符时,将导致无法识别,从而导致兼容性的产生。
posted @ 2009-05-15 22:13 lbom 阅读(1494) | 评论 (2)编辑 收藏

 

从小到大,椰子已经吃过很多次了,但在这些吃椰子有经历中,我只知道一种吃法:开孔à插吸管à喝椰汁à丢椰壳à完事!

       但在今天,事情有了些变化,于是产生的椰子的第二种吃法。

我和夫人在散步时,顺便准备到超市买点水果,看到水果区的椰子又大又好且正在打特价。心中一动,就挑了个大的,买回家准备细细品尝一番。在按通常吃法吸光椰子中的水汁后,突然想起,我们平时很喜欢吃超市中的一种叫椰角小吃的,又甜又韧很有嚼劲,但椰角是长大哪的呢?不会是椰子树上的另一种产品吧!看着椰子开口的硬壳下面的白色软组织,我们就突发奇想,这白色软组织会不会就是那椰角呢?

说动就动,先用刀将空椰子壳砍开,发现其内层确实是一层约0.8cm厚的白色果肉。小心的切下一小块尝一尝,味道淡淡的,很有韧劲,确实就是那椰角的源料。这就是我发现的椰子的第二种吃法了。

在生活中有很多事:你没经历,就不会想到;当你想不到时,美好的事情就可能错过!
    这就是生活。

                                                                                    2009/5/5

posted @ 2009-05-05 22:31 lbom 阅读(2336) | 评论 (2)编辑 收藏

 

在我们公司的软件研发体系中,存在着三种截然不同的软件开发方式。而我,作为公司最老同事之一,也是这三种开发模式的亲历者,曾不只一次的被公司同事问过我关于这三类方式之间的异同点。于是利用空闲时间,对其进行一番整理、分析和对比。

1、全能型

部门经理在接到项目之后,将此项目交给部门内的熟练程序员后,此程序员就自动被委任其为项目经理。从此开始,程序员将根据项目售前方案和销售合同内容,在项目进行过程中分别担当起项目经理、功能设计师、数据存贮设计师、程序员、测试员和项目实施人员等诸多角色。并在项目进行的过程中,带领少量其它程序员和辅助资源来完成此项目的所有工作。

此类项目其功能单一且不复杂,只是为了帮助用户提升某一项工作的工作效率或解决客户在其工作中的一些问题,如工作日志信息的采集和分析业务项目、办公用品的申请和领用等。它们因为其涉及范围小,使用人员不多,从而具有项目总费用少、开发和实施周期短、对性能要求不高的特点。

在此类开发模式中,程序员由于其工作的全面性,使他们在进入项目组后,能够得到很快且全面的提升,并会在与客户交往的过程中,建立起良好的客户关系处理经验,为其今后的成长和发展打开良好的基础。

由于项目需要,程序员需要掌握全面技能,容易造成其在项目开发过程中需要全面的接触项目管理及人际关系、需求分析、数据库及对象设计、人机交互和用户体验设计、系统设计和开发、测试和系统提升、应用实施和售后维护等诸多截然不同的领域范围;所以,作为此类程序员,其工作压力之大,事务之复杂、综合素质要求之高,是其它模式的程序员所无法对比的,这也是造成此类项目按时完成率极低、尾款回收困难、项目售后工作难做、用户满意度差、二次项目获取困难的根本原因。

同时,由于程序员被大量的非开发性事务所干扰,造成他们无法专心致力于专业技能的学习和提升,也就无法造就一支高效率、高稳定性、配合默契的开发队伍,这也是造成公司内此类人才大量流失的重要原因。

2、英雄型

       部门经理在接到此类项目后,按项目所涉及的领域范畴,将其按领域进行分工。以企业信息协同系统为例,我们将进行如下分工:门户信息的获取、聚合、交互和展现工作交给专职于门户开发的程序员;内部邮件系统的分析、设计、和实现将给邮件开发程序员;日程和事务的设计交给日程开发程序员;工作流应用工作交由工作流客户化开发程序员等等。

       在此类型的开发模式中,程序员将会是某一领域内的英雄式人物!由于他长期且相对稳定的负责着这个有限领域范围内的一切事务,可以帮助他在一定时期内进行系统而稳定的业务研究和分析工作,进而成长为此领域内的业务专家。而且,通过持续的对其工作进行迭代式开发、升级和完善,可使此产品在产品品质、适用性和用户体验等方面得到稳定的提升,进而提升了整个产品的品质。

       如果此领域内的工作产品能够得到合理的规划和实现,进而将其进行单独的封装、应用集成和推广,就有可能形成一个具有相当竞争力的产品,从而为公司获取新的销售机会和利润点。

       但是,此类开发模式中的分工也容易造成程序员涉及业务领域单一和适应性窄的缺陷:由于其长期面对和研究着单一业务领域内的业务活动,而无法更多的接收和参考来自于用户、企业和其它行业内的非它业务发展需要和趋势,从而对其在产品领域内的发展产生限制,并造成其产品方向上的不准确或错误定位;由于其长期的在单一领域内工作,并在此领域内获得了公司内的认可,这也将限制他在领域间的流动性。当公司或部门的产品方向和需要调整和改变时,此类程序员就需要被迫改变甚至放弃其在原领域内的所有积累而重新开始,从而造成巨大的浪费。

3、专业型

       项目经理在接到项目之后,根据项目组成员的能力、特长职业规划,对他们进行适当且专业化分工:由业务规划人员负责项目的需求收集、业务规划和需求分析;由系统架构师对系统的进行技术构架和支撑性技术的规划和引进;由数据库专员负责数据库对象的设计和性能调优;由功能分析员在人机交互人员辅助下负责功能设计和人机交互模式;由业务逻辑实现专员根据功能设计进行高性能的业务逻辑处理实现和外部接口的设计和实现;由页面开发人员负责实现人机交互;测试人员负责对系统进行全过程的测试和质量监督;专业化实施人员可快速高效的进行系统实施和在线维护,售后服务工作也将由专人负责;

       通过恰当和合理的分工,将软件研发过程中的各个环节进行拆分,从而将复杂的软件工程分解成一个个相对独立且又紧密关联的工作项,从而有效的降低了软件开发过程中的困难度和风险性;项目经理把分解后的工作项交给项目组中的合适项目成员,并根据项目组成员的能力、工作难度和工作量,制定出科学的项目计划;同时,项目组成员在项目经理的协调和管理下进行密切的分工合作,此举即能调动项目组成员的其工作积极性,又能使他们将工作、兴趣和个人职业成长规划进行有效的结合,从而使其在技能、收入和社会认可度等诸多方面得到快速成长,达到人尽其材,材尽其用的目的。通过使用专业的人做专业的事,公司将在人员分工、资源使用和业务拓展等领域走向专业化、规模化,最终成为专业且强大的产业实体。

       但此开发模式也具有相当的局限性!其一,如何合理的利用项目组资源?项目组成员因其性格、能力和兴趣各有不同,如何能将他们按项目分工和角色组成需要,进行专业化训练和培养;其二,因项目组成员长期单一职能的工作,与其它环节的交叉和交流都受到限制,对其未来的全面发展和综合成长都很不利;其三、各角色之间的分工、合作与工业化生产中的生产线相似,那么,建立与之相适应的质量保证体系,保证各工序之间生产产品的质量,从而从事实上提升软件产品的整体质量?

       通过对这三类开发模式的分析,我们可以看出,它们各有合理性,也又具有的相当的局限性。

       全能型开发模式是早期的CS类项目开发的主要模式,其适用于哪些规模小,程序员少的小规模IT开发企业进行小型项目的开发中。但对于那些工期较长、业务范畴广、复杂度较大的项目,此种开发模板将采用将导致风险最大化,失败几乎是其唯一的结局。

       英雄型开发模式,因项目组成员领域化的分工和合作,使它在通用型复合类产品开发中具有优势。通过对产品的各组成部份进行持续的改进和迭代性开发,使产品在功能、性能、用户体验等方面得到持续的改善和提升,从而有利于产品拓展并在此过程中做大做强,最终取得竞争优势。但此开发模式也将导致项目组成员之间的工作协调、技术互用等方面存在诸多不便;另外,因领域的专业性和不可替换性,也就限制了公司在处理关健人员的流动性方面存在诸多困难,并在核心竞争力的保证方面存在着很大风险。

       专业型开发模式,通过对人员进行专业化分工,从而在软件开发过程中最大的利用了人力资源,提升软件的生产效率,并降低了软件的从业门槛。此方式在新形式下的项目开发和产品研发中都具有相当的竞争力,也易有利于保证公司的核心竞争力。但采用此种开发模式时,需要完善内部的人员激励机制,保证各角色的从业人员都有与之适应的职位规划和发展模式,并能根据项目组成员所处阶段的需要,提供相应的技能培训和交流机会,从而促进其成长,激励其上进。

       总之,采取何种开发模式,要根据公司的实际业务情况,发展规划和人员构成,进行科学的分析之后,再采取行动、从而得到具有延续性和竞争性,并与自身相匹配的软件开发模式。

                                                               2009/4/20

posted @ 2009-04-21 21:52 lbom 阅读(1851) | 评论 (2)编辑 收藏

         

今天,在陪夫人逛街回来的路上,看到一幕惨剧的发生:一位中年男子,从家中跳楼而下,坠落于坚硬的水泥地面上,当场身亡!

人生为何?为已?为亲?还是为他?

人生为已,就应该珍惜自己的生命,为自己这短暂的一生中,充实、幸福和快乐的活着,而不能因为暂时的挫折、失败而绝望和放弃;人生漫漫而无现存路,需要自己去探索和开拓,在此过程中,必然会经历曲折和无法避免的挫折。但是,不经历风雨,如何能见彩虹!挫折过后,往往就伴随着一段平坦的直道;人生艰难,生活、事业、家庭、社会各种矛盾在我这会聚,理不清也扯不断!既然如此,何不干脆看开,将不可调和的矛盾进行暂时地休眠,让时间这个解决矛盾的最好的润滑剂来慢慢解决它。

人生为亲,为父母:他们含辛茹苦的将你从无到有、从幼养成,而你却要在他们需要照顾和看护的年龄离他们而去!你忍心吗!为妻:相汝以沫几十年、同床共枕伴一生!锅碗瓢盆一屋住,酸甜苦辣是生活。你就忍心在她人生路中间,正需你坚强的肩膀作支撑时,却抛下她一人孤苦的走在这漫长的人生路上,你安心吗?人生为子:父亲是儿女的榜样和偶像,他们需要借助你那成熟的智慧来打开事业的大门,也需要你那丰富的阅历来开拓自己的人生路,更需要你成功的经验来保护雏鸟并解决初飞时所遇到的种种风险!在这种关键的时候,你却抛弃了他,你放心吗?

人生为他,为朋友兄弟:有多少美好时光值得回忆,有多少美妙经历值得回味,又有多少坎坷担当值得珍惜!你就此离开,从此兄弟聚首少一人,朋友举杯缺一环!为事业:你正值人生当年,恰逢事业当期,失败你经历,成功应有你,酸甜苦辣都尝遍,还有什么过不去?为社会:当今社会多少不平事,何必事事放我心!不必为人先,也不全落后;比上我不足,哪我就比下,实在比不过,阿Q一把也不错!

生命如此脆弱,稍有不慎就将坠落:走在路上被车撞死,走下楼下被东西砸死,乘车坠桥而死,去医院被错药药死,上班被累死,下班被烦死,既然如此,何必再来一个自己寻死呢!

请珍惜生命,爱护自己!

 

20090330于家中

posted @ 2009-04-13 15:51 lbom 阅读(123) | 评论 (0)编辑 收藏

        项目组使用润乾报表已一年多了,说实话,润乾报表在国内同类产品中属于非常不错的最好的报表开发和应用产品。相应的支持也比较到位,使用人员及交流社区也开展的很合适。在这先给它们作个广告!!!
        在项目中使用润乾报表,对数据进行专业的报表应用和开发,我对其作简单总结:
        1)对其服务器运行系统进行项目性客户化开发,从而利用项目中的权限管理和模块,实现对报表进行访问控制。否则,这对企业级应用将是一个非常大的考验。
        2)润乾报表自带的参数生成模块、报表运行载体的样式、风格都极其简陋,与项目的实际风格可能存在很大的差距。因此必需对其进行深入的扩展和开发。我们项目组的经验就是单独开发参数生成模块和润乾报表载体,如此才保证了报表中心与项目的用户体验和交互性的一致性。
        3)为了更好的利用项目组资源,我们将润乾报表开发人员独立出来,形成专门的报表开发团队。此团队负责根据业务的需要,利用润乾报表开发工具进行报表开发,即开发.raq报表文件。此部份人员可从项目组的普通成员和新进人员中进行培养,而无需占用大量的项目组中中高级开发人员资源,从而节约了项目组的资源。
        4)润乾报表对过JSP标签包含在jsp页面中进行加裁我运行。我们称此jsp页面为润乾报表运行载体。我们根据润乾报表的运行载体进行了科学的分类,并根据分类开发出统一的报表运行载体页面(jsp)。从而避免针对每个报表文件而开发与之对应的运行载体。此举也大为减少了项目组的JSP开发人员的工作量。
        5)建立润乾报表运行专用配置文件,将报表参数生成模块、运行载体及润乾报表三者之间的关系进行配置化管理,并以此为纽带,将润乾报表开发人员、JSP开发人员(开发报表运行载体和报表参数生成功能)联系起来。

         在开发过程中,我们碰到并解决了如下问题:
         1)填报类润乾报表在进行数据验证时,其提示信息(以js的alert("...")方式提示用户)成乱码显示:此问题是由于润乾报表在V4.1以后,统一使用UTF-8作编译编码。因此,要解决此问题,需要将项目的编码也改成UTF-8
         2)在润乾报表的参数赋值需按序依次进行赋值,而不能采用参数名进行统一赋值。因为,如果在润乾报表的SQL中使用了重复的参数进行赋值时,会报参数找不至的错误。
         3)在参数生成模块中将中文参数值传递给润乾报表时,会导致少量的参数值在传递过程中发生改变,如“机油”变成了“箕油”。此问题是由于在urlEncode和urlDecode的bug导致,请在开发时需特别注意。我们是通过自己对信息进行加码和解码来解决此bug。
         4)润乾报表的运行环境与应用服务器的编码方式有关:我们项目和报表中心的编码方式为UTF-8,但运用服务器(WAS6.1)的编码方式为GBK时,通过参数生成功能将中文参数传递给润乾报表时,会出现乱码问题。在将WAS的输出和运行编码改成UTF-8后,才解决此问题。


posted @ 2009-03-05 23:08 lbom 阅读(7968) | 评论 (24)编辑 收藏

        前段时间,项目组安排同事进行项目移植,并考虑在其过程中进行技术预研等相关工作,以对项目进行优化;
        在此过程中,有同事误解面向对象化开发的精髓,在匆匆了了解JavaScript面向对象的方法和示例后,对项目中的公共门户头以对象的方式进行重写。结果,将原30行的单一文件代码变成了400多行,并分布于多个文件了。
        在拿到此结果之后,我是哭笑不得,于是得出了:“新技术的引入必需能够提高生产效率或降低工作难度,否则,没有引入的必要”这句话。

posted @ 2009-02-21 20:52 lbom 阅读(1305) | 评论 (3)编辑 收藏

 

总是听说Vista在软件兼容性上有诸多问题,且一直未得到很好的解决,由于一直使用XP,对此也就不太在意。但是,因工作因素,需要升级我的饭碗(购买新笔记本,操作系统为vista)后,麻烦果然来了:

     先是Oracle数据库安装不了。还好,我在开发时可以使用公司的数据库进行开发,不在本机安装数据库还可节约一笔硬盘空间和内存。故其影响并不大,只是在下班离开公司后,没时使用数据库而已。

     安装Eclipse,继续java项目开发,未发现兼容性问题;

     成功安装Tomcat(版本号为5.5.17),但在启动时,发现其只能用管理员身份进行启动,而无法向往常一样,通过开始菜单直接启动。进入Eclipse,启动项目(WebApplication),发现麻烦来了,不管我用何种方式,TomcatServer一直报服务超时,不能正常启动!

     唉,难道要我重新恢复XP吗,这可不是一张恢复盘的问题,而是我花了两天时间,进行操作系统和相关相关软件安装,我的妈也!!

     到网上查找相关资料,也未获取明确的解决之道;到MS支持网站,没找到合适的方案;问周边同事,得到N种可能的解决方式;经过一天时间,逐个试验,终获解决之道。

      可在环境操作系统变量中添加classpath项,其值如:C:/Program Files/Java/jdk1.5.0_11/lib;C:/Program Files/Java/jdk1.5.0_11/lib/tools.jar

posted @ 2007-12-29 14:52 lbom 阅读(4286) | 评论 (8)编辑 收藏

昨日,在将应用程序(JSF应用,其中包含Tiles包)发布至测试服务器(Solaris8+Tomcate5.5)时,发现其不能正常运行,其错误如下:
......
2006-11-1 17:09:54 org.apache.tiles.servlets.TilesServlet init
信息: Initializing TilesServlet
2006-11-1 17:09:54 org.apache.tiles.servlets.TilesServlet readFactoryConfig
信息: CONFIG FILES DEFINED IN WEB.XML
信息: initializing definitions factory...ets.TilesServlet initDefinitionsFactory
2006-11-1 17:09:54 org.apache.tiles.servlets.TilesServlet saveExceptionMessage
警告: Caught exception when initializing definitions factory
2006-11-1 17:09:54 org.apache.tiles.servlets.TilesServlet saveExceptionMessage
警告: I/O Error reading definitions.
2006-11-1 17:09:54 org.apache.tiles.servlets.TilesServlet saveExceptionMessage
s.
2006-11-1 17:09:54 org.apache.tiles.servlets.TilesServlet saveExceptionMessage
警告: Caught exception when initializing definitions factory
2006-11-1 17:09:54 org.apache.tiles.servlets.TilesServlet saveExceptionMessage
警告: I/O Error reading definitions.
2006-11-1 17:09:54 org.apache.tiles.servlets.TilesServlet saveExceptionMessage
警告: javax.servlet.ServletException: I/O Error reading definitions.
2006-11-1 17:09:55 org.apache.coyote.http11.Http11BaseProtocol start
信息: Starting Coyote HTTP/1.1 on http-8800
......

根据此错误分析,是由于TilesServlet未正确读取tiles.xml配置文件,但在对tiles.xml进行权限变更后,也未解决此问题!!!
但是,此应用在开发环境下是正常的,如何是好??
我和同事在对比开发环境和测试环境之后,发现二者的运行环境差别只有操作系统(UNIX<>WINDOWS XP);
搜索Google和BeiDu,找到一篇相类似的报到,据其所说,当他的系统在断开网络后会出现类似的情况,难道是TilesServlet必需联上互联网?
在分析tiles.xml后,发现,其中有如下一句:
   <!DOCTYPE tiles-definitions PUBLIC "-//Apache Software Foundation//DTD Tiles Configuration 2.0//EN" "http://struts.apache.org/dtds/tiles-config_2_0.dtd">
原来,当互联网断开之后,不能从tiles-config_2_0.dtd中获取验证,导致此文件解释失败,将此删除之后,系统就可正常部属在测试环境之中了.

posted @ 2006-11-02 14:30 lbom 阅读(858) | 评论 (0)编辑 收藏

怀疑论者的 JSF: JSF 组件开发

省时运动使得构建 JSF 组件轻而易举

developerWorks
文档选项
将此页作为电子邮件发送

将此页作为电子邮件发送

未显示需要 JavaScript 的文档选项

Discuss

Sample code


对此页的评价

帮助我们改进这些内容


级别: 中级

Rick Hightower , CTO, ArcMind

2005 年 8 月 16 日

在四部分的 怀疑论者的 JSF 系列的最后一期中,Rick Hightower 介绍了省时运动,它可以一次或永远地说服您:JSF 组件开发要比您想像的更容易。

组件模型的关键考验就是:能否从第三方供应商购买组件,并把它们插入应用程序?与可购买可视 Swing 组件一样,也可以购买 Java ServerFaces (JSF) 组件!需要一个好玩的日历?可以在开源实现和商业组件之间选择。可以选择购买一个,而不是自行开发复杂的基于 Web 的 GUI 组件。

JSF 拥有一个与 AWT 的 GUI 组件模型类似的组件模型。可以用 JSF 创建可重用组件。但不幸的是,存在一个误解:用 JSF 创建组件很困难。不要相信这些从未试过它的人们的 FUD!开发 JSF 组件并不困难。由于不用一遍又一遍重复相同的代码,可以节约时间。一旦创建了组件,就可以容易地把组件拖到任何 JSP、甚至任何 JSF 表单中,如果正在处理的站点有 250 个页面,这就很重要了。JSF 的大多数功能来自基类。因为所有的繁重工作都由 API 和基类完成,所以 JSF 把组件创建变得很容易。

贯穿这个系列,我一直在试图帮助您克服造成许多 Java 开发人员逃避使用 JSF 技术的 FUD。我讨论了对这项技术的基本误解,介绍了它的底层框架和它最有价值的开发特性。有了这些基础工作之后,我认为您已经可以采取行动,开发自己的定制 JSF 组件了。使用 JSF 的东西,我敢保证要比您想像的要更加容易,而且从节约的时间和精力上来说,回报如此之多,多得不能忽略。

这篇文章中的示例是用 JDK 1.5 和 Tomcat 开发的。请单击页面顶部的 示例代码 下载示例源代码。注意,与以前的文章不同,这篇文章没有关联的 build 文件,因为我特意把它留给您作为一个练习了。只要设置 IDE 或编译器,把 /src 中的类编译到 /webapp/WEB-INF/classes,并在 /webapp/WEB-INF/lib 中包含所有 JAR 文件(以及 servlet-api.jarjsp-api.jar,它们包含在 Tomcat 中)。

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 组件
JSF 组件和 JSF 生命周期
提示!

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

更多组件概念

所有 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 标记
在 JSP 中使用 JSF 标记

第 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
连接 JSF 和 JSP

注意,由于没有独立的渲染器,所以可以给 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
绑定 JSF 和 JSP

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



回页首


复合组件

在下一个示例中,我将介绍如何创建这样一个组件(和标记),它可以记住最后一个人离开的位置。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 组件
Field 组件

清单 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. 把渲染器映射到组件
把渲染器映射到组件

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



回页首


结束语

通过这些内容,您已经切实地了解了 JSF 组件开发的核心。当然,在这个领域还有许多其他主题需要涉及 —— 包括发出组件事件、国际化组件、创建 UICommand 样式的组件,以及更多。请参阅 参考资料 获得 JSF 的阅读列表!

在编写这篇文章的过程中,我遇到了 Renderer 的一个技术障碍,它使我发现了 getConvertedValues 方法的工作方式。尽管我以前遇到过 Converter 问题并处理过它,但是那时我是在一个紧张的(生产)日程中做这件事的。在生产工作中进行的研究,不必像在 how-to 文章中做得那么详细;所以这一次,我必须不仅学习如何修补问题,还要学习弄清如何 做对。通过这整个过程,我最终在非常深的层次上学会并体验了 JSF 组件处理工作的方式。所以,有时绕点弯路会看到优美的风景。

我希望在这个由四部分组成的系列中,您已经学到了关于使用 JSF 的优势的充足知识,以及它如何工作的基础知识,还希望您会喜欢进一步深入这项技术。而且当您有时可能迷失方向的时候,请不要陷入 FUD。相反,请记住我说过的:弯路会看到优美的风景,请继续前行。

posted @ 2006-01-10 11:26 lbom 阅读(567) | 评论 (0)编辑 收藏

仅列出标题
共3页: 上一页 1 2 3 下一页