春's profileDENNYPhotosBlogLists Tools Help

Blog


    January 18

    Java代码编写的30条建议 [转]

    1) 类名首字母应该大写。字段、方法以及对象(句柄)的首字母应小写。对于所有标 
    识符,其中包含的所有单词都应紧靠在一起,而且大写中间单词的首字母。例如: 
    ThisIsAClassName 
    thisIsMethodOrFieldName 
    若在定义中出现了常数初始化字符,则大写static final基本类型标识符中的所有字母 
    。这样便可标志出它们属于编译期的常数。 
    Java包(Package)属于一种特殊情况:它们全都是小写字母,即便中间的单词亦是如此 
    。对于域名扩展名称,如com,org,net或者edu等,全部都应小写(这也是Java 1.1和 
    Java 1.2的区别之一)。 


    (2) 为了常规用途而创建一个类时,请采取"经典形式",并包含对下述元素的定义: 
    equals() 
    hashCode() 
    toString() 
    clone()(implement Cloneable) 
    implement Serializable 


    (3) 对于自己创建的每一个类,都考虑置入一个main(),其中包含了用于测试那个类的 
    代码。为使用一个项目中的类,我们没必要删除测试代码。若进行了任何形式的改动, 
    可方便地返回测试。这些代码也可作为如何使用类的一个示例使用。 


    (4) 应将方法设计成简要的、功能性单元,用它描述和实现一个不连续的类接口部分。 
    理想情况下,方法应简明扼要。若长度很大,可考虑通过某种方式将其分割成较短的几 
    个方法。这样做也便于类内代码的重复使用(有些时候,方法必须非常大,但它们仍应 
    只做同样的一件事情)。 


    (5) 设计一个类时,请设身处地为客户程序员考虑一下(类的使用方法应该是非常明确 
    的)。然后,再设身处地为管理代码的人考虑一下(预计有可能进行哪些形式的修改, 
    想想用什么方法可把它们变得更简单)。 


    (6) 使类尽可能短小精悍,而且只解决一个特定的问题。下面是对类设计的一些建议: 
     
    ■一个复杂的开关语句:考虑采用"多形"机制 
    ■数量众多的方法涉及到类型差别极大的操作:考虑用几个类来分别实现 
    ■许多成员变量在特征上有很大的差别:考虑使用几个类 


    (7) 让一切东西都尽可能地"私有"--private。可使库的某一部分"公共化"(一个方法、 
    类或者一个字段等等),就永远不能把它拿出。若强行拿出,就可能破坏其他人现有的 
    代码,使他们不得不重新编写和设计。若只公布自己必须公布的,就可放心大胆地改变 
    其他任何东西。在多线程环境中,隐私是特别重要的一个因素--只有private字段才能在 
    非同步使用的情况下受到保护。 


    (8) 谨惕"巨大对象综合症"。对一些习惯于顺序编程思维、且初涉OOP领域的新手,往往 
    喜欢先写一个顺序执行的程序,再把它嵌入一个或两个巨大的对象里。根据编程原理, 
    对象表达的应该是应用程序的概念,而非应用程序本身。 


    (9) 若不得已进行一些不太雅观的编程,至少应该把那些代码置于一个类的内部。 


    (10) 任何时候只要发现类与类之间结合得非常紧密,就需要考虑是否采用内部类,从而 
    改善编码及维护工作(参见第14章14.1.2小节的"用内部类改进代码")。 


    (11) 尽可能细致地加上注释,并用javadoc注释文档语法生成自己的程序文档。 


    (12) 避免使用"魔术数字",这些数字很难与代码很好地配合。如以后需要修改它,无疑 
    会成为一场噩梦,因为根本不知道"100"到底是指"数组大小"还是"其他全然不同的东西 
    "。所以,我们应创建一个常数,并为其使用具有说服力的描述性名称,并在整个程序中 
    都采用常数标识符。这样可使程序更易理解以及更易维护。 


    (13) 涉及构建器和异常的时候,通常希望重新丢弃在构建器中捕获的任何异常--如果它 
    造成了那个对象的创建失败。这样一来,调用者就不会以为那个对象已正确地创建,从 
    而盲目地继续。 


    (14) 当客户程序员用完对象以后,若你的类要求进行任何清除工作,可考虑将清除代码 
    置于一个良好定义的方法里,采用类似于cleanup()这样的名字,明确表明自己的用途。 
    除此以外,可在类内放置一个boolean(布尔)标记,指出对象是否已被清除。在类的f 
    inalize()方法里,请确定对象已被清除,并已丢弃了从RuntimeException继承的一个类 
    (如果还没有的话),从而指出一个编程错误。在采取象这样的方案之前,请确定fina 
    lize()能够在自己的系统中工作(可能需要调用System.runFinalizersOnExit(true), 
    从而确保这一行为)。 


    (15) 在一个特定的作用域内,若一个对象必须清除(非由垃圾收集机制处理),请采用 
    下述方法:初始化对象;若成功,则立即进入一个含有finally从句的try块,开始清除 
    工作。 


    (16) 若在初始化过程中需要覆盖(取消)finalize(),请记住调用super.finalize()( 
    若Object属于我们的直接超类,则无此必要)。在对finalize()进行覆盖的过程中,对 
    super.finalize()的调用应属于最后一个行动,而不应是第一个行动,这样可确保在需 
    要基础类组件的时候它们依然有效。 


    (17) 创建大小固定的对象集合时,请将它们传输至一个数组(若准备从一个方法里返回 
    这个集合,更应如此操作)。这样一来,我们就可享受到数组在编译期进行类型检查的 
    好处。此外,为使用它们,数组的接收者也许并不需要将对象"造型"到数组里。 


    (18) 尽量使用interfaces,不要使用abstract类。若已知某样东西准备成为一个基础类 
    ,那么第一个选择应是将其变成一个interface(接口)。只有在不得不使用方法定义或 
    者成员变量的时候,才需要将其变成一个abstract(抽象)类。接口主要描述了客户希 
    望做什么事情,而一个类则致力于(或允许)具体的实施细节。 


    (19) 在构建器内部,只进行那些将对象设为正确状态所需的工作。尽可能地避免调用其 
    他方法,因为那些方法可能被其他人覆盖或取消,从而在构建过程中产生不可预知的结 
    果(参见第7章的详细说明)。 


    (20) 对象不应只是简单地容纳一些数据;它们的行为也应得到良好的定义。 


    (21) 在现成类的基础上创建新类时,请首先选择"新建"或"创作"。只有自己的设计要求 
    必须继承时,才应考虑这方面的问题。若在本来允许新建的场合使用了继承,则整个设 
    计会变得没有必要地复杂。 


    (22) 用继承及方法覆盖来表示行为间的差异,而用字段表示状态间的区别。一个非常极 
    端的例子是通过对不同类的继承来表示颜色,这是绝对应该避免的:应直接使用一个"颜 
    色"字段。 


    (23) 为避免编程时遇到麻烦,请保证在自己类路径指到的任何地方,每个名字都仅对应 
    一个类。否则,编译器可能先找到同名的另一个类,并报告出错消息。若怀疑自己碰到 
    了类路径问题,请试试在类路径的每一个起点,搜索一下同名的.class文件。 


    (24) 在Java 1.1 AWT中使用事件"适配器"时,特别容易碰到一个陷阱。若覆盖了某个适 
    配器方法,同时拼写方法没有特别讲究,最后的结果就是新添加一个方法,而不是覆盖 
    现成方法。然而,由于这样做是完全合法的,所以不会从编译器或运行期系统获得任何 
    出错提示--只不过代码的工作就变得不正常了。 


    (25) 用合理的设计方案消除"伪功能"。也就是说,假若只需要创建类的一个对象,就不 
    要提前限制自己使用应用程序,并加上一条"只生成其中一个"注释。请考虑将其封装成 
    一个"独生子"的形式。若在主程序里有大量散乱的代码,用于创建自己的对象,请考虑 
    采纳一种创造性的方案,将些代码封装起来。 


    (26) 警惕"分析瘫痪"。请记住,无论如何都要提前了解整个项目的状况,再去考察其中 
    的细节。由于把握了全局,可快速认识自己未知的一些因素,防止在考察细节的时候陷 
    入"死逻辑"中。 


    (27) 警惕"过早优化"。首先让它运行起来,再考虑变得更快--但只有在自己必须这样做 
    、而且经证实在某部分代码中的确存在一个性能瓶颈的时候,才应进行优化。除非用专 
    门的工具分析瓶颈,否则很有可能是在浪费自己的时间。性能提升的隐含代价是自己的 
    代码变得难于理解,而且难于维护。 


    (28) 请记住,阅读代码的时间比写代码的时间多得多。思路清晰的设计可获得易于理解 
    的程序,但注释、细致的解释以及一些示例往往具有不可估量的价值。无论对你自己, 
    还是对后来的人,它们都是相当重要的。如对此仍有怀疑,那么请试想自己试图从联机 
    Java文档里找出有用信息时碰到的挫折,这样或许能将你说服。 


    (29) 如认为自己已进行了良好的分析、设计或者实施,那么请稍微更换一下思维角度。 
    试试邀请一些外来人士--并不一定是专家,但可以是来自本公司其他部门的人。请他们 
    用完全新鲜的眼光考察你的工作,看看是否能找出你一度熟视无睹的问题。采取这种方 
    式,往往能在最适合修改的阶段找出一些关键性的问题,避免产品发行后再解决问题而 
    造成的金钱及精力方面的损失。 


    (30) 良好的设计能带来最大的回报。简言之,对于一个特定的问题,通常会花较长的时 
    间才能找到一种最恰当的解决方案。但一旦找到了正确的方法,以后的工作就轻松多了 
    ,再也不用经历数小时、数天或者数月的痛苦挣扎。我们的努力工作会带来最大的回报 
    (甚至无可估量)。而且由于自己倾注了大量心血,最终获得一个出色的设计方案,成 
    功的快感也是令人心动的。坚持抵制草草完工的诱惑--那样做往往得不偿失

    关于海量数据的SQL查询优化

    通过内部的计数器得知:访问次数是1071(其中有好多是自己点的:)),人数不是太理想,本来是想看看上万人同时访问的情况:)

    系统资源的占用情况

    内存 —— 很理想。SQL占用的内存最大也没有超过65M,一般是在35M左右;asp.net占用的内存最大也没有超过40M,一般是在25M左右。

    CPU:8%左右,由于访问次数不多,也不够集中,所以这个数值也说明不了什么。自己连续点了n次下一页,发现CPU的使用率飘高,达到了50%左右。
    但是对于100万的记录,AMD XP2000+ 的CPU 几十毫秒的放映速度,因该是可以接受的,甚至是很理想的吧。
    毕竟服务器的CPU要比我的快很多吧,而且记录也很难达到100万吧。

    结果还是很满意的,但是美中不足的是,我想看一下海量访问的情况下的效果,
    希望大家再支持一下,多点几下,谢谢了。呵呵

    另外说明一下:前n页可以在60毫秒内完成,n应该是大于500的,小于多少嘛还没有测试。后n页就比较慢了,需要500毫秒左右。

    下面讨论一下翻页的技巧吧。
    我没有用游标、临时表、not in、in 这些方法,并不是说他们的效率不高,而是我还没有测试过。我只用了 top ,查了两次表。
    大家也可提供一些其他的方法,我来测试一下,看看在100万条的情况下的效果。(请不要给在存储过程里面组串的,看着实在是太费劲了)

     

     讨论的前提是在海量数据的情况下,至少是在10万以上的。如果是很少的数据呢,那怎么翻都可以了。也差不了多少。

    1.设置合理的索引
    首先要做的是设置合理的索引,这个好像经常被忽略,至少很少被谈起。

    注意:主键是索引的一种,而且是最快的一种。如果你都是把主键当作排序字段的话,那么你已经利用了索引。

    不设置合理的索引的话,会导致查询速度非常的慢,甚至会造成超时。

    这方面你可以做一个实验:找一个表,填进去10万条记录,假设有ID 、addedDate等字段,在查询分析器里面执行一下

    select top 10 * from table
    应该立刻就能出现结果。

    然后再执行 select top 10 * from table order by ID(这时ID字段是主键)
    也是立刻就出现了结果。

    然后再执行 select top 10 * from table order by addedDate (这时addedDate字段没有索引)
    你会发现速度很慢。

    现在给addedDate 加一个非聚集索引,然后在执行上面的查询语句,速度也变得很快了。

    可见索引神奇的效果!

    这是翻动百万级记录最基本的设置,具体到我的那个论坛的翻页,我是设置了BoardID、 replyDate两个字段作为联合索引的。
    因为是要在同一个讨论组李翻页,而且是按replyDate排序的。


    2.只返回需要的记录
    对于海量数据,都读出来做缓存,那是不可想象的(记录少的话,也要看利用率,一般都是很浪费的)。
    所以呢如果一页显示20条的话名那就只都读出来20条,这样就很省内存和时间。

    注意:虽然ADO.NET里面有这个方法
    SqlDataAdapter.Fill(DataSet1,startRecord,maxRecords,srcTable);
    但是他还是要先从SQL里面把查询语句的查出来的所有记录都出来,然后在截取指定的记录数。这对于SQL来说是一样的,对于海量数据依然会很慢。

    论坛里的首页用的是select top 20 * from table where boardID = 5 order by replyDate desc
    这样呢就只返回了20条记录,再加上索引的功劳,速度是非常快的。

     

     3.尽量减少字段的长度
    一个表可以建很多的字段,但是字段的总长度不能超过8060B,也就是说如果你建了一个char(8060)的字段,就不能在建其他的字段了。

    我在第一次的测试中(星期天的),把主题的所有信息都放在了一个表里面,包括了一个nvarchar(3600)的主题内容的字段,复制记录的时候发现非常的慢,
    当达到9万的时候,就已经很慢的,勉强把记录数拷贝到了35万,加了索引,测试了一下,翻页速度还是可以的,前n也都是很快的,后n页就很慢了,
    如果再加上查询那就非常之慢了。

    查看了一下数据文件吓了一跳 —— 他居然占用了1.4G的硬盘空间,怪不得拷贝和查询都慢的要死呢。

    于是修改了一下表结构,把那个nvarchar(3600)的主题内容的字段踢了出去,放在一个单独的表里面。
    再重新拷贝记录就非常的快了,很快就把记录数从16表成了1048577。昨天的测试就是在这个条件下进行的。

    4.技巧
    终于到了翻页算法的地方了,呵呵没有等急吧。
    思路呢就是先找到一个标志,然后呢把大于(或小于)这个标志的前n条记录取出来。
    什么?没看懂。没关系,我举个例子吧。

    假设是按ID倒序的,每一页显示10条记录,有100条记录,记录号正好是1到100(怎么这么巧??为了说明方便嘛)

    那么第一页的记录就是100到91、第二页的记录就是90到81、第三页的记录就是80到71......

    我现在要翻到第三页,那么要找到第21行的记录的ID的值(也就是80),然后把小于等于80的记录用top 10 取出来就行了。

    查询语句

    declare @pageSize int --返回一页的记录数
    declare @CurPage int --页号(第几页)1:第一页;2:第二页;......;-1最后一页。

    declare @Count int
    declare @id int

    set @pageSize=10
    set @CurPage =1

    if @CurPage = -1
    begin
    --最后一页
    set rowcount @pageSize
    select @id=ID from table order by ID
    end

    --定位
    if @CurPage > 0
    begin
    set @Count = @pageSize * (@CurPage -1) + 1
    set rowcount @Count
    select @id=ID from table order by ID desc
    end

    --返回记录
    set rowcount @pageSize
    select * from table where ID <=@id order by ID desc

    set rowcount 0


    其中“定位”用了 select @id=ID from table order by ID desc
    这种方法,感觉上是很省内存的,因为只记录了一个ID,

    然后用 select * from table where ID <=@id order by ID desc
    取得最终需要的记录

    set rowcount @pageSize 相当于 top @pageSize 。


    优点:无论翻到哪一页,内存的占用情况都不变,多人访问内存也不会不变,很多人呢,还没有测试出来:)
    缺点:单表、单排序字段。

     

     

     http://community.csdn.net/Expert/TopicView3.asp?id=4182510

    发了这个帖子,回复的人很多,感谢大家的支持。这里有个误会我不得不说明一下,免的误人子弟。

    在帖子里我并不是写了个算法就完事了,而是说了很多翻动海量数据要注意的地方,

    比如建立合理的索引,只返回需要的记录 ,尽量减少字段的长度 等注意到或没有注意到的地方。

    最后说的才是算法,可能是我的表达能力太差了吧,举的例子给大家带来了误会。

    翻页的语句 ( @pageSize * (@CurPage -1) + 1 )

    --定位
    declare @id int
    select top 41 @id=ID from table order by ID desc

    --显示数据
    select top 20 * from table where ID <=@id order by ID desc

    按照ID倒序排列(也就是按照int类型的字段排序)
    一页显示20条记录,这是显示第三页的语句
    @pageSize * (@CurPage -1) + 1 = 20*(3-1) + 1 = 41
    正是因为ID是不连续的所以才需要用第一个语句来定位,如果是连续的那还用第一条语句做什么呢?

    举各少量数据的例子:
    假设有10条记录,ID是:1000,500,320,205,115,110,95,68,4,1。这回不写连续的了免的误会
    一页显示两条记录,现在要显示第三页,那么第三页的id就是 115,110

    先看第一条语句
    select top 5 @id=ID from table order by ID desc
    不知道大家有没有看懂这句,这时print @id 得到的结果是 115。

    再看第二条语句
    select top 2 * from table where ID <=115 order by ID desc
    这时的记录集就是 115,110,也就是我们所需要的记录了。


    注意:不需要连续的ID,也不局限只能按ID排序,你可以换成ReplyDate(最后回复时间)字段,
    当然了declare @id int 要改成 declare @id datetime

    这里的ID 是主键,唯一标识记录的字段,它本身就是一种索引,而且是效率最高的索引。

    A.唯一标识记录的字段的值怎么能随意改动呢,那不乱套了吗?

    B.主键是最快的索引,可能你还没有意识到(一开始我就不知道,学了SQL很久以后才知道的),如果你的算法用它作为排序字段,那么速度会很快,会比用其他字段(没有索引的字段)排序快很多。

    C.用ReplyDate(最后回复时间)来排序,那么就必须给他建立索引(在海量数据的情况下),否则会超时的。


    D.建立索引后,再执行添加、修改、删除会对数据库带来灾难性的折磨??
    一开始我也是这么认为的,但是为了能够翻页,不得不加索引。
    但是接下来的事实确打消了我的顾虑

    先来看添加。
    100万条记录是怎么弄出来的?大家可以看到帖子里有很多标题一样的主题,对了是复制出来的。
    我先加了16条记录,然后加上了索引。注意在insert into 之前就已经建立好了索引!

    接下来就是insert into table (...) select ... from table 影响的行数:
    16、32、64、128、256、512、1024、2048、4096、8192、16384、32768、65536、
    131072、262144、524288 很快记录就达到了100完了。
    最后一次也只不过一两分钟(具体的时间忘记了,反正是很快了)。
    同时,论坛也提供了发贴的功能,只是在批量添加记录的时候,把一些记录的最后回复时间弄成了2006年,
    所以,你发的帖子不会显示在第一页。但是你可以看到,执行时间是很快的。

    可见添加的时候是不成问题的,索引是倒序排列的,所以影响的行数绝对没有你想象的那么多。

    再来看修改
    看了sp1234的回复,加了修改的功能,只是为了测试,所以呢可以修改标题、最后发表时间、分组ID。
    为什么可以修改这几个字段呢?标题是普通字段,最后发表时间和分组ID是索引字段。
    修改这几个字段需要的时间都是很快的,在最后回复时间的右面有 [改] [删] 字样,大家可以试一试。
    同样,修改的时候,影响的行数也不是很多。

    最后看删除
    不多说了,论坛提供了这个功能,试一下就知道了。另外,删除的时候,不用重新建立一遍索引吧?


    在来说一下使用范围吧。
    首先呢这只是一种方法,而不是一个通用的存储过程,也就是说要根据情况作适当的修改。
    最佳使用环境:
    单表,单排序字段,可以利用索引。
    注意事项:
    排序字段不必连续,最好使用int、datetime类型的字段,字符串型的字段没有试过,效果可能会略差。
    表可以没有主键,但是对于海量数据的情况下,必须建立合理的索引。

    有一个比较致命的限制,大家好像都没有发现,那就是排序字段的重复性,
    最好是没有重复的,但不是说绝对不能有重复的记录,有不要紧,只要不跨页就行,跨页的话就会挤掉若干条记录,
    用时间字段来排序,发生重复的记录的可能性就很小了。


    扩展性
    bingbingcha(不思不归,不孟不E,原来是头大灰狼) 的回复很精彩
    -----------------
    这样的技巧在SQL区都讨论过了..速度是很快的..但是满足不了需求的..实用性太差了..现在的企业需要用到分页的大部分都是多表查询..单表分页满足不了需求的..

    这个存储过程可以扩展..用临时表+楼主的方法..是个不错的选择..
    -----------------

    对于多表关联查询,有两种方法,第一种就是bingbingcha说的 —— “用临时表+楼主的方法”,这是在海量数据的时候唯一可行的方法。
    但是在小数据量的时候,这么些就有一点繁琐,而且不容易归纳到通用的写法里。

    先来看一下查询语句据的写法:
    联合的
    SELECT a.ReplyID, a.TopicID
    FROM dbo.BBS_Reply a INNER JOIN
    dbo.BBS_body b ON a.BodyID = b.bodyID
    where a.ReplyID >10

    单表的

    SELECT ReplyID, TopicID
    FROM dbo.BBS_Reply
    where ReplyID >10

    有没有看到相同的地方:
    select 显示的字段
    from 表
    where 条件

    那么单表查询和多表查询有什么区别呢?
    至少有很多的多表(单字段排序)查询都是可用这种方式的。
    注意:我并没有说所有的多表(单字段排序)查询都可以用,看具体情况了。



    这是一个效率最高(需要合理的索引的帮忙),比较通用的翻页方法。不知道这次我有没有讲明白。

    Jsp结合XML+XSLT将输出转换为Html格式

    我们知道 XML+XSLT就可以直接输出到支持XML的浏览器上,如IE 5.0以上,但是,我们还要考虑到有不少浏览器不直接支持XML,在这种情况下,我们需要在服务器上进行转换成html输出到浏览器,这种临时过渡办法恐怕要在一段时间内一直要使用.   使用Jsp 加上tablib标识库,我们可以完成这种转换。

      著名open source项目组jakarta.apache.org推出的系列标识库中,就有这个功能的tanglib:http://jakarta.apache.org/taglibs/doc/xsl-doc/intro.html

      按照jakarta配置方法,有点繁琐,需要修改或定义Web.xml,本人经过摸索,使用下列相当简单的办法,就可以使Jsp能成功运行XSL这个标识库了。

      xsl标识库有三个关键包:
       xerces.jar 可以在http://xml.apache.org/中得到
       xalan.jar 可以在http://xml.apache.org/中得到
       xsl.jar 从http://jakarta.apache.org/taglibs/doc/xsl-doc/intro.html得到

      1.将这三个包放置到Tomcat的common/lib目录下,或者直接放入Classpath环境中。

      2.在JSP中调用标识库:

      原来Jakarta推荐方法是:


    <%@taglib uri="http://jakarta.apache.org/taglibs/xsl-1.0" prefix="xsl" %> 

      这就需要在/WEB-INF/web.xml下定义一下http://jakarta.apache.org/taglibs/xsl-1.0指向。如:


    <taglib>
    <taglib-uri>http://jakarta.apache.org/taglibs/xsl-1.0</taglib-uri>
    <taglib-location>/WEB-INF/xsl.tld</taglib-location>
    </taglib> 

      这种做法虽然很标准,但是,如果你的容器一直使用tomcat,就完全不必了。

      我们的做法是:


    <%@taglib uri="xsl.jar" prefix="xsl" %>  

      我们以Jakarta的XSL taglib附带的Apply.jsp为例,正好了解一下Jsp XML XSLT三者之间的关系:

      Apply.jsp 


    <%@taglib uri="xsl.jar" prefix="xsl" %> 
    <html>
    <head>
    <title>Employee List</title>
    </head>
    <body bgcolor="white">

    <p>下面展示了Jsp的四种组合XML XSLT的方法:
    <p>下面使用apply方法,将已经存在的employees.xml和employeeList.xsl结合在一起

    <xsl:apply xml="/xml/employees.xml" xsl="/xml/employeeList.xsl"/>
    <hr>


    <p>下面是使用已经存在employeeList.xsl 然后在Jsp中自己直接写入XML数据.


    <xsl:apply xsl="/xml/employeeList.xsl">
    <?xml version="1.0" encoding="ISO-8859-1"?>
    <employees>
    <employee id="123">
    <first-name>John</first-name>
    <last-name>Doe</last-name>
    <telephone>800-555-1212</telephone>
    </employee>
    <employee id="456">
    <first-name>Jane</first-name>
    <last-name>Smith</last-name>
    <telephone>888-555-1212</telephone>
    </employee>
    <employee id="789">
    <first-name>George</first-name>
    <last-name>Taylor</last-name>
    <telephone>555-555-1212</telephone>
    </employee>
    </employees>
    </xsl:apply>
    <hr>

    <p>下面使使用include调用的办法,这样一个XSLT样式可以适应不同的XML文件。

    <xsl:apply xsl="/xml/employeeList.xsl">
    <xsl:include page="/xml/employees.xml"/>
    </xsl:apply>
    <hr>

    <p>下面是使用import方法,在page-scope(类似scope="page")中导入XML文件</p>

    <xsl:import id="data" page="/xml/employees.xml"/>
    <xsl:apply nameXml="data" xsl="/xml/employeeList.xsl"/>

    </body>
     

      在上面程序中,展示了四种Jsp组合XML XSLT的方法,基本可以满足我们的需要。注意上面的XML文件路径是"/xml/",这是相对Tomcat容器的绝对路径。

      我们简单看一下employeeList.xsl和employees.xml内容:

      employeeList.xsl类似html中的CSS,主要是对XML中数据显示方式进行定义:

    <?xml version="1.0"?> 
    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 
    <xsl:template match="employees">
    <table border="1" width="100%">
    <tr>
    <th>ID</th>
    <th>Employee Name</th>
    <th>Phone Number</th>
    </tr>
    <xsl:for-each select="employee">
    <tr>
    <td>
    <xsl:value-of select="@id"/>
    </td>
    <td>
    <xsl:value-of select="last-name"/>, 
    <xsl:value-of select="first-name"/>
    </td>
    <td>
    <xsl:value-of select="telephone"/>
    </td>
    </tr>
    </xsl:for-each>
    </table>
    </xsl:template>

    </xsl:stylesheet>

     

    employees.xml

    <?xml version="1.0" encoding="ISO-8859-1"?>


    <employees>
     <employee id="123">
      <first-name>John</first-name>
      <last-name>Doe</last-name>
      <telephone>800-555-1212</telephone>
     </employee>

     <employee id="456">
      <first-name>Jane</first-name>
      <last-name>Smith</last-name>
      <telephone>888-555-1212</telephone>
     </employee>

      <employee id="789">
      <first-name>George</first-name>
      <last-name>Taylor</last-name>
      <telephone>555-555-1212</telephone>
     </employee>
    </employees> 
     

      如果我们在employees.xml顶部加入:


    <?xml:stylesheet type="text/xsl" href="catalog.xsl"?>  

      用支持XML的IE 5.0以上浏览器调用,其显示页面就和Apply.jsp显示页面是一样的。

    一篇关于session的好文章,写的很详细 [转]

    摘要:虽然session机制在web应用程序中被采用已经很长时间了,但是仍然有很多人不清楚session机制的本质,以至不能正确的应用这一技术。本文将详细讨论session的工作机制并且对在Java web application中应用session机制时常见的问题作出解答。

    目录:
    一、术语session
    二、HTTP协议与状态保持
    三、理解cookie机制
    四、理解session机制
    五、理解javax.servlet.http.HttpSession
    六、HttpSession常见问题
    七、跨应用程序的session共享
    八、总结
    参考文档

    一、术语session
    在我的经验里,session这个词被滥用的程度大概仅次于transaction,更加有趣的是transaction与session在某些语境下的含义是相同的。

    session,中文经常翻译为会话,其本来的含义是指有始有终的一系列动作/消息,比如打电话时从拿起电话拨号到挂断电话这中间的一系列过程可以称之为一个 session。有时候我们可以看到这样的话“在一个浏览器会话期间,...”,这里的会话一词用的就是其本义,是指从一个浏览器窗口打开到关闭这个期间 ①。最混乱的是“用户(客户端)在一次会话期间”这样一句话,它可能指用户的一系列动作(一般情况下是同某个具体目的相关的一系列动作,比如从登录到选购商品到结账登出这样一个网上购物的过程,有时候也被称为一个transaction),然而有时候也可能仅仅是指一次连接,也有可能是指含义①,其中的差别只能靠上下文来推断②。

    然而当session一词与网络协议相关联时,它又往往隐含了“面向连接”和/或“保持状态”这样两个含义, “面向连接”指的是在通信双方在通信之前要先建立一个通信的渠道,比如打电话,直到对方接了电话通信才能开始,与此相对的是写信,在你把信发出去的时候你并不能确认对方的地址是否正确,通信渠道不一定能建立,但对发信人来说,通信已经开始了。“保持状态”则是指通信的一方能够把一系列的消息关联起来,使得消息之间可以互相依赖,比如一个服务员能够认出再次光临的老顾客并且记得上次这个顾客还欠店里一块钱。这一类的例子有“一个TCP session”或者 “一个POP3 session”③。

    而到了web服务器蓬勃发展的时代,session在web开发语境下的语义又有了新的扩展,它的含义是指一类用来在客户端与服务器之间保持状态的解决方案④。有时候session也用来指这种解决方案的存储结构,如“把xxx保存在session 里”⑤。由于各种用于web开发的语言在一定程度上都提供了对这种解决方案的支持,所以在某种特定语言的语境下,session也被用来指代该语言的解决方案,比如经常把Java里提供的javax.servlet.http.HttpSession简称为session⑥。

    鉴于这种混乱已不可改变,本文中session一词的运用也会根据上下文有不同的含义,请大家注意分辨。
    在本文中,使用中文“浏览器会话期间”来表达含义①,使用“session机制”来表达含义④,使用“session”表达含义⑤,使用具体的“HttpSession”来表达含义⑥

    二、HTTP协议与状态保持
    HTTP 协议本身是无状态的,这与HTTP协议本来的目的是相符的,客户端只需要简单的向服务器请求下载某些文件,无论是客户端还是服务器都没有必要纪录彼此过去的行为,每一次请求之间都是独立的,好比一个顾客和一个自动售货机或者一个普通的(非会员制)大卖场之间的关系一样。

    然而聪明(或者贪心?)的人们很快发现如果能够提供一些按需生成的动态信息会使web变得更加有用,就像给有线电视加上点播功能一样。这种需求一方面迫使HTML逐步添加了表单、脚本、DOM等客户端行为,另一方面在服务器端则出现了CGI规范以响应客户端的动态请求,作为传输载体的HTTP协议也添加了文件上载、 cookie这些特性。其中cookie的作用就是为了解决HTTP协议无状态的缺陷所作出的努力。至于后来出现的session机制则是又一种在客户端与服务器之间保持状态的解决方案。

    让我们用几个例子来描述一下cookie和session机制之间的区别与联系。笔者曾经常去的一家咖啡店有喝5杯咖啡免费赠一杯咖啡的优惠,然而一次性消费5杯咖啡的机会微乎其微,这时就需要某种方式来纪录某位顾客的消费数量。想象一下其实也无外乎下面的几种方案:
    1、该店的店员很厉害,能记住每位顾客的消费数量,只要顾客一走进咖啡店,店员就知道该怎么对待了。这种做法就是协议本身支持状态。
    2、发给顾客一张卡片,上面记录着消费的数量,一般还有个有效期限。每次消费时,如果顾客出示这张卡片,则此次消费就会与以前或以后的消费相联系起来。这种做法就是在客户端保持状态。
    3、发给顾客一张会员卡,除了卡号之外什么信息也不纪录,每次消费时,如果顾客出示该卡片,则店员在店里的纪录本上找到这个卡号对应的纪录添加一些消费信息。这种做法就是在服务器端保持状态。

    由于HTTP协议是无状态的,而出于种种考虑也不希望使之成为有状态的,因此,后面两种方案就成为现实的选择。具体来说cookie机制采用的是在客户端保持状态的方案,而session机制采用的是在服务器端保持状态的方案。同时我们也看到,由于采用服务器端保持状态的方案在客户端也需要保存一个标识,所以session机制可能需要借助于cookie机制来达到保存标识的目的,但实际上它还有其他选择。

    三、理解cookie机制 
    cookie机制的基本原理就如上面的例子一样简单,但是还有几个问题需要解决:“会员卡”如何分发;“会员卡”的内容;以及客户如何使用“会员卡”。

    正统的cookie分发是通过扩展HTTP协议来实现的,服务器通过在HTTP的响应头中加上一行特殊的指示以提示浏览器按照指示生成相应的cookie。然而纯粹的客户端脚本如JavaScript或者VBScript也可以生成cookie。

    而cookie 的使用是由浏览器按照一定的原则在后台自动发送给服务器的。浏览器检查所有存储的cookie,如果某个cookie所声明的作用范围大于等于将要请求的资源所在的位置,则把该cookie附在请求资源的HTTP请求头上发送给服务器。意思是麦当劳的会员卡只能在麦当劳的店里出示,如果某家分店还发行了自己的会员卡,那么进这家店的时候除了要出示麦当劳的会员卡,还要出示这家店的会员卡。

    cookie的内容主要包括:名字,值,过期时间,路径和域。
    其中域可以指定某一个域比如.google.com,相当于总店招牌,比如宝洁公司,也可以指定一个域下的具体某台机器比如www.google.com或者froogle.google.com,可以用飘柔来做比。
    路径就是跟在域名后面的URL路径,比如/或者/foo等等,可以用某飘柔专柜做比。
    路径与域合在一起就构成了cookie的作用范围。
    如果不设置过期时间,则表示这个cookie的生命期为浏览器会话期间,只要关闭浏览器窗口,cookie就消失了。这种生命期为浏览器会话期的 cookie被称为会话cookie。会话cookie一般不存储在硬盘上而是保存在内存里,当然这种行为并不是规范规定的。如果设置了过期时间,浏览器就会把cookie保存到硬盘上,关闭后再次打开浏览器,这些cookie仍然有效直到超过设定的过期时间。

    存储在硬盘上的cookie 可以在不同的浏览器进程间共享,比如两个IE窗口。而对于保存在内存里的cookie,不同的浏览器有不同的处理方式。对于IE,在一个打开的窗口上按 Ctrl-N(或者从文件菜单)打开的窗口可以与原窗口共享,而使用其他方式新开的IE进程则不能共享已经打开的窗口的内存cookie;对于 Mozilla Firefox0.8,所有的进程和标签页都可以共享同样的cookie。一般来说是用javascript的window.open打开的窗口会与原窗口共享内存cookie。浏览器对于会话cookie的这种只认cookie不认人的处理方式经常给采用session机制的web应用程序开发者造成很大的困扰。

    下面就是一个goolge设置cookie的响应头的例子
    HTTP/1.1 302 Found
    Location: http://www.google.com/intl/zh-CN/
    Set-Cookie: PREF=ID=0565f77e132de138:NW=1:TM=1098082649:LM=1098082649:S=KaeaCFPo49RiA_d8; expires=Sun, 17-Jan-2038 19:14:07 GMT; path=/; domain=.google.com
    Content-Type: text/html




    这是使用HTTPLook这个HTTP Sniffer软件来俘获的HTTP通讯纪录的一部分




    浏览器在再次访问goolge的资源时自动向外发送cookie




    使用Firefox可以很容易的观察现有的cookie的值
    使用HTTPLook配合Firefox可以很容易的理解cookie的工作原理。




    IE也可以设置在接受cookie前询问




    这是一个询问接受cookie的对话框。

    四、理解session机制
    session机制是一种服务器端的机制,服务器使用一种类似于散列表的结构(也可能就是使用散列表)来保存信息。

    当程序需要为某个客户端的请求创建一个session的时候,服务器首先检查这个客户端的请求里是否已包含了一个session标识 - 称为 session id,如果已包含一个session id则说明以前已经为此客户端创建过session,服务器就按照session id把这个 session检索出来使用(如果检索不到,可能会新建一个),如果客户端请求不包含session id,则为此客户端创建一个session并且生成一个与此session相关联的session id,session id的值应该是一个既不会重复,又不容易被找到规律以仿造的字符串,这个 session id将被在本次响应中返回给客户端保存。

    保存这个session id的方式可以采用cookie,这样在交互过程中浏览器可以自动的按照规则把这个标识发挥给服务器。一般这个cookie的名字都是类似于SEEESIONID,而。比如weblogic对于web应用程序生成的cookie,JSESSIONID= ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764,它的名字就是 JSESSIONID。

    由于cookie可以被人为的禁止,必须有其他机制以便在cookie被禁止时仍然能够把session id传递回服务器。经常被使用的一种技术叫做URL重写,就是把session id直接附加在URL路径的后面,附加方式也有两种,一种是作为URL路径的附加信息,表现形式为http://...../xxx;jsessionid= ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764
    另一种是作为查询字符串附加在URL后面,表现形式为http://...../xxx?jsessionid=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764
    这两种方式对于用户来说是没有区别的,只是服务器在解析的时候处理的方式不同,采用第一种方式也有利于把session id的信息和正常程序参数区分开来。
    为了在整个交互过程中始终保持状态,就必须在每个客户端可能请求的路径后面都包含这个session id。

    另一种技术叫做表单隐藏字段。就是服务器会自动修改表单,添加一个隐藏字段,以便在表单提交时能够把session id传递回服务器。比如下面的表单
    <form name="testform" action="/xxx">
    <input type="text">
    </form>
    在被传递给客户端之前将被改写成
    <form name="testform" action="/xxx">
    <input type="hidden" name="jsessionid" value="ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764">
    <input type="text">
    </form>
    这种技术现在已较少应用,笔者接触过的很古老的iPlanet6(SunONE应用服务器的前身)就使用了这种技术。
    实际上这种技术可以简单的用对action应用URL重写来代替。

    在谈论session机制的时候,常常听到这样一种误解“只要关闭浏览器,session就消失了”。其实可以想象一下会员卡的例子,除非顾客主动对店家提出销卡,否则店家绝对不会轻易删除顾客的资料。对session来说也是一样的,除非程序通知服务器删除一个session,否则服务器会一直保留,程序一般都是在用户做log off的时候发个指令去删除session。然而浏览器从来不会主动在关闭之前通知服务器它将要关闭,因此服务器根本不会有机会知道浏览器已经关闭,之所以会有这种错觉,是大部分session机制都使用会话cookie来保存session id,而关闭浏览器后这个 session id就消失了,再次连接服务器时也就无法找到原来的session。如果服务器设置的cookie被保存到硬盘上,或者使用某种手段改写浏览器发出的HTTP请求头,把原来的session id发送给服务器,则再次打开浏览器仍然能够找到原来的session。

    恰恰是由于关闭浏览器不会导致session被删除,迫使服务器为seesion设置了一个失效时间,当距离客户端上一次使用session的时间超过这个失效时间时,服务器就可以认为客户端已经停止了活动,才会把session删除以节省存储空间。

    五、理解javax.servlet.http.HttpSession
    HttpSession是Java平台对session机制的实现规范,因为它仅仅是个接口,具体到每个web应用服务器的提供商,除了对规范支持之外,仍然会有一些规范里没有规定的细微差异。这里我们以BEA的Weblogic Server8.1作为例子来演示。

    首先,Weblogic Server提供了一系列的参数来控制它的HttpSession的实现,包括使用cookie的开关选项,使用URL重写的开关选项,session持久化的设置,session失效时间的设置,以及针对cookie的各种设置,比如设置cookie的名字、路径、域, cookie的生存时间等。

    一般情况下,session都是存储在内存里,当服务器进程被停止或者重启的时候,内存里的session也会被清空,如果设置了session的持久化特性,服务器就会把session保存到硬盘上,当服务器进程重新启动或这些信息将能够被再次使用, Weblogic Server支持的持久性方式包括文件、数据库、客户端cookie保存和复制。

    复制严格说来不算持久化保存,因为session实际上还是保存在内存里,不过同样的信息被复制到各个cluster内的服务器进程中,这样即使某个服务器进程停止工作也仍然可以从其他进程中取得session。

    cookie生存时间的设置则会影响浏览器生成的cookie是否是一个会话cookie。默认是使用会话cookie。有兴趣的可以用它来试验我们在第四节里提到的那个误解。

    cookie的路径对于web应用程序来说是一个非常重要的选项,Weblogic Server对这个选项的默认处理方式使得它与其他服务器有明显的区别。后面我们会专题讨论。

    关于session的设置参考[5] http://e-docs.bea.com/wls/docs70/webapp/weblogic_xml.html#1036869

    六、HttpSession常见问题
    (在本小节中session的含义为⑤和⑥的混合)


    1、session在何时被创建
    一个常见的误解是以为session在有客户端访问时就被创建,然而事实是直到某server端程序调用 HttpServletRequest.getSession(true)这样的语句时才被创建,注意如果JSP没有显示的使用 <% @page session="false"%> 关闭session,则JSP文件在编译成Servlet时将会自动加上这样一条语句 HttpSession session = HttpServletRequest.getSession(true);这也是JSP中隐含的 session对象的来历。

    由于session会消耗内存资源,因此,如果不打算使用session,应该在所有的JSP中关闭它。

    2、session何时被删除
    综合前面的讨论,session在下列情况下被删除a.程序调用HttpSession.invalidate();或b.距离上一次收到客户端发送的session id时间间隔超过了session的超时设置;或c.服务器进程被停止(非持久session)

    3、如何做到在浏览器关闭时删除session
    严格的讲,做不到这一点。可以做一点努力的办法是在所有的客户端页面里使用javascript代码window.oncolose来监视浏览器的关闭动作,然后向服务器发送一个请求来删除session。但是对于浏览器崩溃或者强行杀死进程这些非常规手段仍然无能为力。

    4、有个HttpSessionListener是怎么回事
    你可以创建这样的listener去监控session的创建和销毁事件,使得在发生这样的事件时你可以做一些相应的工作。注意是session的创建和销毁动作触发listener,而不是相反。类似的与HttpSession有关的listener还有 HttpSessionBindingListener,HttpSessionActivationListener和 HttpSessionAttributeListener。

    5、存放在session中的对象必须是可序列化的吗
    不是必需的。要求对象可序列化只是为了session能够在集群中被复制或者能够持久保存或者在必要时server能够暂时把session交换出内存。在 Weblogic Server的session中放置一个不可序列化的对象在控制台上会收到一个警告。我所用过的某个iPlanet版本如果 session中有不可序列化的对象,在session销毁时会有一个Exception,很奇怪。

    6、如何才能正确的应付客户端禁止cookie的可能性
    对所有的URL使用URL重写,包括超链接,form的action,和重定向的URL,具体做法参见[6]
    http://e-docs.bea.com/wls/docs70/webapp/sessions.html#100770

    7、开两个浏览器窗口访问应用程序会使用同一个session还是不同的session
    参见第三小节对cookie的讨论,对session来说是只认id不认人,因此不同的浏览器,不同的窗口打开方式以及不同的cookie存储方式都会对这个问题的答案有影响。

    8、如何防止用户打开两个浏览器窗口操作导致的session混乱
    这个问题与防止表单多次提交是类似的,可以通过设置客户端的令牌来解决。就是在服务器每次生成一个不同的id返回给客户端,同时保存在session里,客户端提交表单时必须把这个id也返回服务器,程序首先比较返回的id与保存在session里的值是否一致,如果不一致则说明本次操作已经被提交过了。可以参看《J2EE核心模式》关于表示层模式的部分。需要注意的是对于使用javascript window.open打开的窗口,一般不设置这个id,或者使用单独的id,以防主窗口无法操作,建议不要再window.open打开的窗口里做修改操作,这样就可以不用设置。

    9、为什么在Weblogic Server中改变session的值后要重新调用一次session.setValue
    做这个动作主要是为了在集群环境中提示Weblogic Server session中的值发生了改变,需要向其他服务器进程复制新的session值。

    10、为什么session不见了
    排除session正常失效的因素之外,服务器本身的可能性应该是微乎其微的,虽然笔者在iPlanet6SP1加若干补丁的Solaris版本上倒也遇到过;浏览器插件的可能性次之,笔者也遇到过3721插件造成的问题;理论上防火墙或者代理服务器在cookie处理上也有可能会出现问题。
    出现这一问题的大部分原因都是程序的错误,最常见的就是在一个应用程序中去访问另外一个应用程序。我们在下一节讨论这个问题。

    七、跨应用程序的session共享

    常常有这样的情况,一个大项目被分割成若干小项目开发,为了能够互不干扰,要求每个小项目作为一个单独的web应用程序开发,可是到了最后突然发现某几个小项目之间需要共享一些信息,或者想使用session来实现SSO(single sign on),在session中保存login的用户信息,最自然的要求是应用程序间能够访问彼此的session。

    然而按照Servlet规范,session的作用范围应该仅仅限于当前应用程序下,不同的应用程序之间是不能够互相访问对方的session的。各个应用服务器从实际效果上都遵守了这一规范,但是实现的细节却可能各有不同,因此解决跨应用程序session共享的方法也各不相同。

    首先来看一下Tomcat是如何实现web应用程序之间session的隔离的,从 Tomcat设置的cookie路径来看,它对不同的应用程序设置的cookie路径是不同的,这样不同的应用程序所用的session id是不同的,因此即使在同一个浏览器窗口里访问不同的应用程序,发送给服务器的session id也可以是不同的。


      

    根据这个特性,我们可以推测Tomcat中session的内存结构大致如下。




    笔者以前用过的iPlanet也采用的是同样的方式,估计SunONE与iPlanet之间不会有太大的差别。对于这种方式的服务器,解决的思路很简单,实际实行起来也不难。要么让所有的应用程序共享一个session id,要么让应用程序能够获得其他应用程序的session id。

    iPlanet中有一种很简单的方法来实现共享一个session id,那就是把各个应用程序的cookie路径都设为/(实际上应该是/NASApp,对于应用程序来讲它的作用相当于根)。
    <session-info>
    <path>/NASApp</path>
    </session-info>

    需要注意的是,操作共享的session应该遵循一些编程约定,比如在session attribute名字的前面加上应用程序的前缀,使得 setAttribute("name", "neo")变成setAttribute("app1.name", "neo"),以防止命名空间冲突,导致互相覆盖。


    在Tomcat中则没有这么方便的选择。在Tomcat版本3上,我们还可以有一些手段来共享session。对于版本4以上的Tomcat,目前笔者尚未发现简单的办法。只能借助于第三方的力量,比如使用文件、数据库、JMS或者客户端cookie,URL参数或者隐藏字段等手段。

    我们再看一下Weblogic Server是如何处理session的。


      

    从截屏画面上可以看到Weblogic Server对所有的应用程序设置的cookie的路径都是/,这是不是意味着在Weblogic Server中默认的就可以共享session了呢?然而一个小实验即可证明即使不同的应用程序使用的是同一个session,各个应用程序仍然只能访问自己所设置的那些属性。这说明Weblogic Server中的session的内存结构可能如下




    对于这样一种结构,在 session机制本身上来解决session共享的问题应该是不可能的了。除了借助于第三方的力量,比如使用文件、数据库、JMS或者客户端 cookie,URL参数或者隐藏字段等手段,还有一种较为方便的做法,就是把一个应用程序的session放到ServletContext中,这样另外一个应用程序就可以从ServletContext中取得前一个应用程序的引用。示例代码如下,

    应用程序A
    context.setAttribute("appA", session); 

    应用程序B
    contextA = context.getContext("/appA");
    HttpSession sessionA = (HttpSession)contextA.getAttribute("appA"); 

    值得注意的是这种用法不可移植,因为根据ServletContext的JavaDoc,应用服务器可以处于安全的原因对于context.getContext("/appA");返回空值,以上做法在Weblogic Server 8.1中通过。

    那么Weblogic Server为什么要把所有的应用程序的cookie路径都设为/呢?原来是为了SSO,凡是共享这个session的应用程序都可以共享认证的信息。一个简单的实验就可以证明这一点,修改首先登录的那个应用程序的描述符weblogic.xml,把cookie路径修改为/appA 访问另外一个应用程序会重新要求登录,即使是反过来,先访问cookie路径为/的应用程序,再访问修改过路径的这个,虽然不再提示登录,但是登录的用户信息也会丢失。注意做这个实验时认证方式应该使用FORM,因为浏览器和web服务器对basic认证方式有其他的处理方式,第二次请求的认证不是通过 session来实现的。具体请参看[7] secion 14.8 Authorization,你可以修改所附的示例程序来做这些试验。

    八、总结
    session机制本身并不复杂,然而其实现和配置上的灵活性却使得具体情况复杂多变。这也要求我们不能把仅仅某一次的经验或者某一个浏览器,服务器的经验当作普遍适用的经验,而是始终需要具体情况具体分析。
    摘要:虽然session机制在web应用程序中被采用已经很长时间了,但是仍然有很多人不清楚session机制的本质,以至不能正确的应用这一技术。本文将详细讨论session的工作机制并且对在Java web application中应用session机制时常见的问题作出解答。

    commons-logging和Log4j 日志管理

    为什么要用日志(Log?

    这个……就不必说了吧。

    为什么不用System.out.println()?

    功能太弱;不易于控制。如果暂时不想输出了怎么办?如果想输出到文件怎么办?如果想部分输出怎么办?……

    为什么同时使用commons-loggingLog4j?为什么不仅使用其中之一?

    Commons-loggin的目的是为“所有的Java日志实现”提供一个统一的接口,它自身的日志功能平常弱(只有一个简单的SimpleLog?),所以一般不会单独使用它。

    Log4j的功能非常全面强大,是目前的首选。我发现几乎所有的Java开源项目都会用到Log4j,但我同时发现,所有用到Log4j的项目一般也同时会用到commons-loggin。我想,大家都不希望自己的项目与Log4j绑定的太紧密吧。另外一个我能想到的“同时使用commons-loggingLog4j”的原因是,简化使用和配置。

           强调一点,“同时使用commons-loggingLog4j”,与“单独使用Log4j”相比,并不会带来更大的学习、配置和维护成本,反而更加简化了我们的工作。我想这也是为什么“所有用到Log4j的项目一般也同时会用到commons-loggin”的原因之一吧。

     

    Commons-logging能帮我们做什么?

    l         提供一个统一的日志接口,简单了操作,同时避免项目与某个日志实现系统紧密a耦合

    l         很贴心的帮我们自动选择适当的日志实现系统(这一点非常好!)

    l         它甚至不需要配置

     

    这里看一下它怎么“‘很贴心的’帮我们‘自动选择’‘适当的’日志实现系统”:

    1)        首先在classpath下寻找自己的配置文件commons-logging.properties,如果找到,则使用其中定义的Log实现类;

    2)        如果找不到commons-logging.properties文件,则在查找是否已定义系统环境变量org.apache.commons.logging.Log,找到则使用其定义的Log实现类;

    3)        否则,查看classpath中是否有Log4j的包,如果发现,则自动使用Log4j作为日志实现类;

    4)        否则,使用JDK自身的日志实现类(JDK1.4以后才有日志实现类);

    5)        否则,使用commons-logging自己提供的一个简单的日志实现类SimpleLog

    (以上顺序不保证完全准确,请参考官方文档)

     

    可见,commons-logging总是能找到一个日志实现类,并且尽可能找到一个“最合适”的日志实现类。我说它“很贴心”实际上是因为:1、可以不需要配置文件;2、自动判断有没有Log4j包,有则自动使用之;3、最悲观的情况下也总能保证提供一个日志实现(SimpleLog)。

           可以看到,commons-logging对编程者和Log4j都非常友好。

           为了简化配置commons-logging,一般不使用commons-logging的配置文件,也不设置与commons-logging相关的系统环境变量,而只需将Log4jJar包放置到classpash中就可以了。这样就很简单地完成了commons-loggingLog4j的融合。如果不想用Log4j了怎么办?只需将classpath中的Log4jJar包删除即可。

    就这么简单!

    代码应该怎么写?

    我们在需要输出日志信息的“每一人”类中做如下的三个工作:

    1、导入所有需的commongs-logging类:

    import org.apache.commons.logging.Log;

    import org.apache.commons.logging.LogFactory;

    如果愿意简化的话,还可以两行合为一行:

    import org.apache.commons.logging.*;

     

    2、在自己的类中定义一个org.apache.commons.logging.Log类的私有静态类成员:

    private static Log log = LogFactory.getLog(YouClassName.class);

    注意这里定义的是static成员,以避免产生多个实例。

    LogFactory.getLog()方法的参数使用的是当前类的class,这是目前被普通认为的最好的方式。为什么不写作LogFactory.getLog(this.getClass())?因为static类成员访问不到this指针!

     

    3、使用org.apache.commons.logging.Log类的成员方法输出日志信息:

    log.debug("111");

    log.info("222");

    log.warn("333");

    log.error("444");

    log.fatal("555");

    这里的log,就是上面第二步中定义的类成员变量,其类型是org.apache.commons.logging.Log,通过该类的成员方法,我们就可以将不同性质的日志信息输出到目的地(目的地是哪里?视配置可定,可能是stdout,也可能是文件,还可能是发送到邮件,甚至发送短信到手机……详见下文对log4j.properties的介绍):

    l         debug()   输出“调试”级别的日志信息;

    l         info()      输出“信息”级别的日志信息;

    l         warn()    输出“警告”级别的日志信息;

    l         error()     输出“错误”级别的日志信息;

    l         fatal()      输出“致命错误”级别的日志信息;

    根据不同的性质,日志信息通常被分成不同的级别,从低到高依次是:“调试(DEBUG)”“信息(INFO)”“警告(WARN)”“错误(ERROR)”“致命错误(FATAL)”。为什么要把日志信息分成不同的级别呢?这实际上是方便我们更好的控制它。比如,通过Log4j的配置文件,我们可以设置“输出‘调试’及以上级别的日志信息”(即“调试”“信息”“警告”“错误”“致命错误”),这对项目开发人员可能是有用的;我们还可以设置“输出“警告”及以上级别的日志信息”(即“警告”“错误”“致命错误”),这对项目最终用户可能是有用的。

           仅从字面上理解,也可以大致得出结论:最常用的应该是debug()info();而warn()error()fatal()仅在相应事件发生后才使用。

     

     

    从上面三个步骤可以看出,使用commons-logging的日志接口非常的简单,不需要记忆太多东西:仅仅用到了两个类Log, LogFactory,并且两个类的方法都非常少(后者只用到一个方法,前者经常用到的也只是上面第三步中列出的几个),同时参数又非常简单。

    上面所介绍的方法是目前被普通应用的,可以说是被标准化了的方法,几乎所有的人都是这么用。如果不信,或想确认一下,就去下载几个知名的Java开源项目源代码看一下吧。

     

    下面给出一个完整的Java类的代码:

     

    package liigo.testlog;

     

    import org.apache.commons.logging.Log;

    import org.apache.commons.logging.LogFactory;

     

     

    public class TestLog

    {

        private static Log log = LogFactory.getLog(TestLog.class);

     

        public void test()

        {

            log.debug("111");

            log.info("222");

            log.warn("333");

            log.error("444");

            log.fatal("555");

        }

     

        public static void main(String[] args)

        {

            TestLog testLog = new TestLog();

            testLog.test();

        }

    }

     

     

    只要保证commons-loggingjar包在classpath中,上述代码肯定可以很顺利的编译通过。那它的执行结果是怎么样的呢?恐怕会有很大的不同,请继续往下看。

     

    Log4j在哪里呢?它发挥作用了吗?

    应该注意到,我们上面给出的源代码,完全没有涉及到Log4j——这正是我们所希望的,这也正是commons-logging所要达到的目标之一。

    可是,怎么才能让Log4j发挥它的作用呢?答案很简单,只需满足“classpath中有Log4jjar包”。前面已经说过了,commons-logging会自动发现并应用Log4j。所以只要它存在,它就发挥作用。(它不存在呢?自然就不发挥作用,commons-logging会另行选择其它的日志实现类。)

     

    注意:配置文件log4j.propertiesLog4j来说是必须的。如果classpath中没有该配置文件,或者配置不对,将会引发运行时异常。

     

           这样,要正确地应用Log4j输出日志信息,log4j.properties的作用就很重要了。好在该文件有通用的模板,复制一份(稍加修改)就可以使用。几乎每一个Java项目目录内都会有一个log4j.properties文件,可下载几个Java开源项目源代码查看。本文最后也附一个模板性质的log4j.properties文件,直接复制过去就可以用,或者根据自己的需要稍加修改。后文将会log4j.properties文件适当作一些介绍。

    关于Log4j比较全面的配置

    LOG4J的配置之简单使它遍及于越来越多的应用中了:Log4J配置文件实现了输出到控制台、文件、

    回滚文件、发送日志邮件、输出到数据库日志表、自定义标签等全套功能。择其一二使用就够用了

     

    log4j.rootLogger=DEBUG,CONSOLE,A1,im
    log4j.addivity.org.apache=true

     

    # 应用于控制台

    log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
    log4j.appender.Threshold=DEBUG
    log4j.appender.CONSOLE.Target=System.out
    log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
    log4j.appender.CONSOLE.layout.ConversionPattern=[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n
    #log4j.appender.CONSOLE.layout.ConversionPattern=[start]%d{DATE}[DATE]%n%p[PRIORITY]%n%x[NDC]%n%t[THREAD] n%c[CATEGORY]%n%m[MESSAGE]%n%n


    #应用于文件

    log4j.appender.FILE=org.apache.log4j.FileAppender
    log4j.appender.FILE.File=file.log
    log4j.appender.FILE.Append=false
    log4j.appender.FILE.layout=org.apache.log4j.PatternLayout
    log4j.appender.FILE.layout.ConversionPattern=[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n
    # Use this layout for LogFactor 5 analysis



    # 应用于文件回滚

    log4j.appender.ROLLING_FILE=org.apache.log4j.RollingFileAppender
    log4j.appender.ROLLING_FILE.Threshold=ERROR
    log4j.appender.ROLLING_FILE.File=rolling.log
    log4j.appender.ROLLING_FILE.Append=true
    log4j.appender.ROLLING_FILE.MaxFileSize=10KB
    log4j.appender.ROLLING_FILE.MaxBackupIndex=1
    log4j.appender.ROLLING_FILE.layout=org.apache.log4j.PatternLayout
    log4j.appender.ROLLING_FILE.layout.ConversionPattern=[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n


    #应用于socket
    log4j.appender.SOCKET=org.apache.log4j.RollingFileAppender
    log4j.appender.SOCKET.RemoteHost=localhost
    log4j.appender.SOCKET.Port=5001
    log4j.appender.SOCKET.LocationInfo=true
    # Set up for Log Facter 5
    log4j.appender.SOCKET.layout=org.apache.log4j.PatternLayout
    log4j.appender.SOCET.layout.ConversionPattern=[start]%d{DATE}[DATE]%n%p[PRIORITY]%n%x[NDC]%n%t[THREAD]%n%c[CATEGORY]%n%m[MESSAGE]%n%n


    # Log Factor 5 Appender
    log4j.appender.LF5_APPENDER=org.apache.log4j.lf5.LF5Appender
    log4j.appender.LF5_APPENDER.MaxNumberOfRecords=2000



    # 发送日志给邮件

    log4j.appender.MAIL=org.apache.log4j.net.SMTPAppender
    log4j.appender.MAIL.Threshold=FATAL
    log4j.appender.MAIL.BufferSize=10
    log4j.appender.MAIL.From=web@www.wuset.com
    log4j.appender.MAIL.SMTPHost=www.wusetu.com
    log4j.appender.MAIL.Subject=Log4J Message
    log4j.appender.MAIL.To=web@www.wusetu.com
    log4j.appender.MAIL.layout=org.apache.log4j.PatternLayout
    log4j.appender.MAIL.layout.ConversionPattern=[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n



    # 用于数据库
    log4j.appender.DATABASE=org.apache.log4j.jdbc.JDBCAppender
    log4j.appender.DATABASE.URL=jdbc:mysql://localhost:3306/test
    log4j.appender.DATABASE.driver=com.mysql.jdbc.Driver
    log4j.appender.DATABASE.user=root
    log4j.appender.DATABASE.password=
    log4j.appender.DATABASE.sql=INSERT INTO LOG4J (Message) VALUES ('[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n')
    log4j.appender.DATABASE.layout=org.apache.log4j.PatternLayout
    log4j.appender.DATABASE.layout.ConversionPattern=[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n


    log4j.appender.A1=org.apache.log4j.DailyRollingFileAppender
    log4j.appender.A1.File=SampleMessages.log4j
    log4j.appender.A1.DatePattern=yyyyMMdd-HH'.log4j'
    log4j.appender.A1.layout=org.apache.log4j.xml.XMLLayout

    #自定义Appender

    log4j.appender.im = net.cybercorlin.util.logger.appender.IMAppender

    log4j.appender.im.host = mail.cybercorlin.net
    log4j.appender.im.username = username
    log4j.appender.im.password = password
    log4j.appender.im.recipient = corlin@cybercorlin.net

    log4j.appender.im.layout=org.apache.log4j.PatternLayout
    log4j.appender.im.layout.ConversionPattern =[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n

    September 08

    青岛之行

    虽然从青岛回来已经有几天了,但是自己还时不时的沉浸在那美丽的海边城市的生活中。
    在绿树如荫的八大处,一对对幸福的新婚夫妇拍着婚纱照,让羡慕不已。
    在海岸旁边的海军博物官,看着停靠在那里的101战舰(撒刘少奇骨灰的战舰),不仅设想起那混沌的时代。
    还有美丽且有特色的崂山,让人不解的天后宫,......
    都会让人留恋忘返。
    不过有点遗憾的是,早就听说青岛美女多,可惜亲身体验发现有点言过其实。
     

    技术文档

    form设置了enctype="multipart/form-data" 属性后的问题
    enctype="multipart/form-data"是上传二进制数据;
    form里面的input的值以2进制的方式传过去,所以request就得不到值了。
    文件传输和文本传输的编码不一样。
    要获取文本框的值可以使用专门的文件上传组件。
    不过Resin除外。
     
    用Tomcat实现容器内认证

    在数据库里新建两个表
    A、用户表
    create table users (user_name varchar(20) not null,
                                   user_pass varchar(20) not null,
                                   PRIMARY KEY (user_name)) ;
    B、用户与角色关联表
    create table user_roles (user_name varchar(20) not null,
                                            role_name varchar(20) not null,
                                            PRIMARY KEY (user_name)) ;
    C、插入数据
    insert into users values('user1', 'password');
    insert into user_roles values('user1', 'manager');

    在tomcat的server.xml里加入描述
          <Realm  className="org.apache.catalina.realm.JDBCRealm"
                  driverName="org.gjt.mm.mysql.Driver"
                  connectionURL="jdbc:mysql://localhost/databaseName"
                  connectionName=yourname connectionPassword=yourpassword
                  userTable="users" userNameCol="user_name" userCredCol="user_pass"
                  userRoleTable="user_roles" roleNameCol="role_name" />

    在自己应用程序的web.xml里加入描述(基于表单)
    <security-constraint>
      <web-resource-collection>
       <web-resource-name>My Test</web-resource-name>
       <url-pattern>/get.jsp</url-pattern>
      </web-resource-collection>
      <auth-constraint>
        <role-name>manager</role-name>    //能够访问的角色,可以多个
      </auth-constraint>
    </security-constraint>

    <login-config>
      <auth-method>FORM</auth-method>
        <realm-name>My Test</realm-name>
      <form-login-config>
      <form-login-page>/login.jsp</form-login-page>
      <form-error-page>/fail_login.html</form-error-page>  //认证失败后跳转的页面
      </form-login-config>
    </login-config>

    登录表单必须包含输入用户姓名和口令的字段,它们必须被分别命名为j_username和j_password,表单将这二个值发送给j_security_check逻辑名字。
      下面是一个该表单如何在HTML网页中实现的例子:
    <form method="POST" action="j_security_check">
      <input  type="text" name="j_username">
      <input  type="password" name="j_password">
        <input  type="submit" value="确定"/>
    </form>

    这样当对get.jsp进行访问时,tomcat就会自动转到login.jsp页面实现认证。对于简单的认证,小型系统,采用Tomcat实现容器内认证是方便的。在页面中调用request.getRemoteUser()可得到当前访问的用户名

    经常到的javaScript技术代码

    一。验证类
    数字验证类
    1.1 整数
    /^(-|\+)?\d+$/.test(str)
    1.2 大于0的整数 (用于传来的ID的验证)
    /^\d+$/.test(str)
    1.3 负整数的验证
    /^-\d+$/.test(str)
    2、时间类
    2.1 短时间,形如 (13:04:06)
    function isTime(str)
    {
    var a = str.match(/^(\d{1,2})(?(\d{1,2})\2(\d{1,2})$/);
    if (a == null) {alert('输入的参数不是时间格式'); return false;}
    if (a[1]>24 || a[3]>60 || a[4]>60)
    {
    alert("时间格式不对");
    return false
    }
    return true;
    }
    2.2 短日期,形如 (2003-12-05)
    function strDateTime(str)
    {
    var r = str.match(/^(\d{1,4})(-|\/)(\d{1,2})\2(\d{1,2})$/);
    if(r==null)return false;
    var d= new Date(r[1], r[3]-1, r[4]);
    return (d.getFullYear()==r[1]&&(d.getMonth()+1)==r[3]&&d.getDate()==r[4]);
    }
    2.3 长时间,形如 (2003-12-05 13:04:06)
    function strDateTime(str)
    {
    var reg = /^(\d{1,4})(-|\/)(\d{1,2})\2(\d{1,2}) (\d{1,2})\d{1,2})\d{1,2})$/;
    var r = str.match(reg);
    if(r==null)return false;
    var d= new Date(r[1], r[3]-1,r[4],r[5],r[6],r[7]);
    return (d.getFullYear()==r[1]&&(d.getMonth()+1)==r[3]&&d.getDate()==r[4]&&d.getHours()==r[5]&&d.getMinutes()==r[6]&&d.getSeconds()==r[7]);
    }
    2.4 只有年和月。形如(2003-05,或者2003-5)
    2.5 只有小时和分钟,形如(12:03)
    3、表单类
    3.1 所有的表单的值都不能为空

    3.2 多行文本框的值不能为空。
    3.3 多行文本框的值不能超过sMaxStrleng
    3.4 多行文本框的值不能少于sMixStrleng
    3.5 判断单选框是否选择。
    3.6 判断复选框是否选择.
    3.7 复选框的全选,多选,全不选,反选
    3.8 文件上传过程中判断文件类型
    4、字符类
    4.1 判断字符全部由a-Z或者是A-Z的字字母组成

    4.2 判断字符由字母和数字组成。

    4.3 判断字符由字母和数字,下划线,点号组成.且开头的只能是下划线和字母
    /^([a-zA-z_]{1})([\w]*)$/g.test(str)
    4.4 字符串替换函数.Replace();
    5、浏览器类
    5.1 判断浏览器的类型
    window.navigator.appName
    5.2 判断ie的版本
    window.navigator.appVersion
    5.3 判断客户端的分辨率
    window.screen.height; window.screen.width;

    6、结合类
    6.1 email的判断。
    function ismail(mail)
    {
    return(new RegExp(/^\w+((-\w+)|(\.\w+))*\-AT-[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/).test(mail));
    }
    6.2 手机号码的验证
    6.3 身份证的验证
    function isIdCardNo(num)
    {
    if (isNaN(num)) {alert("输入的不是数字!"); return false;}
    var len = num.length, re;
    if (len == 15)
    re = new RegExp(/^(\d{6})()?(\d{2})(\d{2})(\d{2})(\d{3})$/);
    else if (len == 18)
    re = new RegExp(/^(\d{6})()?(\d{4})(\d{2})(\d{2})(\d{3})(\d)$/);
    else {alert("输入的数字位数不对!"); return false;}
    var a = num.match(re);
    if (a != null)
    {
    if (len==15)
    {
    var D = new Date("19"+a[3]+"/"+a[4]+"/"+a[5]);
    var B = D.getYear()==a[3]&&(D.getMonth()+1)==a[4]&&D.getDate()==a[5];
    }
    else
    {
    var D = new Date(a[3]+"/"+a[4]+"/"+a[5]);
    var B = D.getFullYear()==a[3]&&(D.getMonth()+1)==a[4]&&D.getDate()==a[5];
    }
    if (!B) {alert("输入的身份证号 "+ a[0] +" 里出生日期不对!"); return false;}
    }
    return true;
    }
    -------------------------------------------------------------------------------------

    3.7 复选框的全选,多选,全不选,反选

    全选


    全选

    function checkAll(str)
    {
    var a = document.getElementsByName(str);
    var n = a.length;
    for (var i=0; ia[i].checked = window.event.srcElement.checked;
    }
    function checkItem(str)
    {
    var e = window.event.srcElement;
    var all = eval("document.hrong."+ str);
    if (e.checked)
    {
    var a = document.getElementsByName(e.name);
    all.checked = true;
    for (var i=0; i{
    if (!a[i].checked){ all.checked = false; break;}
    }
    }
    else all.checked = false;
    }


    3.8 文件上传过程中判断文件类型


    -------------------------------------------------------------------------------------
    1.身份证严格验证:

    2.验证IP地址

    function isip(s){
    var check=function(v){try{return (v<=255 && v>=0)}catch(x){return false}};
    var re=s.split(".")
    return (re.length==4)?(check(re[0]) && check(re[1]) && check(re[2]) && check(re[3])):false
    }

    var s="202.197.78.129";
    alert(isip(s))

    3.加sp1后还能用的无边框窗口!!


    /*--- Special Thanks For andot ---*/

    /*
    This following code are designed and writen by Windy_sk
    You can use it freely, but u must held all the copyright items!
    /

    /- Thanks For andot Again ---*/

    var CW_width = 400;
    var CW_height = 300;
    var CW_top = 100;
    var CW_left = 100;
    var CW_url = "/";
    var New_CW = window.createPopup();
    var CW_Body = New_CW.document.body;
    var content = "";
    var CSStext = "margin:1px;color:black; border:2px outset;border-style:expression(onmouseout=onmouseup=function(){this.style.borderStyle='outset'}, onmousedown=function(){if(event.button!=2)this.style.borderStyle='inset'});background-color:buttonface;width:16px;height:14px;font-size:12px;line-height:11px;cursor:Default;";

    //Build Window
    include.startDownload(CW_url, function(source){content=source});

    function insert_content(){
    var temp = "";
    CW_Body.style.overflow = "hidden";
    CW_Body.style.backgroundColor = "white";
    CW_Body.style.border = "solid black 1px";
    content = content.replace(/]*)>/g,"");
    temp += "";
    temp += "";
    temp += "Chromeless Window For IE6 SP1";
    temp += "";
    temp += "?";
    temp += "0";
    temp += "1";
    temp += "x";
    temp += "";
    temp += "";
    temp += content;
    temp += "

    ";
    temp += "";
    CW_Body.innerHTML = temp;
    }

    setTimeout("insert_content()",1000);

    var if_max = true;
    function show_CW(){
    window.moveTo(10000, 10000);
    if(if_max){
    New_CW.show(CW_top, CW_left, CW_width, CW_height);
    if(typeof(New_CW.document.all.include)!="undefined"){
    New_CW.document.all.include.style.width = CW_width;
    New_CW.document.all.Max.innerText = "1";
    }

    }else{
    New_CW.show(0, 0, screen.width, screen.height);
    New_CW.document.all.include.style.width = screen.width;
    }
    }

    window.onfocus = show_CW;
    window.onresize = show_CW;

    // Move Window
    var drag_x,drag_y,draging=false

    function drag_move(e){
    if (draging){
    New_CW.show(e.screenX-drag_x, e.screenY-drag_y, CW_width, CW_height);
    return false;
    }
    }

    function drag_down(e){
    if(e.button==2)return;
    if(New_CW.document.body.offsetWidth==screen.width && New_CW.document.body.offsetHeight==screen.height)return;
    drag_x=e.clientX;
    drag_y=e.clientY;
    draging=true;
    e.srcElement.setCapture();
    }

    function drag_up(e){
    draging=false;
    e.srcElement.releaseCapture();
    if(New_CW.document.body.offsetWidth==screen.width && New_CW.document.body.offsetHeight==screen.height) return;
    CW_top = e.screenX-drag_x;
    CW_left = e.screenY-drag_y;
    }


     
    电话号码的验证

    要求:
      (1)电话号码由数字、"("、")"和"-"构成
      (2)电话号码为3到8位
      (3)如果电话号码中包含有区号,那么区号为三位或四位
      (4)区号用"("、")"或"-"和其他部分隔开
      (5)移动电话号码为11或12位,如果为12位,那么第一位为0
      (6)11位移动电话号码的第一位和第二位为"13"
      (7)12位移动电话号码的第二位和第三位为"13"
      根据这几条规则,可以与出以下正则表达式:
      (^[0-9]{3,4}\-[0-9]{3,8}$)|(^[0-9]{3,8}$)|(^\([0-9]{3,4}\)[0-9]{3,8}$)|(^0{0,1}13[0-9]{9}$)

    function PhoneCheck(s) {
    var str=s;
    var reg=/(^[0-9]{3,4}\-[0-9]{3,8}$)|(^[0-9]{3,8}$)|(^\([0-9]{3,4}\)[0-9]{3,8}$)|(^0{0,1}13[0-9]{9}$)/
    alert(reg.test(str));
    }

    --------------------------------------------------------------------------------------


    ---------------------------------------------------------------------------------

    //检验法人代码
    function isCorporationCode(s){
    var patrn=/^(\d){15}$/;
    if (!patrn.exec(s)) return false
    return true
    }

    //校验登录名:只能输入5-20个以字母开头、可带数字、“_”、“.”的字串
    function isRegisterUserName(s)
    {
    var patrn=/^[a-zA-Z]{1}([a-zA-Z0-9._]){4,19}$/;
    if (!patrn.exec(s)) return false
    return true
    }
    //校验用户姓名:只能输入1-30个以字母开头的字串
    function isTrueName(s)
    {
    var patrn=/^[^`~!@#$%^&*()+-=|\\\[\]\{\}:;\'\,.<>/? 0-9]{2,19}$/;
    if (!patrn.exec(s)) return false
    return true
    }

    //校验密码:只能输入6-15个字母、数字
    function isPasswd(s)
    {
    var patrn=/^[a-zA-Z0-9]{6,15}$/;
    if (!patrn.exec(s)) return false
    return true
    }

    //检验体重
    function isAvoirdupois(s)
    {
    var patrn=/^[1-9]{1}[0-9]{0,2}$/;
    var patrn2=/^[1-9]{1}$/;
    var intPart=s.indexOf('.');
    var decPart=s.lastIndexOf('.');
    if (intPart==-1 && patrn.exec(s) && eval(s)>=2 && eval(s)<=200) return true
    if (intPart!=decPart || intPart==0 || intPart+2!=s.length) return false
    if (!patrn.exec(s.substring(0,intPart)) || !patrn2.exec(s.substring(decPart+1))) return false
    if (eval(s)<2 || eval(s)>200) return false
    return true
    }

    //校验普通电话、传真号码:可以“+”开头,除数字外,可含有“-”
    function isTel(s)
    {
    var patrn=/^[+]{0,1}(\d){1,3}[ ]?([-]?((\d)|[ ]){1,12})+$/;
    if (!patrn.exec(s)) return false
    return true
    }

    //校验手机号码:必须以数字开头,除数字外,可含有“-”
    function isMobile(s)
    {
    var patrn=/^[+]{0,1}(\d){1,3}[ ]?([-]?((\d)|[ ]){1,12})+$/;
    if (!patrn.exec(s)) return false
    return true
    }

    //校验地区代码
    function isAreaCode(s)
    {
    var patrn=/^(\d){6}$/;
    if (!patrn.exec(s)) return false
    return true
    }

    //校验邮政编码
    function isPostalCode(s)
    {
    var patrn=/^[1-9]{1}(\d){5}$/;
    if (!patrn.exec(s)) return false
    return true
    }

    //校验搜索关键字
    function isSearch(s)
    {
    var patrn=/^[^`~!@#$%^&*()+=|\\\[\]\{\}:;\'\,.<>/? ]{1}[^`~!@$%^&()+=|\\\[\]\{\}:;\'\,.<>?]{0,19}$/;
    if (!patrn.exec(s)) return false
    return true
    }

    //校验是否为ip地址
    function isIP(s)
    {
    var patrn=/^[0-9.]{1,20}$/;
    if (!patrn.exec(s)) return false
    return true
    }

    //检验页码是否正确
    function isPage(s)
    {
    var patrn=/^[1-9]{1}[0-9]{0,2}$/;
    if (!patrn.exec(s)) return false
    return true
    }

    //表单输入值错误提示
    function vErr(o,s){
    alert(s);
    if (o) o.focus();
    return false;
    }

    //判断单选
    function chkRadio(o){
    for (i=0;iif (o[i].checked) return true;
    }
    return false;
    }
    //====================================表单类================================
    // 文件上传过程中判断文件类型
    /*

    /

    //表单的值不能为空
    function isNull(elem){
    //var pattern=/^\s+|\s+$/;
    if(elem.replace(/(^\s+|\s$)/g, "")==""){
    return false;
    }else{
    return true;
    }
    }
    //不能超过imax字符
    function imax(elem){
    if(elem.length>imax){
    return false;
    }else{
    return true;
    }
    }
    //不能少于imix字符
    function imix(elem){
    if(elem.lengthreturn false;
    }else{
    return true;
    }
    }
    //输入为中文判断
    function isChinese(elem){
    var pattern=/[^\x00-\xff]/g;
    if(pattern.test(elem)){
    //包含中文
    return false;
    }else{
    //不包含中文
    return true;
    }
    }

    / *********************************************************************************************************************

    .cMenu {
    FILTER: alpha(opacity=0);BACKGROUND-COLOR: #D6D3CE;BORDER-BOTTOM: #666666 2px solid; BORDER-LEFT: #E4E4E4 2px solid; BORDER-RIGHT: #666666 2px solid; BORDER-TOP: #E4E4E4 2px solid; COLOR: #000000; CURSOR: default; FONT-SIZE: 9pt; color:#000000;FONT-WEIGHT: normal; LINE-HEIGHT: 20px; POSITION: absolute; VISIBILITY: hidden; WIDTH: 110px
    }
    .menuitems {
    font-size:9pt;
    MARGIN: 2px;
    PADDING-BOTTOM: 0px;
    PADDING-LEFT: 15px;
    PADDING-RIGHT: 3px;
    PADDING-TOP: 0px;
    }

    <!--[if IE]>
    onmouseover=highlightie5()>

    电话号码的验证

    要求:
      (1)电话号码由数字、"("、")"和"-"构成
      (2)电话号码为3到8位
      (3)如果电话号码中包含有区号,那么区号为三位或四位
      (4)区号用"("、")"或"-"和其他部分隔开
      (5)移动电话号码为11或12位,如果为12位,那么第一位为0
      (6)11位移动电话号码的第一位和第二位为"13"
      (7)12位移动电话号码的第二位和第三位为"13"
      根据这几条规则,可以与出以下正则表达式:
      (^[0-9]{3,4}\-[0-9]{3,8}$)|(^[0-9]{3,8}$)|(^\([0-9]{3,4}\)[0-9]{3,8}$)|(^0{0,1}13[0-9]{9}$)


    <script language="javascript">
    function PhoneCheck(s) {
    var str=s;
    var reg=/(^[0-9]{3,4}\-[0-9]{3,8}$)|(^[0-9]{3,8}$)|(^\([0-9]{3,4}\)[0-9]{3,8}$)|(^0{0,1}13[0-9]{9}$)/
    alert(reg.test(str));
    }
    </script>
    <input type=text name="iphone">
    <input type=button onclick="PhoneCheck(document.all.iphone.value)" value="Check">
    ----------------------------------------------------------------------------------------

    <%
    '********************************************
    '函数功能:正则表达式校验
    '参数patrn:要校验的类型
    ' "User" - 用户名
    ' "Truename" - 英文姓名
    ' "Passwd" - 密码
    ' "Tel" - 电话/传真
    ' "Mobil" - 手机
    ' "Date" - 日期(格式:yyyy-mm-dd)
    ' "Email" - 电子邮件
    ' "Postalcode" - 邮政编码
    ' "Search" - 搜索关键字
    ' "Int" - 整数
    '参数strng:要校验的字串
    '返回值:校验结果,正确返回true,错误返回false
    '********************************************
    Function IsVerify(patrn,strng)
    strng=Trim(strng)
    Select Case patrn
    Case "User" '用户名
    patrn="^[a-z]{1}([a-z0-9]|[._]){2,19}$"
    Case "Truename" '英文姓名
    patrn="^[a-zA-Z]{1,30}$"
    Case "Passwd" '密码
    patrn="^(\w){6,20}$"
    Case "Tel" '电话/传真
    patrn="^[+]{0,1}(\d){1,3}[ ]?([-]?((\d)|[ ]){1,12})+$"
    Case "Mobil" '手机
    patrn="^(\d)+[-]?(\d){6,12}$"
    Case "Date" '日期(格式:yyyy-mm-dd)
    patrn="^[12]{1}(\d){3}[-][01]?(\d){1}[-][0123]?(\d){1}$"
    Case "Email" '电子邮件
    patrn="^((\w)|[-]|[.])+@(((\w)|[-])+[.])+[a-z]{2,4}$"
    Case "Postalcode" '邮政编码
    patrn="^[a-z0-9 ]{3,12}$"
    Case "Search" '搜索关键字
    patrn="^[^`~!@#$%^&*()+=|\\\[\]\{\}:;\'\,.<>/?]{1}[^`~!@$%^&()+=|\\\[\]\{\}:;\'\,.<>?]{0,19}$"
    Case "Int" '整数
    patrn="^[1-9]{1}[0-9]{0,6}$"
    Case "Array"
    patrn="^[0-9]{1}([0-9]|[\,]){0,150}$"
    End Select
    Dim regEx
    Dim Match
    Set regEx = New RegExp
    regEx.Pattern = patrn
    regEx.IgnoreCase = True
    regEx.Global = True
    Matches = regEx.test(strng)
    IsVerify = Matches
    Set regEx = Nothing
    End Function

    '********************************************
    '函数功能:对输入框的特殊字串进行过滤保存
    '参数strPass:过滤前的字符串
    '返回值:过滤后的字符串
    '********************************************
    Function SQLencode(byVal strPass)
    strPass = Replace(strPass, "&", "&")
    strPass = Replace(strPass, "<", "<")
    strPass = Replace(strPass, ">", ">")
    strPass = Replace(strPass, """", """)
    strPass = Replace(strPass, "'", "'")
    strPass = Replace(strPass, " ", " ")
    strPass = Replace(strPass,chr(13)&chr(10),"<br/>")
    SQLencode = strPass
    End Function

    '********************************************
    '函数功能:对SQLencode函数过滤后的字符串进行还原
    '参数strPass:经过过滤后的字符串
    '返回值:还原的过滤前的字符串
    '********************************************
    Function SQLdecode(byVal strPass)
    If Not isNull(strPass) Then
    strPass = Replace(strPass, "<", "<")
    strPass = Replace(strPass, ">", ">")
    strPass = Replace(strPass, ""","""" )
    strPass = Replace(strPass, "'","'" )
    strPass = Replace(strPass,"<br/>",chr(13)&chr(10))
    End If
    SQLdecode = strPass
    End Function

    '********************************************
    '函数功能:生成随机密码(字符为数字与大小写字母集合)
    '参数length:密码长度
    '返回值:随机密码
    '********************************************
    Function random(length)
    Dim n,str
    rnds=""
    Randomize
    For i=1 To length
    n=Int(75*Rnd+48)
    If (n>57 and n<65) Or (n>90 and n<97) Then
    i=i-1
    Else
    rnds=rnds&Chr(n)
    End If
    Next
    random=rnds
    End Function

    '********************************************
    '函数功能:对密码进行加密/解密(最大长度为15位)
    '********************************************
    Function Encrypt(preString)
    Dim pwds,s
    pwds=""
    s=Left(preString,15)
    For i=1 To Len(s)
    seed=170-Asc(Mid(s,i,1))-i
    pwds=pwds & Chr(seed)
    Next
    Encrypt=pwds
    End Function

    '********************************************
    '函数功能:格式化当前时间字串
    '参数:无
    '返回值:返回当前时间的纯数字字符串方式的表示(例如:当前时间2001-10-3 2:34:6,返回字符串"20011003023406")
    '********************************************
    Function TheDate()
    y=year(date())
    m=month(date())
    d=day(date())
    h=Hour(time())
    n=Minute(time())
    s=Second(time())
    If Len(m)=1 Then m="0"&m
    If Len(d)=1 Then d="0"&d
    If Len(h)=1 Then h="0"&h
    If Len(n)=1 Then n="0"&n
    If Len(s)=1 Then s="0"&s
    thedate=y&m&d&h&n&s
    End Function

    '********************************************
    '函数功能:返回字符串的长度
    '参数:字符串
    '返回值:字符串长度
    '********************************************
    Function strLen(str)
    dim p_len
    p_len=0
    strlen=0
    if trim(str)<>"" then
    p_len=len(trim(str))
    for xx=1 to p_len
    if asc(mid(str,xx,1))<0 then
    strlen=int(strlen) + 2
    else
    strlen=int(strlen) + 1
    end if
    next
    end if
    End Function

    '********************************************
    '函数功能:发送邮件通知
    '参数:字符串
    '返回值:成功/失败
    '********************************************
    Function SendMail(ToAddress, Subject, Body)
    On Error Resume Next
    Set objMail = Server.CreateObject("JMail.Message")
    objMail.From = ADR_Email
    objMail.FromName = "ADR管理中心"
    objMail.Subject = Subject
    objMail.AddRecipient ToAddress
    objMail.Body = Body
    If objMail.Send("") Then
    SendMail = True
    Else
    SendMail = False
    End If
    If Err.Number<> 0 Then SendMail = False
    Set objMail = Nothing
    On Error Goto 0
    End Function
    %>
    -----------------------------------------------------------------------------------

    //检验法人代码
    function isCorporationCode(s){
    var patrn=/^(\d){15}$/;
    if (!patrn.exec(s)) return false
    return true
    }

    //校验登录名:只能输入5-20个以字母开头、可带数字、“_”、“.”的字串
    function isRegisterUserName(s)
    {
    var patrn=/^[a-zA-Z]{1}([a-zA-Z0-9._]){4,19}$/;
    if (!patrn.exec(s)) return false
    return true
    }
    //校验用户姓名:只能输入1-30个以字母开头的字串
    function isTrueName(s)
    {
    var patrn=/^[^`~!@#$%^&*()+-=|\\\[\]\{\}:;\'\,.<>/? 0-9]{2,19}$/;
    if (!patrn.exec(s)) return false
    return true
    }

    //校验密码:只能输入6-15个字母、数字
    function isPasswd(s)
    {
    var patrn=/^[a-zA-Z0-9]{6,15}$/;
    if (!patrn.exec(s)) return false
    return true
    }

    //检验体重
    function isAvoirdupois(s)
    {
    var patrn=/^[1-9]{1}[0-9]{0,2}$/;
    var patrn2=/^[1-9]{1}$/;
    var intPart=s.indexOf('.');
    var decPart=s.lastIndexOf('.');
    if (intPart==-1 && patrn.exec(s) && eval(s)>=2 && eval(s)<=200) return true
    if (intPart!=decPart || intPart==0 || intPart+2!=s.length) return false
    if (!patrn.exec(s.substring(0,intPart)) || !patrn2.exec(s.substring(decPart+1))) return false
    if (eval(s)<2 || eval(s)>200) return false
    return true
    }

    //校验普通电话、传真号码:可以“+”开头,除数字外,可含有“-”
    function isTel(s)
    {
    var patrn=/^[+]{0,1}(\d){1,3}[ ]?([-]?((\d)|[ ]){1,12})+$/;
    if (!patrn.exec(s)) return false
    return true
    }

    //校验手机号码:必须以数字开头,除数字外,可含有“-”
    function isMobile(s)
    {
    var patrn=/^[+]{0,1}(\d){1,3}[ ]?([-]?((\d)|[ ]){1,12})+$/;
    if (!patrn.exec(s)) return false
    return true
    }

    //校验地区代码
    function isAreaCode(s)
    {
    var patrn=/^(\d){6}$/;
    if (!patrn.exec(s)) return false
    return true
    }

    //校验邮政编码
    function isPostalCode(s)
    {
    var patrn=/^[1-9]{1}(\d){5}$/;
    if (!patrn.exec(s)) return false
    return true
    }

    //校验搜索关键字
    function isSearch(s)
    {
    var patrn=/^[^`~!@#$%^&*()+=|\\\[\]\{\}:;\'\,.<>/? ]{1}[^`~!@$%^&()+=|\\\[\]\{\}:;\'\,.<>?]{0,19}$/;
    if (!patrn.exec(s)) return false
    return true
    }

    //校验是否为ip地址
    function isIP(s)
    {
    var patrn=/^[0-9.]{1,20}$/;
    if (!patrn.exec(s)) return false
    return true
    }

    //检验页码是否正确
    function isPage(s)
    {
    var patrn=/^[1-9]{1}[0-9]{0,2}$/;
    if (!patrn.exec(s)) return false
    return true
    }

    //表单输入值错误提示
    function vErr(o,s){
    alert(s);
    if (o) o.focus();
    return false;
    }

    //判断单选
    function chkRadio(o){
    for (i=0;i<o.length;i++){
    if (o[i].checked) return true;
    }
    return false;
    }

    为tomcat页面设置访问权限

    在web应用中,对页面的访问控制通常通过程序来控制,流程为:
    登录 -> 设置session -> 访问受限页面时检查session是否存在,如果不存在,禁止访问
    对于较小型的web应用,可以通过tomcat内置的访问控制机制来实现权限控制。采用这种机制的好处是,程序中无需进行权限控制,完全通过对tomcat的配置即可完成访问控制。
    为了在tomcat页面设置访问权限控制,在项目的WEB-INFO/web.xml文件中,进行如下设置:

    <web-app>

    <!--servlet等其他配置-->

    <security-constraint>

    <web-resource-collection>

    <display-name>Example Security Constraint</display-name>

    <web-resource-name>My Test</web-resource-name>

    <url-pattern>/ddly/admin/*</url-pattern> </web-resource-collection>

    <auth-constraint> <role-name>role1</role-name>

    <role-name>tomcat</role-name>

     </auth-constraint> </security-constraint> <login-config>

    <auth-method>BASIC</auth-method>

    <realm-name>My Test</realm-name> </login-config>

    </web-app>

     

    其中,<url-pattern>中指定受限的url,可以使用通配符*,通常对整个目录进行访问权限控制。
    <auth-constraint>中指定哪些角色可以访问<url-pattern>指定的url,在<role-name>中可以设置一个或多个角色名。
    使用的角色名来自tomcat的配置文件${CATALINA_HOME}/conf/tomcat-users.xml。
    <login-config>中设置登录方式,<auth-method>的取值为BASIC或FORM。如果为BASIC,浏览器在需要登录时弹出一个登录窗口。如果为FORM方式,需要指定登录页面和登录失败时的提示信息显示页面。
    使用FORM方式的配置样例如下:

    <login-config>

    <auth-method>FORM</auth-method>

    <realm-name>Example Form-Based Authentication Area</realm-name>

     <form-login-config>

    <form-login-page>/login.jsp</form-login-page>

    <form-error-page>/error.jsp</form-error-page>

     </form-login-config>

    </login-config>

     

    其中的<form-login-page>指定登录页面url,<form-error-page>指定登录失败时的提示页面url。
    登录页面中,form的action,以及其中的用户名和密码两个参数的名称,都应取固定的值。登录的后台处理程序为j_security_check;用户名和密码的参数名称分别为:j_username和j_password。
    如下是登录页面(如:login.jsp)的一段示例代码:

    <form method="POST" action='<%= response.encodeURL("j_security_check") %>' >

    <table border="0" cellspacing="5">

    <tr>

    <th align="right">Username:</th>

    <td align="left"><input type="text" name="j_username"></td>

    </tr>

    <tr>

    <th align="right">Password:</th>

    <td align="left"><input type="password" name="j_password"></td>

    </tr>

    <tr>

    <td align="right"><input type="submit" value="Log In"></td>

    <td align="left"><input type="reset"></td>

     </tr>

    </table>

    </form>

    表现层框架Struts/Tapestry/JSF架构比较

    Struts/Tapestry/JSF是目前J2EE表现层新老组合的框架技术。从诞生时间上看,Struts应该比较早,使用得非常广泛,Tapestry 3.0逐渐引起广泛的重视,正当Tapestry即将大显身手时期,SUN推出JSF标准技术,虽然JSF一开始推出尚不成熟,留出了一段空白期,但是随着JSF1.1标准推出,JSF开始正面出击,粉面隆重登场了。

      其实,JSF和Tapestry也并不是那种头碰头的相同竞争性技术,两者还是各有侧重点的,不过比较细微,但是这种细微点在实现一个大工程时可能带来不同的感受和变化。

      首先,我们从一个高度来抽象一下表现层框架应有的技术架构,下图可以说所有表现层框架技术都必须实现的功能架构图:

     

      当然,我们不必废话罗嗦MVC模式,MVC模式是基准模式,现在框架技术已经不必再拼是否是MVC模式了。 在上图MVC模式基础上,一个表现层框架无外乎要实现图中的三个功能:

    1.在当前页面能够显示一个组件对象的内容;而不是象纯JSP那样,需要在Jsp页面写入“调用对象方法”的Java代码。

    2.当用户按下页面的提交按扭或链接后,事件发生,这时应该触发服务器端并将当前页面的参数提交给服务器。这种机制表现在Form表单提交和有参数的链接<a href=""></a>

    3.从一个页面视图直接跳转到另外一个页面视图,单纯的导航作用。

    我们通过下表来比较这 三种框架在实现上图各个功能时技术细节,从而得出他们的异同点和偏重点。

      Struts Tapestry3.0 JSF
    在View显示的组件要求

    组件必须继承ActionForm

    分显式调用和隐式调用
    组件必须继承BaseComponent
    普通POJO
    无需继承
    Managed Bean
    组件在View显示粒度 View页面只能显示与表单对应的ActionForm,配置中Action ActionForm 页面一般只能1:1:1关系。 可将组件嵌入页面任何一行,对使用组件数量无限制。 同Tapestry
    页面分区tiles 使用Tiles标签库实现,需要另外tiles-def.xml配置文件 组件有自己的视图页面,通过调用组件即直接实现多个页面组合。强大自然的页面组合是其特点。 通过组件+标签库实现Subview,但如需重用Layout,还要结合Tiles.
    页面跳转 使用标签库html:link中写明目标URL,URL名称需要对照配置文件的path命名,与组件Action耦合。 URL名称是目标的组件名称,不涉及URL和路径等操作,方便稳固。 类似Struts,也需要在配置文件中查找,与组件分离。
    参数传递 使用html:link时传递参数超过一个以上处理麻烦。 直接调用组件,直接赋予参数,没有参数个数限制 参数分离传递给组件
    事件触发 通过表单提交submit激活,不能细化到表单里字段。 能够给于表单每个字段贴一个事件,事件组件必须实现PageListener接口 同Tapestry,事件组件必须实习ActionListener 接口

    Struts组件编程模型

      Struts实现组件编程时有一些复杂:经常为一个页面中需要引入多个组件而头疼,因为Struts中无法直接引入多个组件,必须绕一些圈子:

      一般分两种情况:如果同一个Action就可以对付这些组件,那么在这种情况下有两个办法:

    1.将这多个组件装入一个ActionForm中,如使用MapForm等机制;

    2.手工将多个组件装入request/session等scope中,然后根据其名称在jsp中获得。

      这两个方法都有缺点: 第一种办法经常一个ActionForm弄得面目全非,变成一个大杂烩,违反了OO分派封装的原则;第2种办法其实又回到jsp编程;

      第二种情况,如果这些组件必须有预先由不同的Action来处理,每个组件必须经过Action -->ActionForm流程,在这种情况下有两种办法:

    1.使用Tiles, 不同流程输出到同一个页面的不同区域。是一种并行处理方式。

    2. 对多个流程首尾相连,第一Action forward结果是第二个Action,最后输出一个Jsp,在这个jsp中就可以使用前面多个流程的多个ActionForm了,这属于串行方式。

    Struts组件模型缺点

      Struts组件编程必须限定在Action/ActionForm/JSP这三个框框中做文章,难度相对比较大,而Tapestry/JSF则没有太多这些技术框框限制,两者在组件编程方面更让编程者自由一些,方便一些,这也是组件型框架的优势吧。

    Struts标签库

      在Struts中,经常需要使用标签库来显示组件ActionForm中内容,这就涉及到一个结合的问题,标签库是别人写的,参考Struts的标签库用法,而组件是自己的,难度和麻烦就体现在这个结合点上。

      JSF基本思路和Struts差不多,只不过换了不同标签库,也需要标签库+组件的结合思考,不过因为组件这里是通用组件,没有什么限制,所以这样比Struts要轻松一些。

      Tapestry使用了组件库概念替代了标签库,没有标签库概念,这样就没有标签库和自己的组件需要结合的问题,都是组件的使用,组件中分Tapestry标准组件和自己定义的组件,这也是接触了Jsp体系的人学习Tapestry面临的一个思路转换。

      具体以页面跳转为例子,页面跳转是靠链接<a href="目标"></a> 实现,链接是页面经常使用的元素。

      Struts提供的html:link在频繁使用就特别不方便,尤其在传递多个参数时:其中html:link的page值,是跳转对方页面或Action的path,这个path一般需要到struts-config.xml查找Action的相应path,一旦配置文件path值修改,涉及到这个所有相关页面都要修改。

      JSF将链接概念划分两个方面:导航性质和事件激活,在导航方面还是需要到配置faces-config查询Navigation的from-outcome的值。

      由于Tapestry没有标签库概念,只有组件或页面两个概念,因此,链接跳转目标要么是组件,要么是页面,简洁简单,它没有多余的path概念,就是组件名,也就是对象名称,组件名称和path名称合二为一。

    总结

      JSF在很大程度上类似Struts,而不是类似Tapestry,可以说是一种Struts 2.0,都是采取标签库+组件的形式,只是JSF的组件概念没有象Struts那样必须继承ActionForm的限制;JSF在事件粒度上要细腻,不象Struts那样,一个表单一个事件,JSF可以细化到表单中的每个字段上。

      JSF只有在组件和事件机制这个概念上类似Tapestry,但是不似Tapestry那样是一个完全组件的框架,所以,如果你做一个对页面要求灵活度相当高的系统,选用Tapestry是第一考虑。

      Struts/JSF则适合在一般的数据页面录入的系统中,对于Struts和JSF的选用,我目前个人观点是:如果你是一个新的系统,可以直接从JSF开始;如果你已经使用Struts,不必转换,如果需要切换,可以将JSF和Tapestry一起考虑。

      另外,JSF/Tapestry不只是支持Html,也支持多种客户端语言如WML或XUI等。

      这三者之间关系:如果说Struts是左派;那Tapestry则是右派;而JSF则是中间派,中庸主义是SUN联盟的一贯策略。

    July 20

    设计模式的阐释

    当前,解决软件开发的效率和质量的问题,复用是重要途径。人们逐渐由原来的代码拷贝粘贴式的复用,转到了基于软构件的复用,也产生了基于构件软件开发CBSD和基于构件软件工程CBSE等概念和研究。尽管如此,人们的复用层次仍停留在代码实现层次。
    模式的复用,包括体系结构模式和设计模式的复用,将复用的层次提高到分析和设计层。把复用的重点放在抽象层次上,这将从根本上解决软件开发中的问题。将人们的成功设计经验不断形成模式,并通过建立模式库将这些模式分类和描述,为以后的软件开发设计提供指导。

    在浏览了很多讨论设计模式和应用框架的论坛后,发现人们在该类版块主要讨论的东西有:
    1、讨论某一具体设计模式。
    某人刚学习设计模式不久,然后发布自己对某模式的理解,然后发贴请教高手如何应用该模式。
    2、讨论应用框架。
    人们热衷与比较各类应用框架,MVC,如讨论Spring, Struts等。比较他们的有缺点,互有支持者。
    3、讨论应用框架使用中遇到的问题。
    讨论这些框架的具体应用。请教如何解决在应用这些框架时遇到的问题。

    人们讨论的问题都比较实际,主要目的还是为了解决实际的日常工作中的项目的问题,这很自然和可以理解。我想即使是设计模式、体系结构、应用框架的讨论社区,能常光顾的还是做编码这一层的人居多做系统分析和架构设计的人少,所以不难理解人们讨论的是以实际编码实际应用为主,而讨论模式采掘、体系结构设计和应用框架开发的几乎没有。

     

    在进行下一步讨论之前,需要来点相关定义、概念和理论。
    1、模式的定义:
    什么是模式?应该没有一个很精确的定义,但我们可以通俗的来说。模式是特定的‘语境’中重复出现的设计‘问题’的‘解决方案’。模式是人们经验的积累,人们利用经验来指导新的软件的设计。
    2、模式的分类:
    由模式用于解决问题的规模和它的抽象程度高低来划分,由高至低可分为体系结构模式(architectural pattern)、设计模式(design pattern)和惯用法(idiom)。
    我们通常讨论的都是设计模式。体系结构模式很容易理解,即如何设计一个应用系统范围的结构特性。在有了整体架构后,就需要用设计模式来解决一些子系统的模块的细化的设计问题。可能大家唯独对惯用法模式比较陌生。体系结构模式和设计模式都是语言无关的,独立与具体语言,抽象度高。而惯用法则是针对某一种具体编程语言的底层模式。描述如何使用给定语言的特性来解决问题。比如在C++中如何编程解决垃圾回收的问题,而显然Java中就不用考虑这个问题。这就是惯用法。代码层次的模式,代码层次的编程经验。
    3、模式语言:
    通过体系结构模式的指导确定软件的体系结构。设计模式呢则解决某一具体问题的设计问题,如工厂模式,单例模式等解决对象创建问题。由体系结构模式到设计模式,这个跨度十分大,模式语言正好解决这个问题。
    模式语言是一组相关模式的组合。通常一些设计模式单独使用意义不大,而通常是一组模式结合着使用的。例如一个分布式J2EE应用的业务层通过值对象来进行数据传递(值对象模式),通过一个代理来给其它层提供服务(代理模式),通过封装复杂的调用提供一个简单统一的调用(门面模式),而在该应用的显示层则采用(装饰器模式)等等模式。这样,这一能解决某一问题的整套模式的集合,就是一个模式语言。它解决问题的粒度更大。
    4、模式库(pattern repository):
    就是存放模式的一个仓库,呵呵。是一个存放人们成功设计经验的仓库。将各种模式分类、描述和存储,提供方便的检索和浏览方式,让人们学习模式,学习模式的使用。
    5、模式的采掘:
    模式既然是人们的经验,显然我们不能够发明模式,而只能够从已进行的设计中总结成功的经验,发现模式。这个过程,就是模式采掘。

    模式的层次

    1、讨论如何更好的描述设计模式,讨论模式之间的关系,如何分类和组织模式,以让后来者系统的学习。
    2、讨论模式语言,用多个模式的组合解决更大更复杂的问题。如何描述和分类组织模式语言。
    3、讨论软件体系结构模式,提出某一应用领域的软件的体系结构的设计,并讨论如何描述和如何应用之。
    4、讨论如何采掘模式,发现新的模式。在已开发的多个系统中采掘成功的设计经验,然后描述并提出。
    5、讨论如何构建一个模式库。这个模式库应具有的功能,她如何分类描述各种模式,如何描述模式间的关系。

    以上共有五点,其实,最终可归纳为一点,即第五点,前四点都是为第五点而服务的。模式库的研究也就是研究如何对设计模式,模式语言,体系结构模式的分类和描述,如何采掘新模式等等。本人想建立一个体系结构模式库和设计模式模式库,收集、分类、组织和索引模式。或让新手学习过来人的成功经验,或基于这些模式以及结合构件库系统形成半自动的大规模的基于复用开发的软件生产线。

    我们先讨论一下模式分类吧。现在我知道的有如下一些分类方法:
    1、按规模和抽象程度划分:
    体系结构模式、设计模式、惯用法。
    2、按解决的问题划分:
    例如分布式系统、交互式系统、访问控制、通信、资源处理等问题。
    3、1+2,模式类别+问题类别,形成一个二维的模式分类图式。

    随着模式库中模式的不断增多,理解和使用模式库就越困难。一个好的模式分类方法,有助于人们更好的理解模式。好的模式分类图式需具有如下一些特性:
    +简单、易学,而不是复杂、难使用;
    +少而精的分类标准;
    +每个分类标准反应模式的自然属性;
    +根据该分类能让用户找到一组可能适用的模式,而不是找到唯一适用模式;
    +应对新模式的集成开发,而不需要对现有分类进行重构。

     



     

    从重构的角度学习bridge设计模式

    从重构的角度学习bridge设计模式

    Bridge模式是一个在实际系统中经常应用的模式。它最能体现设计模式的原则
    针对接口进行编程,和使用聚合不使用继承这两个原则。


    由于我们过分的使用继承,使类的结构过于复杂,不易理解,难以维护。特别
    是在Java中由于不能同时继承多个类,这样就会造成多层继承,维护更难。
    Bridge模式是解决多层继承的根本原因。如果你在实现应用中一个类,需要继承
    两个以上的类,并且这两者之间又持有某种关系,它们两个都会有多种变化。
    Bridge模式是把这两个类,分解为一个抽象,一个实现,使它们两个分离,这样
    两种类可以独立的变化。
    抽象就是,把一个实体的共同概念(相同的步骤),抽取出来(分解出几个相互独立的步骤),
    作为一个过程。如我们把数据库的 操作抽象为一个过程,有几个步骤,创建SQL语句,
    发送到数据库处理,取得结果。

    实现就是怎样完成这个抽象步骤,如发送到数据库,需要结合具体的数据库,考虑怎样完成这个步骤等。
    并且同一步骤可能存在不同的实现,如对不同的数据库需要不同的实现。

    现在我们假设一个情况,也是WEB中经常遇到的,在一个page有输入框,
    如客户信息的姓名,地址等,输入信息后,然后按查找按钮,把查找的结果显示出来。
    我们现在假设查找客户信息和帐户信息,它们在不同的表中。
    但是我们的系统面对两种人群,总部的它们信息保存到oracle数据库,但是各个分公司
    的数据保存在Sybase中,数据库的位置等各不相同,这两种的操作不同。

    下面是我们一般首先会使用的方式,使用if else进行,判断,这样使用系统
    难以维护,难以扩展,不妨你增加一种查询,或者一种数据库试试????
    public class SearchAction(){
    public Vector searchData(string ActionType,String DbType){
    String SQL="";
    if(ActionType.equal("查找客户信息")){
    //如果是查询客户信息,拼SQL语句从客户表中读取数据
    SQL="select * from Customer "

    if(dbType.equal("oracle")){
    //从总部数据库读取,数据库为Oracle
    String connect_string ="jdbc:oracle:thin:hr/hr@localhost:1521:HRDB";
    DriverManager.registerDriver (new oracle.jdbc.OracleDriver());
    Connection conn = DriverManager.getConnection (connect_string);
    // Create a statement
    Statement stmt = conn.createStatement ();
    ResultSet rset = stmt.executeQuery (SQL);
    //以下省略部分动态从数据库中取出数据,组装成Vector,返回
    ..................................
    ...................................
    }else(dbType.equal("sybase")){
    //从分公司数据库读取,数据库为Sybase
    String connect_string ="jdbc:sybase:Tds:cai/cai@192.168.1.12:1521:FIN";
    DriverManager.registerDriver (new com.sybase.jdbc.SybDriver());
    Connection conn = DriverManager.getConnection (connect_string);
    // Create a statement
    Statement stmt = conn.createStatement ();
    ResultSet rset = stmt.executeQuery (SQL);
    //以下省略部分动态从数据库中取出数据,组装成Vector,返回
    ..................................
    ...................................

    }

    }else if(ActionType.equal("查找帐户信息")){
    //如果是查询帐户信息,拼接SQL语句从帐户表中读取数据
    SQL="select * from Account "
    if(dbType.equal("oracle")){
    ..........................
    ..........................
    (作者注:此处省略从oracle读取,约300字)

    }else if(dbType.equal("Sybase")){
    ..........................
    ..........................
    (作者注:此处省略从Sybase读取,约300字)
    }

    }
    }
    }

    如果你认为这写的比较弱智,应该进行使用函数,但是你也会大量使用if else.???

    于是我们进行重构,首先我们学习过DAO模式,就是把数据读取进行分里,我们定义一个
    共同的接口,它负责数据库的操作,然后根据不同的数据库进行实现,在我们的查询操作中,
    使用接口,进行操作,这样就可以不用考虑具体的实现,我们只管实现过程。

    查询共同接口:
    public interface searchDB{
    public Vector searchFromDB(String SQL)
    }
    Oracle数据库的查询实现
    public class searchDBOracleImpl{
    public Vector searchFromDB(String SQL){
    //从总部数据库读取,数据库为Oracle
    String connect_string ="jdbc:oracle:thin:hr/hr@localhost:1521:HRDB";
    DriverManager.registerDriver (new oracle.jdbc.OracleDriver());

    ResultSet rset = stmt.executeQuery (SQL);
    .............................
    ............................

    }
    }
    Sybase数据库的查询实现
    public class searchDBSysbaseImpl{
    public Vector searchFromDB(String SQL){
    //从分公司数据库读取,数据库为Sysbase
    String connect_string ="jdbc:sybase:Tds:cai/cai@192.168.1.12:1521:FIN";
    DriverManager.registerDriver (new com.sybase.jdbc.SybDriver());
    ResultSet rset = stmt.executeQuery (SQL);
    .............................
    ............................

    }
    }

    这样在我们的查询中就可以使用接口searchDB,但是创建有是一个问题,因为我们不能
    静态的确定,查询的数据库类型,必须动态确定,于是我们又想到使用简单工厂方法,
    来分别创建这里的具体实现,根据类别,创建
    public class searchFactory{
    public static searchDB createSearch(int DBType){
    if(DBType.equal("oracle")){
    return searchDBOracleImpl();
    }else if(DBType.equal("sybase")){
    return searchDBSysbaseImpl();
    }
    }
    }
    于是我们的查询代码可以改变为这样了;
    public class SearchAction(){

    public Vector searchData(string ActionType,String DbType){
    String SQL="";
    if(ActionType.equal("查找客户信息")){
    //如果是查询客户信息,拼SQL语句从客户表中读取数据
    SQL="select * from Customer "
    searchDB obj=searchFactory.createSearch(DbType);
    return obj.searchFromDB(SQL);

    }else if(ActionType.equal("查找帐户信息")){
    //如果是查询帐户信息,拼接SQL语句从帐户表中读取数据
    SQL="select * from Account "
    searchDB obj=searchFactory.createSearch(DbType);
    return obj.searchFromDB(SQL);
    }
    }
    }
    是不是简单一些,如果增加一个新的数据库,对我们只需增加一个新的数据库实现便可,
    老的代码,不需改变,这样便实现开-闭原则(Open-closed原则),在我们的查询查询
    中使用的是接口,这就是设计模式的原则,针对接口进行编程,并且使用聚合,而不是直接的继承
    大家,可以考虑使用继承来完成该工作怎样实现?????

    上面是把实现进行分离,实现可以动态变化!!!!!

    我们把查询的操作的具体数据库实现进行了分离,增强了灵活性,但是我们的查询。
    仍然使用了if else这样仍然不易进行扩展,于是我们进行抽象一个查询操作的过程,
    把它分成几个具体步骤,创建SQL语句,发送到数据库,执行查询,返回结果。
    它们虽然是不同的查询,SQL各不相同,不同数据库执行不同,返回结果的内容不同。但是
    这个过程却是不变的,于是我们声明一个抽象类,来完成这个过程。

    public abstract class searchAction{
    searchDB obj;
    //两个步骤
    public searchDB createSearchImple(int DbType){
    return searchFactory.createSearch(DbType);
    }
    public abstract String createSQL();

    //查询过程,最后返回结果
    public vector searchResult(int DbType){
    obj=createSearchImple(DbType);
    return obj.searchFromDB(createSQL())
    }
    }

    //我们客户查询,操作
    public class searchCustomerAction{
    public String createSQL(){
    return "select * from Customer"
    }
    }
    //我们的帐户查询操作
    public class searchAccountAction{
    public String createSQL(){
    return "select * from account"
    }
    }

    这样我们的查询编程简单的创建SQL语句,我们应该再创建一个工厂方法,
    来完成创建它们
    public class actionFactory{
    public static searchAction ceateAction(int actionType){
    if(actionType.equal("customer")){
    return searchCustomerAction();
    }else if(actionType.equal("account")){
    return searchAccountAction();
    }
    }
    }
    这样我们把查询操作的过程进行了抽象,定义了步骤,和具体过程,经过我们的两次改变
    把抽象部分和实现部分进行分离,使他们都可以独立的变化,增强灵活性。
    我们再看当初查询实现,现在经过这两次的地修改,变成了什么模样?如下:

    public class SearchAction(){
    public Vector searchData(string ActionType,String DbType){
    searchAction action=actionFactory.ceateAction(ActionType);
    return action.searchResult(DbType);
    }
    现在假如增加一个数据库类型,将会改变那些??,如果增加一种查询操作需要改变那些???

    讨论点:
    1:在我们的重构过程中,
    怎样使用设计模式原则的???
    现在如果增加功能,遵循开闭原则吗??
    2:我们使用了两个简单工厂,这是为了简化,一般最好使用抽象工厂方法,
    如果改为抽象工厂,怎样修改???



    我打算写一系列的文章介绍设计模式,
    希望从重构的角度考虑模式的应用,而不是
    直接介绍模式,这样对初学者容易入门,step by step。
    如果在自己的代码中遇到类似的情景,可以进行重构。


    从重构学习proxy,预告
    为什么EJB有Home,remote,bean这三种角色?
    为什么又客户端与容器进行交互??
    介绍在EJB中的应用???

    从重构学习decorator??

    设计模式在EJB中的应用

    什么是设计模式
    设计模式是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。

    毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。

    GoF的“设计模式”是第一次将设计模式提升到理论高度,并将之规范化,本书提出了23种基本设计模式,自此,在可复用面向对象软件的发展过程中,新的大量的设计模式不断出现。

    设计模式和框架
    现在,可复用面向对象软件系统现在一般划分为三大类:应用程序 工具箱和框架(Framework),我们平时开发的具体软件都是应用程序;Java的API属于工具箱;而框架是构成一类特定软件可复用设计的一组相互协作的类。EJB(Enterprise JavaBeans)是Java应用于企业计算的框架.

    框架通常定义了应用体系的整体结构 类和对象的关系等等设计参数,以便于具体应用实现者能集中精力于应用本身的特定细节。框架主要记录软件应用中共同的设计决策,框架强调设计复用,因此框架设计中必然要使用设计模式.

    另外,设计模式有助于对框架结构的理解,成熟的框架通常使用了多种设计模式,如果你熟悉这些设计模式,毫无疑问,你将迅速掌握框架的结构,我们一般开发者如果突然接触EJB J2EE等框架,会觉得特别难学,难掌握,那么转而先掌握设计模式,无疑是给了你剖析EJB或J2EE系统的一把利器。

    EJB中的设计模式
    下面我们从设计模式的角度看看EJB的框架是怎样的?在这之前假设你已经大概了解了设计模式。

    EJB是采取多层结构,原先我们数据库开发基本是应用程序(商业逻辑运算)直接调用数据库驱动,在EJB中,为将商业逻辑计算和数据库截然分开,使用多个结构式模式:Adapter模式和Bridge模式等.这样做的好处显然有三个:

    1.分离了商业逻辑层和数据访问层;
    2.能同时支持多个数据库;
    3.但数据库类型更换时,不会设计到商业逻辑代码的大量修改.

    EJB中将对数据库进行调用(如发出select等语句)称为会话bean(Sessionbean),而将对应数据库一个个记录的bean称为实体bean(Entity bean);由这两种类型的bean完成对数据库的访问.

    会话bean一般和客户端应用是一一对应,而和数据库端联系紧密的是实体bean,EJB在实体bean(或直接在会话bean)和数据库之间使用了Adapter模式和Bridge模式,无意在实体bean和数据库之间又多了一层,称之为DAO(Data Access Object ),DAO实际就是设计模式的混合体.

    我们以Java的宠物店中的Catalog为例,这是专门处理宠物店中的宠物类别,在对数据库访问中,有两个主要程序:CatalogEJB和CatalogDAO,我们从具体代码中看看设计模式是怎么应用的.

    Bridge模式和Adapter模式
    我们首先看看CatalogEJB代码:

    public class CatalogEJB implements SessionBean {

      protected CatalogDAO dao;

      //从DAO工厂中获取一个DAO 这是调用工厂(factory)模式的一个实例
      public void ejbCreate() {
        try {
          dao = CatalogDAOFactory.getDAO();
        }
        catch (CatalogDAOSysException se) {
          Debug.println("Exception getting dao " + se);
          throw new EJBException(se.getMessage());
        }
      }

      ....

     

    }

    我们发现在CatalogEJB中并没有通常的会话bean那样有对数据库操作的"select .. from ."等之类SQL操作语句,这些都被封装到DAO的具体实现中(Concrete class).

    在Catalog这个示例中使用了设计模式的Bridge模式,判断是否是某种模式,主要依据其参与者的种类和相互关系,我们先看看Bridge模式的定义和参与者:

    Bridge模式是将抽象和行为划分开来,各自独立,但能动态的结合起来(好象搭建了一座桥)。在本例中,是将商业逻辑和数据库访问这样的行为划分开来,数据库访问专门放置在DAO中了。

    Bridge模式需要两个接口(抽象类和接口通称为接口),一个用来封装抽象部分,本例中是封装商业逻辑,是CatalogEJB;还有一个是封装行为(Implementor),本例中是CatalogDAO,看看CatalogDAO代码:


    public interface CatalogDAO {

      public Category getCategory(String categoryID, Locale l)
      throws CatalogDAOSysException;

      public Page getCategories(int start, int count, Locale l)
      throws CatalogDAOSysException;

      public Product getProduct(String productID, Locale l)
      throws CatalogDAOSysException;

      public Page getProducts(String categoryID, int start, int count, Locale l)
      throws CatalogDAOSysException;

      public Item getItem(String itemID, Locale l)
      throws CatalogDAOSysException;

      public Page getItems(String productID, int start, int size, Locale l)
      throws CatalogDAOSysException;

      public Page searchItems(String query, int start, int size, Locale l)
      throws CatalogDAOSysException;

    }

    Bridge模式中参与者还需要有行为接口的具体实现(ConcreteImplementor),在本例中是CatalogDAOImpl,虽然在目前宠物店中只有一个ConcreteImplementor,但是可扩展为到Mysql XML等数据源访问,比如你可以自己新增一个叫CatalogDAOImplMysql,也是作为CatalogDAO的子类。

    看看CatalogDAO的一个子类CatalogDAOImpl的代码:

    public class CatalogDAOImpl implements CatalogDAO {

      protected static DataSource getDataSource()
        throws CatalogDAOSysException {
        try {
          InitialContext ic = new InitialContext();
          return (DataSource) ic.lookup(JNDINames.CATALOG_DATASOURCE);
        }
        catch (NamingException ne) {
          throw new CatalogDAOSysException("NamingException while looking "
            + "up DB context : "
            + ne.getMessage());
        }
      }

      //具体Select语句在这里出现,这里主要是Oracle 数据库的访问语句

      public Category getCategory(String categoryID, Locale l)
      throws CatalogDAOSysException {

        Connection c = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        Category ret = null;

        try {
          c = getDataSource().getConnection();

          ps = c.prepareStatement("select a.catid, name, descn "
              + "from (category a join "
              + "category_details b on "
              + "a.catid=b.catid) "
              + "where locale = ? "
              + "and a.catid = ?",
          ResultSet.TYPE_SCROLL_INSENSITIVE,
          ResultSet.CONCUR_READ_ONLY);
          ps.setString(1, l.toString());
          ps.setString(2, categoryID);
          rs = ps.executeQuery();
          if (rs.first()) {
            ret = new Category(rs.getString(1).trim(),
            rs.getString(2),
            rs.getString(3));
          }
          rs.close();
          ps.close();

          c.close();
          return ret;
        }
        catch (SQLException se) {
          throw new CatalogDAOSysException("SQLException: "
          + se.getMessage());
        }

        ....

    }

    Bridge模式参与者总结如下:

    商业逻辑抽象类 (CatalogEJB)

    • 抽象的商业逻辑操作.
    • 对DAOImplementor调用.
    • 不关心是具体什么数据源被使用(无论是Oracle还是JDBC还是XML).

    DAO(Data Access Object) (CatalogDAO)

    • 对数据源的抽象操作行为.
    • 提供了非常方便访问和维护管理数据的API结构.

    DAOImplementor (CatalogDAOImpl 有可能有CatalogDAOImplSybase CatalogDAOImplMysql 等)

    • 实现具体的DAO接口内容.
    • 使用Adapter模式,将特定的数据源驱动接口适配到DAO接口中去

    数据源 ( Oracle, or Sybase database via JDBC API)

    提供访问具体数据库的驱动接口,如包括连接池等.

    在使用数据源驱动接口时,需要使用Adapter模式,Adapter模式将两个不相关的类纠合在一起使用,Adapter模式实际是使用组合(composition)和继承(inheritance)两种方式再生类,在著名的"think in Java"的"类再生"专门提到这两个方式.

    很显然,如果你对Bridge模式和Adapter模式熟悉,那么对宠物店中的Catalog理解就会非常快,同样,在宠物店其他部分如订单 用户注册 等都能迅速理解。

    Factory模式和Singleton模式
    该模式类似new,是用来创建对象的,使用Factory模式是为了实现面向对象的基本原则.封装(Encapsulation)和分派(Delegation);将创建对象与使用对象进行分工。因此在平时开发过程中,尽量使用Factory模式创建对象。

    本例CatalogEJB中是使用Factory模式获得一个DAO的具体实例对象,见上面CatalogEJB代码中注释。我们看看CatalogDAOFactory的代码:

    public class CatalogDAOFactory {

      public static CatalogDAO getDAO() throws CatalogDAOSysException {

        CatalogDAO catDao = null;
        try {
          InitialContext ic = new InitialContext();
          String className = (String) ic.lookup(JNDINames.CATALOG_DAO_CLASS);
          catDao = (CatalogDAO) Class.forName(className).newInstance();
        } catch (NamingException ne) {
          ...

        }
        return catDao;
    }

    在CatalogDAOFactory可以依据系统的配置文件,动态获得DAO的方法,之所以采取动态方式,当然便于用户自己增加自己的DAO方式,而不必修改代码,只要直接修改配置文件就可以。

    如果在这里只需要CatalogDAOFactory产生一个实例,可以采取Singleton模式,Singleton的目的是控制类实例对象的创建,并且允许整个程序只在一点对它进行访问。Singleton本身类只能创建一个,是单线程。

    public class CatalogDAOFactory {

      private static CatalogDAO catDao = null;

      public static CatalogDAO getIntance(){
        if (catDao==null)
          try {
            InitialContext ic = new InitialContext();
            String className =
               (String) ic.lookup(JNDINames.CATALOG_DAO_CLASS);
            catDao = (CatalogDAO) Class.forName(className).newInstance();
          } catch (NamingException ne) {
            ...

          }
         }
        return catDao;

      }
    }

    那么在CatalogEJB的调用从
    dao = CatalogDAOFactory.getDAO();
    要改为
    dao = CatalogDAOFactory.getIntance();

    Facade模式
    在EJB应用中,有两个端点,这一端是用户端,另外一端是EJB,通常在这两个端点间会增加一层,用来松散两个端点之间的耦合,比如在宠物店例子中,考虑到不同身份的用户有不同的操作流程,比如顾客注册进入后,需要浏览目录,下订单,而商店管理者进入后需要确认或者否定订单,或者检查库存。这些功能需要借助Session bean和Entity bean完成。

    但是如果用户端直接和这些bean互动,会有以下问题:

    1. 用户端必须注意和这些beans的所有有联系或互动的事情,无法阻止用户端可能不恰当的使用这些beans.
    2.如果EJB的API改动,那么用户端的一些代码也要修改。无疑扩展性很差。
    3.即使这些beans都在同一台服务器上,用户端还是用remote方式来调用它们,造成网络无故拥挤。

    那么我们使用Facade模式来解决这个问题,Facade的定义是为子系统中的一组接口提供一个一致的界面,很显然我们需要为这些bean提供一个统一的对外界面。如下图:

     

    在宠物店中,ShoppingClientFacadeLocalEJB是面对所有用户端操作的统一界面,用户端操作就不直接和那些EJB如CustomerEJB或ShoppingCartEJB有联系,而是都通过ShoppingClientFacadeLocalEJB来联系的。代码如下:

    public class ShoppingClientFacadeLocalEJB implements SessionBean {

      ...

      //和CustomerEJB联系
      public CustomerLocal getCustomer() throws FinderException {
        if (userId == null) {
          ...
        }
        try {
          InitialContext ic = new InitialContext();
          Object o = ic.lookup("java:comp/env/ejb/petstore/local/customer");
          CustomerLocalHome home =(CustomerLocalHome)o;
          customer = home.findByPrimaryKey(userId);
        } catch (javax.naming.NamingException nx) {
          ...
        }

        return customer;
      }

      .....

      //和ShoppingCartEJB联系
      public ShoppingCartLocal getShoppingCart() {
        if (cart == null) {
          try {
            InitialContext ic = new InitialContext();
            Object o = ic.lookup("java:comp/env/ejb/cart/Cart");
            ShoppingCartLocalHome home =(ShoppingCartLocalHome)o;
            cart = home.create();
          } catch (javax.ejb.CreateException cx) {
           ...
          }
        }
        return cart;
      }

      ....

    }

    Facade模式参与者:

    SessionFacade (ShoppingClientFacadeLocalEJB)

    • 提供一组操作流程
    • 将真正工作委托到EJB的bean.

    EJB的bean (CustomerEJB, ShoppingCartEJB等等)

    • 执行基本的商业逻辑操作
    • 没有任何对SessionFacade的调用.

    这样不但可扩展性大大增强,效率也提高了,用户端只需要一次Remote对SessionFacade调用就可以了,而SessionFacade会自动定位到与它同一台服务器的那些邻居bean(CustomerEJB, ShoppingCartEJB等等),无疑减少网络拥挤,提高了速度.

    总结
    在EJB的具体使用中,使用合适的设计模式,不但使代码可重用性 可拓展性增强,最重要的是能提高效率和速度,我们知道EJB框架由于考虑大型系统中事务安全等各方面问题,效率性能有所欠缺,那么我们在具体问题具体应用时,使用设计模式可以弥补这个问题。

    例如Proxy模式可以为我们在访问巨大的需要花费一定时间才能展开的对象时,提供一个代理,这样不会因为那个巨大对象而影响当前运行速度,EJB中的那些bean很显然属于巨大对象(因为它们有反复的数据库操作,这些很费时间〕。

    Flyweight模式是避免大量拥有相同内容的小类的开销(如耗费内存),使大家共享一个类(元类).当你要从EJB中获取一系列字符串,而这些字符串中肯定有许多是重复的,那么我们可以将这些重复的字符串储存在Flyweight池(pool)中以达到共享。

    AOP vs Decorator

    更确切地说,我们在比较AOP的拦截器和Decorator模式,它们非常相似,一些AOP框架本身就是使用Decorator模式来实现拦截器功能的。

      过滤器是架构设计模式中比较常用的一种,几乎每个灵活动态系统都需要过滤器,特别是当我们的数据以内存状态出现时,过滤器无疑成为领域层的一个核心业务逻辑,当然如果你还是使用面向数据库的编程模式,过滤器功能就被你用SQL语句的where语法给替代了,那么以下你可能不必再看,请浏览这篇文章:状态对象:数据库的替代者

      当我们在一个AOP框架下编程,经常会问自己,到底过滤器这个功能是应该做成拦截器还是
    Decorator,当我在Jdon Framework下重写JiveJdon时就不只一次的问我自己。

      其实这是一个分析模式中的过滤器实现问题,也是一个现实设计的问题:在Servlet Filter和职责链以及装饰模式Decorator和AOP几个方面如何选择?

      选择标准无外乎软件的两个终极目标:简单和高质量;高质量是反映在细粒度方面,简单则是在进行设计实现时比较容易方便。

      在粒度粗细方面,又有一个衡量指标:功能覆盖范围,也是一个scope,例如:如果功能需求要为某个类的方法实现实现过滤,但是,你使用一个Servlet Filter这样过滤器实现,虽然
    它也能够实现我们的目标,但是它对所有的Servlet请求都进行过滤,这无疑范了杀鸡取卵的错误,造成系统性能上的损失。

      以一个具体案例为例子:
      我们在论坛显示时,希望动态过滤掉不应该出现的字眼,也就是在帖子显示之前,加载一个过滤器,对内容进行替换。

      这里的Model是ForumMessage,而对应的Service则是ForumMessageService; ForumMessageService的getMessage可以获得帖子内容,这里我们在getMessage之前加一个过滤器;那么这里应该如何选择呢?

      因为我在Jdon Framework这样的Ioc/AOP框架下实现,所以想到的是AOP,我们定义一个拦截器,拦截的对象是ForumMessageService的getMessage方法,进行配置文件配置即可。似乎很简单很酷,可是真的很美丽吗?

      这里,我们从原理上这几个过滤器实现进行解析一下:
      我这里举个例子,你想向处于同一个城市对方送达一个信件,这时有两种选择:
      1.委托高效的TNT UPS等快递公司
      2.亲自跑一趟。

      这两种哪个效率最高?无疑是亲自跑一趟,可能半个小时或一个小时解决问题,但是你一旦委托第三方,我想,无论快递公司如何高效,肯定没有你自己解决快速。所以,当你能够自己处理把握时,亲自动手无疑最快的。

      编码时也类似这样,如果你能在编码时,能够确定拦截那个类哪个方法,有什么比你直接写代码还能执行更快的呢?

      AOP的拦截器实现原理也是这样,当然,正是有这种考虑,所以所有AOP框架内,只有 AspectJ性能是最好的,因为它在你编译代码时就将你的拦截意图实现了,这属于静态织入 weaving,这就类似你自己写代码实现,但是与你自己写代码不同的是,你自己不但要写拦截器,而且还要自己手工将拦截器语句插入被拦截的那段代码,现在,使用AspectJ你就不用做后者了,这也是整合了AspectJ的Spring 2.0给我们带来的效能。所以,为了在编译代码时做手脚,就不能使用SUN的JDK原来的javac了,必须用他们自己特定的javac了。有得必有失吧。

      理解了AOP拦截器拦截的原理,你可能感慨:原来所有的动态AOP(不改换编译器的AOP框架)拦截效能没有Decorator直接指定要快啊。所以,如果你在编码设计阶段,可以知道你所要拦截的方法,那么,无疑直接使用Decorator模式组成过滤器是一种好方式。

      这也是我先期不怎么看好使用动态AOP实现的Spring 1.X版本,也不太热衷于同样使用
    动态AOP实现、虽符合EJB 3.0规范的JBoss 4.0了。可是,为什么总是有人迫不及待地告别EJB 2.x,夸张Spring,然后又狂炫EJB 3.0。在接纳他们之前,先把盒子打开看看验货一下,做一个普通的消费者应有的理性。

      当然,不是说动态AOP没有用武之地,它类似SOA的集成作用,可以在不用修改原来代码结构上强行加入过滤器。而且AOP不只是拦截器,还有introduction,这一神奇功能可以突破Java单继承法则,显得象儿童世界里面的神奇魔法一样。

      从某个方面来说,动态AOP和职责链非常类似,职责链类似击鼓传花,更像典型的打太极拳,推来推去,这也是政府效率不高的主要原因,同样,用在软件系统里,性能最差。想象一下,当这朵花传到真正主人面前时,而这个主人是传花环节中最后一个,这种效率和直接抛绣球一样,直接将花抛给真正主人相比,无疑最耗时间的,所以击鼓传花游戏就是玩的这个耗时间,在鼓声中,消耗时间,煎熬折磨你的心思,它玩的不是要把花如何快速准确地送给某人,它的目的性不确切。

      我们的软件系统不能这么玩吧?

      总结如下:过滤器实现方式在不保证功能前提下,从性能角度考虑有如下先后顺序:Decorator或Proxy模式;AOP拦截器。

      考虑使用AOP拦截器时,最好选择那些受众面积比较广的功能,例如一些基础通用功能:权限检查;事务机制;Pool等,这些功能不是针对某个具体类或方法(方法权限除外),而是一系列类,这样使用动态AOP拦截器,就是有些性能损耗也是值得的,而且是必要的,使用其他方法也会引起这样的损耗。

      如果过滤器是业务逻辑的一部分,而且在设计时,我们可以确定这些过滤器,这样我们使用Decorator模式或Proxy模式进行特定指定的拦截,当然,因为每个类/接口都需要一个附加的Decorator/Proxy,如果某个过滤功能是很多类都需要的,会形成很多Decorator/Proxy附加类,当点形成面时,这时AOP切面概念就应该浮现在你脑海,这时升级使用AOP拦截器就更好。Decorator/Proxy在点上针对性相当强,特别在这个点上有一系列过滤器需要实现时。

      职责链和Decorator/AOP拦截器是有些区别的,在一个动态运行系统中,有两个概念:由客户端触发的请求对象,该请求对象需要穿透一系列过滤器(防火墙),最终可能达到持久层数据库。Decorator/AOP拦截器是对过滤器管理的一种模式,也就是说:怎么设计过滤器类;过滤器类关系是怎样;而职责链不是对类关系管理定义,而是为了处理某个请求对象而实现的。他们区别在于目标对象不一样,所以职责链是一种很具体的行为。

      在这个层面上,Command模式和其是相竞争的,Command模式类似直接抛绣球,知道目的,能够最有效率,但是前提在设计编码阶段你必须知道你的目的地;Command模式和职责链的区别与Decorator和AOP拦截器的区别是类似的。

    GoF模式打开的新境界

    GoF模式打开的新境界
    没有知晓GoF模式之前,我们总是以为编码就是写一些代码,然后运行,复杂吗?如果我们来分析一下GoF模式三个类型,你会发现平时熟视无睹的代码中隐藏如此多考虑方面。

    GOF模式三种类型:结构型模式、创建型模式和行为型模式其实函括了OO编码的三个方面:静态类关系、类创建成为运行时对象实例;运行时的对象运行行为,也就是说,我们在编码阶段不但考虑现阶段各个类之间静态解耦关系,而且还要考虑这些代码激活后,运行时的情况。

    而以往过程化编程中,编码状况=运行状况,如何先后编码,这些编码运行时就按照这些先后编码顺序执行,两者是统一的,不可能出现运行时可能和编码时预想不一样,更何况需要我们还要在进行类编码时,考虑这些类运行时是如何实现的,有如何对这些类运行时的关系进行解耦和分离呢?所以,我们“天生”就无法理解设计模式,因为我们从来就认为软件就是实现功能,哪里还会考虑到实现同样功能会涉及各种考量了呢?

    如果说设计模式是程序员的圣经,那么不掌握设计模式可能就是异教徒,从此教徒和异教徒两者之间就缺乏沟通对话平台,就象鸡对鸭讲话了。

    命令模式

    命令模式前言
    第一章:通常的命令模式
    第二章:简化的命令模式
    第三章:其他要说的内容
    通常的命令模式:
    1.1 通常命令模式有一下几个角色

    调用者:(命令的执行者)
    生成有序的命令队列
    按顺序执行命令操作
    提供撤销命令操作
    记录已经操作的命令
    抽象命令:
    抽象的命令接口
    具体命令:
    具体的命令。
    由三个要素组成:执行者,执行者要作的操作和被执行的对象组成。当然还可以有其他,比如将对象执行成什么结果。例如:调用Mypait类(执行者)将My rectangle(对象)填充(操作)为红色(结果)。这样就可以完全描述一个命令了。
    执行者:
    真正执行逻辑操作的对象

    1.2原型:

    //调用者
    public class Invoker{
    List commands; //命令集合

    public void setCommands(List commands){
    this.commands = commands;
    }

    public void addCommand (Command command,int i){
    commands.add(i,command);
    }
    public void removeCommand (int i){
    commands.add(i,command);
    }
    //得代执行命令
    public void action(){
    for(Iterator it = list.iterator();it.hasNext();){
    Command command = Command) it.next();
    Command. execute();
    }
    }
    ……………
    //还可以有丰富的redo和undo操作;(当然一些都给基于命令类提供的相应方法)
    }

    //抽象命令
    abstract class Command
    {
    abstract public void execute();
    abstract public void unexecute();
    abstract public void reexecute();
    //一般有这样这个方法,根据需要可以增删
    }

    // 具体的命令类1:写作命令,选择一个作者(Author类实例对象),让他写作(调用它的write方法)写作的对象是书(Book的实例对象)形成了一个写作的命令,写作的对象是Book的实例
    public class WriteCommand implement Command
    {
    Author author; //执行者
    Book book; //要执行的对象
    public WriteCommand (Author author,Book book) {
    this. author = author;
    this. book = book;
    }
    // 在这里执行要执行的操作
    public override void Execute()
    {
    author.write (book);
    }
    }

    // 具体的命令类2: 出版命令,选择一个出版社(publisher类实例对象),让他出版书(调用它的publisherBook方法)出版的对象是书(Book的实例对象)形成了一个出版的命令
    public class PublishCommand implement Command
    {
    Publisher publisher;
    Book book;
    public PublishCommand (Publisher publisher) {
    this. publisher = publisher;
    this. book = book;
    }
    // Methods
    public override void Execute()
    {
    publisher. publisherBook(book);
    }
    }

    // Publisher和Author类为执行者


    这样我们的客户端代码就可以这样写:
    //如果我要出一本书
    //一本空白的书
    Book book = new Book();
    //先找一个作者和出版社
    Author author = new Author();
    Publisher publisher = new Publisher ();
    //产生命令集合
    Command writeCommand = new WriteCommand (author,book);
    Command publishCommand = new PublishCommand(publisher,book);
    List commands = new List ();
    Commands.add(writeCommand);
    //找个调用者,把命令给它,让他来根据命令协调工作
    Invoker invoker = new invoker();
    Invoker.setCommands(commands);
    public void addCommand (Command command,int i){
    commands.add(i,command);
    }
    invoker.action();

    特点:
    1. 分布登记统一执行:
    在作程序时,经常碰到一些需求,先注册一些操作,并不马上执行,等最终确定后统一执行。如一个具体的例子:用户定制自己的报表,可以订阅饼,柱,折线,曲线图,客户选择相应的报表组合,这样对应一个命令集合,在没确定之前用户可以增删这些报表(命令),等最终确定统一交给调用者根据命令执行,生成组合报表。实现了命令分布提出,确定后统一执行的功能。

    2.形如流水线操作:还是出书的例子
    //先是一本空白的书:
    Book book = new Book();
    //找几个作者
    Author author1 = new Author();
    Author author2 = new Author();
    //把写1,2章的名类分别给这两个作者
    Command writeCommand = new Write1Command (author1,book);
    Command writeCommand = new Write2Command (author2,book);
    List commands = new List ();
    Commands.add(writeCommand);
    //调用者
    Invoker invoker = new invoker();
    Invoker.setCommands(commands);
    //流水写书
    invoker.action()
    实际上在aciton这一方法中,invoker按照命令,让两个作者流水写作这本书。(类似一个书的流水线加工工厂)
    这样我们的书就被流水加工成功(当然这本书只有两章)

    这样就给了我们一种系统设计的框架,
    模型+工具+命令
    客户端产生命令,命令调用工具操作模型。
    Book 相当于模型
    Author 相当于和多工具类中的一个
    Command 命令

    3.系统需要支持命令的撤消(undo)。提供redo()方法
    我们可以和容易的加入undo和redo,这个不难理解

    4.在Invoker中我们可以实现跟踪,和日志。

    5.当系统需要为某项复制增加形的功能的时候,命令模式使新的功能(表现为一种命令)很容易地被加入到服务种里。
    命令联系了工具类即执行类和系统逻辑,

    简化/变化的命令模式:
    命令模式的角色比较多,在实际应用种我们可以根据所需要的功能和不需要的功能加以简化。

    1》去掉 调用者
    产生命令集合后,我们可以直接在client中迭代执行执行操作
    2》 变化 调用者 成为 跟踪者
    //调用者
    public class Invoker{
    List commands; //已经执行完毕的命令集合
    public void addCommand (Command command,int i){
    commands.add(i,command);
    }
    public void action(Command command){
    //执行操作
    command. execute();
    //
    commands.add(command);
    }
    }
    ……………
    //还可以有丰富的redo和undo操作;(当然一些都给基于命令类提供的相应方法)
    }
    这样这个类就记录了所有执行过的操作。

    3》去掉 命令 用map替代
    我们完全可以用map代替命令,这样无需定义各种命令类
    我们改进例子
    Author author = new Author();
    Publisher publisher = new Publisher ();
    Map m = new HashMap;
    m.put(author, write);
    m.put(author, publisherBook);
    在Invoker的action方法:
    得代map
    运用java反射来调用方法;

    4》去掉执行者:
    直接在命令中(execute方法种)加业务逻辑。这样只适合于简单的小的系统.

    其他要说的内容
    1》 将某些参数传给某个方发的方式很多,除了当作方法的参数外还可以当作类的成员便俩变量传入:
    这就为命令的抽象带来了极大的方便
    abstract class Command
    {
    abstract public void execute();
    }
    当我们已经有了执行者(类Test)方法execute(args1,args2 ….argsn)
    我们不必向Command加入execute(args1,args2 ….argsn)抽象方法,在说即使加了,在我们迭代的时候也无法判断或十分不容易判断哪个命令调用哪个execute方法。
    那么我们可以这样
    class ConcreteCommand : Command
    {
    Test test;
    args1
    args2
    …..
    argsn
    public override void Execute()
    {
    test. execute (args1,args2 ….argsn);
    }
    }
    2》 在想跟踪操作的时候,一般为每一个操作对象分配一个调用者,操作对象在调用者中设置。(可以抽象出一个总的调用者,来协调调用每一个具体的调用者)
    3》 命令的抽象粒度我觉得是要注意的。
    4》 理解思想,不要机械的照搬。消化成自己的,加以灵活的运用和创造在是根本出路。
    所谓命令模式的根本思想就是在 先形成命令,在根据命令执行。

    李开复给中国学生的信

    李开复给中国学生的第四封信:大学四年应是这样度过

    李开复博士:现任微软公司全球副总裁,是微软亚洲研究院的首任院长。在学术领域,他是攻坚挫锐的科研天才;在管理层面,他又是运筹帷幄的领军人物。作为一位天资卓越的华裔学者,他正在创造着一个又一个奇迹。同时,李开复博士极为关注中国教育,先后给中国学生写了三封饱含关切之情的来信,在国内青年学生中产生了巨大影响。他还为中国学生开通了“开复学生网(www.kaifulee.com)”。近日,李开复博士又撰写了“给中国大学生的第四封信”,通过具有广泛影响力的《中国青年报》、《中国青年》杂志、《大学生》杂志、《文汇读书周报》等媒体刊登。今天,微软与李开复博士又将这封信授权新浪网发表,以飨青年朋友们,并欢迎青年朋友们通过“开复学生网”对“第四封信”进行深入的讨论。

      相关文章:李开复写给中国学生的一封信:从诚信谈起

                   李开复给中国学生的第二封信:从优秀到卓越

                   李开复给中国学生的第三封信:成功、自信、快乐

      给中国学生的第四封信:大学四年应是这样度过

      李开复

      2005年2月

      今天,我回复了“开复学生网”开通以来的第1000个问题。关掉电脑后,始终有一封学生来信萦绕在我的脑海里,挥之不去:

      开复老师:

      就要毕业了。

      回头看自己所谓的大学生活,

      我想哭,不是因为离别,而是因为什么都没学到。

      我不知,简历该怎么写,若是以往我会让它空白。

      最大的收获也许是……对什么都没有的忍耐和适应……

      这封来信道出了不少大三、大四学生的心声。大学期间,有许多学生放任自己、虚度光阴,还有许多学生始终也找不到正确的学习方向。当他们被第一次补考通知唤醒时,当他们收到第一封来自应聘企业的婉拒信时,这些学生才惊讶地发现,自己的前途是那么渺茫,一切努力似乎都为时已晚……

      这“第四封信”是写给那些希望早些从懵懂中清醒过来的大学生,那些从未贪睡并希望把握自己的前途和命运的大学生以及那些即将迈进大学门槛的未来大学生们的。在这封信中,我想对所有同学说:

      大学是人一生中最为关键的阶段。从入学的第一天起,你就应当对大学四年有一个正确的认识和规划。为了在学习中享受到最大的快乐,为了在毕业时找到自己最喜爱的工作,每一个刚进入大学校园的人都应当掌握七项学习:学习自修之道、基础知识、实践贯通、兴趣培养、积极主动、掌控时间、为人处事。只要做好了这七点,大学生临到毕业时的最大收获就绝不会是“对什么都没有的忍耐和适应”,而应当是“对什么都可以有的自信和渴望”。只要做好了这七点,你就能成为一个有潜力、有思想、有价值、有前途的快乐的毕业生。

      大学:人生的关键

      大学是人生的关键阶段。这是因为,进入大学是你终于放下高考的重担,第一次开始追逐自己的理想、兴趣。这是你离开家庭生活,第一次独立参与团体和社会生活。这是你不再单纯地学习或背诵书本上的理论知识,第一次有机会在学习理论的同时亲身实践。这是你第一次不再由父母安排生活和学习中的一切,而是有足够的自由处置生活和学习中遇到的各类问题,支配所有属于自己的时间。

      大学是人生的关键阶段。这是因为,这是你一生中最后一次有机会系统性地接受教育。这是你最后一次能够全心建立你的知识基础。这可能是你最后一次可以将大段时间用于学习的人生阶段,也可能是最后一次可以拥有较高的可塑性、集中精力充实自我的成长历程。这也许是你最后一次能在相对宽容的,可以置身其中学习为人处世之道的理想环境。

      大学是人生的关键阶段。在这个阶段里,所有大学生都应当认真把握每一个“第一次”,让它们成为未来人生道路的基石;在这个阶段里,所有大学生也要珍惜每一个“最后一次”,不要让自己在不远的将来追悔莫及。在大学四年里,大家应该努力为自己编织生活梦想,明确奋斗方向,奠定事业基础。

      大学四年每个人都只有一次,大学四年应这样度过……

      自修之道:从举一反三到无师自通

      记得我在哥伦比亚大学任助教时,曾有位中国学生的家长向我抱怨说:“你们大学里到底在教些什么?我孩子读完了大二计算机系,居然连VisiCalc 都不会用。”

      我当时回答道:“电脑的发展日新月异。我们不能保证大学里所教的任何一项技术在五年以后仍然管用,我们也不能保证学生可以学会每一种技术和工具。我们能保证的是,你的孩子将学会思考,并掌握学习的方法,这样,无论五年以后出现什么样的新技术或新工具,你的孩子都能游刃有余。”

      她接着问:“学最新的软件不是教育,那教育的本质究竟是什么呢?”

      我回答说:“如果我们将学过的东西忘得一干二净时,最后剩下来的东西就是教育的本质了。”

      我当时说的这句话来自教育家B. F. Skinner的名言。所谓“剩下来的东西”,其实就是自学的能力,也就是举一反三或无师自通的能力。大学不是“职业培训班”,而是一个让学生适应社会,适应不同工作岗位的平台。在大学期间,学习专业知识固然重要,但更重要的还是要学习独立思考的方法,培养举一反三的能力,只有这样,大学毕业生才能适应瞬息万变的未来世界。我认识的不少在中国读完大学来美国念研究生的朋友。他们认为来美国后,不论是学习,工作还是生活他们最缺乏的是独立思考的能力因为在国内时他们很少独立思考和独立决策。

      上中学时,老师会一次又一次重复每一课里的关键内容。但进了大学以后,老师只会充当引路人的角色,学生必须自主地学习、探索和实践。走上工作岗位后,自学能力就显得更为重要了。微软公司曾做过一个统计:在每一名微软员工所掌握的知识内容里,只有大约10%是员工在过去的学习和工作中积累得到的,其他知识都是在加入微软后重新学习的。这一数据充分表明,一个缺乏自学能力的人是难以在微软这样的现代企业中立足的。

      自学能力必须在大学期间开始培养。许多同学总是抱怨老师教得不好,懂得不多,学校的课程安排也不合理。我通常会劝这些学生说:“与其诅咒黑暗,不如点亮蜡烛”。 大学生不应该只会跟在老师的身后亦步亦趋,而应当主动走在老师的前面。例如,大学老师在一个课时里通常要涵盖课本中几十页的信息内容,仅仅通过课堂听讲是无法把所有知识学通、学透的。最好的学习方法是在老师讲课之前就把课本中的相关问题琢磨清楚,然后在课堂上对照老师的讲解弥补自己在理解和认识上的不足之处。

      中学生在学习知识时更多地是追求“记住”知识,而大学生就应当要求自己“理解”知识并善于提出问题。对每一个知识点,都应当多问几个“为什么”。一旦真正理解了理论或方法的来龙去脉,大家就能举一反三地学习其他知识,解决其他问题,甚至达到无师自通的境界。

      事实上,很多问题都有不同的思路或观察角度。在学习知识或解决问题时,不要总是死守一种思维模式,不要让自己成为课本或经验的奴隶。只有在学习中敢于创新,善于从全新的角度出发思考问题,学生潜在的思考能力、创造能力和学习能力才能被真正激发出来。

      《礼记.学记》上讲:“独学而无友,则孤陋而寡闻”。也就是说,大学生应当充分利用学校里的人才资源,从各种渠道吸收知识和方法。如果遇到好的老师,你可以主动向他们请教,或者请他们推荐一些课外的参考读物。除了资深的教授以外,大学中的青年教师、博士生、硕士生乃至自己的同班同学都是最好的知识来源和学习伙伴。每个人对问题的理解和认识都不尽相同,只有互帮互学,大家才能共同进步。

      有些同学曾告诉我说,他们很羡慕我在读书时能有一位获得过图灵奖的大师传道授业。其实,虽然我非常推崇我的老师,但他在大学期间并没有教给我多少专业知识。他只是给我指明了大方向,让我分享他的经验,给我提供研究的资源,并教我做人的方法。他没有时间也没有必要指导我学习具体的专业知识。我在大学期间积累的专业知识都是通过自学获得的。刚入门时,我曾多次红着脸向我的师兄请教最基本的知识内容,开会讨论时我曾问过不少肤浅的问题,课余时间我还主动与同学探讨、切磋。“三人行必有我师”,大学生的周围到处是良师益友。只要珍惜这些难得的机会,大胆发问,经常切磋,我们就能学到最有用的知识和方法。

      大学生应该充分利用图书馆和互联网,培养独立学习和研究的本领,为适应今后的工作或进一步的深造做准备。首先,除了学习老师规定的课程以外,大学生一定要学会查找书籍和文献,以便接触更广泛的知识和研究成果。例如,当我们在一门课上发现了自己感兴趣的课题,就应当积极去图书馆查阅相关文献,了解这个课题的来龙去脉和目前的研究动态。熟练和充分地使用图书馆资源,这是大学生特别是那些有志于科学研究的大学生的必备技能之一。读书时,应尽量多读一些英文原版教材。有些原版教材写得深入浅出,附有大量实例,比中文教材还适于自学。其次,在书本之外,互联网也是一个巨大的资源库,大学生们可以借助搜索引擎在网上查找各类信息。“开复学生网”开通半年以来,我发现很多同学其实并没有很好地掌握互联网的搜索技巧,有时他们提出的问题只要在搜索引擎中简单检索一下,就能轻易找到答案。还有些同学很容易相信网上的谣言,而不会利用搜索引擎自己查考、求证。除了搜索引擎以外,网上还有许多网站和社区也是很好的学习园地。

      自学时,不要因为达到了学校的要求就沾沾自喜,也不要认为自己在大学里功课好就足够了。在二十一世纪的今天,人才已经变成了一个国际化的概念。当你对自己的成绩感到满意时,我建议你开始自学一些国际一流大学的课程。例如,美国麻省理工学院(MIT)的开放式课程已经在网上无偿发布出来,大家不妨去看看MIT的网上课程,做做MIT的网上试题。当你可以自如地掌握MIT课程时,你就可以更加自信地面对国际化的挑战了。

      总之,善于举一反三,学会无师自通,这是大学四年中你可以送给自己的最好的礼物。

      基础知识:数学、英语、计算机、互联网

      我曾经说过,中国学生的一大优势是扎实的基础知识,如数学、物理等。但是,最近几年,同学们在目睹了很多速成的例子(如丁磊陈天桥等)之后,也迫切希望能驶上成功的快车道。这渐渐形成了一种追求速成的浮躁风气。有许多大学生梦想在毕业后就立即能做“经理”、“老板”,还有许多大学生入学时直接选择了“管理”专业,因为他们认为从这样的专业毕业后马上就可以成为企业的管理者。可不少学生进入了管理专业后,才发现自己对本专业的学习毫无兴趣。其实,管理专业和其他专业一样,都是传授基础知识和基本方法的地方,没有哪个专业可以保证学生在毕业时就能走上领导岗位。无论同学们所学的是哪个专业,大学毕业才是个人事业的真正开始。想做企业领导或想做管理工作的同学也必须从基层做起,必须首先在人品方面学会做人,在学业方面打好基础。

      如果说大学是一个学习和进步的平台,那么,这个平台的地基就是大学里的基础课程。在大学期间,同学们一定要学好基础知识其中包括数学、英语、计算机和互联网的使用,以及本专业要求的基础课程(如商学院的财务、经济等课程)。在科技发展日新月异的今天,应用领域里很多看似高深的技术在几年后就会被新的技术或工具取代。只有对基础知识的学习才可以受用终身。另一方面,如果没有打下好的基础,大学生们也很难真正理解高深的应用技术。最后,在许多的中国大学里,教授对基础课程也比对最新技术有更丰富的教学经验。

      数学是理工科学生必备的基础。很多学生在高中时认为数学是最难学的,到了大学里,一旦发现本专业对数学的要求不高,就会彻底放松对数学知识的学习,而且他们看不出数学知识有什么现实的应用或就业前景。但大家不要忘记,绝大多数理工科专业的知识体系都建立在数学的基石之上。例如,要想学好计算机工程专业,那至少要把离散数学(包括集合论、图论、数理逻辑等)、线性代数、概率统计和数学分析学好;要想进一步攻读计算机科学专业的硕士或博士学位,可能还需要更高的数学素养。同时,数学也是人类几千年积累的智慧结晶,学习数学知识可以培养和训练人的思维能力。通过对几何的学习,我们可以学会用演绎、推理来求证和思考的方法;通过学习概率统计,我们可以知道该如何避免钻进思维的死胡同,该如何让自己面前的机会最大化。所以,大家一定要用心把数学学好,不能敷衍了事。学习数学也不能仅仅局限于选修多门数学课程,而是要知道自己为什么学习数学,要从学习数学的过程中掌握认知和思考的方法。

      二十一世纪里最重要的沟通工具就是英语。有些同学在大学里只为了考过四级、六级而学习英语,有的同学仅仅把英语当作一种求职必备的技能来学习,甚至还有人认为学习和使用英语等于崇洋媚外。其实,学习英语的根本目的是为了掌握一种重要的学习和沟通工具。在未来的几十年里,世界上最全面的新闻内容,最先进的思想和最高深的技术,以及大多数知识分子间的交流都将用英语进行。因此,除非你甘心做一个与国际脱节的人,英语学习是至关重要的。在软件行业里,不但编程语言是以英语为基础设计出来的,最重要的教材、论文、参考资料、用户手册等资源也大多是用英语写就的。学英语绝不等于崇洋媚外。中国正在走向世界,中国需要学习西方的先进思想和先进科学技术,学好英语才是真正的爱国。

      很多中国留学生的英语考试成绩不错,也高分考过四级、六级、托福,但是留学美国后上课时却很难听懂课程内容,和外国同学交流时就更加困难。我们该如何学好英语呢?既然英语是最重要的沟通工具,那么,最重要的学习方法就是尽量与实践结合起来,不能只“学”不“用”,更不能只靠背诵的方式学习英语。读书时,大家尽量阅读原版的专业教材(如果英语不够好,可以先从中英对照的教材看起),并适当地阅读一些自己感兴趣的专业论文,这可以同时提高英语和相关专业的知识水平。其次,提高英语听说能力的最好方法是直接与那些以英语为母语的外国人对话。现在有很多在中国学习和工作的外国人,他们中的不少人为了学中文,很愿意与中国学生对话、交流,这是很好的学习机会。此外,大家不要把学英语当作一件苦差事,完全可以用有趣的方法学习英语。例如,可以多看一些名人的对话或演讲,多看一些小说、戏剧甚至漫画。初学者可以找英文原版的教学节目和录像来学习,有一定基础的则应该看英文电视或电影。看一部英文电影时,最好先在有字幕的时候看一遍,同时查考生词、熟悉句式,然后在不加字幕的情况下再看一遍,仅靠耳朵去听。听英文广播也是很好的练习英文听力的方法,大家每天最好能抽出半小时到一小时的时间收听广播并尽量理解其中的内容,有必要的话还可以录下来反复收听。在互联网上也有许多互动式的英语学习网站,大家可以在网站上用游戏、自我测试、双语阅读等方式提升英语水平。总之,勇于实践、持之以恒是学习英语的必由之路。

      信息时代已经到来,大学生在信息科学与信息技术方面的素养也已成为他们进入社会的必备基础之一。虽然不是每个大学生都需要懂得计算机原理和编程知识,但所有大学生都应能熟练地使用计算机、互联网、办公软件和搜索引擎,都应能熟练地在网上浏览信息和查找专业知识。在二十一世纪里,使用计算机和网络就像使用纸和笔一样是人人必备的基本功。不学好计算机,你就无法快捷全面地获得自己需要的知识或信息。

      最后,每个特定的专业也有它自己的基础课程。以计算机专业为例,许多大学生只热衷于学习最新的语言、技术、平台、标准和工具,因为很多公司在招聘时都会要求这些方面的基础或经验。这些新技术虽然应该学习,但计算机基础课程的学习更为重要,因为语言和平台的发展日新月异,但只要学好基础课程(如数据结构、算法、编译原理、计算机原理、数据库原理等)就可以万变不离其宗。有位同学生动地把这些基础课程比拟为计算机专业的内功,而把新的语言、技术、平台、标准和工具比拟为外功。那些只懂得追求时髦的学生最终只知道些招式的皮毛,而没有内功的积累,他们是不可能成为真正的高手的。

      虽然我一向鼓励大家追寻自己的兴趣,但在这里仍需强调,生活中有些事情即便不感兴趣也是必须要做的。例如,打好基础,学好数学、英语和计算机的使用就是这一类必须做的事情。如果你对数学、英语和计算机有兴趣,那你是幸运儿,可以享受学习的乐趣;但就算你没有兴趣,你也必须把这些基础打好。打基础是苦功夫,不愿吃苦是不能修得正果的。

      实践贯通:“做过的才真正明白”

      上高中时,许多学生会向老师提出“为什么?有什么用?”的问题,通常,老师给出的答案都是“不准问”。进入大学后,这些问题的答案应该是“不准不问”。在大学里,同学们应该懂得每一个学科的知识、理论、方法与具体的实践、应用如何结合起来,尤其是工科的学生更是如此。

      有一句关于实践的谚语是这样说的:“我听到的会忘掉,我看到的能记住,我做过的才真正明白。”

      无论学习何种专业、何种课程,如果能在学习中努力实践,做到融会贯通,我们就可以更深入地理解知识体系,可以牢牢地记住学过的知识。因此,我建议同学们多选些与实践相关的专业课。实践时,最好是几个同学合作,这样,既可经过实践理解专业知识,也可以学会如何与人合作,培养团队精神。如果有机会在老师手下做些实际的项目,或者走出校门打工,只要不影响课业,这些做法都是值得鼓励的。外出打工或做项目时,不要只看重薪酬待遇(除非生活上确实有困难),有时候,即便待遇不满意,但有许多培训和实践的机会,我们也值得一试。

      以计算机专业为例,实践经验对于软件开发来说更是必不可少的。微软公司希望应聘程序员的大学毕业生最好有十万行的编程经验。理由很简单:实践性的技术要在实践中提高。计算机归根结底是一门实践的学问,不动手是永远也学不会的。因此,最重要的不是在笔试中考高分,而是实践能力。但是,在与中国学生的交流过程中,我很惊讶地发现,中国某些学校计算机系的学生到了大三还不会编程。这些大学里的教学方法和课程的确需要更新。如果你不巧是在这样的学校中就读,那你就应该从打工、自学或上网的过程中寻求学习和实践的机会。在网上可以找到许多实践项目,例如,有一批爱好编程的学生建立了一个讨论软件技术的网站(www.diyinside.com),在其中共享他们的知识和实践经验,并成功举办了很多次活动(如在各大高校举办校园技术教育会议),还出版了帮助学生提高技术、解答疑难方面的图书,该网站有多位成员获得了“微软最有价值的专家”的称号。

      培养兴趣:开拓视野,立定志向

      孔子说:“知之者不如好之者,好之者不如乐之者。”我在“给中国学生的第三封信”中曾深入论述了快乐和兴趣是一个人成功的关键。如果你对某个领域充满激情,你就有可能在该领域中发挥自己所有的潜力,甚至为它而废寝忘食。这时候,你已经不是为了成功而学习,而是为了“享受”而学习了。在“第三封信”中,我也曾谈到我自己是如何在大学期间放弃了我不感兴趣的法律专业而进入我所热爱的计算机专业学习的。

      有些同学问我,如何像我一样能找到自己的兴趣呢?我觉得,首先要客观地评估和寻找自己的兴趣所在:不要把社会、家人或朋友认可和看重的事当作自己的爱好;不要以为有趣的事就是自己的兴趣所在,而是要亲身体验它并用自己的头脑做出判断;不要以为有兴趣的事情就可以成为自己的职业,例如,喜欢玩网络游戏并不代表你会喜欢或有能力开发网络游戏;不要以为有兴趣就意味着自己有这方面的天赋,不过,你可以尽量寻找天赋和兴趣的最佳结合点,例如,如果你对数学有天赋但又喜欢计算机专业,那么你完全可以做计算机理论方面的研究工作。

      最好的寻找兴趣点的方法是开拓自己的视野,接触众多的领域。唯有接触你才能尝试,唯有尝试你才能找到自己的最爱。而大学正是这样一个可以让你接触并尝试众多领域的独一无二的场所。因此,大学生应当更好地把握在校时间,充分利用学校的资源,通过使用图书馆资源、旁听课程、搜索网络、听讲座、打工、参加社团活动、与朋友交流、使用电子邮件和电子论坛等不同方式接触更多的领域、更多的工作类型和更多的专家学者。当年,如果我只是乖乖地到法律系上课,而不去尝试旁听计算机系的课程,我就不会去计算机中心打工,也不去找计算机系的助教切磋,就更不会发现自己对计算机的浓厚兴趣。

      通过开拓视野和接触尝试,如果你发现了自己真正的兴趣爱好,这时就可以去尝试转系的可能性、尝试课外学习、选修或旁听相关课程;你也可以去找一些打工或假期实习的机会,进一步理解相关行业的工作性质;或者,努力去考自己感兴趣专业的研究生,重新进行一次专业选择。其实,本科读什么专业并不能完全决定毕业后的工作方向,正如我所强调的那样,大学期间的学习过程培养的是你的学习能力,只要具备了这种能力,即使从事的是全新的工作,你也能在边做边学的过程中获取足够的知识和经验。

      除了“选你所爱”,大家也不妨试试“爱你所选”。有些同学后悔自己在入学时选错了专业,以至于对所学的专业缺乏兴趣,没有学习动力;有些同学则因为追寻兴趣而“走火入魔”,毕业后才发现荒废了本专业的课程;另一些同学因为在学习上遇到了困难或对本专业抱有偏见,就以兴趣为借口,不愿意面对自己的专业。这些做法都是不正确的。在大学中,转系可能并不容易,所以,大家首先应尽力试着把本专业读好,并在学习过程中逐渐培养自己对本专业的兴趣。此外,一个专业里可能有很多不同的领域,也许你对专业里的某一个领域会有兴趣。现在,有很多专业发展了交叉学科,两个专业的结合往往是新的增长点。因此,只要多接触、多尝试,你也许就会碰到自己真正感兴趣的方向。“数字笔”的发明人王坚博士在微软亚洲研究院负责用户界面的研究,可是谁又能想到他从本科到博士所学的都是心理学专业,而用户界面又正是计算机和心理学专业的最佳结合点。另一方面,就算你毕业后要从事其他的行业,你依然可以把自己的专业读好,这同样能成为你在新行业中的优势。例如,有一位同学不喜欢读工科,想毕业后进入服务业发展,我就建议他先把工科读好,将来可以在服务业中以精通技术作为自己的特长。

      人生的路很长,每个人都可以有很多不同的兴趣爱好。在追寻兴趣之外,更重要的是要找寻自己终身不变的志向。有一本书的作者曾访问了几百个成功者,问他们有哪件事是他们今天已经懂得,但在年轻时却留下了遗憾的事情。在受访者的回答中,最多的一种是:“希望在年轻时就有前辈告诉我、鼓励我去追寻自己的理想和志向。”相比之下,兴趣固然关键,但志向更为重要。例如,我的志向是“使影响力最大化”,多年以来,我有许多兴趣爱好,如语音识别、对弈软件、多媒体、研究到开发的转换、管理学、满足用户的需求、演讲和写作、帮助中国学生等等,兴趣可以改变,但我的志向是始终不渝的。因此,大家不必把某种兴趣当作自己最后的目标,也不必把任何一种兴趣的发展道路完全切断,在志向的指引下,不同的兴趣完全可以平行发展,实在必要时再做出最佳的抉择。志向就像罗盘,兴趣就像风帆,两者相辅相成、缺一不可,它们可以让你驶向理想的港湾。

      积极主动:果断负责,创造机遇

      创立“开复学生网”时,我的初衷是“帮助学生帮助自己”。但让我很惊讶的是,更多的学生希望我直接帮他们做出决定,甚至仅在简短的几句自我介绍后就直接对我说:“只有你能告诉我,我该怎么做”。难道一个陌生人会比你更知道自己该怎么做吗?我慢慢认识到,这种被动的思维方式是从小在中国的教育环境中培养出来的。被动的人总是习惯性地认为他们现在的境况是他人和环境造成的,如果别人不指点,环境不改变,自己就只有消极地生活下去。持有这种态度的人,事业还没有开始,自己就已经被击败,我从来没见过这样消极的人可以取得持续的成功。

      从大学的第一天开始,你就必须从被动转向主动,你必须成为自己未来的主人,你必须积极地管理自己的学业和将来的事业,理由很简单:因为没有人比你更在乎你自己的工作与生活。“让大学生活对自己有价值”是你的责任。许多同学到了大四才开始做人生和职业规划,而一个主动的学生应该从进入大学时就开始规划自己的未来。

      积极主动的第一步是要有积极的态度。大家可以用我在“第三封信”里推荐的方法,积极规划自己的人生目标,追寻兴趣并尝试新的知识和领域。纳粹德国某集中营的一位幸存者维克托.弗兰克尔曾说过:“在任何特定的环境中,人们还有一种最后的自由,就是选择自己的态度。”

      积极主动的第二步是对自己的一切负责,勇敢面对人生。不要把不确定的或困难的事情一味搁置起来。比如说,有些同学认为英语重要,但学校不考试就不学英语;或者,有些同学觉得自己需要参加社团磨练人际关系,但是因为害羞就不积极报名。但是,我们必须认识到,不去解决也是一种解决,不做决定也是一个决定,这样的解决和决定将使你面前的机会丧失殆尽。对于这种消极、胆怯的作风,你终有一天会付出代价的。

      积极主动的第三步是要做好充分的准备:事事用心,事事尽力,不要等机遇上门;要把握住机遇,创造机遇。中国科技大学校长朱清时院士在大三时被分配到青海做铸造工人。但他不像其他同学那样放弃学习,整天打扑克、喝酒。他依然终日钻研数理化和英语。六年后,中国科学院要在青海做一个重要的项目,这时朱校长就脱颖而出,开始了他辉煌的事业。很多人可能说他运气好,被分配到缺乏人才的青海,才有这机会。但是,如果他没有努力学习,也无法抓住这个机遇。所以,做好充分的准备,当机遇来临时,你才能抓住它。

      积极主动的第四步是“以终为始”,积极地规划大学四年。任何规划都将成为你某个阶段的终点,也将成为你下一个阶段的起点,而你的志向和兴趣将为你提供方向和动力。如果不知道自己的志向和兴趣,你应该马上做一个发掘志向和兴趣的计划;如果不知道毕业后要做什么,你应该马上制定一个尝试新领域的计划;如果不知道自己最欠缺什么,你应该马上写一份简历,找你的老师、朋友打分,或自己审阅,看看哪里需要改进;如果毕业后想出国读博士,你应该想想如何让自己在申请出国前有具体的研究经验和学术论文;如果毕业后想进入某个公司工作,你应该收集该公司的招聘广告,以便和你自己的履历对比,看自己还欠缺哪些经验。只要认真制定、管理、评估和调整自己的人生规划,你就会离你自己的目标越来越近。

      掌控时间:事分轻重缓急,人应自控自觉

      除了积极主动的态度,大学生还要学会安排自己的时间,管理自己的事务。一位同学是这么描述大学生活的:

      “大学和高中相比似乎没有什么太大的区别,每天依旧是学习,每次考试后依旧是担心考试成绩……不同的只是大学里上网的时间和睡觉的时间多了很多,压力也小了很多。”

      这位同学并不明白,“时间多了很多”正是大学与高中之间巨大的差别。时间多了,就需要自己安排时间、计划时间、管理时间。

      安排时间出了做一个时间表外,更重要的是“事分轻重缓急”。在《高效能人士的七个习惯》一书中,作者史蒂芬.柯维提出,“重要事”和“紧急事”的差别是人们浪费时间的最大理由之一。因为人的惯性是先做最紧急的事,但这么做会导致一些重要的事被荒废掉。例如,我认为这篇文章里谈到的各种学习都是“重要的”,但它们不见得都是老师布置的必修课业,采纳我的建议的同学们依然会因为考试、交作业等紧急的事情而荒废了打好基础、学习做人等重要的事情。因此,每天管理时间的一种好方法是,早上确定今天要做的紧急事和重要事,睡前回顾一下,这一天有没有做到两者的平衡。

      每个人都有许多“紧急事”和“重要事”,想把每件事都做到最好是不切实际的。我建议大家把“必须做的事”和“尽量做的事”分开。必须做的事要做到最好,但尽量做的事尽力而为即可。建议大家用良好的态度和宽广的胸怀接受那些你暂时不能改变的事情,多关注那些你能够改变的事情。此外,还要注意生物钟的运行规律,按时作息,劳逸结合,这样才能在学习时有最好的状态。

      大学四年是最容易迷失方向的时期。大学生必须有自控的能力,让自己交些好朋友,学些好习惯,不要沉迷于对自己无益的习惯(如网络游戏)里。一位积极、主动的中国学生在“开复学生网”上劝告其他同学:“不要玩游戏,至少不要玩网络游戏。我所认识的专业水平比较高的大学朋友中没有一个玩网络游戏的。沉迷于网络游戏是对于现实的逃避,是不愿面对自己不足的一面。我认为,要脱离网络游戏,就得珍惜自己宝贵的大学时间,找到自己感兴趣的方向,做一些有意义并能给自己带来满足感的事情。”

      为人处事:培养友情,参与群体

      很多大学生入校时都是第一次离开父母,离开自己生长的环境。进入校园开始集体生活后,如何与同学、朋友以及社团的同事相处就成为了大学生学习内容的一部分。大学是大家最后一次可以在相对宽松的环境中学习、培养、训练如何与人相处的机会。在未来,人们在社会里、在工作中与人相处的能力会变得越来越重要,甚至超过了工作本身。所以,大学生要好好把握机会,培养自己的交流意识和团队精神。

      “人际交往能力不够强,人际圈子不够广,但又没有什么特长可以引起大家的注意,在社团里也不知道怎么和其他人有效地建立联系。”这是一些大学生在人际交往方面经常遇到的困惑。对于如何在大学期间提高人际交往能力,我的建议是:

      第一,以诚待人,以责人之心责己、以恕己之心恕人。对别人要抱着诚挚、宽容的胸襟,对自己要怀着自我批评、有过必改的态度。与人交往时,你怎样对待别人,别人也会怎样对待你。这就好比照镜子一样,你自己的表情和态度,可以从他人对你流露出的表情和态度中一览无遗。你若以诚待人,别人也会以诚待你。你若敌视别人,别人也会敌视你。最真挚的友情和最难解的仇恨都是由这种“反射”原理逐步造成的。因此,当你想修正别人时,你应该先修正自己。你想别人怎么对你,你就应该怎么对人。你想他人理解你,你就要首先理解他人。

      第二,培养真正的友情。如果能做到第一点,很多大学时的朋友就会成为你一辈子的知己。在一起求学和寻求自身发展的道路上,这样的友谊弥足珍贵。交朋友时,不要只去找与你性情相近或只会附和你的人做朋友。好朋友有很多种:乐观的朋友、智慧的朋友、脚踏实地的朋友、幽默风趣的朋友、激励你上进的朋友、提升你能力的朋友、帮你了解自己的朋友、对你说实话的朋友等等。此外,大学时谈恋爱也可以教你如何照顾别人,增进同理心和自控力,但恋爱这件事要随缘,不必为了谈恋爱而谈恋爱。

      第三,学习团队精神和沟通能力。社团是微观的社会,参与社团是步入社会前最好的磨练。在社团中,可以培养团队合作的能力和领导才能,也可以发挥你的专业特长。但更重要的是,你要做一个诚心诚意的服务者和志愿者,或在担任学生工作时主动扮演同学和老师之间沟通桥梁的角色,并以此锻炼自己的沟通能力,为同学和老师服务。这样的学习过程也不会很轻松,挫折是肯定有的,但是不要灰心,大学社团里的人际交往是一种不用“付学费”的学习,犯了错误也可以重头来过。

      第四,从周围的人身上学习。在班级里、社团中,多观察周围的同学,特别是那些你觉得交往能力和沟通能力特别强的同学,看他们是如何与人相处的。比如,看他们如何处理交往中的冲突、如何说服他人和影响他人、如何发挥自己的合作和协调能力、如何表达对他人的尊重和真诚、如何表示赞许或反对,如何在不冒犯他人的情况下充分展示个性等等。通过观察和模仿,你渐渐地会发现,自己的人际交往能力会有意想不到的改进。在学校里,每一个朋友都可以成为你的良师,他们的热心、幽默、机智、博学、正直、沟通、礼貌等品德都可以成为你的学习对象。同时那些你不喜欢的人和事也可以为你敲响警钟,警告你千万不要做那样的人和事。当然,你也应当慷慨地帮助每一个朋友,试着做他们的良师和模范。

      第五,提高自身修养和人格魅力。如果觉得没有特长、没有爱好可能会成为自己人际交往能力提高的一个障碍,那么,你可以有意识地去选择和培养一些兴趣爱好。共同的兴趣和爱好也是你与朋友建立深厚感情的途径之一。很多在事业上有所建树的人都不是只会闭门苦读的书呆子,他们大多都有自己的兴趣和爱好。我在微软亚洲研究院的同事中就有绘画、桥牌和体育运动方面的高手。业余爱好不仅是人际交往的一种方式,还可以让大家发掘出自己在读书以外的潜能。例如,体育锻炼既可以发挥你的运动潜能,也可以培养你的团队合作精神。如果真的没有什么兴趣爱好,那么,多读些好书丰富自己的知识也可以改进自己的人际交往能力,因为没有什么比智慧和渊博更能体现一个人的人格魅力了。

      所以,学会与人相处,这也是大学中的一门“必修课”。

      对大学生们的期望

      踏入大学校门时,你还是一个忙碌的、青涩的、被动的、为分数读书的、被家庭保护着的中学毕业生。

      就读大学时,你应当掌握七项学习,学好自修之道、基础知识、实践贯通、兴趣培养、积极主动、掌控时间、为人处事。

      经过大学四年,你会从思考中确立自我,从学习中寻求真理,从独立中体验自主,从计划中把握时间,从交流中锻炼表达,从交友中品味成熟,从实践中赢得价值,从兴趣中攫取快乐,从追求中获得力量。

      离开大学时,只要做到了这些,你最大的收获将是“对什么都可以拥有的自信和渴望”。你就能成为一个有潜力、有思想、有价值、有前途的中国未来的主人翁。

      所以,我认为大学四年应是这样度过。

    Eclipse启动错误

    Eclipse启动错误
    Eclipse启动错误

    错误提示:

    java.lang.ClassNotFoundException: javax.xml.parsers.SAXParserFactory

    原因:

    eclipse用了不恰当的jre.

    eclipse3需要jre1.4支持,但通过path变量找到的jre环境要低,

    如jrm1.3. 

    此类问题在《Java深度历险》一书中有详细介绍。

    解决办法:

    1, 在系统path变量中,将正确的jdk\bin目录设置在前,设置后注销,重新登录.

    2,eclipse启动目录创建批处理文件,eclipse启动时带vm参数,指定javaw

    eclipse -vm C:\j2sdk1.4.2_04\bin\javaw