Vincent

          Vicent's blog
          隨筆 - 74, 文章 - 0, 評論 - 5, 引用 - 0
          數據加載中……

          2006年9月1日

          一、 橋梁(Bridge)模式

               摘要: 一、?橋梁(Bridge)模式 橋梁模式是一個非常有用的模式,也是比較復雜的一個模式。熟悉這個模式對于理解面向對象的設計原則,包括"開-閉"原則(OCP)以及組合/聚合復用原則(CARP)都很有幫助。理解好這兩個原則,有助于形成正確的設計思想和培養良好的設計風格。 注:《Java與模式》一書認為Bridge模式不是一個使用頻率很高的模式,我不太贊同,我認為Bridge模式中...  閱讀全文

          posted @ 2006-09-18 13:38 Binary 閱讀(3289) | 評論 (2)編輯 收藏

          Hibernate Validator 簡介

          在項目的業務屬性中,你是不是要經常驗證屬性的取值范圍呢. 想要了解比較優美的解決方案嗎???????????

          看看Hibernate Validator 是怎么做的吧.一見到她,相信你就會說: Oh God, 這就是我需要的.

          任何獲得Matrix授權的網站,轉載請保留以下作者信息和鏈接:
          作者:icess(作者的blog:http://blog.matrix.org.cn/page/icess)
          關鍵字:Hibernate Validator

          用Annotations 給類或者類的屬性加上約束(constraint),在運行期檢查屬性值是很優雅的.Hibernate Validator就是這樣的一個框架.該框架是十分容易的(就像參考文檔中宣稱的那樣),幾乎沒有什么學習曲線,Validator 是一個驗證框架 不需要和Hibernate的其他部分綁定就可以使用,只要在你的項目中添加Hibernate-annotations.jar庫就可以了.那么下面就讓我們看看怎么使用吧.

          Person.java 類

          /*
          ? *?Created?on?2006-1-12 Person.java
          ? *?@author?
          ? */
          package? test.annotation.validator;

          import? org.hibernate.validator.Length;
          import? org.hibernate.validator.Min;
          import? org.hibernate.validator.Valid;
           

          //@Serializability? //測試自定義約束
          public?class? Person?{

          ?? private? String?name;
          ?? private?int? age;
          ?? private? Address?address;
          ??
          ?? public? Person()?{}
          ??
          ?? @Valid //注意此處
          ?? public? Address?getAddress()?{
          ???? return? address;
          ?? }
          ?? public?void? setAddress(Address?address)?{
          ???? this .address?=?address;
          ?? }
          ??
          ?? @Min(value?=? 1 )
          ?? public?int? getAge()?{
          ???? return? age;
          ?? }
          ?? public?void? setAge( int? age)?{
          ???? this .age?=?age;
          ?? }
          ??
          ?? @Length(min?=? 4 )
          ?? public? String?getName()?{
          ???? return? name;
          ?? }
          ?? public?void? setName(String?name)?{
          ???? this .name?=?name;
          ?? }
          }

           

          Address.java 類

          /*
          ? *?Created?on?2006-1-12 Address.java
          ? *?@author?
          ? */
          package? test.annotation.validator;

          import? org.hibernate.validator.Length;
          import? org.hibernate.validator.Max;
          import? org.hibernate.validator.Min;

          public?class? Address?{

          ?? private? String?street;
          ?? private?int? num;
          ??
          ?? public? Address()?{}
          ??
          ?? @Min(value?=? 1 )
          ?? @Max(value?=? 100 )
          ?? public?int? getNum()?{
          ???? return? num;
          ?? }
          ?? public?void? setNum( int? num)?{
          ???? this .num?=?num;
          ?? }
          ??
          ?? @Length(min?=? 3 ,max?=? 8 )
          ?? public? String?getStreet()?{
          ???? return? street;
          ?? }
          ?? public?void? setStreet(String?street)?{
          ???? this .street?=?street;
          ?? }
          }

          上面是兩個用 Validator Annotations 注釋的 類. 每個屬性都用 約束限制了.? 下面看看測試的類吧:

          TestValidator.java 類

          /*
          ? *?Created?on?2006-1-12
          ? *?@author?icerain
          ? */
          package? test.annotation.validator;

          import? org.hibernate.validator.ClassValidator;
          import? org.hibernate.validator.InvalidValue;


          public?class? TestValidator?{
          ?? public?void? test()?{
          ???? Address?add?=? new? Address();
          ???? add.setNum( 0 );
          ???? add.setStreet( "1" );
          ????
          ???? Person?p?=? new? Person();
          ???? p.setAddress(add);
          ???? p.setAge( 0 );
          ???? p.setName( "ice" );
          ????
          ???? /******************Test?validator?********/

          ??? // 注意該處只驗證了Person 為了說明 @Valid 注釋的使用
          ???? ClassValidator<Person>?classValidator?=? new? ClassValidator<Person>?(Person. class );
          ???? InvalidValue[]?validMessages?=?classValidator.getInvalidValues(p);
          ???? for? (InvalidValue?value?:?validMessages)?{
          ??????
          ???? System.out.println( "InvalidValue?的長度是:"? +?validMessages.length
          ???????? + "?.?驗證消息是:?"? +?value.getMessage()?
          ???????? +? "?.?PropertyPath?是:"? +?value.getPropertyPath()
          ???????? + "?.\n\t?PropertyName?是:?"? +value.getPropertyName()
          ???????? +? "Value?是:?"? +?value.getValue()
          ???????? + "?Bean?是:?" +?value.getBean()
          ???????? + "\n\t?BeanClass?是:"? +?value.getBeanClass());
          ???? }
          ?? }
          ??
          ?? public?static?void? main(String[]?args)?{
          ???? new? TestValidator().test();
          ?? }
          }

           

          程序的輸出如下

          InvalidValue 的長度是:4 . 驗證消息是: 必須大于等于 1 . PropertyPath 是:age .

          PropertyName 是: age. Value 是: 0 Bean 是: test.annotation.validator.Person@dd87b2

          BeanClass 是:class test.annotation.validator.Person

          InvalidValue 的長度是:4 . 驗證消息是: 長度必須介于 4 與 2147483647 之間 . PropertyPath 是:name .

          PropertyName 是: name. Value 是: ice Bean 是: test.annotation.validator.Person@dd87b2

          BeanClass 是:class test.annotation.validator.Person

          InvalidValue 的長度是:4 . 驗證消息是: 必須大于等于 1 . PropertyPath 是:address.num .

          PropertyName 是: num. Value 是: 0 Bean 是: test.annotation.validator.Address@197d257

          BeanClass 是:class test.annotation.validator.Address

          InvalidValue 的長度是:4 . 驗證消息是: 長度必須介于 3 與 8 之間 . PropertyPath 是:address.street .

          PropertyName 是: street. Value 是: 1 Bean 是: test.annotation.validator.Address@197d257

          BeanClass 是:class test.annotation.validator.Address

          可以看出不滿足約束的值都被指出了.

          同時該句: ClassValidator<Person>?classValidator?=?new?ClassValidator<Person>?(Person.class);

          我們只驗證了 Person. 在Person里面的Address的屬性 由于有@Valid Annotations 所以 Address的相關屬性也被機聯驗證了 .

          如果 把@Valid Annotations 去掉,結果如下:

          InvalidValue 的長度是:2 . 驗證消息是: 必須大于等于 1 . PropertyPath 是:age .

          PropertyName 是: age. Value 是: 0 Bean 是: test.annotation.validator.Person@18fef3d

          BeanClass 是:class test.annotation.validator.Person

          InvalidValue 的長度是:2 . 驗證消息是: 長度必須介于 4 與 2147483647 之間 . PropertyPath 是:name .

          PropertyName 是: name. Value 是: ice Bean 是: test.annotation.validator.Person@18fef3d

          BeanClass 是:class test.annotation.validator.Person

          可以看出 沒有驗證 Address.

          當然了 ,你還可以只驗證一個屬性 , 沒有必要驗證整個類.只需要在調用 classValidator.getInvalidValues(p,"age")方法時 加上你要驗證的屬性就可以了.如我們只想驗證age 屬性 把代碼改為如下所示:

          InvalidValue[] validMessages = classValidator.getInvalidValues(p,"age"); / /只驗證age 屬性

          運行結果如下:

          InvalidValue 的長度是:1 . 驗證消息是: 必須大于等于 1 . PropertyPath 是:age .

          PropertyName 是: age. Value 是: 0 Bean 是: test.annotation.validator.Person@1457cb

          BeanClass 是:class test.annotation.validator.Person

          只是驗證了 age 屬性.

          怎么樣 ,很簡單吧. 關于 Hibernate Validator 內建的驗證Annotations 大家可以看看 API 或者 參考文檔(中文版我正在翻譯中 請訪問我的 Blog 獲得最新信息).

          如果你要寫自己的約束呢 , 你不用擔心 ,這也是很容易的. 任何約束有兩部分組成: [約束描述符 即注釋]the constraint descriptor (the annotation) 和[約束validator 即 實現類] the constraint validator (the implementation class).下面我們擴展Hibernate Test suit 中的一個Test 來講解一下.

          首先: 要聲明一個 constraint descriptor .如下:

          package? test.annotation.validator;

          import? java.lang.annotation.Documented;
          import?static? java.lang.annotation.ElementType.TYPE;
          import?static? java.lang.annotation.ElementType.FIELD;
          import?static? java.lang.annotation.ElementType.METHOD;
          import? java.lang.annotation.Retention;
          import?static? java.lang.annotation.RetentionPolicy.RUNTIME;
          import? java.lang.annotation.Target;

          import? org.hibernate.validator.ValidatorClass;

          /**
          ? *?Dummy?sample?of?a?bean-level?validation?annotation
          ? *
          ? *? @author? Emmanuel?Bernard
          ? */
          @ValidatorClass(SerializabilityValidator. class )
          @Target({METHOD,FIELD,TYPE})
          @Retention(RUNTIME)
          @Documented
          public? @interface?Serializability?{
          ?? int? num()? default? 11 ;
          ?? String?message()? default? "bean?must?be?serialiable" ;
          }

          @ValidatorClass(SerializabilityValidator. class ) 指出了 constraint validator 類.

          @Target({METHOD,FIELD,TYPE})
          @Retention(RUNTIME)
          @Documented????????????????

          這幾個我就不用解釋了吧.

          Serializability?里面聲明了一個 message 顯示約束的提示信息. num 只是為了說明一個方面 在這里面沒有實際用途用 .

          然后就是 實現一個 constraint validator 類 該類要實現Validator<ConstraintAnnotation>.這里是SerializabilityValidator.java 如下:

          //$Id:?SerializabilityValidator.java,v?1.3?2005/11/17?18:12:11?epbernard?Exp?$
          package? test.annotation.validator;

          import? java.io.Serializable;

          import? org.hibernate.validator.Validator;

          /**
          ? *?Sample?of?a?bean-level?validator
          ? *
          ? *? @author? Emmanuel?Bernard
          ? */
          public?class? SerializabilityValidator? implements? Validator<Serializability>,?Serializable?{
          ?? public?boolean? isValid(Object?value)?{
          ??? //這里只是Validator 里面的 實現驗證規則的 方法. value 是要驗證的值.
          ???? System.out.println( "IN?SerializabilityValidator?isValid:" +value.getClass()+ ":?"? +value.toString());
          ???? return? value?instanceof?Serializable;
          ??}

          ??public?void?initialize(Serializability?parameters)?{
          ????//?在這里可以 取得
          constraint descriptor 里面的屬性 如上面我們聲明的 num
          ???? System.out.println( "IN?SerializabilityValidator:?parameters:" +?parameters.num()?);
          ?? }
          }

          然后在你的類中應用@Serializability? 就可以約束一個類實現Serializable 接口了. 如下:

          在我們的Person.java類 添加@Serializability? Annotations ,把Person.java 中的 //@Serializability //測試自定義約束 注釋去掉就ok了.

          運行結果如下:

          InvalidValue 的長度是:3 . 驗證消息是: bean must be serialiable . PropertyPath 是:null .

          PropertyName 是: null. Value 是: test.annotation.validator.Person@1a73d3c Bean 是: test.annotation.validator.Person@1a73d3c

          BeanClass 是:class test.annotation.validator.Person

          現在把Person類實現 java.io.Serializable 接口 則沒有出現 驗證錯誤消息.

          消息的國際化也是很簡單的,把 Serializability? 中的message 改為以{}擴住的 屬性文件的Key就可以了

          public? @interface?Serializability?{
          ?? int? num()? default? 11 ;
          ?? String?message()? default? "{Serializable}"; //"bean?must?be?serialiable"; //消息的國際化
          }

          然后編輯資料文件. 注意 該資源文件中要包括 Hibernate Validator 內建的資源. 可以在該org\hibernate\validator\resources 包里面的資源文件基礎上修改 ,在打包里面 這樣就可以了. 自己打包可能不太方便.你可以把該包里面的文件復制出來.然后放到你自己的項目包下在自己編輯, 該測試中 我是放在 test\resources 包下的.

          然后在 資源文件中添加 Serializable = '''''' 這么一行, 樣例如下:

          #DefaultValidatorMessages.properties (DefaultValidatorMessages_zh.properties 不再列出^_^)

           

          #下面是 Hibernate Validator 內建的國際化消息

          validator.assertFalse= assertion failed

          validator.assertTrue= assertion failed

          validator.future= must be a future date

          validator.length= length must be between {min} and {max}

          validator.max= must be less than or equal to {value}

          validator.min= must be greater than or equal to {value}

          validator.notNull= may not be null

          validator.past= must be a past date

          validator.pattern= must match "{regex}"

          validator.range= must be between {min} and {max}

          validator.size= size must be between {min} and {max}

          #下面是自定義的消息

          Serializable= Bean not Serializable? //加上自己定義的國際化消息.

          在構造 ClassValidator 時要添上 資源文件 如下:(在測試類中)

          ClassValidator<Person> classValidator = new ClassValidator<Person> (Person.class,ResourceBundle.getBundle("test.resources.DefaultValidatorMessages"));//加載資源

          這樣就可以了 .? 當然 你還可以 更改 Hibernate Validator 的消息(不是在上面的資源文件中直接修改 validator.length = ... 等等 ) , 還記得 Validator 注釋中有個 message 元素嗎? 你以前用的都是默認值,現在你可以該為你自己定義的了. 如:validator.length 我把他改為 "該字符串的長度不符合規定范圍范圍". 在資源文件中添加一行鍵值屬性對(key定義為 "myMsg")如下:

          myMsg=該字符串的長度不符合規定范圍范圍

          并且還要在 @Length 注釋中提供message的引用的key 如下 @Length(min = 4,message = "{ myMsg }")

          再一次運行測試 ,我們就可以看到上面兩條自定義綁定的消息了 .如下:

          InvalidValue 的長度是:3 . 驗證消息是: Bean 不是 可 Serializable . PropertyPath 是:null .
          PropertyName 是: null. Value 是: test.annotation.validator.Person@1bd4722 Bean 是: test.annotation.validator.Person@1bd4722
          BeanClass 是:class test.annotation.validator.Person


          InvalidValue 的長度是:3 . 驗證消息是: 該字符串的長度不符合規定范圍范圍 . PropertyPath 是:name .
          PropertyName 是: name. Value 是: ice Bean 是: test.annotation.validator.Person@1bd4722
          BeanClass 是:class test.annotation.validator.Person

          怎么樣,比你想象的簡單吧.

          OK 上面我們討論了 Hibernate Validator 的主要用法: 但是 該框架有什么用呢? ^_^

          看到這里其實不用我在多說了 大家都知道怎么用,什么時候用. 作為一篇介紹性文章我還是在此給出一個最常用的例子吧,更好的使用方式大家慢慢挖掘吧.

          比如 : 你現在在開發一個人力資源(HR)系統 (其實是我們ERP課程的一個作業 ^_^), 里面要處理大量的數據,尤其是在輸入各種資料時 如 登記員工信息. 如果你公司的員工的年齡要求是18 -- 60 那么你所輸入的年齡就不能超出這個范圍. 你可能會說這很容易啊 , 不用Validator就可以解決啊.這保持數據前驗證就可以啦 如if ( e.getAge() > 60 || e.getAge() < 18 ) ........ 給出錯誤信息 然后提示重新輸入不就OK啦 用得著 興師動眾的來個第三方框架嗎?

          是啊 當就驗證這一個屬性時, 沒有必要啊 ! 但是一個真正的HR 系統,會只有一個屬性要驗證嗎? 恐怕要有N多吧

          你要是每一個都那樣 寫一段驗證代碼 是不是很煩啊 ,況且也不方便代碼重用. 現在考慮一些 Validator 是不是更高效啊,攔截到 約束違例的 屬性 就可以直接得到 國際化的消息 可以把該消息顯示到一個彈出對話框上 提示更正? !

          Validator的用處不只這一種 ,你可以想到如何用呢 ! 歡迎發表你的高見!!

          OK 到此 我們的 Hibernate Validator 之旅就要先告一段落了 . 希望這是令你心曠神怡的一次寒冬之旅,

          把你學到的應用到你的項目中吧,一定會提高你的生產率的. 相信我 ,沒錯的? ^_^ !

          posted @ 2006-09-01 14:05 Binary 閱讀(459) | 評論 (0)編輯 收藏

          Hibernate Annotations 實戰(二)

          -- hbm.xml 與 Annotations 性能比較

          任何獲得Matrix授權的網站,轉載請保留以下作者信息和鏈接:
          作者:icess(作者的blog:http://blog.matrix.org.cn/page/icess)
          關鍵字:Hibernate Validator

          我在前面一篇文章<Hibernate Annotations 實戰-- 從 hbm.xml 到 Annotations>:

          中,有很多開發者在談論中提到,有沒有必要從 hbm.xml 往 Annotations 上轉移. 那么在這篇文章中我們就來討論一下 hbm.xml 與 Annotations的優缺點,看看那種情況最適合你.

          首先,討論一下 xml 配置文件的優點, 個人認為主要優點就是當你改變底層配置時 不需要改變和重新編譯代碼,只需要在xml 中更改就可以了,例如 Hibernate.cfg.xml 當你要更改底層數據庫時, 只要更改配置文件就可以了.Hibernate會為你做好別的事情.

          那么xml的缺點呢,個人認為有以下幾點:

          • 描述符多,不容易記憶,掌握 要深入了解還有看DTD文件

          • 無法做自動校驗,需要人工查找

          • 讀取和解析xml配置要消耗一定時間,導致應用啟動慢,不便于測試和維護

          • 當系統很大時,大量的xml文件難以管理

          • 運行中保存xml配置需要消耗額外的內存

          • 在O/R Mapping的時候需要在java文件和xml配置文件之間交替,增大了工作量

          其中第一 二點 借助于先進的IDE 可能不是什么問題. 但是對初學者還是個問題 ^_^.

           

          下面我們看看 Annotations的 特性吧! 可以解決xml遇到的問題,有以下優點

          • 描述符減少。以前在xml配置中往往需要描述java屬性的類型,關系等等。而元數據本身就是java語言,從而省略了大量的描述符

          • 編譯期校驗。錯誤的批注在編譯期間就會報錯。

          • 元數據批注在java代碼中,避免了額外的文件維護工作

          • 元數據被編譯成java bytecode,消耗的內存少,讀取也很快,利于測試和維護

          關于 映射文件是使用 hbm.xml 文件還是使用 Annotations 我們來看看2者的性能吧. 先聲明一下,個人認為映射文件一旦配置好就不會在很大程度上改變了.所以使用xml文件并不會帶來很大的好處.如果你認為 映射文件在你的項目中也經常變化,比如一列String數據 ,今天你使用 length="16" 明天你認為 該數據的長度應該更長才能滿足業務需求 于是改為length="128" 等等類似的問題 . 如果你經常有這方面的變動的話,下面的比較你可以不用看了 , 你應該使用 xml文件 因為Annotations 無法很好的滿足你的要求.

          現在讓我們就來看看2者的性能比較吧.

          (說明: 這里只是比較查找 插入 的時間快慢,沒有比較除運行時間以外的其他性能,如 內存占用量 等等)

          先來看看測試程序和配置.

          首先在 Hibernate.cfg.xml 文件中去掉了

          <property name="hibernate.hbm2ddl.auto">update</property>

          這一行, 因為在前面的實驗中以及建立了數據庫表了 不再需要更新了.如果你是第一次運行該例子 還是要該行的.

          Test.java 如下:

          /*
          ?*?Created?on?2005
          ?*?@author?
          ?*/
          package?test.hibernate.annotation;

          import?org.hibernate.Session;
          import?org.hibernate.Transaction;

          public?class?Test?{
          ??
          ??public?static?void?main(String?[]?args)?{
          ????long?start?=?0;
          ????long?end?=?0;
          ????start?=?System.currentTimeMillis();??//程序開始時間
          ????
          ????Session?s?=?HibernateUtil.currentSession();
          ????long?mid?=??System.currentTimeMillis();??//初始化完畢的時間 (可能此時并沒有初始化完畢^_^)
          ????
          ????Transaction?tx?=?s.beginTransaction();????
          ????/********************測試讀取的代碼************************/
          ????Person?p?=?null;
          ????for(int?i?=?1;?i?<=?100;?i?++)?{
          ????p?=?(Person)?s.get(Person.class,?i);
          ????System.out.println(p.getName());
          ????}
          ????System.out.println(p.getName());

          ??? /********************測試讀取1次的代碼************************/
          ????Person?p?=?null;
          ????p?=?(Person)?s.get(Person.class,?1);
          ????System.out.println(p.getName());
          ????/*********************測試插入的代碼*************************************/
          ????/*
          ????for?(int?i?=?0;?i?<?100;?i?++)?{
          ??????Person?p?=?new?Person();
          ??????p.setAge(i+1);
          ??????p.setName("icerain"+i);
          ??????p.setSex("male"+i);
          ??????s.save(p);
          ??????s.flush();
          ????}
          ????*/
          ????tx.commit();
          ????HibernateUtil.closeSession();
          ????
          ????end?=?System.currentTimeMillis();?//測試結束時間
          ????System.out.println("String[]?-?start?time:?"?+?start);
          ????System.out.println("String[]?-?end?time:?"?+?end);
          ????System.out.println("Init?time?:?"?+?(mid-start)); // 打印初始化用的時間
          ????System.out.println("Last?time?is?:"?+(end?-?mid)?); //打印 數據操作的時間
          ????System.out.println("Total?time?:?"?+(end?-?start)); //打印總時間
          ?
          ?}
          }

          Annotations 包中的Person.java 如下

          package?test.hibernate.annotation;

          import?java.util.LinkedList;
          import?java.util.List;

          import?javax.persistence.AccessType;
          import?javax.persistence.Basic;
          import?javax.persistence.Entity;
          import?javax.persistence.GeneratorType;
          import?javax.persistence.Id;
          import?javax.persistence.Table;
          import?javax.persistence.Transient;

          /**
          ?*?Person?generated?by?hbm2java
          ?*/

          @SuppressWarnings("serial")
          @Entity(access?=?AccessType.PROPERTY)
          @Table
          public?class?Person?implements?java.io.Serializable?{
          ??private?Integer?id;
          ??private?String?name;
          ??private?String?sex;
          ??private?Integer?age;
          ??private?List?list?=?new?LinkedList();

          ??//?Constructors
          ??/**?default?constructor?*/
          ??public?Person()?{
          ??}

          ??/**?constructor?with?id?*/
          ??public?Person(Integer?id)?{
          ????this.id?=?id;
          ??}

          ??//?Property?accessors
          ??@Id(generate=GeneratorType.AUTO)
          ??public?Integer?getId()?{
          ????return?this.id;
          ??}

          ??public?void?setId(Integer?id)?{
          ????this.id?=?id;
          ??}

          ??@Basic
          ??public?String?getName()?{
          ????return?this.name;
          ??}

          ??public?void?setName(String?name)?{
          ????this.name?=?name;
          ??}

          ??@Basic
          ??public?String?getSex()?{
          ????return?this.sex;
          ??}

          ??public?void?setSex(String?sex)?{
          ????this.sex?=?sex;
          ??}

          ??@Basic
          ??public?Integer?getAge()?{
          ????return?this.age;
          ??}

          ??public?void?setAge(Integer?age)?{
          ????this.age?=?age;
          ??}
          ??@Transient
          ??public?List?getList()?{
          ????return?list;
          ??}
          ??public?void?setList(List?list)?{
          ????this.list?=?list;
          ??}

          }

          其他的代碼幾乎沒有改變:

          下面的每種類型的測試都測試了3次以上, 取中間的測試時間.

          測試機器配置:

          CPU:? AMD Athlon (xp) 2000+

          內存: 784880KB

          硬盤: 三星 SP0812N

          讀取一次??的比較:(單位: 毫秒)

          使用Annotations 的測試數據使用Xml文件的測試數據簡要說明
          Init time : 2444Init time : 2431測試前我認為該項結果xml應該比較大,要讀取映射文件啊,實際情況不是這樣,不知道為什么?
          Last time is :62Last time is :85相差比較大不知道為什么?
          Total time : 2506Total time : 2516xml文件總體上慢了一點

          ?? 讀取100次的比較:

          使用Annotations 的測試數據使用Xml文件的測試數據簡要說明
          Init time : 2437Init time : 2422和前面初始化差不多
          Last time is :438Last time is :484有時間差
          Total time : 2875Total time : 2906也是xml文件總體上慢了一點

          插入100次的比較:

          使用Annotations 的測試數據使用Xml文件的測試數據簡要說明
          Init time : 2453Init time : 2469和前面初始化差不多
          Last time is :469Last time is :656有時間差
          Total time : 2922Total time : 3125也是xml文件總體上慢了一點

          從上面的三次對比中大家可以看到 初始化的部分幾乎兩者是一樣的, 在數據操作上面 使用xml文件 總是比使用Annotations 慢一點.在我們只操縱一個只有幾個屬性的小持久化類的操作中就有 幾十毫秒的差距. 幾十毫秒在計算機中算不算很大 大家應該都知道,我就不在多說了.

          總結: 經過 xml 文件 和Annotations 的優缺點和 性能上的對比.現在使用那個作為你持久化映射策略.我相信大家都會正確選擇的.

          測試后記: 經過多次測試 感覺有時候很不穩定 ,有的時候很穩定不知道是測試有問題還是別的問題.大家可以自己測試一下. 有什么新的發現 請大家討論討論.

          posted @ 2006-09-01 14:04 Binary 閱讀(342) | 評論 (0)編輯 收藏

          第一個Hibernate with Annotation程式

          Hibernate是ORM的解決方案,其底層對數據庫的操作依賴于JDBC,所以您必須先取得JDBC驅動程序,在這邊所使用的是MySQL,所以您必須至 MySQL? Connector/J 取得MySQL的JDBC驅動程序。

          接下來至
          Hibernate 官方網站 取得Hibernate 3.2Hibernate Annotations 3.2

          您必須安裝JDK 5.0才可以使用Hibernate Annotations的功能。

          解開Hibernate 3.2的zip檔案后,當中的hibernate3.jar是必要的,而在lib目錄中還包括了許多jar檔案,您可以在 Hibernate 3.0官方的參考手冊 上找到這些jar的相關說明,其中必要的是 antlr、dom4j、CGLIB、asm、Commons Collections、Commons Logging、 EHCache,Hibernate底層還需要Java Transaction API,所以您還需要jta.jar。

          解開Hibernate Annotations 3.2的zip檔案后,您需要hibernate-annotations.jar、ejb3-persistence.jar這兩個檔案。

          到這邊為止,總共需要以下的jar檔案:


          Hibernate可以運行于單機之上,也可以運行于Web應用程序之中,如果是運行于單機,則將所有用到的jar檔案(包括JDBC驅動程序)設定至CLASSPATH中,如果是運行于Web應用程序中,則將jar檔案置放于WEB-INF/lib中。

          如果您還需要額外的Library,再依需求加入,例如JUnit、Proxool等等,接下來可以將etc目錄下的 log4j.properties復制至Hibernate項目的Classpath下,并修改一下當中的 log4j.logger.org.hibernate為error,也就是只在在錯誤發生時顯示必要的訊息。

          接著設置基本的Hibernate配置文件,可以使用XML或Properties檔案,這邊先使用XML,檔名預設為hibernate.cfg.xml:

          <?xml version="1.0" encoding="utf-8"?>
          <!DOCTYPE hibernate-configuration PUBLIC
          ? "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
          ? "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
          ?
          <hibernate-configuration>
          ??? <session-factory>
          ??????? <!-- 顯示實際操作數據庫時的SQL -->
          ??????? <property name="show_sql">true</property>
          ??????? <!-- SQL方言,這邊設定的是MySQL -->
          ??????? <property name="dialect">org.hibernate.dialect.MySQLDialect</property>
          ??????? <!-- JDBC驅動程序 -->
          ??????? <property name="connection.driver_class">com.mysql.jdbc.Driver</property>
          ??????? <!-- JDBC URL -->
          ??????? <property name="connection.url">jdbc:mysql://localhost/demo</property>
          ??????? <!-- 數據庫使用者 -->
          ??????? <property name="connection.username">root</property>
          ??????? <!-- 數據庫密碼 -->
          ??????? <property name="connection.password">123456</property>
          ?
          ??????? <!-- 以下設置對象與數據庫表格映像類別 -->
          ??????? <mapping class="onlyfun.caterpillar.User"/>
          ??? </session-factory>
          </hibernate-configuration>

          這邊以一個簡單的單機程序來示范Hibernate的配置與功能,首先作數據庫的準備工作,在MySQL中新增一個demo數據庫,并建立user表格:
          CREATE TABLE user (
          id INT(11) NOT NULL auto_increment PRIMARY KEY,
          name VARCHAR(100) NOT NULL default'',
          age INT
          );
          對于這個表格,您有一個User類別與之對應,表格中的每一個字段將對應至User實例上的Field成員。

          package onlyfun.caterpillar;
          ?
          import javax.persistence.*;
          ?
          @Entity
          @Table(name="user") // 非必要,在表格名稱與類別名稱不同時使用
          public class User {
          ? @Id
          ? @GeneratedValue(strategy=GenerationType.AUTO)
          ??? private Integer id;
          ?
          ? @Column(name="name") // 非必要,在字段名稱與屬性名稱不同時使用
          ??? private String name;
          ?
          ? @Column(name="age")
          ??? private Integer age; // 非必要,在字段名稱與屬性名稱不同時使用
          ???
          ??? // 必須要有一個預設的建構方法
          ??? // 以使得Hibernate可以使用Constructor.newInstance()建立對象
          ??? public User() {
          ??? }
          ?
          ??? public Integer getId() {
          ??????? return id;
          ??? }
          ?
          ??? public void setId(Integer id) {
          ??????? this.id = id;
          ??? }
          ?
          ??? public String getName() {
          ??????? return name;
          ??? }
          ?
          ??? public void setName(String name) {
          ??????? this.name = name;
          ??? }
          ???
          ??? public Integer getAge() {
          ??????? return age;
          ??? }
          ?
          ??? public void setAge(Integer age) {
          ??????? this.age = age;
          ??? }
          }

          其中id是個特殊的屬性,Hibernate會使用它來作為主鍵識別,您可以定義主鍵產生的方式,這邊設定為自動產生主鍵,可以看到,實體標識,主鍵生成,以及相關映像,都可以使用Annotation來完成。

          接下來撰寫一個測試的程序,這個程序直接以Java程序設計人員熟悉的語法方式來操作對象,而實際上也直接完成對數據庫的操作,程序將會將一筆數據存入表格之中:
          package onlyfun.caterpillar;
          ?
          import org.hibernate.SessionFactory;
          import org.hibernate.Session;
          import org.hibernate.Transaction;
          import org.hibernate.cfg.AnnotationConfiguration;
          import org.hibernate.cfg.Configuration;
          ?
          public class HibernateAnnotationDemo {
          ?
          ??? public static void main(String[] args) {
          ??????? // 需要AnnotationConfiguration讀取Annotation訊息
          ??????? Configuration config = new AnnotationConfiguration().configure();
          ??????? // 根據 config 建立 SessionFactory
          ??????? // SessionFactory 將用于建立 Session
          ??????? SessionFactory sessionFactory = config.buildSessionFactory();
          ?
          ??????? // 將持久化的物件
          ??????? User user = new User();
          ??????? user.setName("caterpillar");
          ??????? user.setAge(new Integer(30));????
          ?
          ??????? // 開啟Session,相當于開啟JDBC的Connection
          ??????? Session session = sessionFactory.openSession();
          ??????? // Transaction表示一組會話操作
          ??????? Transaction tx= session.beginTransaction();
          ??????? // 將對象映像至數據庫表格中儲存
          ??????? session.save(user);
          ??????? tx.commit();
          ??????? session.close();
          ??????? sessionFactory.close();
          ??????
          ??????? System.out.println("新增資料OK!請先用MySQL觀看結果!");
          ??? }
          }

          注意,使用Annotation時,需要的是AnnotationConfiguration類別。

          如您所看到的,程序中只需要直接操作User對象,并進行Session與Transaction的相關操作,Hibernate就會自動完成對數據庫的操作,您看不到任何一行JDBC或SQL的陳述,撰寫好以上的各個檔案之后,各檔案的放置位置如下:


          接著可以開始運行程序,結果如下:
          Hibernate: insert into user (name, age) values (?, ?)
          新增資料OK!請先用MySQL觀看結果!

          執行結果中顯示了Hibernate所實際使用的SQL,由于這個程序還沒有查詢功能,所以要進入MySQL中看看新增的數據,如下:
          mysql> select * from user;
          +----+-----------------+------+
          | id??? | name???????? | age? |
          +----+-----------------+------+
          |? 1??? | caterpillar? | 30?? |
          +----+-----------------+------+
          1 row in set (0.03 sec)

          posted @ 2006-09-01 14:00 Binary 閱讀(305) | 評論 (0)編輯 收藏

          Hibernate Annotations 實戰

               摘要: 任何獲得Matrix授權的網站,轉載請保留以下作者信息和鏈接: 作者:icess(作者的blog:http://blog.matrix.org.cn/page/icess)關鍵字:Hibernate Validator 下面讓我們先看一個通常用 hbm.xml 映射文件的例子. 有3個類 .HibernateUtil.java 也就是 Hibernate文檔中推薦的工具類,Pers...  閱讀全文

          posted @ 2006-09-01 13:59 Binary 閱讀(409) | 評論 (0)編輯 收藏

          在filter中關閉session

          利用Thread-Specific Storage撰寫一個HibernateUtil

          HibernateSessionUtil.java
          								import java.io.Serializable;

          import net.sf.hibernate.HibernateException;
          import net.sf.hibernate.Session;
          import net.sf.hibernate.SessionFactory;
          import net.sf.hibernate.Transaction;

          public class HibernateSessionUtil implements Serializable
          {
          publicstaticfinal ThreadLocal tLocalsess = new ThreadLocal();

          publicstaticfinal ThreadLocal tLocaltx = new ThreadLocal();

          /*
          * getting the thread-safe session for using
          */
          publicstatic Session currentSession(){
          Session session = (Session) tLocalsess.get();

          //open a new one, if none can be found.
          try{
          if (session == null){
          session = openSession();
          tLocalsess.set(session);
          }
          }catch (HibernateException e){
          thrownew InfrastructureException(e);
          }
          return session;
          }

          /*
          * closing the thread-safe session
          */
          publicstatic void closeSession(){

          Session session = (Session) tLocalsess.get();
          tLocalsess.set(null);
          try{
          if (session != null && session.isOpen()){
          session.close();
          }

          }catch (HibernateException e){
          thrownew InfrastructureException(e);
          }
          }

          /*
          * begin the transaction
          */
          publicstatic void beginTransaction(){
          Transaction tx = (Transaction) tLocaltx.get();
          try{
          if (tx == null){
          tx = currentSession().beginTransaction();
          tLocaltx.set(tx);
          }
          }catch (HibernateException e){
          thrownew InfrastructureException(e);
          }
          }

          /*
          * close the transaction
          */
          publicstatic void commitTransaction(){
          Transaction tx = (Transaction) tLocaltx.get();
          try{
          if (tx != null && !tx.wasCommitted() && !tx.wasRolledBack())
          tx.commit();
          tLocaltx.set(null);
          }catch (HibernateException e){
          thrownew InfrastructureException(e);
          }
          }

          /*
          * for rollbacking
          */
          publicstatic void rollbackTransaction(){
          Transaction tx = (Transaction) tLocaltx.get();
          try{
          tLocaltx.set(null);
          if (tx != null && !tx.wasCommitted() && !tx.wasRolledBack()){
          tx.rollback();
          }
          }catch (HibernateException e){
          thrownew InfrastructureException(e);
          }
          }

          privatestatic Session openSession() throws HibernateException{
          return getSessionFactory().openSession();
          }

          privatestatic SessionFactory getSessionFactory() throws HibernateException{
          return SingletonSessionFactory.getInstance();
          }
          }

           filter中的程式碼如下

          HibernateSessionCloser.java
          								public class HibernateSessionCloser implements Filter{

          protected FilterConfig filterConfig = null;

          public void init(FilterConfig filterConfig)throws ServletException{
          this.filterConfig = filterConfig;
          }

          public void destroy(){
          this.filterConfig = null;
          }

          public void doFilter(ServletRequest request, ServletResponse response,
          FilterChain chain)
          throws IOException, ServletException {
          try{
          chain.doFilter(request, response);
          }
          finally{
          try{
          HibernateSessionUtil.commitTransaction();
          }catch (InfrastructureException e){
          HibernateSessionUtil.rollbackTransaction();
          }finally{
          HibernateSessionUtil.closeSession();
          }
          }

          }
          }

          然後在操作資料庫之前加上

          HibernateSessionUtil.beginTransaction();
          HibernateSessionUtil.currentSession();//取得Session

          posted @ 2006-09-01 13:51 Binary 閱讀(471) | 評論 (0)編輯 收藏

          acegi-security-sample-contacts-filter例子學習(二)

          功能實現分析

          這個例子使用了HSQL做數據庫,spring的AOP作為基礎,使用Acegi做安全控制組件。
          聯系人管理的web應用在啟動時候,會做一系列初始化動作:
          1. 讀取web.xml文件,

          2. 并解析文件里的內容。
          a) context-param元素。
          i. contextConfigLocation屬性。這個屬性定義了spring所需要的3個屬性文件。它們分別是:applicationContext -acegi-security.xml、applicationContext-common-business.xml、 applicationContext-common-authorization.xml
          ii. log4jConfigLocation屬性。這個屬性定義了log4j配置文件。

          b) filter元素。
          這里定義了acegi的一個過濾器。Acegi的大部分過濾器都是這樣配置的。使用FilterToBeanProxy組件,給它傳遞一個targetClass屬性。這個targetClass必須實現javax.servlet.Filter接口。
          這里配置的是FilterChainProxy。這個FilterChainProxy比較好用,可以為它定義一串filter屬性。這些filter將會按照定義的順序被調用。例如,
          <bean id="filterChainProxy" class="net.sf.acegisecurity.util.FilterChainProxy">
          <property name="filterInvocationDefinitionSource">
          <value>
          CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
          PATTERN_TYPE_APACHE_ANT
          /**=httpSessionContextIntegrationFilter,authenticationProcessingFilter,basicProcessingFilter,rememberMeProcessingFilter,anonymousProcessingFilter,securityEnforcementFilter
          </value>
          </property>
          </bean>
          這個過濾器的mapping是“/*”。
          c) listener元素。
          i. ContextLoaderListener。這個是Spring使用來加載根applicationcontext。并分別解析 applicationContext-acegi-security.xml、applicationContext-common- business.xml、applicationContext-common-authorization.xml等配置文件,把相關的對象初始化
          iii. Log4jConfigListener。這個是spring用來初始化log4j組件的listener。
          iv. HttpSessionEventPublisher。這個組件將發布HttpSessionCreatedEvent和HttpSessionDestroyedEvent事件給spring的applicationcontext。
          d) servlet元素。
          i. contacts。這里采用了spring的MVC框架, 所以這個servlet是spring MVC的一個核心控制器(org.springframework.web.servlet.DispatcherServlet)。這個servlet 啟動時候,會從contacts-servlet.xml里面讀取信息,并做相關的初始化。
          v. remoting。也是spring MVC的一個核心控制器。與contacts不同,這個servlet主要是提供web services服務。這個servlet啟動時候, 會從remoting-servlet.xml里面讀取信息,并做相關的初始化。
          e) taglib元素。這里定義了spring的標f) 簽庫。
          3. 解析applicationContext-acegi-security.xml。
          a) 過濾器鏈。定義了一個FilterChainProxy,b) 并指c) 定了一系列的過濾器鏈。httpSessionContextIntegrationFilter, authenticationProcessingFilter,basicProcessingFilter,rememberMeProcessingFilter,anonymousProcessingFilter,securityEnforcementFilter
          d) 認證管理器。這個管理器由acegi提供。這個管理器需要一個providers參數。這個providers參數包含了提供系統認證的對象。
          i. daoAuthenticationProvider。一般用戶認證。
          ii. anonymousAuthenticationProvider。匿名用戶認證。
          iv. rememberMeAuthenticationProvider。記住我認證。

          e) 密碼加密。這里定義了一個acegi的Md5算法加密對象Md5PasswordEncoder。
          f) 定義了一個jdbcDao實現類。這個類由acegi提供的net.sf.acegisecurity.providers.dao.jdbc.JdbcDaoImpl。這個對象需要一個dataSource的參數。
          g) 定義daoAuthenticationProvider。這個對象由acegi提供。它有3個屬性:
          authenticationDao。這里指向前面定義的jdbcDao。
          userCache。這里指向后面定義的user緩存對象。
          passwordEncoder。這里指向前面定義的密碼加密對象。
          h) 用戶緩存管理。
          為了緩存user,這里使用spring的ehcache來緩存user。緩存機制:
          i. 定義緩存管理器――CacheManager。這個對象是spring的EhCacheManagerFactoryBean對象
          ii. 定義user緩存實際執行對象――UserCacheBackend。這個對象是spring的EhCacheFactoryBean。它有兩個屬性:
          1. cacheManager。這里指向前面定義的緩存管理器。
          2. cacheName。
          iii. 定義user緩存――UserCache。它是acegi提供的EhCacheBasedUserCache對象。它有一個屬性:
          1. cache。這里指向的是前面定義的userCacheBackend。

          i) 定義接收來自DaoAuthenticationProvider的認證事件的listener――LoggerListener。
          j)
          4. 解析applicationContext-common-business.xml。
          a) dataSource.
          這里使用了spring的DriverManagerDataSource對象。這個對象是一個JDBC數據源的定義。
          b) TransactionManager。這里使用spring的DataSourceTransactionManager對象。
          c) 事務攔截器。這里使用spring的事務攔截器TransactionInterceptor。它有2個屬性:
          transactionManager。這個屬性指向前面定義的TransactionManager。
          transactionAttributeSource。這個屬性里, 指定了ContactManager的各個方法的事務方面的要求。
          d) DataSourcePopulator。
          使用sample.contact.DataSourcePopulator對象,往HSQL里創建相關的表結構和數據。
          實現原理:DataSourcePopulator 實現了接口 InitializingBean。其中afterPropertiesSet方法將在spring初始化DataSourcePopulator后被調用。
          e) ContactDao。這里指向一個ContactDaoSpring對象。它繼承spring的 JdbcDaoSupport,g) 并實現ContactDao接口。它是真正實現JDBC操作的對象。
          h) ContactManager。這里使用的是spring的ProxyFactoryBean。它有2個屬性:
          i. ProxyInterfaces。代理接口:sample.contact.ContactManager

          ii. InterceptorNames。攔截器名稱。可以有多個,iv. 這里包括:transactionInterceptor、contactManagerSecurity、contactManagerTarget。其中,v. transactionInterceptor是前面定義的事務攔截器。ContactManagerSecurity則是在 applicationContext-common-authorization.xml里定義的方法調用授權。
          i) ContactManagerTarget。這里指向的是sample.contact.ContactManagerBackend對象。 ContactManagerBackend實現了ContactManager接口和InitializingBean接口。它有2個自定義屬性: contactDao和basicAclExtendedDao。這里會調用ACL的API去做些創建權限和刪除權限的工作。

          posted @ 2006-09-01 13:45 Binary 閱讀(892) | 評論 (0)編輯 收藏

          acegi-security-sample-contacts-filter例子學習(一)

          這是一個 Acegi 官方的例子。它以聯系人的管理為例子,說明如何使用 Acegi 作權限控制。這個例子包含在 acegi 的包里面。下載地址: http://prdownloads.sourceforge.net/acegisecurity/acegi-security-0.8.3.zip?download

          聯系人管理說明了下列中心的Acegi安全控制能力:

          • Role-based security (基于角色的安全) ――每個責任人都是某個角色的一員。而角色被用來限制對某些安全對象的訪問。
          • Domain object instance security (域對象實例安全) ――合同,這個系統里的主要域對象,擁有一個訪問控制列表( ACL ),用來指明誰允許讀、管理和刪除對象。
          • Method invocation security (方法調用安全)―― 這個 ContactManager 服務層對象 包含一些受保護的和公開的方法。
          • Web request security Web 請求安全) ――這個“ /secure URI 路徑被使用 Acegi 安全保護,使得沒有 ROLE_USER 角色的用戶無法訪問。 .
          • Security unaware application objects (保護未知的應用對象) ――受保護的對象與 Acegi 之間沒有明顯的耦合或契約,所以它們沒有察覺到安全是由 Acegi 提供的。 *
          • Security taglib usage (安全標簽庫使用) ――所有的 JSP 使用 Acegi 安全標簽庫來封裝安全信息。 *
          • Fully declarative security( 完全聲明式的安全 ) ――每一個安全方面特性都是在 application context 里面使用標準的 Acegi 安全對象來配置的。 *
          • Database-sourced security data (支持數據庫來源的安全數據) ――所有的用戶、角色和 ACL 信息都可以從一個兼容 JDBC 的內存數據庫獲得。
          • Integrated form-based and BASIC authentication (集成基于表單和 BASIC 驗證)―― 任何 BASIC 驗證頭部被檢測以及作為驗證使用。默認使用基于表單的普通交互式驗證。
          • Remember-me services (記住我的服務)―― Acegi 安全的插件式的“ remember-me 策略被演示。在登錄表單里有一個相關的選擇框與之對應。

          聯系人管理的業務功能描述:

          1.1. 每個用戶登錄后,可以看到一個聯系人列表。例如,

          marissa's Contacts

          id

          Name

          Email

          1

          John Smith

          john@somewhere.com

          Del

          Admin Permission

          2

          Michael Citizen

          michael@xyz.com



          3

          Joe Bloggs

          joe@demo.com

          Del


          4

          Karen Sutherland

          karen@sutherland.com

          Del

          Admin Permission

          Add

          說明:用戶沒有權限訪問的聯系人信息,將不會顯示。

          2.2. 用戶可以增加新的聯系人信息。

          3.3. 如果有刪除權限,用戶可以看到在聯系人后面有一個“ Del ”鏈接。用戶可以點擊這個鏈接來刪除某個聯系人信息。

          4.4. 如果有管理權限,用戶可以看到在聯系人后面有一個“ Admin Permission ”鏈接。用戶可以點擊這個鏈接來管理訪問這個聯系人的權限。例如,

          Administer Permissions

          sample.contact.Contact@26807f: Id: 1; Name: John Smith; Email: john@somewhere.com

          -R--- [2] dianne

          Del

          -RW-D [22] peter

          Del

          A---- [1] marissa

          Del

          Add Permission Manage

          說明:每一行記錄包含有 3 列。

          第一列表示權限,例如,“ -RW-D ”表示可讀、可寫、可刪除。

          第二列也表示權限,但它是以類似 unix 權限的數字表達。例如,“ [22] , 表示可讀、可寫、可刪除。

          第三列是用戶名稱。

          每一行記錄后面都有一個“ Del ”鏈接。點擊這個鏈接,可以刪除掉指定用戶對這個聯系人信息的權限。

          5.5. 用戶可以為某個聯系人信息添加權限。例如,

          Add Permission

          Contact:

          sample.contact.Contact@1787005: Id: 1; Name: John Smith; Email: john@somewhere.com


          Recipient:


          Permission:


          說明:權限是動態添加的。例如,上圖中給用戶 scott 增加了讀聯系人 John 的權限。那么 scott 馬上就可以看到聯系人 John 的信息了。

          posted @ 2006-09-01 13:44 Binary 閱讀(636) | 評論 (0)編輯 收藏

          WebWork教程-ServletDispatcher

               摘要: ServletDispatcher 原理 ServletDispatcher 是默認的處理 Web Http 請求的調度器,它是一個 JavaServlet ,是 WebWork 框架的控制器。...  閱讀全文

          posted @ 2006-09-01 13:41 Binary 閱讀(653) | 評論 (0)編輯 收藏

          WebWork教程-validator

          驗證框架
          WebWork 提供了在 Action 執行之前,對輸入數據的驗證功能,它使用了其核心 XWork 的驗證框架。提供了如下功能:
          1、?? 可配置的驗證文件。它的驗證文件是一個獨立的 XML 配置文件,對驗證的添加、修改只需更改配置文件,無需編譯任何的 Class
          2、?? 驗證文件和被驗證的對象完全解藕。驗證對象是普通的 JavaBean 就可以了(可以是 FormBean 、域對象等),它們不需實現任何額外的方法或繼承額外的類。
          3、?? 多種不同的驗證方式。因為它驗證功能是可以繼承的,所以可以用多種不同的方式指定驗證文件,比如:通過父類的 Action 、通過 Action 、通過 Action 的方法、通過 Action 所使用的對象,等等。
          4、?? 強大的表達式驗證。它使用了 OGNL 的表達式語言,提供強大的表達式驗證功能。
          5、?? 同時支持服務器端和客戶端驗證。
          下面我們來看看如何為用戶注冊添加驗證功能:
          1、?? 注冊我們的驗證類型
          WebWork 為不同的驗證要求提供不同的驗證類型。一個驗證類型,一般是有一個類來提供。這個類必須實現接口: com.opensymphony.xwork.validator.Validator ,但我們在寫自己的驗證類型時,無需直接實現 Validator 接口,它有抽象類可供直接使用如 ValidatorSupport FieldValidatorSupport 等。
          驗證類型在使用之前,必須要在 ValidatorFactory com.opensymphony.xwork.validator . ValidatorFactory )中 注冊。可以有二種方法實現驗證類型的注冊。一、寫程序代碼進行注冊,它使用 ValidatorFactory 類的靜態方法: registerValidator(String name, String className) 二、使用配置文件 validators.xml 進行注冊,要求把文件 validators.xml 放到 ClassPath 的跟目錄中( /WEB-INF/classes )。但在實際開發中,一般都使用第二中注冊方法。我們的驗證類型注冊如下:
          <validators>
          ??? <validator name="required" class="com.opensymphony.xwork.validator.validators.RequiredFieldValidator"/>
          ?? ?<validator name="requiredstring" class="com.opensymphony.xwork.validator.validators.RequiredStringValidator"/>
          ??? <validator name="int" class="com.opensymphony.xwork.validator.validators.IntRangeFieldValidator"/>
          ??? <validator name="date" class="com.opensymphony.xwork.validator.validators.DateRangeFieldValidator"/>
          ??? <validator name="expression" class="com.opensymphony.xwork.validator.validators.ExpressionValidator"/>
          ??? <validator name="fieldexpression" class="com.opensymphony.xwork.validator.validators.FieldExpressionValidator"/>
          ??? <validator name="email" class="com.opensymphony.xwork.validator.validators.EmailValidator"/>
          ??? <validator name="url" class="com.opensymphony.xwork.validator.validators.URLValidator"/>
          ??? <validator name="visitor" class="com.opensymphony.xwork.validator.validators.VisitorFieldValidator"/>
          ??? <validator name="conversion" class="com.opensymphony.xwork.validator.validators.ConversionErrorFieldValidator"/>
          ??? <validator name="stringlength" class="com.opensymphony.xwork.validator.validators.StringLengthFieldValidator"/>
          </validators>
          注冊驗證類型的配置文件非常簡單。它使用標簽 <validator > 提供名-值對的形式注冊。這樣我們的驗證文件就可以直接引用它的名字。
          2、?? 開啟 Action 的驗證功能
          ? 如果 Action 要使用驗證框架的驗證功能,它必須在配置文件中指定攔截器“ validation ”,它的定義如下:
          <interceptor name="validation" class="com.opensymphony.xwork.validator.ValidationInterceptor"/>
          我們的驗證文件必須以 ActionName-validation.xml 格式命名,它必須被放置到與這個 Action 相同的包中。你也可以為這個 Action 通過別名的方式指定驗證文件,它的命名格式為: ActionName-aliasname-validation.xml 。“ ActionName ”是我們 Action 的類名;“ aliasname ”是我們在配置文件( xwork.xml )中定義這個 Action 所用到的名稱。這樣,同一個 Action 類,在配置文件中的不同定義就可以對應不同的驗證文件。驗證框架也會根據 Action 的繼承結構去查找 Action 的父類驗證文件,如果找到它會去執行這個父類的驗證。
          ?
          3、?? 實現我們的驗證文件: RegisterActionSupport-validation.xml
          <!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0//EN" "http://www.opensymphony.com/xwork/xwork-validator-1.0.dtd">
          <validators>
          ??? <field name="user.username">
          ??? <field-validator type="requiredstring">
          ??????????? <message>You must enter a value for username.</message>
          ??????? </field-validator>
          ??? </field>
          ??? <field name="user.password">
          ??? <field-validator type="requiredstring">
          ??????????? <message>You must enter a value for password.</message>
          ??????? </field-validator>
          ??????? <field-validator type="fieldexpression">
          ??????????? <param name="expression">user.password == verifyPassword</param>
          ??????????? <message>Passwords don't match.</message>
          ??????? </field-validator>
          ??? </field>
          ??? <field name="user.email">
          ??? <field-validator type="email">
          ??????????? <message>You must enter a valid email.</message>
          ??????? </field-validator>
          ??? </field>
          ??? <field name="user.age">
          ??? <field-validator type="int">
          ??????????? <param name="min">6</param>
          ??????????? <param name="max">100</param>
          ??????????? <message>Age must be between ${min} and ${max}, current value is ${user.age}.</message>
          ?????? ?</field-validator>
          ??? </field>
          </validators>
          說明:
          1 )、 <field > 標簽代表一個字段,它的屬性“ name ”和頁面輸入框的“ name ”屬性必需完全一致,其實它也就是我們的表達式語言。
          2 )、 <field-validator > 標簽定義我們的驗證規則, type 屬性的值就是就是我們前面定義的驗證類型。
          3 )、驗證文件中,字段的數據是通過表達式語言從我們的值堆棧( OgnlValueStack )中取得,一般是 Action Model 對象。例如:我們的字段“ user.age ”,它會通過 Action getUser().getAge() 來取得用戶輸入的年齡,再來根據驗證的類型“ int ”和最大值最小值的參數來判斷輸入的數據是否能通過驗證。
          4 )、不管驗證是否通過,我們的 Action 都會執行,但如果驗證沒有通過,它不會調用 Action execute() 方法。
          ?
          4、?? 顯示 Action 的驗證錯誤信息
          如果用戶輸入的數據驗證沒有通過,我們需重新返回輸入頁面,并給出錯誤信息提示。攔截器棧“ validationWorkflowStack ”為我們實現了這個功能。它首先驗證用戶輸入的數據,如果驗證沒有通過將不執行我們 Action execute() 方法,而是將請求重新返回到輸入頁面。
          我們的 xwork.xml 配置文件如下:
          <action name="registerSupport" class="example.register.RegisterActionSupport">
          ??????????? <result name="success" type="dispatcher">
          ??????????????? <param name="location">/register-result.jsp</param>
          ??????????? </result>
          ??????????? <result name="input" type="dispatcher">
          ??????????????? <param name="location">/registerSupport.jsp</param>
          ??????????? </result>
          ??????????? <interceptor-ref name="validationWorkflowStack"/>
          ??????? </action>
          ?
          通過接口 ValidationAware 我們可以獲得類級別或字段級別的驗證錯誤信息,這個錯誤信息也就是我們驗證文件中 <message> 標簽里的數據。 ActionSupport 類已實現了此接口,這樣在應用中我們的 Action 只要繼承 ActionSupport 類就可以了。 RegisterActionSupport .java 代碼如下:
          package example.register;
          ?
          import com.opensymphony.xwork.ActionSupport;
          ?
          public class RegisterActionSupport extends ActionSupport {
          ?
          ??? private User user= new User();
          ??? private String verifyPassword;
          ???
          ??? public User getUser(){
          ??????? returnthis.user;
          ??? }
          ???
          ??? public String execute(){
          ??????? // 在這里調用用戶注冊的業務邏輯,比如:將注冊信息存儲到數據庫
          ??????? return SUCCESS;
          ??? }
          ?
          ??? public String getVerifyPassword(){
          ??????? returnthis.verifyPassword;
          ??? }
          ???
          ??? publicvoid setVerifyPassword(String verPassword){
          ??????? this.verifyPassword = verPassword;
          ??? }
          }
          我們 WebWork UI 標簽庫直接提供了驗證錯誤信息顯示功能。如果字段級別的驗證沒有通過,它會在輸入框上方顯示驗證文件定義的錯誤提示信息。我們將用戶輸入的頁面更改如下:
          registerSupport.jsp
          <%@ taglib uri="webwork" prefix="ww" %>
          <html>
          <head><title>Register Example</title></head>
          <body>
          <table border=0 width=97%>
          <tr><td align="left">
          ??? <ww:form name="'test'" action="'/example/registerSupport.action'" method="'POST'">
          ??????????? <ww:textfield label="'Username'" name="'user.username'" required="true"/>
          ??????????? <ww:textfield label="'Password'" name="'user.password'" required="true"/>
          ??????????? <ww:textfield label="'VerifyPassword'" name="'verifyPassword'" required="true"/>
          ?????????? ?<ww:textfield label="'Email'" name="'user.email'" required="true"/>
          ??????????? <ww:textfield label="'Age'" name="'user.age'" required="true"/>
          ??????????? <ww:submit value="'Submit'"/>
          ???????? </ww:form>
          </td></tr>
          </table>
          </body>
          </html>
          我們上面的例子使用的是服務器端驗證。 WebWork 也為我們提供了方便的客戶端驗證。它將驗證自動生成 JavaScript 腳本。如果要使用客戶端驗證只需改變相應的驗證類型就可以了(輸入頁面的表單必需使用 <ww:form> 標簽,并設置屬性“ validate="true" ”)。具體的驗證類型可以在 WebWork 的包 com.opensymphony.webwork.validators 中找到。

          posted @ 2006-09-01 13:40 Binary 閱讀(681) | 評論 (0)編輯 收藏

          WebWork教程- Interceptor(攔截器)

               摘要: Interceptor (攔截器)將 Action 共用的行為獨立出來,在 Action 執行前后運行。這也就是我們所說的 AOP ( Aspect Oriented Programming ,面向切面編程),它是分散關注的編程方法,它將通用需求功能從不相關類之中分離出來;同時,能夠使得很多類共享一個行為,一...  閱讀全文

          posted @ 2006-09-01 13:39 Binary 閱讀(616) | 評論 (0)編輯 收藏

          WebWork介紹-Action篇

               摘要: Action 簡介 Action 在 MVC 模式中擔任控制部分的角色 , 在 WebWork 中使用的最多 , 用于接收頁面參數,起到對 HttpRequest 判斷處理作用。每個請求的動作都對應于一個相應的 ...  閱讀全文

          posted @ 2006-09-01 13:38 Binary 閱讀(230) | 評論 (0)編輯 收藏

          Log4J學習筆記

          一、簡介
            在程序中輸出信息的目的有三:一是監視程序運行情況;一是將程序的運行情況記錄到日志文件中,以備將來查看;一是做為調試器。但信息輸出的手段不僅限于System.out.println()或System.out.print(),還有日志記錄工具可以選擇。與System.out.pringln()和System.out.print()相比,日志記錄工具可以控制輸出級別,并且可以在配置文件中對輸出級別進行設置,這樣開發階段的信息在程序發布后就可以通過設置輸出級別來消除掉,而無須對代碼進行修正了。現在流行的日志記錄工具很多, Log4J就是其中的佼佼者。
            Log4J是由著名開源組織Apache推出的一款日志記錄工具,供Java編碼人員做日志輸出之用,可以從網站http://logging.apache.org/log4j上免費獲得,最新版本1.2.11。獲得logging-log4j-1.2.11.zip文件后,解壓縮,需要的是其中的log4j-1.2.11.jar文件,將該文件放到特定的文件夾中備用,我放到了我機器的G:\YPJCCK\Log4J\lib文件夾中。
            這里選擇的IDE是Eclipse和JBuilder。Eclipse用的是3.0.1加語言包,可以到www.eclipse.org網站上下載;JBuilder用的是JBuilder 2005。
          二、配置類庫
            下面打開Eclipse或JBuilder。
            如果使用的是Eclipse,那么在Eclipse打開后,點擊菜單"文件"->"新建"->"項目",打開"新建項目"對話框:

          請選中"Java項目",點擊"下一步",進入"新建Java項目"對話框:

          在這個對話框中需要設置項目的名稱以及項目所在目錄,我為自己的項目起名為Log4JTest,目錄為G:\YPJCCK\Log4J\Eclipse\ Log4JTest。設置好后點擊"下一步",進入下一個窗口。在這個窗口中選擇名為"庫"的選項卡,然后點擊"添加外部JAR"按鈕,將保存于特定文件夾中的log4j-1.2.11.jar文件引用進來。

          設置好后,點擊"完成",至此,已經具備了在Eclipse下使用Log4J的環境。
            如果使用的是JBuilder,那么在JBuilder打開后,點擊菜單"Tools"->"Configure" ->"Libraries",打開"Configure Libraries"對話框:

          點擊"New"按鈕,打開"New Library Wizard"對話框:

          使用"Add"按鈕將保存于特定文件夾中的log4j-1.2.11.jar文件引用進來,并設置Name,即該類庫的名字,我將Name設置為 Log4J。設置好后點擊"OK"按鈕,回到"Configure Libraries"對話框,再點擊"OK"按鈕,則JUnit類庫已經被添加到JBuilder當中。
            下面繼續,在JBuilder中創建項目。點擊菜單"File"->"New Project",打開"Project Wizard"對話框:

          在這個窗口中設置項目名稱及存放目錄,我的項目名稱仍為Log4JTest,路徑為G:/YPJCCK/log4J/JBuilder/Log4JTest。點擊"Next"進入下一個窗口:

          在這個窗口中選擇"Required Libraries"選項卡,點擊"Add"按鈕,將剛才設置的JUnit庫引用進來。然后點擊"Next"按鈕,進入下一個窗口:

          在這個窗口中用鼠標點擊Encoding下拉列表框,然后按一下"G"鍵,選中相應選項,此時該項目的字符集就被設置成GBK了。如果做的是國內項目,這絕對是個好習慣。最后點擊"Finish",項目創建完成。
          三、編寫一個簡單的示例
            在了解Log4J的使用方法之前,先編寫一個簡單的示例,以對Log4J有個感性認識。
          如果使用的是Eclipse,請點擊"文件"->"新建"->"類",打開"新建Java類"對話框,設置包為 piv.zheng.log4j.test,名稱為Test,并確保"public static void main(String[] args)"選項選中;如果使用的是JBuilder,請點擊"File"->"New Class",打開"Class Wizard"對話框,設置Package為piv.zheng.log4j.test,Class name為Test,并確保"Generate main method"選項選中。設置完成后,點擊"OK"。代碼如下:
            package piv.zheng.log4j.test;
            
            import org.apache.log4j.Logger;
            import org.apache.log4j.Level;
            import org.apache.log4j.SimpleLayout;
            import org.apache.log4j.ConsoleAppender;
            
            public class Test {
              
              public static void main(String[] args) {
                SimpleLayout layout = new SimpleLayout();
                
                ConsoleAppender appender = new ConsoleAppender(layout);
                
                Logger log = Logger.getLogger(Test.class);
                log.addAppender(appender);
                log.setLevel(Level.FATAL);
                
                log.debug("Here is DEBUG");
                log.info("Here is INFO");
                log.warn("Here is WARN");
                log.error("Here is ERROR");
                log.fatal("Here is FATAL");
              }
            }
          至此,示例編寫完成。請點擊運行按鈕旁邊的倒三角,選擇"運行為"->"2 Java應用程序"(Eclipse),或者在Test類的選項卡上點擊鼠標右鍵,在調出的快捷菜單中點擊"Run using defaults"(JBuilder),運行程序,觀察從控制臺輸出的信息。
          四、Log4J入門
            看過程序的運行效果后可能會奇怪,為何控制臺只輸出了"FATAL - Here is FATAL"這樣一條信息,而程序代碼中的log.debug()、log.info()等方法也都設置了類似的內容,卻沒有被輸出?其實答案很簡單,但在公布之前,先來了解一下Log4J的使用。
            請先看前邊的示例代碼,會發現,示例中用到了Logger、Level、 ConsoleAppender、SimpleLayout等四個類。其中Logger類使用最多,甚至輸出的信息也是在其對象log的fatal方法中設置的,那么Logger究竟是做什么的呢?其實Logger就是傳說中的日志記錄器(在Log4J中稱為Category),創建方法有三:
            1.根Category,默認創建,獲取方法:

          Logger log = Logger.getRootLogger();

            2.用戶創建的Category,方法:

          Logger log = Logger.getLogger("test");

          其中字符串test是為Category設定的名稱。Category的名稱允許使用任何字符,但區分大小寫,例如:

          Logger l1 = Logger.getLogger("x");
          Logger l2 = Logger.getLogger("X");

          l1和l2就是兩個Category;而如果名稱完全相同,例如:

          Logger l1 = Logger.getLogger("x");
          Logger l2 = Logger.getLogger("x");

          l1和l2就是同一個Category。此外,符號"."在Category的名稱中有特殊作用,這一點將在后邊介紹。
            3.與方法2類似,只是參數由字符串換成了類對象,其目的是通過類對象獲取類的全名。這個方法比較常用,示例中使用的就是這個方法。
            那么Category是如何輸出信息的呢?其實示例中用到的debug、info、warn、error、fatal等五個方法都是用來輸出信息的。什么,怎么這么多?原因很簡單,Log4J支持分級輸出。Log4J的輸出級別有五個,由低到高依次是DEBUG(調試)、INFO(信息)、WARN(警告)、ERROR(錯誤)和FATAL(致命),分別與以上方法對應。當輸出級別設置為DEBUG時,以上方法都能夠輸出信息,當輸出級別設置為INFO 時,則只有debug方法將不能再輸出信息,依此類推,當輸出級別設置為FATAL時,就只有fatal方法可以輸出信息了。現在再回頭看前邊的問題,為何只有設置給fatal方法的信息被輸出就不難理解了,示例中有這樣一行代碼:

          log.setLevel(Level.FATAL);

          正是這行代碼將log對象的輸出級別設成了FATAL。在為log對象設置輸出級別時用到了Level類,該類中定義了DEBUG、INFO、WARN、 ERROR、FATAL等五個靜態對象,與五個輸出級別相對應。此外,Level還有兩個特殊的靜態對象ALL和OFF,前者允許所有的方法輸出信息,其級別其實比DEBUG還低;后者則會禁止所有的方法輸出信息,其級別比FATAL要高。除前邊示例中用到的五個方法,Logger還提供了這五個方法的重載,以在輸出信息的同時拋出異常,以fatal方法為例:

          log.fatal("Here is FATAL", new Exception("Exception"));

          執行后輸出信息:
            FATAL - Here is FATAL
            java.lang.Exception: Exception
              at piv.zheng.log4j.test.Test.main(Test.java:24)
          其他方法類似。此外,Logger還提供了log方法,該方法不針對任何輸出級別,需要在調用時設置,例如:

          log.log(Level.FATAL, "Here is FATAL");
          log.log(Level.FATAL, "Here is FATAL", new Exception("Exception"));

          雖然一般情況下log方法不如其它方法方便,但由于允許設置級別,因此log方法在很多時候反而比其它方法更靈活,甚至可以在輸出級別為OFF時輸出信息。不過log方法主要是給用戶自定義的輸出級別用的,而且設立OFF輸出級別的目的也為了不輸出任何信息,因此請不要在log方法中使用OFF來輸出信息。
            此外,Category的輸出級別并非必須,若未設置,子Category會默認使用其父Category的輸出級別,若父Category也沒設置,就使用再上一級Category的設置,直到根Category為止。根Category默認輸出級別為DEBUG,因此在示例中,若將 "log.setLevel(Level.FATAL);"一行注釋掉,則所有方法都會輸出信息。
            下面簡單介紹一下Log4J中 Category的繼承關系。其實在Log4J中Category之間是存在繼承關系的,根Category默認創建,是級別最高的Category,用戶創建的Category均繼承自它。而用戶創建的Category之間也存在繼承關系,例如:

          Logger lx = Logger.getLogger("x");
          Logger lxy = Logger.getLogger("xy");
          Logger lx_y = Logger.getLogger("x.y");
          Logger lx_z = Logger.getLogger("x.z");
          Logger lx_y_z = Logger.getLogger("x.y.z");

          其中的lx_y、lx_z就是lx的子Category,而lx_y_z是lx_y的子Category。但lxy并不是lx的子Category。也許有點亂,下面來一個一個看。首先看與lx_y、lx_z對應的Category的名稱"x.y"和"x.z","."前邊的是什么,"x",這說明與名稱為 "x"的Category對應lx就是它們的父Category;而與lx_y_z對應的Category的名稱"x.y.z",最后一個"."前邊的是什么,"x.y",這說明lx_y是lx_y_z的父Category;至于lxy,由于與之對應的Category名稱"xy"之間沒有".",因此它是一個與lx同級的Category,其父Category就是根Category器。此外還有一種情況,例如有一個名稱為"a.b"的 Category,如果沒有名稱為"a"的Category,那么它的父Category也是根Category。前邊說過,"."在Category名稱中有特殊作用,其實它的作用就是繼承。至此,為何使用類對象來創建Category也就不難理解了。
            可是,僅有Category是無法完成信息輸出的,還需要為Category添加Appender,即Category的輸出源。前邊的例子使用的是ConsoleAppender,即指定 Category將信息輸出到控制臺。其實Log4J提供的Appender有很多,這里選擇幾常用的進行介紹。
            1.org.apache.log4j.WriterAppender,可以根據用戶選擇將信息輸出到Writer或OutputStream。
            示例代碼:
              SimpleLayout layout = new SimpleLayout ();
              
              //向文件中輸出信息,OutputStream示例
              WriterAppender appender1 = null;
              try {
                appender1 = new WriterAppender(layout, new FileOutputStream("test.txt"));
              }
              catch(Exception ex) {}
              
              //向控制臺輸出信息,Writer示例
              WriterAppender appender2 = null;
              try {
                appender2 = new WriterAppender(layout, new OutputStreamWriter(System.out));
              }
              catch(Exception ex) {}
              
              //Category支持同時向多個目標輸出信息
              Logger log = Logger.getLogger(Test.class);
              log.addAppender(appender1);
              log.addAppender(appender2);
              
              log.debug("output");
          這個示例由第一個示例修改而來,沒有設置輸出級別,而且向Category中添加了兩個輸出源,運行后會在控制臺中輸出"DEBUG - output",并在工程目錄下生成test.txt文件,該文件中也記錄著"DEBUG - output"。若要將test.txt文件放到其它路徑下,例如f:,則將"test.txt"改為"f:/test.txt",又如e:下的temp 文件夾,就改為"e:/temp/test.txt"。后邊FileAppender、RollingFileAppender以及 DailyRollingFileAppender設置目標文件時也都可以這樣來寫。
            2.org.apache.log4j.ConsoleAppender,向控制臺輸出信息,繼承了WriterAppender,前邊的示例使用的就是它。
            3.org.apache.log4j.FileAppender,向文件輸出信息,也繼承了WriterAppender。
            示例代碼:
              SimpleLayout layout = new SimpleLayout();
              
              //若文件不存在則創建文件,若文件已存在則向文件中追加信息
              FileAppender appender = null;
              try {
                appender = new FileAppender(layout, "test.txt");
              } catch(Exception e) {}
              
              Logger log = Logger.getLogger(Test.class);
              log.addAppender(appender);
              log.debug("output");
          這個示例也由第一個示例修改而來,運行后會在工程目錄下生成test.txt文件,該文件中記錄著"DEBUG - output"。再次運行程序,查看文件,則"DEBUG - output"有兩行。
            另外,FileAppender還有一個構造:

          FileAppender(Layout layout, String filename, boolean append)

          與示例的類似,只是多了一個boolean型的參數append。append參數是個開關,用來設置當程序重啟,而目標文件已存在時,是向目標文件追加信息還是覆蓋原來的信息,當值為true時就追加,這是FileAppender默認的,當值為false時則覆蓋。此外,FileAppender還提供了setAppend方法來設置append開關。
            4.org.apache.log4j.RollingFileAppender,繼承了 FileAppender,也是向文件輸出信息,但文件大小可以限制。當文件大小超過限制時,該文件會被轉為備份文件或刪除,然后重新生成。文件的轉換或刪除與設置的備份文件最大數量有關,當數量大于0時就轉為備份文件,否則(小于等于0)刪除,默認的備份文件數量是1。轉換備份文件非常簡單,就是修改文件名,在原文件名之后加上".1",例如文件test.txt,轉為備份文件后文件名為"test.txt.1"。但若同名的備份文件已存在,則會先將該備份文件刪除或更名,這也與設置的備份文件最大數量有關,若達到最大數量就刪除,否則更名。若備份文件更名時也遇到同樣情況,則使用同樣的處理方法,依此類推,直到達到設置的備份文件最大數量。備份文件更名也很簡單,就是將擴展名加1,例如test.txt.1文件更名后變為test.txt.2, test.txt.2文件更名后變為test.txt.3。
            示例代碼:
              SimpleLayout layout = new SimpleLayout();
              
              //若文件不存在則創建文件,若文件已存在則向文件中追加內容
              RollingFileAppender appender = null;
              try {
                appender = new RollingFileAppender(layout, "test.txt");
              } catch(Exception e) {}
              //限制備份文件的數量,本例為2個
              appender.setMaxBackupIndex(2);
              //限制目標文件的大小,單位字節,本例為10字節
              appender.setMaximumFileSize(10);
              
              Logger log = Logger.getLogger(Test.class);
              log.addAppender(appender);
              
              log.debug("output0");
              log.debug("output1");
              log.debug("output2");
          程序運行后,會在工程目錄下生成test.txt、test.txt.1和test.txt.2三個文件,其中test.txt內容為空,而后兩個文件則分別記錄著"DEBUG - output2"和"DEBUG - output1",這是怎么回事?原來由于目標文件大小被限制為10字節,而三次使用log.debug方法輸出的信息都超過了10字節,這樣就導致了三次備份文件轉換,所以test.txt內容為空。而備份文件最大數量被設為2,因此第一次轉換的備份文件就被刪掉了,而后兩次的則保存下來。此外,由于 test.txt轉換備份文件時是先轉為test.txt.1,再轉為test.txt.2,因此最后test.txt.2的內容是"DEBUG - output1",而test.txt.1是"DEBUG - output2",這點千萬別弄混了。
            另外,RollingFileAppender還提供了兩個方法:
            (1)setMaxFileSize,功能與setMaximumFileSize一樣,但參數是字符串,有兩種情況:一是僅由數字組成,默認單位為字節,例如"100",即表示限制文件大小為100字節;一是由數字及存儲單位組成,例如"1KB"、"1MB"、"1GB",其中單位不區分大小寫,分別表示限制文件大小為1K、1M、1G。
            (2)rollOver,手動將目標文件轉換為備份文件,使用起來較靈活,適用于復雜情況。
            示例代碼:
              SimpleLayout layout = new SimpleLayout();
              
              RollingFileAppender appender = null;
              try {
                appender = new RollingFileAppender(layout, "test.txt");
              } catch(Exception e) {}
              appender.setMaxBackupIndex(2);

              Logger log = Logger.getLogger(Test.class);
              log.addAppender(appender);
              
              log.debug("output0");
              appender.rollOver();
              log.debug("output1");
              appender.rollOver();
              log.debug("output2");
              appender.rollOver();
          這里沒限制目標文件大小,但程序運行后,效果與上例相同。
            5.org.apache.log4j.DailyRollingFileAppender,也繼承了FileAppender,并且也是向文件輸出信息,但會根據設定的時間頻率生成備份文件。
            時間頻率格式簡介:
            '.'yyyy-MM,按月生成,生成時間為每月最后一天午夜過后,例如test.txt在2005年7月31日午夜過后會被更名為test.txt.2005-07,然后重新生成。
            '.'yyyy-ww,按周生成,生成時間為每周六午夜過后,例如test.txt在2005年8月13日午夜過后會被更名為test.txt.2005-33,33表示當年第33周。
            '.'yyyy-MM-dd,按天生成,生成時間為每天午夜過后,例如2005年8月16日午夜過后,test.txt會被更名為test.txt.2005-08-16。
            '.'yyyy-MM-dd-a,也是按天生成,但每天會生成兩次,中午12:00過后一次,午夜過后一次,例如test.txt在2005年8月16 日12:00過后會被更名為test.txt.2005-8-16-上午,午夜過后會被更名為test.txt.2005-8-16-下午。
            '.'yyyy-MM-dd-HH,按小時生成,例如test.txt在2005年8月16日12:00過后會被更名為test.txt.2005-8-16-11。
            '.'yyyy-MM-dd-HH-mm,按分鐘生成,例如test.txt在2005年8月16日12:00過后會被更名為test.txt.2005-8-16-11-59。
            示例代碼:
              SimpleLayout layout = new SimpleLayout();
              
              DailyRollingFileAppender appender = null;
              try {
                appender = new DailyRollingFileAppender(layout, "test.txt", "'.'yyyy-MM-dd-HH-mm");
              } catch(Exception e) {}
              
              Logger log = Logger.getLogger(Test.class);
              log.addAppender(appender);
              log.debug("output");
          編碼完成后運行程序,等一分鐘后再次運行,由于我是在2005年8月17日15:42分第一次運行程序的,因此工程目錄下最終有兩個文件test.txt和test.txt.2005-08-17-15-42。
            6.org.apache.log4j.AsyncAppender,用于管理不同類型的Appender,也能實現同時向多個源輸出信息,但其執行是異步的。
            示例代碼:
              SimpleLayout layout = new SimpleLayout();
              
              //向控制臺輸出
              ConsoleAppender appender1 = null;
              try {
                appender1 = new ConsoleAppender(layout);
              } catch(Exception e) {}
              
              //向文件輸出
              FileAppender appender2 = null;
              try {
                appender2 = new FileAppender(layout, "test.txt");
              } catch(Exception e) {}
              
              //使用AsyncAppender實現同時向多個目標輸出信息
              AsyncAppender appender = new AsyncAppender();
              appender.addAppender(appender1);
              appender.addAppender(appender2);
              
              Logger log = Logger.getLogger(Test.class);
              log.addAppender(appender);
              log.debug("output");
          此外,AsyncAppender和Logger都提供了更多的方法來管理Appender,例如getAppender、 getAllAppenders、removeAppender和removeAllAppenders,分別用來獲取指定的Appender、獲取全部 Appender、移除指定的Appender以及移除全部Appender。
            7.org.apache.log4j.jdbc.JDBCAppender,將信息輸出到數據庫。
            示例代碼:
              JDBCAppender appender = new JDBCAppender();
              appender.setDriver("com.mysql.jdbc.Driver");
              appender.setURL("jdbc:mysql://localhost:3306/zheng");
              appender.setUser("root");
              appender.setPassword("11111111");
              appender.setSql("insert into log4j (msg) values ('%m')");
              
              Logger log = Logger.getLogger(Test.class);
              log.addAppender(appender);
              log.debug("output");
          這里使用的數據庫是MySQL 5.0.4beta,用戶名root,密碼11111111,我在其中建了一個庫zheng,包含表log4j,該表只有一個字段msg,類型為varchar(300)。此外,本例用到的JDBC驅動可以從http://dev.mysql.com/downloads/connector/j/3.1.html下載,版本3.1.8a,下載mysql-connector-java-3.1.8a.zip文件后解壓縮,需要其中的mysql-connector- java-3.1.8-bin.jar文件。下面再來看代碼。由于JDBCAppender內部默認使用PatternLayout格式化輸出信息,因此這里沒用到SimpleLayout,而appender.setSql所設置的SQL語句就是PatternLayout所需的格式化字符串,故此其中才有"%m"這樣的字符,有關PatternLayout的具體內容后邊介紹。執行后,表log4j增加一條記錄,內容為"output"。
            8.org.apache.log4j.nt.NTEventLogAppender,向Windows NT系統日志輸出信息。
            示例代碼:
              SimpleLayout layout = new SimpleLayout();
              
              NTEventLogAppender appender = new NTEventLogAppender("Java", layout);

              Logger log = Logger.getLogger(Test.class);
              log.addAppender(appender);
              log.debug("output");
          注意,要完成此示例,還需向C:\WINNT\system32文件夾(我的操作系統裝在了C:\)中復制一個名為 NTEventLogAppender.dll的文件。如果跟我一樣用的是Log4J 1.2.11,實在對不住,Log4J 1.2.11并未提供該文件。雖然logging-log4j-1.2.11.zip文件解壓縮后,其下的src\java\org\apache\ log4j\nt文件夾中有一個make.bat文件執行后可以編譯出該文件,但還需要配置,很麻煩。還好,條條大道通羅馬,1.2.11不行,就換 1.2.9,可以從http://apache.justdn.org/logging/log4j/1.2.9下載,下載后解壓縮logging-log4j-1.2.9.zip文件,在其下的src\java\org\apache\log4j\nt文件夾中找到 NTEventLogAppender.dll,復制過去就可以了。程序執行后,打開"事件查看器",選擇"應用程序日志",其中有一條來源為Java的記錄,這條記錄就是剛才輸出的信息了。
            9.org.apache.log4j.lf5.LF5Appender,執行時會彈出一個窗口,信息在該窗口中以表格的形式顯示。
            示例代碼:
              LF5Appender appender = new LF5Appender();
              Logger log = Logger.getLogger(Test.class);
              log.addAppender(appender);
              log.debug("output");
          由于LF5Appender不需要Layout格式化輸出信息,因此這里沒有設置。此外LF5Appender還提供了一個setMaxNumberOfRecords方法,用來限制信息在表格中顯示的行數。
            10.org.apache.log4j.net.SocketAppender,以套接字方式向服務器發送日志,然后由服務器將信息輸出。
            示例代碼:
            //指定要連接的服務器地址及端口,這里使用的是本機9090端口
            SocketAppender appender = new SocketAppender("localhost", 9090);
            Logger log = Logger.getLogger(Test.class);
            log.addAppender(appender);
            log.debug("output");
          SocketAppender不需要設置Layout,因為SocketAppender不負責輸出信息。那么如何看到信息輸出的效果呢?這就需要SocketServer和SimpleSocketServer了。
            示例代碼1:
              package piv.zheng.log4j.test;
              
              import org.apache.log4j.net.SocketServer;
              
              public class TestServer {
                public static void main(String[] args) {
                  SocketServer.main(new String[]{"9090", "test.properties", "G:/YPJCCK/Log4J"});
                }
              }
          這是SocketServer的示例。SocketServer只有一個靜態方法main,該方法意味著SocketServer不僅可以在代碼中被調用,也可以用java命令執行。main方法只有一個參數,是個字符串數組,但要求必須有三個元素:元素一用來指定端口,本例為9090;元素二用來指定輸出信息時需要的配置文件,該文件放在工程目錄下,本例使用的test.properties內容如下:
            log4j.rootLogger=, console
            log4j.appender.console =org.apache.log4j.ConsoleAppender
            log4j.appender.console.layout=org.apache.log4j.SimpleLayout
          該配置指定SocketServer使用ConsoleAppender以SimpleLayout格式輸出信息;元素三用來指定一個路徑,以存放.lcf 文件,我指定的是本機的G:/YPJCCK/Log4J文件夾。.lcf文件也是輸出信息時使用的配置文件,格式與元素二所指定的配置文件一樣,但 test.properties是默認配置文件,即當.lcf文件找不到時才使用。那么.lcf文件如何命名呢?其實.lcf文件的名稱并不是隨意起的,當SocketAppender與SocketServer建立連接時,SocketServer就會獲得SocketAppender所在計算機的IP 地址與網絡ID,并將其格式化成"網絡ID/IP地址"這樣的字符串,然后獲取其中的網絡ID作為.lcf文件的主名,例如 "zhengyp/127.0.0.1",其中的"zhengyp"就是主文件名,而后再根據這個文件名來調用相應的.lcf文件。這意味著對不同的計算機可以提供不同的配置文件,使信息輸出時有不同的效果。此外,SocketServer還默認了一個名為generic.lcf的文件,用于處理網絡ID 獲取不到或其他情況,本例是用的就是這個文件,內容如下:
            log4j.rootLogger=, console
            log4j.appender.console =org.apache.log4j.ConsoleAppender
            log4j.appender.console.layout=org.apache.log4j.PatternLayout
            log4j.appender.console.layout.ConversionPattern=%m%n
          該配置指定SocketServer使用ConsoleAppender以PatternLayout格式輸出信息。運行程序時請先運行 SocketServer,再運行SocketAppender。SocketAppender運行結束后,就可以從SocketServer的控制臺看到輸出的信息了。
            示例代碼2:
              package piv.zheng.log4j.test;
              
              import org.apache.log4j.net.SimpleSocketServer;

              public class TestServer {
                public static void main(String[] args) {
                  SimpleSocketServer.main(new String[]{"9090", "test.properties"});
                }
              }
          這是SimpleSocketServer的示例,與SocketServer相比,只允許指定一個默認的配置文件,而無法對不同計算機使用不同的配置文件。
            11.org.apache.log4j.net.SocketHubAppender,也是以套接字方式發送日志,但與SocketAppender相反,SocketHubAppender是服務器端,而不是客戶端。
            示例代碼:
              //指定服務器端口,這里使用的是本機9090端口
              SocketHubAppender appender = new SocketHubAppender(9090);
              
              Logger log = Logger.getLogger(Test.class);
              log.addAppender(appender);
              while (true) {
                Thread.sleep(1000);
                log.debug("output"); //輸出信息
              }
          由于SocketHubAppender一旦運行就開始發送消息,而無論有無接收者,因此這里使用了while語句并將條件設為true以保證程序持續運行。不過為了保證性能,這里還使用了Thread.sleep(1000),這樣程序每循環一次都休眠1秒,如果機器性能不好,還可以將值設的再大些。此外,由于SocketHubAppender也不負責輸出信息,因此同樣不需要設置Layout。那么如何看到信息輸出的效果呢?這里我自己寫了個客戶端程序,代碼如下:
            package piv.zheng.log4j.test;
            
            import java.net.Socket;
            import java.lang.Thread;
            import org.apache.log4j.LogManager;
            import org.apache.log4j.PropertyConfigurator;
            import org.apache.log4j.net.SocketNode;
            
            public class TestClient {
              public static void main(String[] args) throws Exception {
                //創建客戶端套接字對象
                Socket s = new Socket("localhost", 9090);
                //調用配置文件
                PropertyConfigurator.configure("test.properties");
                //從套接字中恢復Logger,并輸出信息
                new Thread(new SocketNode(s, LogManager.getLoggerRepository())).start();
              }
            }
          由于SocketHubAppender與SocketAppender一樣,發送的也是SocketNode對象,因此編寫該程序時參考了 SocketServer的源碼。此外,這里的配置文件直接使用了上例的test.properties文件。運行程序時請先運行 SocketHubAppender,再運行客戶端程序,然后從客戶端的控制臺就可以看到效果了。
            13.org.apache.log4j.net.TelnetAppender,與SocketHubAppender有些類似,也是作為服務器發送信息,但TelnetAppender發送的不是SocketNode對象,而是Category輸出的結果。
            示例代碼:
              SimpleLayout layout = new SimpleLayout();
              
              TelnetAppender appender = new TelnetAppender();
              appender.setLayout(layout); //設置Layout
              appender.setPort(9090); //設置端口號
              appender.activateOptions(); //應用設置
              
              Logger log = Logger.getLogger(Test.class);
              log.addAppender(appender);
              
              while (true) {
                java.lang.Thread.sleep(1000);
                log.debug("output"); //輸出信息
              }
              //appender.close();
          注意最后一行被注釋掉的代碼,若該行代碼執行,則TelnetAppender的資源會被清理,從而導致TelnetAppender無法繼續運行。那么如何看到信息輸出的效果呢?這里提供兩種方法:方法一,使用Telnet工具,我使用的就是Windows自帶的Telnet。運行 TelnetAppender程序后,點擊[開始]菜單->[運行],在"運行"框中輸入"telnet",回車,telnet客戶端彈出,這是一個命令行程序,輸入命令"open localhost 9090",回車,然后就可以看到效果了。方法二,自己寫程序,代碼如下:
            package piv.zheng.log4j.test;
            
            import java.net.*;
            import java.io.*;
            
            public class TestClient {
              public static void main(String[] args) throws Exception {
                //創建客戶端套接字對象
                Socket s = new Socket("localhost", 9090);
                //將BufferedReader與Socket綁定,以輸出Socket獲得的信息
                BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream()));
                //獲得信息并輸出
                String line = in.readLine();
                while (line != null) {
                  System.out.println(line);
                  line = in.readLine();
                }
              }
            }
            13.org.apache.log4j.net.SMTPAppender,向指定的電子郵件發送信息,但只能發送ERROR和FATAL級別的信息,而且還沒提供身份驗證功能。
            示例代碼:
            SimpleLayout loyout = new SimpleLayout();
            
            SMTPAppender appender = new SMTPAppender();
            appender.setLayout(loyout); //設置Layout
            appender.setFrom("zhengyp@126.com"); //設置發件人
            appender.setSMTPHost("smtp.126.com"); //設置發送郵件服務器
            appender.setTo("zhengyp@126.com"); //設置收件人
            appender.setSubject("Log4J Test"); //設置郵件標題
            appender.activateOptions(); //應用設置
            
            Logger log = Logger.getLogger(Test.class);
            log.addAppender(appender);
            log.debug("Here is DEBUG");
            log.info("Here is INFO");
            log.warn("Here is WARN");
            log.error("Here is ERROR");
            log.fatal("Here is FATAL");
          要運行此示例,還需要JavaMail 和JAF,前者是Sun推出的電子郵件類庫,可以從http://java.sun.com/products/javamail/downloads/index.html下載,最新版本1.3.3,下載javamail-1_3_3-ea.zip壓縮包后需要其中的mail.jar文件;后者全稱是JavaBeans Activation Framework,提供了對輸入任意數據塊的支持,并能相應地對其進行處理,可以從http://www.sun.com/download中找到,最新版本1.1,下載jaf-1_1-ea.zip壓縮包后需要其中的activation.jar文件。不過,程序運行后會拋出兩次異常,分別是log.error和log.fatal方法導致的,失敗的原因很簡單,我用的郵件服務器需要身份驗證。
            14.piv.zheng.log4j.test.SMTPAppender,自定義的,依照Log4J提供的SMTPAppender修改而來,增加了身份驗證功能,并去掉了對級別的限制。由于代碼太長,所以放到了另一篇文章《自定義SMTPAppender的源碼》中,有興趣的請自行去查看。
            示例代碼:
              SimpleLayout layout = new SimpleLayout();
              
              SMTPAppender appender = new SMTPAppender(layout);
              appender.setFrom("zhengyp@126.com"); //發件人
              appender.setSMTPHost("smtp.126.com"); //發送郵件服務器
              appender.setTo("zhengyp@126.com"); //收件人
              appender.setSubject("Log4J Test"); //郵件標題
              appender.setAuth("true"); //身份驗證標識
              appender.setUsername("zhengyp"); //用戶名
              appender.setPassword("1111111"); //密碼
              appender.activateOptions(); //應用設置
              
              Logger log = Logger.getLogger(Test.class);
              log.addAppender(appender);
              log.debug("output");
          同樣需要JavaMail 和JAF。程序運行后會發送一封郵件,快去查看一下自己的郵箱吧^_^
            此外,Log4J還提供了SyslogAppender、JMSAppender(均在org.apache.log4j.net包下)以及更多的 Appender,或者用來向Unix操作系統的syslogd服務發送信息,或者通過JMS方式發送信息,或者以其他方式發送信息。由于條件有現,就不再介紹了。
            不過,在前邊的示例中還使用了SimpleLayout和PatternLayout來格式化輸出的信息,這里也簡單介紹一下。
            1.org.apache.log4j.SimpleLayout,一直用的就是它,輸出的格式比較簡單,就是"級別 - 信息"。
            2.org.apache.log4j.HTMLLayout,以HTML格式輸出信息。
            示例代碼:
              HTMLLayout layout = new HTMLLayout();
              layout.setTitle("Log4J Test"); //HTML頁標題
              
              FileAppender appender = null;
              try {
                appender = new FileAppender(layout, "test.html");
              } catch(Exception e) {}
              
              Logger log = Logger.getLogger(Test.class);
              log.addAppender(appender);
              log.debug("output");
          程序運行后會在工程目錄下生成一個HTML頁,可以用瀏覽器來查看。
            3.org.apache.log4j.xml.XMLLayout,以XML格式輸出信息。
            示例代碼:
              XMLLayout layout = new XMLLayout();
              
              FileAppender appender = null;
              try {
                appender = new FileAppender(layout, "test.xml");
              } catch(Exception e) {}
              
              Logger log = Logger.getLogger(Test.class);
              log.addAppender(appender);
              log.debug("output");
          程序運行后會在工程目錄下生成一個test.xml文件。
            4.org.apache.log4j.TTCCLayout,輸出信息的同時輸出日志產生時間、相關線程及Category等信息。
            示例代碼:
              TTCCLayout layout = new TTCCLayout();
              //是否打印與TTCCLayout關聯的Category的名稱,默認為true,表示打印
              layout.setCategoryPrefixing(true);
              //是否打印當前線程,默認為true,表示打印
              layout.setThreadPrinting(true);
              //是否打印輸出和當前線程相關的NDC信息,默認為true,表示打印
              layout.setContextPrinting(true);
              //設置日期時間格式
              layout.setDateFormat("iso8601");
              //設置時區
              layout.setTimeZone("GMT+8:00");
              //設置時區后需要調用此方法應用設置
              layout.activateOptions();
              
              ConsoleAppender appender = new ConsoleAppender(layout);
              
              Logger log = Logger.getLogger(Test.class);
              log.addAppender(appender);
              log.debug("output");
          注意,TTCCLayout輸出的時間格式及時區是可以設置的:
            (1)setDateFormat,設置日期時間格式,有五個常用值:"NULL",表示不輸出;"RELATIVE",輸出信息所用的時間,以毫秒為單位,默認使用該值;"ABSOLUTE",僅輸出時間部分;"DATE",按當前所在地區顯示日期和時間;"ISO8601",按ISO8601標準顯示日期和時間。這些字符串不區分大小寫。此外,還可以使用時間模式字符來格式化日期時間,詳細內容請參考J2SE文檔中的 java.text.SimpleDateFormat類。
            (2)setTimeZone,設置時區,詳細內容請參考J2SE文檔中的java.util.TimeZone類和java.util.SimpleTimeZone類。但請注意,當日期格式為"RELATIVE"時,設置時區會造成沖突。
            5.org.apache.log4j.PatternLayout,用模式字符靈活指定信息輸出的格式。
            示例代碼:
              String pattern = "Logger: %c %n"
                  + "Date: %d{DATE} %n"
                  + "Message: %m %n";
              PatternLayout layout = new PatternLayout(pattern);
              
              ConsoleAppender appender = new ConsoleAppender(layout);
              
              Logger log = Logger.getLogger(Test.class);
              log.addAppender(appender);
              log.debug("output");
          模式字符串簡介:
            %c:Category名稱。還可以使用%c{n}的格式輸出Category的部分名稱,其中n為正整數,輸出時會從Category名稱的右側起查 n個".",然后截取第n個"."右側的部分輸出,例如Category的名稱為"x.y.z",指定格式為"%c{2}",則輸出"y.z"。
            %C:輸出信息時Category所在類的名稱,也可以使用%C{n}的格式輸出。
            %d:輸出信息的時間,也可以用%d{FormatString}的格式輸出,其中FormatString的值請參考TTCCLayout的setDateFormat方法,但NULL和RELATIVE在%d中無法使用。
            %F:輸出信息時Category所在類文件的名稱。
            %l:輸出信息時Category所在的位置,使用"%C.%M(%F:%L)"可以產生同樣的效果。
            %L:輸出信息時Category在類文件中的行號。
            %m:信息本身。
            %M:輸出信息時Category所在的方法。
            %n:換行符,可以理解成回車。
            %p:日志級別。
            %r:輸出信息所用的時間,以毫秒為單位。
            %t:當前線程。
            %x:輸出和當前線程相關的NDC信息。
            %X:輸出與當前現成相關的MDC信息。
            %%:輸出%。
          此外,還可以在%與模式字符之間加上修飾符來設置輸出時的最小寬度、最大寬度及文本對齊方式,例如:
            %30d{DATE}:按當前所在地區顯示日期和時間,并指定最小寬度為30,當輸出信息少于30個字符時會補以空格并右對齊。
            %-30d{DATE}:也是按當前所在地區顯示日期和時間,指定最小寬度為30,并在字符少于30時補以空格,但由于使用了"-",因此對齊方式為左對齊,與默認情況一樣。
            %.40d{DATE}:也是按當前所在地區顯示日期和時間,但指定最大寬度為40,當輸出信息多于40個字符時會將左邊多出的字符截掉。此外,最大寬度只支持默認的左對齊方式,而不支持右對齊。
            %30.40d{DATE}:如果輸出信息少于30個字符就補空格并右對齊,如果多于40個字符,就將左邊多出的字符截掉。
            %-30.40d{DATE}:如果輸出信息少于30個字符就補空格并左對齊,如果多于40個字符,就將左邊多出的字符截掉。
          五、Log4J進階
            了解以上內容后,就已經初步掌握Log4J了,但要想靈活使用Log4J,則還需要了解其配置功能。這里簡單介紹一下。
            1.org.apache.log4j.BasicConfigurator,默認使用ConsoleAppender以PatternLayout (使用PatternLayout.TTCC_CONVERSION_PATTERN,即"%r [%t] %p %c %x - %m%n"格式)輸出信息。
            示例代碼:
              BasicConfigurator.configure();
              Logger log = Logger.getLogger(Test.class);
              log.debug("output");
          注意,BasicConfigurator及其它Configurator其實都只對根Category進行配置,但由于用戶創建的Category會繼承根Category的特性(聲明,許多資料介紹Category繼承關系時都主要在討論輸出級別,而事實上,Category間繼承的不僅是輸出級別,所有特性都可以繼承),因此輸出時仍會顯示BasicConfigurator配置的效果。此外,還可以使用configure方法指定Appender,以自定義輸出。BasicConfigurator允許同時指定多個Appender。
            示例代碼:
              SimpleLayout layout1 = new SimpleLayout();
              ConsoleAppender appender1 = new ConsoleAppender(layout1);
              BasicConfigurator.configure(appender1);
              
              String pattern = "Logger: %c %n"
                  + "Date: %d{DATE} %n"
                  + "Message: %m %n";
              PatternLayout layout2 = new PatternLayout(pattern);
              FileAppender appender2 = null;
              try {
                appender2 = new FileAppender(layout2, "test.log", false);
              }
              catch(Exception e){}
              BasicConfigurator.configure(appender2);
              
              Logger log = Logger.getLogger(Test.class);
              log.debug("output");
          這里用BasicConfigurator指定了兩個Appender,即ConsoleAppender和FileAppender,程序運行后信息會在以SimpleLayout輸出到控制臺的同時以PatternLayout輸出到test.log文件。若要清除這些Appender,可以調用 BasicConfigurator的resetConfiguration方法。
            2. org.apache.log4j.PropertyConfigurator,調用文本配置文件輸出信息,通常使用.properties文件。配置文件以"鍵=值"的形式保存數據,注釋以"#"開頭。PropertyConfigurator和配置文件在介紹SocketAppender和 SocketHubAppender時曾提到過。使用PropertyConfigurator可以避免硬編碼。
            示例代碼:
              PropertyConfigurator.configure("test.properties");
              Logger log = Logger.getLogger(Test.class);
              log.debug("output");
          要完成該示例,還需要在工程目錄下創建一個test.properties文件,內容如下:
            ##設置根Category,其值由輸出級別和指定的Appender兩部分組成
            #這里設置輸出級別為DEBUG
            log4j.rootLogger=DEBUG,appender
            ##輸出信息到控制臺
            #創建一個名為appender的Appender,類型為ConsoleAppender
            log4j.appender.appender=org.apache.log4j.ConsoleAppender
            #設置appender以SimpleLayout輸出
            log4j.appender.appender.layout=org.apache.log4j.SimpleLayout
          此外,PropertyConfigurator也允許同時指定多個Appender,例如:
            #這里沒有設置輸出級別,但指定了兩個Appender
            log4j.rootLogger=,appender1,appender2
            #輸出信息到控制臺
            log4j.appender.appender1=org.apache.log4j.ConsoleAppender
            log4j.appender.appender1.layout=org.apache.log4j.SimpleLayout
            #輸出信息到文件
            log4j.appender.appender2=org.apache.log4j.FileAppender
            log4j.appender.appender2.File=test.log
            log4j.appender.appender2.Append=false
            log4j.appender.appender2.layout=org.apache.log4j.PatternLayout
            log4j.appender.appender2.layout.ConversionPattern=Logger: %c %nDate: %d{DATE} %nMessage: %m %n
          關于更多配置,網上示例很多,這里不再贅述。但要說明一件事,就是配置文件中的鍵是怎么來的。參照后一個示例,查看 PropertyConfigurator源碼,會發現"log4j.rootLogger"是定義好的,只能照寫;而"log4j.appender" 字樣也可以找到,與指定的Appender名稱appender1、appender2聯系起來,log4j.appender.appender1和 log4j.appender.appender2也就不難理解了;再看下去,還能找到"prefix + ".layout"",這樣log4j.appender.appender1.layout也有了;可是 log4j.appender.appender2.File 和log4j.appender.appender2.Append呢?還記得前邊介紹FileAppender時曾提到的setAppend方法嗎?其實FileAppender還有個getAppend方法,這說明FileAppender具有Append屬性。那么File呢?當然也是 FileAppender的屬性了。至于log4j.appender.appender2.layout.ConversionPattern也一樣,只不過FileAppender換成了PatternLayout。其實別的Appender和Layout的屬性也都是這樣定義成鍵來進行設置的。此外,定義鍵時,屬性的首字母不區分大小寫,例如"File",也可以寫成"file"。
            3. org.apache.log4j.xml.DOMConfigurator,調用XML配置文件輸出信息。其定義文檔是log4j- 1.2.11.jar中org\apache\log4j\xml包下的log4j.dtd文件。與PropertyConfigurator相比, DOMConfigurator似乎是趨勢。
            示例代碼:
              DOMConfigurator.configure("test.xml");
              Logger log = Logger.getLogger(Test.class);
              log.debug("output");
          要完成該示例,也需要在工程目錄下創建一個test.xml文件,內容如下:
            <?xml version="1.0" encoding="UTF-8" ?>
            <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
            <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
              <!-- 輸出信息到控制臺
              創建一個名為appender的Appender,類型為ConsoleAppender -->
              <appender name="appender" class="org.apache.log4j.ConsoleAppender">
                <!-- 設置appender以SimpleLayout輸出 -->
                <layout class="org.apache.log4j.SimpleLayout"/>
              </appender>
              <!-- 設置根Category,其值由輸出級別和指定的Appender兩部分組成
              這里設置輸出級別為DEBUG -->
              <root>
                <priority value ="debug" />
                <appender-ref ref="appender"/>
              </root>
            </log4j:configuration>
          此外,DOMConfigurator也允許同時指定多個Appender,例如:
            <?xml version="1.0" encoding="UTF-8" ?>
            <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
            <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
              <!-- 輸出信息到控制臺 -->
              <appender name="appender1" class="org.apache.log4j.ConsoleAppender">
                <layout class="org.apache.log4j.SimpleLayout"/>
              </appender>
              <!-- 輸出信息到文件 -->
              <appender name="appender2" class="org.apache.log4j.FileAppender">
                <param name="File" value="test.log"/>
                <param name="Append" value="false"/>
                <layout class="org.apache.log4j.PatternLayout">
                  <param name="ConversionPattern" value="Logger: %c %nDate: %d{DATE} %nMessage: %m %n"/>
                </layout>
              </appender>
              <!-- 這里沒有設置輸出級別,但指定了兩個Appender -->
              <root>
                <appender-ref ref="appender1"/>
                <appender-ref ref="appender2"/>
              </root>
            </log4j:configuration>
          由于以上兩個示例是在PropertyConfigurator的兩個示例基礎上改的,而且也寫了注釋,因此這里只簡單介紹一下<param> 標記。<param>標記有兩個屬性,name和value,前者的值也是Appender或Layout的屬性名,作用與 log4j.appender.appender2.File這樣的鍵一樣。設置時,首字母同樣不區分大小寫,例如"File"也可以寫成"file"。此外還請注意,使用這兩段XML代碼時應將中文注釋去掉,或者把<?xml version="1.0" encoding="UTF-8" ?>中的UTF-8改成GBK或GB2312,否則會導致錯誤。這里使用的UTF-8是XML默認的字符集。
            4. org.apache.log4j.lf5.DefaultLF5Configurator,默認使用LF5Appender來輸出信息,需要調用 log4j-1.2.11.jar中org\apache\log4j\lf5\config包下的defaultconfig.properties文件。
            示例代碼:
              try {
                DefaultLF5Configurator.configure();
              }
              catch(Exception e){}
              Logger log = Logger.getLogger(Test.class);
              log.debug("output");
            下面討論另外一個話題:Diagnostic Context。Diagnostic Context意為診斷環境,針對于多用戶并發環境,在這種環境下,通常需要對每個客戶端提供獨立的線程以處理其請求,此時若要在日志信息中對客戶端加以區分,為每個線程分別創建Category是個辦法。但這樣做并不高效,反而會導致大量資源被占用。Diagnostic Context所要解決的就是這個問題。Diagnostic Context會為當前線程提供一定空間,然后將信息保存到該空間供Category調用。與創建一個Category相比,這點信息所占的資源自然要少得多。
            1.org.apache.log4j.NDC。NDC是Nested Diagnostic Context的簡寫,意為嵌套診斷環境,使用時提供一個堆棧對象來保存信息。堆棧的特點是數據后進先出、先進后出,即清理堆棧時,后保存的數據會被先清掉,而先保存的數據則被后清掉。
            示例代碼:
              PatternLayout layout = new PatternLayout("%m %x%n");
              ConsoleAppender appender = new ConsoleAppender(layout);
              Logger log = Logger.getLogger(Test.class);
              log.addAppender(appender);
              
              String tmp = "zhengyp"; //模擬從客戶端獲取的信息
              log.debug("Start");
              NDC.push(tmp); //添加信息到堆棧中
              log.debug("Before");
              NDC.pop(); //將信息從堆棧中移除
              log.debug("After");
              NDC.remove(); //將當前線程移除,退出NDC環境
              log.debug("End");
          這里使用了PatternLayout來格式化信息,其模式字符%x就是用來輸出NDC信息的。程序運行后會輸出如下內容:
            Start
            Before zhengyp
            After
            End
          可以看到,第二行輸出時由于已向堆棧中添加了信息,因此"zhengyp"也會同時輸出;而第三行輸出時由于信息已被移除,因此就沒再輸出"zhengyp"。不過這個示例僅簡單演示了NDC的用法,而沒有顯示出NDC的堆棧特性,所以下面再提供一個示例,代碼如下:
            TTCCLayout layout = new TTCCLayout();
            ConsoleAppender appender = new ConsoleAppender(layout);
            Logger log = Logger.getLogger(Test.class);
            log.addAppender(appender);
            
            log.debug("Start");
            NDC.push("zhengyp"); //添加信息到堆棧中
            log.debug("Test1");
            NDC.push("192.168.0.1"); //向堆棧中追加信息
            log.debug("Test2");
            NDC.pop(); //從堆棧中移除信息,但移除的只是最后的信息
            log.debug("Test3");
            NDC.pop(); //再次從堆棧中移除信息
            log.debug("Test4");???
            log.debug("End");
          這里格式化輸出信息使用的是TTCCLayout,還記得其setContextPrinting方法嗎?程序運行后,從輸出的信息就可以看到效果了。此外,NDC還提供了其他方法:
            (1)get,獲取堆棧中的全部信息。以上例為例,當輸出Test2時,使用該方法會獲得"zhengyp 192.168.0.1"。
            (2)peek,獲取堆棧中最后的信息。仍以上例為例,當輸出Test1時會獲得"zhengyp",Test2時為"192.168.0.1",而當輸出Test3時由于"192.168.0.1"已被移除,"zhengyp"又成了最后的信息,因此獲得的仍是"zhengyp"。
            (3)clear,清空堆棧中的全部信息。
            (4)setMaxDepth,設置堆棧的最大深度,即當前的信息可以保留多少,對之后追加的信息沒有影響。當需要一次清掉多條信息時,使用setMaxDepth會比多次調用pop方便。
            2.org.apache.log4j.MDC。MDC是Mapped Diagnostic Context的簡寫,意為映射診斷環境,提供了一個Map對象來保存信息。Map對象使用Key、Value的形式保存值。
            示例代碼:
              PatternLayout layout = new PatternLayout("%m %X{name} %X{ip}%n");
              ConsoleAppender appender = new ConsoleAppender(layout);
              Logger log = Logger.getLogger(Test.class);
              log.addAppender(appender);
              
              log.debug("Start");
              //添加信息到Map中
              MDC.put("name", "zhengyp1");
              MDC.put("ip", "192.168.1.1");
              log.debug("Test1");
              
              //添加信息到Map中,若Key重復,則覆蓋之前的值
              MDC.put("name", "zhengyp2");
              MDC.put("ip", "192.168.1.2");
              log.debug("Test2");
              
              //將信息從Map中移除,此時信息不再輸出
              MDC.remove("name");
              MDC.remove("ip");
              log.debug("End");
          這個示例演示了MDC的基本用法,格式化信息用的也是PatternLayout,模式字符為"%X",其格式必須為"%X{Key}"。其中Key就是向 Map對象添加信息時put方法所用的Key,這里為name和ip。由于可以使用"%X{Key}"輸出信息,因此MDC使用起來會比NDC更靈活。此外,MDC還提供了get方法來獲取指定Key的信息。
          六、小結
            用了近半個月,終于大概掌握了Log4J。由于本文是邊學邊寫的,目的是將Log4J的用法記錄下來,而非提供一份中文參考,因此內容并不細致,但盡量提供了示例。不過到最后才發現,示例存在問題,其實Logger做為類的static成員比較恰當,而我為了圖方便,竟直接寫到了main方法中,這一點還請注意。
            此外,這里再推薦一下《The Complete log4j Manual》,是對Log4J較詳細的介紹,在網上可以找到,只不過是英文的。


          posted @ 2006-09-01 13:25 Binary 閱讀(436) | 評論 (0)編輯 收藏

          主站蜘蛛池模板: 苗栗县| 奇台县| 萨嘎县| 乾安县| 南部县| 博爱县| 东港市| 成都市| 任丘市| 江西省| 泌阳县| 宁化县| 凤翔县| 岑溪市| 汝阳县| 大化| 八宿县| 蚌埠市| 中西区| 德保县| 翁牛特旗| 曲松县| 徐汇区| 喜德县| 龙南县| 德保县| 金阳县| 乌鲁木齐县| 大兴区| 宁阳县| 徐闻县| 碌曲县| 泊头市| 怀安县| 乌鲁木齐市| 陇西县| 田阳县| 通州市| 镇原县| 泰州市| 深州市|