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 阅读(3256) | 评论 (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 阅读(438) | 评论 (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 阅读(321) | 评论 (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 阅读(278) | 评论 (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 阅读(388) | 评论 (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 阅读(445) | 评论 (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 阅读(873) | 评论 (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 阅读(618) | 评论 (0)编辑 收藏

WebWork教程-ServletDispatcher

     摘要: ServletDispatcher 原理 ServletDispatcher 是默认的处理 Web Http 请求的调度器,它是一个 JavaServlet ,是 WebWork 框架的控制器。...  阅读全文

posted @ 2006-09-01 13:41 Binary 阅读(634) | 评论 (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 阅读(659) | 评论 (0)编辑 收藏

WebWork教程- Interceptor(拦截器)

     摘要: Interceptor (拦截器)将 Action 共用的行为独立出来,在 Action 执行前后运行。这也就是我们所说的 AOP ( Aspect Oriented Programming ,面向切面编程),它是分散关注的编程方法,它将通用需求功能从不相关类之中分离出来;同时,能够使得很多类共享一个行为,一...  阅读全文

posted @ 2006-09-01 13:39 Binary 阅读(598) | 评论 (0)编辑 收藏

WebWork介绍-Action篇

     摘要: Action 简介 Action 在 MVC 模式中担任控制部分的角色 , 在 WebWork 中使用的最多 , 用于接收页面参数,起到对 HttpRequest 判断处理作用。每个请求的动作都对应于一个相应的 ...  阅读全文

posted @ 2006-09-01 13:38 Binary 阅读(216) | 评论 (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 阅读(419) | 评论 (0)编辑 收藏