j2ee网络日志

来源:互联网 发布:巅峰网络传世教程 编辑:程序博客网 时间:2024/05/16 10:56

cglib的应用

    Proxy可以看作是微型的AOP,的的确确提供了代码在继承和委托之外的第三个封装途径(河的第三条岸?),只要有足够的想象力,可以做得非常好玩,Spring的源码里用Proxy就用得很随便,看得我非常眼红。
    选择cglib (Code Generation Librayr),是因为AOP框架一般比较大,丁点事情都要在XML文件里配置一番,所以要到Component级的事情才好麻烦AspectJ它们出手,平时的小代码封装,拿个cgilb/proxy玩玩就够了。众所周知,jdk自带的动态proxy必须基于接口,所以就跟风hibernate,用一下cglib。

   cglib说起来神奇,用起来一页纸不到就讲完了。
    它的原理就是用Enhancer类生成一个原有类的子类,则原有类的每个方法调用都会调用它的MethodInterceptor接口的public Object intercept(Object o,Method method,Object[] args,MethodProxy proxy)函数
    在intercept()函数里,你可以在执行Object result=proxy.invokeSuper(o,args);来执行原有函数,在执行前后加入自己的东西,也可以瞒天过海,完全干别的。

AOP里讲的在cglib是这样做的:

   public class LogDAOProxy implements MethodInterceptor
  {
       private Logger log=Logger.getLogger(AOPInstrumenter.class);
       private Enhancer enhancer=new Enhancer();
 
       //生成DAO的子类
       public Object getDAO(Class clz)
       {
           enhancer.setSuperclass(clz);
           enhancer.setCallback(this);
           return enhancer.create();
       }
 
      //默认拦截子类
      public Object intercept(Object o,Method method,Object[] args,MethodProxy proxy) throws Throwable
      {
           log.info("调用日志方法"+method.getName());
           Object result=proxy.invokeSuper(o,args);
           return result;
      }
}

    应用的代码:

    LOgDAOProxy proxy = new LogDAOProxy();
    GoodsDAO  dao = (GoodsDAO)proxy.getDAO(GoodsDAO.class);
    //dao.insert(goods);

 

Hibernat 的SessionFactory的getCurrentSesion()方法详解

如果在hibernate.cfg.xml中配置了
  <property name="hibernate.transaction.manager_lookup_class">org.hibernate.transaction.JBossTransactionManagerLookup</property> 
  <property name="hibernate.transaction.factory_class">org.hibernate.transaction.CMTTransactionFactory</property>
  <property
name="hibernate.current_session_context_class">jta</property>
这个配置的意思是当前对于这个SessionFactory(org.hibernate.transaction.CMTTransactionFactory的实例)来说,方法getCurrentSessiong()这个操作都应该在Container Manager Transaction中进行的,此时这个方法会将Session和Transaction进行绑定,对于应用来说则只需调用getCurrentSession就可以了,无需关心Session的Commit和Close.但是如果不是在一个Container Manager Transaction的Bean中调用SessionFactory.getCurrentSession(),则会抛出如下"org.hibernate.HibernateException: Unable to locate current JTA transaction"
,我想是因为容器没有为当前的Bean开始事务,所以这个方法无法绑定Session到当前的JTA transaction中去.

所以在配置前要想清楚是不是所有的操作都是在CMT中进行的,如果不是的话,不能够进行这样的操作.在一个应用中,往往有多个senarior,有的是通过CMT的session bean来调用,而有的则是通过Service直接调用DAO,要解决这个问题的话,可以配置多个SessiongFactory,将其Bind到容器的JNDI树中去.在调用的时候根据当前的Senaior来取不同的SessionFactory.

例如可以为所有通过的CMT管理的Bean作上述配置,对于BMT管理的配置如下:
  <property name="hibernate.transaction.manager_lookup_class">org.hibernate.transaction.JBossTransactionManagerLookup</property> 
  <property name="hibernate.transaction.factory_class">org.hibernate.transaction.JTATransactionFactory</property>
  <property
name="hibernate.current_session_context_class">jta</property>
使用的代码如下:
//BMTidiomwithgetCurrentSession()
try{
 UserTransactiontx=(UserTransaction)newInitialContext()
 .lookup("java:comp/UserTransaction");
 tx.begin();
 //DosomeworkonSessionboundtotransaction
 factory.getCurrentSession().load(...);
 factory.getCurrentSession().persist(...);
 tx.commit();
}
catch(RuntimeExceptione){
 tx.rollback();
 throwe;//ordisplayerrormessage
}

其实这里与CMT不同的就是要手动开始一个Transaction,SessionFactory检查这个Transaction是否是Begin,然后绑定一个Session到这个Transaction上去.

如果是在非托管的环境的应用的话,用JDBCTransactionFactory就可以了,另外对于hibernate.current_session_context_class可以设置为Thread,通过Session.getCurrentSesion()这个方法,让每个Thread公用一个session,同样你也无须关心Sesion的打开和关闭.
//Non-managedenvironmentidiomwithgetCurrentSession()
try{
 factory.getCurrentSession().beginTransaction();
 //dosomework
 ...
 factory.getCurrentSession().getTransaction().commit();
}
catch(RuntimeExceptione){
 factory.getCurrentSession().getTransaction().rollback();
 throwe;//ordisplayerrormessage
}
注意如果采取这种方式获得Session,即使对查询语句也需要开始事务,否则会抛异常.
org.hibernate.HibernateException: createSQLQuery is not valid without active transaction

如果对于CMT,BMT和非托管环境都要用到的,则不再适合用SessionFactory.getCurrentSession(),而需要用OpenSession,并自己负责事务的提交以及Sesion的Close.

将jdk1.5编写代码运行在1.4中

为了使用1.5所提供的便利特性而又需要在1.4下运行因此不得不借助其他工具了。
所幸的是有一个开源项目可以将jdk1.5编写的代码在1.4平台上运行。
 
下载地址: http://prdownloads.sourceforge.net/retroweaver/retroweaver-1.2.3.zip?download
 

Spring2.0 国际化异常原因

下午,学习spring国际化的功能。如果使用FileSystemXmlApplicationContext 加载配置文件则国际化属性文件必须位于配置文件所在的文件夹中,否则国际化报错。
 
如果message文件位于class path中则需要使用 ClassPathXmlApplicationContext 加载spring的配置文件。
 
public class TestMessages extends TestCase {
//-------------------------------------------------------------------------------------------
//message文件可以位于class path中。
public void xtestMessage() {
  ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
  
  Object[] msgObj = {"Office", Calendar.getInstance().getTime()};
  
  String msg = ctx.getMessage("userinfo", msgObj, Locale.getDefault());
  
  System.out.println(msg);
 }
 
//-------------------------------------------------------------------------------------------
//message文件必须位于bean.xml的相同目录中。
 public void testFormat() {
  ApplicationContext ctx = new FileSystemXmlApplicationContext("bean.xml");
  Object[] msgObj = {"Office", Calendar.getInstance().getTime()};
  
  String msg = ctx.getMessage("userinfo", msgObj, Locale.getDefault());
  
  System.out.println(msg);
 }
}
 
//-------------------------------------------------------------------------------------------
 
 
9月15日

log4j 模版

#log4j.rootLogger=debug, file
log4j.rootLogger=info,stdout,file
### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c:%L - %m%n
### direct messages to file toptais.log ###
log4j.appender.file=org.apache.log4j.RollingFileAppender
log4j.appender.file.MaxFileSize=10MB
log4j.appender.file.MaxBackupIndex=10
log4j.appender.file.File=D:/Projects/DataTransform/DataTransform.log
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d %-5p [%t] (%13F:%L) %3x - %m%n
 
### log prepared statement cache activity ###
log4j.logger.org.hibernate=error
log4j.logger.com.topsoft=error
log4j.logger.org.apache=error
log4j.logger.net.sf=error
#log4j.logger.data=info
#log4j.logger.commondata=info
 

将jbpm3.1中的 websale 部署到Tomcat + mysql上

琢磨了一上午,总算把websale部署到topcat和mysql上了。

 

1、在src/resoure中创建文件夹 mysqldb,将hsqldb中的两个文件 create.db.hibernate.properties 和 identity.db.xml复制到 mysqldb中,并修改 create.db.hibernate.properties 中的数据库联接:

# these properties are used by the build script to create
# a mysql database in the build/db directory that contains
# the jbpm tables and a process deployed in there
hibernate.dialect=org.hibernate.dialect.MySQLDialect
hibernate.connection.driver_class=com.mysql.jdbc.Driver
hibernate.connection.url=jdbc:mysql://localhost/testjbpm
hibernate.connection.username=root
hibernate.connection.password=root
hibernate.show_sql=true

2、在根目录创建 build.deploy.mysql.xml 文件,内容如下:

    <project name="jbpm.deploy" basedir=".">

 <description>jbpm deployment build file</description>

 <!-- ================== -->
 <!-- === PROPERTIES === -->
 <!-- ================== -->
 <property file="${user.home}/jbpm/build/build.local.properties" />
 <property file="build.properties" />

 <!-- ================= -->
 <!-- === CLASSPATH === -->
 <!-- ================= -->
 <path id="classpath.ant">
  <pathelement path="build/classes.identity" />
  <pathelement path="build/classes.jbpm" />
  <pathelement path="build/classes.webapp" />
  <pathelement path="src/config.files" />
  <fileset dir="lib" includes="**/*.jar" />
 </path>

 <!-- ======================== -->
 <!-- === 创建数据库并部署流程 === -->
 <!-- ======================== -->
 <target name="create.db" depends="declare.jbpm.tasks" description="creates a hypersonic database with the jbpm tables and loads the processes in there">
  <jbpmschema actions="create" cfg="${basedir}/src/config.files/hibernate.cfg.xml" properties="${basedir}/src/resources/mysqldb/create.db.hibernate.properties" />
  <loadidentities file="${basedir}/src/resources/mysqldb/identity.db.xml" cfg="${basedir}/src/config.files/hibernate.cfg.xml" properties="${basedir}/src/resources/mysqldb/create.db.hibernate.properties" />
  <ant antfile="build.xml" target="build.processes" inheritall="false" />
  <deployprocess cfg="${basedir}/src/config.files/hibernate.cfg.xml" properties="${basedir}/src/resources/mysqldb/create.db.hibernate.properties">
   <fileset dir="build" includes="*.process" />
  </deployprocess>
 </target>

 <!-- ==================== -->
 <!-- === 构建webapp应用 === -->
 <!-- ==================== -->
 <target name="build.webapp" description="builds jbpm.war">
  <ant antfile="build.xml" target="build.webapp" />
  <mkdir dir="build/jbpm.war.dir" />
  <copy todir="build/jbpm.war.dir">
   <fileset dir="src/resources/jbpm.war" />
  </copy>
  <copy todir="build/jbpm.war.dir/WEB-INF/lib">
   <fileset dir="build" includes="jbpm-webapp-${jbpm.version}.jar" />
   <fileset dir="build" includes="jbpm-${jbpm.version}.jar" />
   <fileset dir="build" includes="jbpm-identity-${jbpm.version}.jar" />
   <fileset dir="lib/jsf" includes="*.jar" />
   <fileset dir="lib/dom4j" includes="*.jar" />
   <fileset dir="lib/hibernate" includes="*.jar" />
   <fileset dir="lib/jboss" includes="asm.jar" />
   <fileset dir="lib/jboss" includes="asm-attrs.jar" />
   <fileset dir="lib/jboss" includes="antlr-2.7.5H3.jar" />
   <fileset dir="lib/jboss" includes="bsh-1.3.0.jar" />
   <fileset dir="lib/jboss" includes="cglib-2.1_2jboss.jar" />
   <fileset dir="lib/jboss" includes="commons-collections.jar" />
   <fileset dir="lib/jboss" includes="commons-logging.jar" />
   <fileset dir="lib/jboss" includes="log4j.jar" />
   <fileset dir="lib/jboss" includes="xercesImpl.jar" />
   <fileset dir="lib/jboss" includes="xml-apis.jar" />
   <fileset dir="lib/jdbc" includes="*.jar" />
   <fileset dir="lib/commons" includes="commons-digester-*.jar, commons-beanutils-*.jar, commons-fileupload-*.jar" />
  </copy>
  <copy todir="build/jbpm.war.dir/WEB-INF/classes">
   <fileset dir="src/config.files" includes="jbpm.cfg.xml, hibernate.cfg.xml, ehcache.xml, log4j.properties" />
  </copy>
  <jar destfile="build/jbpm.war">
   <fileset dir="build/jbpm.war.dir" />
  </jar>
 </target>

 

 <!-- ========================= -->
 <!-- === 部署webapp到Tomcat === -->
 <!-- ========================= -->
 <target name="deploy.webapp" depends="build.webapp" description="deploys the jbpm webapp">
  <ant antfile="build.xml" target="build.jbpm" />
  <delete file="D:/java/Tomcat 5.5/webapps/jbpm.war" />

  <copy todir="D:/java/Tomcat 5.5/webapps">
   <fileset dir="build" includes="jbpm.war" />
  </copy>
 </target>

 <!-- ======================= -->
 <!-- === 部署流程定义到数据 === -->
 <!-- ======================= -->
 <target name="deploy.processes" depends="declare.jbpm.tasks" description="deploys the jbpm processes">
  <ant antfile="build.xml" target="build.processes" inheritall="false" />
  <deployprocess cfg="src/config.files/hibernate.cfg.xml" properties="${basedir}/src/resources/mysqldb/create.db.hibernate.properties">
   <fileset dir="build" includes="*.process" />
  </deployprocess>
 </target>


 <!-- ============== -->
 <!-- === 声明任务 === -->
 <!-- ============== -->
 <target name="declare.jbpm.tasks">
  <ant antfile="build.xml" target="build" />
  <taskdef file="src/java.jbpm/org/jbpm/ant/jbpm.ant.tasks.properties" format="properties">
   <classpath refid="classpath.ant" />
  </taskdef>
 </target>
</project>

4、将mysq的jdbc复制到lib下;
 
5、修改build.doploy.mysql.xml文件中的 deploy.webapp 任务对应的tomcat路径;
 
6、在mysql中创建 testjbpm数据库,注意和配置文件相一致;
 
7、修改 src/config.files中的 hibernate.cfg.xml文件中 数据库配置,参考第一布;
 
最后使用ant创建数据库、构建应用并部署,OK!

log4j 简明手册

log4j 简明手册

Ceki Gülcü

March 2002

Copyright&amp;copy; 2000-2004 The Apache Software Foundation. 版权所有。Log4j软件是在遵守Apache Software License 1.1版的条例下发行的,Apache Software License的复制件被包括在log4j发布的LICENSE.txt文件里。这个简短手册也借用了The complete log4j manual 里的一些内容,The complete log4j manual包含最新的更为详尽的信息。  The complete log4j manual

摘要

这个文档资料描述了log4j API,它的独特的特性和设计原理。Log4j是由许多作者共同参与的开放源代码项目。它允许开发人员以任意的精细程度控制哪些日志说明被输出。通过使用外部的配置文件,可以在运行时配置它。最好的是,log4j 开发包很容易上手。注意,它也可能会使一些开发人员着迷。

简 介

几乎每个大的应用程序都有它自己的日志和跟踪程序的API。顺应这一规则,E.U. SEMPER项目组决定编写它自己的程序跟踪API(tracing API)。这开始于1996年早期。经过无数的工作,更改和性能加强,这个API终于成为一个十分受欢迎的Java日志软件包,那就是log4j。这个软件包的发行遵守open source动议认证的Apache Software License。最新的log4j版本包括全部的源代码,类文件和文档资料,可以在 http://logging.apache.org/log4j/找到它们。另外,log4j已经被转换成 C, C++, C#, Perl, Python, Ruby, 和 Eiffel 语言。

把log statements插入到你的代码中是一种排错的低技能办法。这也许是唯一的方法,因为排错工具并不总是可以被使用或者适用于你的程序。对于多线程的应用程序和多数发行的应用程序,通常就是这样的情形。

经验告诉我们logging是开发过程中重要的一环。它具有多种优点。首先,它能精确地提供运行时的上下文(context)。一旦在程序中加入了Log 代码,它就能自动的生成并输出logging信息而不需要人为的干预。另外,log信息的输出可以被保存到一个固定的地方,以备以后研究。除了在开发过程中发挥它的作用外,一个性能丰富的日志记录软件包能当作一个审计工具(audit tool)使用。

Brian W. Kernighan 和 Rob Pike 在他们的"The Practice of Programming" 书中这样写到:  "The Practice of Programming"

作为个人的选择,除了得到一大堆程序跟踪信息或一两个变量值以外,我们倾向於不使用排错器。一个原因是在详细而复杂的数据结构和控制流程中很容易迷失;我们发现认真思考并在关键处加入自我检查代码和输出指令,比起一步步看程序要效率高。在日志说明里查找比在明智地放置自我检查代码后的输出里查找要费时。而决定在哪里放置打印指令要比在日志说明里一步步找到关键的代码要省时间。更重要的是,自我检查的排错指令和程序并存;而排错sessions是暂时的。

Logging确实也有它的缺陷。它降低了程序运行的速度。它太冗长,查看时很容易错过。为了减少这些负面影响,log4j 被设计得可靠,高效和灵活。因为,记录日志很少是一个应用程序的主要焦点,log4j API 尽量做到容易被理解和使用。

Loggers, Appenders and Layouts

Log4j 有三个主要组件:loggers, appenderslayouts。这三类组件一起应用,可以让开发人员能够根据日志的类型和级别进行记录,并且能在程序运行时控制log信息输出的格式和往什么地方输出信息。

Logger hierarchy

任何logging API 与简单的System.out.println输出调试信息方法比较,最主要的优点在于它能够关闭一些调试信息输出而不影响其他人的调试。这种能力的实现是假设这些logging空间,也就是所有的可能发生的日志说明空间,可以根据程序开发人员选择的标准进行分类。这一观察以前使得我们选择了category作为这个软件包的中心概念。但是,在log4j 1.2版本以后,Logger类取代了Category类。对于那些熟悉早先版本的log4j的开发人员来说,Logger类只不过是Category类的一个别名。

Loggers是被命名的实体。Logger的名字大小写有区别(case-sensitive),并且它们遵守阶层式的命名规则:

Named Hierarchy

如果一个logger 的名字后面跟着一个点号(dot),它就是点号(dot)后面的那个logger的前辈( ancestor),是这个晚辈(descendant) 的前缀。如果在它自己和这个晚辈之间没有其它的前辈,它和这个晚辈之间就是关系。

例如,叫做"com.foo"的logger是叫做 "com.foo.Bar"的logger的父辈 。同样地,"java"是"java.util" 的父辈,是"java.util.Vector"的前辈。大多数开发人员都熟悉这种命名方法。  "com.foo"  "com.foo.Bar"  "java"  "java.util"  "java.util.Vector"

根(root)logger 位于logger 阶层的最上层。它在两个方面很特别:

  1. 它总是存在的,
  2. 不能通过使用它的名字直接得到它。

通过这个类的静态方法Logger.getRootLogger得到它(指RootLogger)。所有其他的loggers是通过静态方法Logger.getLogger来实例化并获取的。这个方法Logger.getLogger把所想要的logger的名字作为参数。 Logger类的一些其它基本方法在下面列出:

package org.apache.log4j; public class Logger {   // Creation and retrieval methods:   public static Logger getRootLogger();   public static Logger getLogger(String name);   // printing methods:   public void debug(Object message);   public void info(Object message);   public void warn(Object message);   public void error(Object message);   public void fatal(Object message);   // generic printing method:   public void log(Level l, Object message); }

Loggers可以被指派优先级别。DEBUG, INFO, WARN, ERRORFATAL这组级别在org.apache.log4j.Level类中有定义。你也可以通过Level类的子类去定义你自己的优先级别,尽管我们不鼓励你这样做。在后面我们会讲到一个更好的方法。

如果一个logger没有被指定优先级别,它将继承最接近的祖先所被指定的优先级别。下面是更多关于优先级别的信息:

Level Inheritance

对于一个给定的logger C,它继承的级别等于logger阶层里,从C开始往root logger上去的第一个non-null级别。

要保证所有的loggers最终都继承一个优先级别,root logger总是有一个被指派的优先级。

下面是具有各种指派优先级别值的四个表格,以及根据上面的规则所得出的继承优先级别。

Logger
name(名称)
指派
级别
继承
级别
Proot Proot X none Proot X.Y none Proot X.Y.Z none Proot 例子

在上面的示例1中,只有root logger被指派了级别。这个级别的值,Proot,被其它的loggers X, X.YX.Y.Z继承了。

Logger
name(名称)
指派
级别
继承
级别
Proot Proot X Px Px X.Y Pxy Pxy X.Y.Z Pxyz Pxyz 例子

在上面的示例2中,所有的loggers都有一个指派的级别值。不需要级别继承。

Logger
name(名称)
指派
级别
继承
级别
Proot Proot X Px Px X.Y none Px X.Y.Z Pxyz Pxyz 例子

在示例3中,loggers root, X 和 X.Y.Z 分别被指派级别值Proot, PxPxyz。Logger X.Y 从它的父辈X那里继承它的级别值。 Logger
name(名称)
指派
级别
继承
级别
Proot Proot X Px Px X.Y none Px X.Y.Z none Px 例子

在示例4中,loggers root和X 分别被指派级别值ProotPx。Logger X.YX.Y.Z继承它们最接近的父辈X的被指派的级别值。

日志请求是通过调用一个日志实例的打印方法(之一)而产生的。这些打印方法是 log4j/Logger.html#debug(java.lang.Object)">debug, info, warn, error, fatal 和 log。

根据定义,打印方法决定一个日志请求的级别。例如,如果c是一个日志实例,那么语句c.info("..") 就是级别为INFO的一个日志请求。  c.info("..")

只有一个日志请求(A logging request)的级别高于或等于它的logger级别的时候才能够被执行。否则,则被认为这个日志请求不能被执行。一个没有被定义优先级别的logger将从层次关系中的前辈那里继承优先级别。这个规则总结如下:

Basic Selection Rule

在一个级别为q(被指定的或继承的)的logger里,一个级别为p的日志请求,只有在p >= q 时才能够被执行。

consolefiles,GUI components,remote socket servers, JMS,NT Event Loggers,和 remote UNIX Syslog daemons。它还可以同时将log信息输出到多个输出设备中。   NT Event Loggers

这个规则是log4j的核心。它假设级别是有先后顺序的。对于标准的优先级别来说,DEBUG < INFO < WARN < ERROR < FATAL

这里是一个关于这个规则的例子:

   // get a logger instance named "com.foo"   Logger  logger = Logger.getLogger("com.foo");   // Now set its level. Normally you do not need to set the   // level of a logger programmatically. This is usually done   // in configuration files.   logger.setLevel(Level.INFO);   Logger barlogger = Logger.getLogger("com.foo.Bar");   // This request is enabled, because WARN >= INFO.   logger.warn("Low fuel level.");   // This request is disabled, because DEBUG < INFO.   logger.debug("Starting search for nearest gas station.");   // The logger instance barlogger, named "com.foo.Bar",   // will inherit its level from the logger named   // "com.foo" Thus, the following request is enabled   // because INFO >= INFO.   barlogger.info("Located nearest gas station.");   // This request is disabled, because DEBUG < INFO.   barlogger.debug("Exiting gas station search");

以一样的叁数名字调用getLogger方法,返回的reference总是指向完全相同的logger对象。

例如,在这里:

   Logger x = Logger.getLogger("wombat");   Logger y = Logger.getLogger("wombat");
x和y指向完全相同的logger对象。

因此,通过这种方式可以配置一个logger,而不需要传递references就能在其他地方得到相同的实例。在生物的父子关系中父母总是排放在孩子们前面, log4j loggers与此有相互矛盾的地方,那就是log4j loggers可以以任何顺序被产生和配置。特别的是,一个"parent" logger 会找到并连接他的后代,即使他是在他们之后被定义。

Log4j环境通常是在程序被初始化的时候被配置的。最好的方式是通过阅读一个配置文件去配置。我们会马上讨论到这方面的内容。

Log4j使得通过软件组件的名称去定义loggers的名字很容易。这可以通过在每个类中静态地instantiating一个logger,让logger的名字与这个合格的java类文件名相同来完成。这是一种有用并且直观的定义loggers的方式。因为日志的输出带有产生它们的logger的名字,这种命名策略使我们能够很方便地识别这些log信息的来源。不过,尽管这是通用的一种loggers命名策略,Log4j没有限制怎样对loggers进行命名。开发程序员可以根据自己的喜好随意定义 loggers。  software component

当然,至今所知的最好的命名策略还是以它们所在的类的名称来命名 loggers。

Appenders and Layouts

基于自身的logger选择性地使用或不使用日志请求(logging requests )的能力仅仅整个Log4j能力的一部分。Log4j允许将log信息输出到许多不同的输出设备中。用log4j的语言来说,一个log信息输出目的地就叫做一个appender。目前,log4j 的appenders可以将log信息输出到

多个appenders可以和一个logger连接在一起。

使用addAppender方法把一个appender加入到给定的logger上。一个给定的 logger的每一个被允许的日志请求都会被传递给这个logger的所有appenders,以及阶层中高级别的appenders。换句话说appenders是从logger阶层中不断添加地被继承的。例如,一个 console appender加给了root logger,那么,这个root logger所有被允许输出的日志信息将被输出到console。如果你又给一个名字为C的logger添加了一个 file appender,那么C 以及C的子辈的所有被允许的日志信息将被同时输出到 file appenderconsole appender。可以通过把additivity flag设置为false来覆盖这个默认的行为从而使appender的继承关系不再是添加性的。  Each enabled logging request for a given logger will be forwarded to all the appenders in that logger as well as the appenders higher in the hierarchy.  setting the additivity flag

支配appender添加性的规则总结如下:

Appender Additivity

Logger C的log输出信息将被输出到C的所有appenders和它的前辈的 appenders。这就是"appender additivity"的意思。

但是,如果logger C的前辈,比如说P,P的additivity flag被设置为 false,那么,C的输出信息将被输出到C的所有appenders中去,以及它的前辈的——截止在P那里,包括P在内的,appenders中去,但是不会输出到P的前辈的 appenders中去。

默认情况下,Loggers的additivity flag设置为true

PatternLayout 是标准log4j发行包中的一部分,它让用户根据和C语言中的printf方法相似的转换模式指定输出格式。

下面的表格显示一个示例:

Logger
name(名称)
添加的
Appenders
Additivity
旗标
输出目标 注释 A1 not applicable A1 Root logger是无名的,但是可以通过Logger.getRootLogger() 来访问。Root logger没有附带默认的appender。 x A-x1, A-x2 true A1, A-x1, A-x2 "x" 和root logger里的Appenders。 x.y none true A1, A-x1, A-x2 "x" 和root logger里的Appenders。 x.y.z A-xyz1 true A1, A-x1, A-x2, A-xyz1 "x.y.z", "x" 和root logger里的Appenders。 安全 A-sec false A-sec 因为additivity flag被设置为 false,所以没有appender继承积累。 security.access none true A-sec 因为"security" logger里的additivity flag被设置为false,所以仅仅只有"security" logger的appenders。

通常,用户不仅希望自己指定log信息的输出目的地,而且,他们还希望指定 log信息的输出格式。这可以通过和appender相关的layout实现。Layout负责根据用户的需要去格式化log信息的输出,而appender负责将一个格式化过的 log信息输出到它的目的地。

例如,具有"%r [%t] %-5p %c - %m%n" 转换格式的PatternLayout 将输出以下的式样:

176 [main] INFO org.foo.Bar - Located nearest gas station.

第一个区域是从程序开始运行到输出日志信息所用的毫秒数。第二个区域是产生日志请求的线程。第三个区域是这个log语句的优先级别。第四个区域是和日志请求相关联的logger名字。在'-' 之后的文字是这个log信息的内容。

同样重要的是,log4j 将根据用户指定的标准来表达log信息的内容。例如,如果你经常需要日志记录Oranges,Oranges是你当前项目中使用的一个对象类型,那么你可以注册一个OrangeRenderer,这样每当需要日志记录一个 orange时,OrangeRenderer就会被调用。

对象的表达遵照类阶层(class hierarchy)形式。例如,假设oranges是 fruits,你注册了一个FruitRenderer,那么,包括oranges在内的所有的fruits 都将由FruitRenderer来表达,除非你自己为orange注册了一个特定的 OrangeRenderer

Object renderers必须实施ObjectRenderer界面。

配 置

在程序代码中插入这些日志请求需要相当大的工作量。调查显示,大约%4左右的代码是logging。因此,即便是中等大小的应用程序也需要在它们的代码中至少包含有几千行的log语句。就从这个数目来看,管理这些log语句而不用人工地去修改它们是十分重要的。

Log4j环境是完全能够通过编程来配置的。但是使用配置文件去配置则更灵活。目前,Log4j的配置文件是以XML格式和JAVA properties (key=value) 格式编写的。

假设我们有个叫MyApp的程序使用log4j,让我们来看看这是怎样做到的:

 import com.foo.Bar; // Import log4j classes. import org.apache.log4j.Logger; import org.apache.log4j.BasicConfigurator; public class MyApp {   // Define a static logger variable so that it references the   // Logger instance named "MyApp".   static Logger logger = Logger.getLogger(MyApp.class);   public static void main(String[] args) {     // Set up a simple configuration that logs on the console.     BasicConfigurator.configure();     logger.info("Entering application.");     Bar bar = new Bar();     bar.doIt();     logger.info("Exiting application.");   } }

MyApp类首先引入log4j的相关类,然后定义一个命名为MyApp的静态logger变量,而这个名字恰好和MyApp的类名一样。

MyApp类还使用了被定义在com.foo包中的Bar类:

 package com.foo; import org.apache.log4j.Logger; public class Bar {   static Logger logger = Logger.getLogger(Bar.class);   public void doIt() {     logger.debug("Did it again!");   } }

通过调用BasicConfigurator.configure 方法产生一个相当简单的log4j的设置。这个方法将一个 ConsoleAppender添加到root logger,从而让log信息输出到 console。通过把PatternLayout设置为 %-4r [%t] %-5p %c %x - %m%n来确定输出格式。

注意,默认的root logger被指派为Level.DEBUG

MyApp的输出是这样的:

0    [main] INFO  MyApp  - Entering application.36   [main] DEBUG com.foo.Bar  - Did it again!51   [main] INFO  MyApp  - Exiting application.

下面的图形描绘了在调用BasicConfigurator.configure方法之后,MyApp的对象图表。

注意,log4j 的子代loggers只和它们现有的前辈链接。在这里,名字叫 com.foo.Bar的logger直接和root logger链接,因此绕过了没有被使用的com 或com.foo loggers。这样极大地提高了log4j的性能并减少了内存(memory)的使用。

通过调用BasicConfigurator.configure方法来配置MyApp类。其它的类只需要引入org.apache.log4j.Logger类,获取它们想要使用的loggers,就可以输出 log。

先前的例子总是输出同样的log信息。幸运的是,很容易修改MyApp程序就可以在程序运行时对log输出进行控制。下面是略加修改后的版本:

 import com.foo.Bar; import org.apache.log4j.Logger; import org.apache.log4j.PropertyConfigurator; public class MyApp {   static Logger logger = Logger.getLogger(MyApp.class.getName());   public static void main(String[] args) {     // BasicConfigurator replaced with PropertyConfigurator.     PropertyConfigurator.configure(args[0]);     logger.info("Entering application.");     Bar bar = new Bar();     bar.doIt();     logger.info("Exiting application.");   } }

这个例子中MyApp指示PropertyConfigurator方法去解读配置文件并设置相应的logging 。

这里是一个配置文件的示例,这个配置文件产生和前面BasicConfigurator例子完全一样的输出结果:

# Set root logger level to DEBUG and its only appender to A1.log4j.rootLogger=DEBUG, A1# A1 is set to be a ConsoleAppender.log4j.appender.A1=org.apache.log4j.ConsoleAppender# A1 uses PatternLayout.log4j.appender.A1.layout=org.apache.log4j.PatternLayoutlog4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n

假设我们不再需要com.foo软件包里任何组件的日志输出,下面的配置文件展示了达到这一目的的一种可能的方法:

log4j.rootLogger=DEBUG, A1log4j.appender.A1=org.apache.log4j.ConsoleAppenderlog4j.appender.A1.layout=org.apache.log4j.PatternLayout# Print the date in ISO 8601 formatlog4j.appender.A1.layout.ConversionPattern=%d [%t] %-5p %c - %m%n# Print only messages of level WARN or above in the package com.foo.log4j.logger.com.foo=WARN

由这个文件所配置的MyApp的日志输出如下:

2000-09-07 14:07:41,508 [main] INFO  MyApp - Entering application.2000-09-07 14:07:41,529 [main] INFO  MyApp - Exiting application.

因为logger com.foo.Bar 没有指定的优先级别,它就从com.foo中继承优先级别,而com.foo的优先级别在配置文件中被设置为WARN。 Bar.doIt 方法里的 log语句的级别为DEBUG,比WARN级别低。所以,doIt()方法的日志请求就被压制住了。

这里是另一个使用多个appenders的配置文件。

log4j.rootLogger=debug, stdout, Rlog4j.appender.stdout=org.apache.log4j.ConsoleAppenderlog4j.appender.stdout.layout=org.apache.log4j.PatternLayout# Pattern to output the caller's file name and line number.log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%nlog4j.appender.R=org.apache.log4j.RollingFileAppenderlog4j.appender.R.File=example.loglog4j.appender.R.MaxFileSize=100KB# Keep one backup filelog4j.appender.R.MaxBackupIndex=1log4j.appender.R.layout=org.apache.log4j.PatternLayoutlog4j.appender.R.layout.ConversionPattern=%p %t %c - %m%n

调用以这个配置文件增强了的MyApp会把下列输出信息输出到控制台(console)上。

 INFO [main] (MyApp2.java:12) - Entering application.DEBUG [main] (Bar.java:8) - Doing it again! INFO [main] (MyApp2.java:15) - Exiting application.

另外,当root logger增加了第二个appender时,log信息将同时也被输出到 example.log文件中。当example.log文件达到100KB 后,example.log文件将被rolled over。当roll-over 发生时,example.log 的老版本将自动被移到 example.log.1中去。

注意,要获得这些不同的logging行为并不需要重新编译代码。我们还可以简单地通过修改log配置文件把log信息输出到UNIX Syslog daemon中,把所有 com.foo的日志输出转指向NT Event logger 中,或者把log事件输出到远程 log4j服务器中,当然它要根据局部服务器规则进行log,例如可以把log事件输出到第二个log4j服务器中去。

默认的初始化过程

Log4j库没有对它的环境作任何假设。特别是,没有默认的log4j appenders。不过在一些精细定义过的情况下,这个Logger类的静态的initializer会试图自动配置log4j。 Java语言确保一个类的静态的initializer在这个类被装载到内存里时被调用一次,而且仅仅一次。这点很重要,要记住不同的classloaders会装载同一个类的不同复制版。这些同一个类的不同复制版在JVM看来是完全不相关的。

默认的初始化在这样的环境中很有用处,那就是同一个程序依据运行时的环境作不同用处。例如,同样一个程序可以在web-server的控制下作为单独的程序,作为一个applet,或者作为一个servlet被使用。

默认的初始化运算法则定义如下:

  1. log4j.defaultInitOverride的系统属性设置为 "false"以外的任何值将会造成 log4j跳过默认的初始化过程。

     

  2. resource这个string变量设置为log4j.configuration系统属性的值。最好的方法指定默认初始化文件是通过log4j.configuration系统属性来指定。在log4j.configuration系统属性没有被定义的情况下,把resource这个string变量设置成它的默认值"log4j.properties"。

     

  3. resource变量转换为一个URL。

     

  4. 如果这个resource变量不能转换为一个URL,例如,因为 MalformedURLException的缘故,那么就通过调用 org.apache.log4j.helpers.Loader.getResource(resource, Logger.class)在 classpath上搜寻resource,它会返回一个URL。注意, string "log4j.properties"是一个不合式的URL。  org.apache.log4j.helpers.Loader.getResource(resource, Logger.class)

    有关搜寻地址列单,请参看Loader.getResource(java.lang.String)

  5. 如果不能找到URL,那就放弃默认的初始化。否则,从URL配置log4j 。

    Configurator.html">PropertyConfigurator将被用于解读URL来配置log4j,除非这个URL以".xml"扩展符结束,若这个URL以".xml"扩展符结束,DOMConfigurator则被使用。你可以选择性地指定一个客户自己的configurator。log4j.configuratorClass系统属性的值就是你客户自己的configurator的类名。你指定的客户configurator必须 实施Configurator接口。

配置示例

Tomcat下默认的初始化

默认的log4j初始化在web-server环境中特别有用。在Tomcat 3.x and 4.x下,你应该把log4j.properties放置在你的网络程序的WEB-INF/classes目录下面。 Log4j自己会去找到属性文件并初始化。这样做又简单又有效。

你可以选择在Tomcat启动之前设置系统属性log4j.configuration 。对于 Tomcat 3.x ,TOMCAT_OPTS 环境变量被用来设置命令行选项。对于 Tomcat 4.0,使用CATALINA_OPTS环境变量而不是TOMCAT_OPTS 。

例子

Unix shell 命令

export TOMCAT_OPTS="-Dlog4j.configuration=foobar.txt"
告诉log4j 使用文件foobar.txt 作为默认的配置文件。这个文件应该被放置在你的网络应用程序的WEB-INF/classes目录下面。文件将通过 PropertyConfigurator被读取。每个网络应用程序使用不同的默认配置文件,因为每个文件都是和每个网络应用程序相关的。

例子

Unix shell 命令

   export TOMCAT_OPTS="-Dlog4j.debug -Dlog4j.configuration=foobar.xml"
告诉log4j输出log4j-内部排错信息,并使用文件foobar.xml 作为默认的配置文件。这个文件应该被放置在你的网络应用程序的WEB-INF/classes目录下面。因为文件以.xml扩展符结尾,将使用DOMConfigurator来读取。每个网络应用程序使用不同的默认配置文件,因为每个文件都是和每个网络应用程序相关的。

例子

Windows shell 命令

   set TOMCAT_OPTS=-Dlog4j.configuration=foobar.lcf -Dlog4j.configuratorClass=com.foo.BarConfigurator
告诉log4j使用文件foobar.lcf 作为默认的配置文件。这个文件应该被放置在你的网络应用程序的WEB-INF/classes 目录下面。根据log4j.configuratorClass 系统属性的定义 ,文件将通过将使用客户自己的configurator—— com.foo.BarConfigurator被读取。每个网络应用程序使用不同的默认配置文件,因为每个文件都是和一个网络应用程序相关的。

例子

Windows shell 命令

   set TOMCAT_OPTS=-Dlog4j.configuration=file:/c:/foobar.lcf
告诉log4j使用文件c:/foobar.lcf 作为默认的配置文件。这个配置文件完全由 URL file:/c:/foobar.lcf指定。因此,这个相同的配置文件将被所有网络应用程序使用。  c:/foobar.lcf

不同的网络应用程序通过它们各自的classloaders装载log4j的类。因此,每个 log4j环境的image会独自地,没有任何相互协调地行动。例如,在多个网络应用程序的配置中,FileAppenders若定义得完全相同,它们就会编写相同的文件。这样的结果就不那么令人满意。你必须保证不同的网络应用程序的log4j配置不使用相同的系统资源。

初始化servlet

还可以使用一个特别的servlet来进行log4j初始化。这里就是个示例:

package com.foo;import org.apache.log4j.PropertyConfigurator;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.PrintWriter;import java.io.IOException;public class Log4jInit extends HttpServlet {  public  void init() {    String prefix =  getServletContext().getRealPath("/");    String file = getInitParameter("log4j-init-file");    // if the log4j-init-file is not set, then no point in trying    if(file != null) {      PropertyConfigurator.configure(prefix+file);    }  }  public  void doGet(HttpServletRequest req, HttpServletResponse res) {  }}

在web.xml文件里为你的网络应用程序定义下面的servlet。

  <servlet>    <servlet-name>log4j-init</servlet-name>    <servlet-class>com.foo.Log4jInit</servlet-class>    <init-param>      <param-name>log4j-init-file</param-name>      <param-value>WEB-INF/classes/log4j.lcf</param-value>    </init-param>    <load-on-startup>1</load-on-startup>  </servlet>

编写一个initialization servlet 是最灵活的方式来初始化log4j。不受任何限制,你可以在这个servlet的init()方法里放入任何代码。

Nested Diagnostic Contexts

实际情况下的大多数系统都需要同时处理多个客户端问题。在这种系统的典型的多线程实施中,通常是不同的线程去分别处理不同的客户需求。Logging特别适合于复杂的程序跟踪和排错。一个通常的处理办法是通过给每个客户产生一个新的分离开的logger来达到把不同的客户的日志输出信息区分开来。但这促进了loggers的增殖,加大了logging的管理负担。

一个更简洁的技术是独特地标记来自于同一个客户的每一个日志请求。Neil Harrison 在他的书中"Patterns for Logging Diagnostic Messages," in Pattern Languages of Program Design 3, edited by R. Martin, D. Riehle, and F. Buschmann (Addison-Wesley, 1997) 对这个方法进行了描述。  Pattern Languages of Program Design 3

要独特地标记每个日志请求,用户把上下文信息送入NDC,NDC是 Nested Diagnostic Context的缩写。NDC类展示如下。

  public class NDC {    // Used when printing the diagnostic    public static String get();    // Remove the top of the context from the NDC.    public static String pop();    // Add diagnostic context for the current thread.    public static void push(String message);    // Remove the diagnostic context for this thread.    public static void remove();  }

NDC类是作为一个保存线程上下文的stack来独个线程(per thread) 管理的。注意,org.apache.log4j.NDC类中所有的方法都是静态的。假设NDC打印功能被打开,每一次若有日志请求,相应的log4j组件就把这个当前线程的整个 NDC stack包括在日志输出中打印出来。这样做不需要用户干预,用户只需要在代码中明确指定的几点通过pushpop方法将正确的信息放到NDC中就行了。相反,per-client logger方法需要在代码中作很多更改。

为了说明这一点,我们举个有关一个servlet把信息内容发送到多个客户的例子。这个Servlet程序在开始接到客户端的请求,执行其它代码之前,首先创建一个NDC。该上下文信息可能是客户端的主机名,以及其他请求中固有的信息,通常是包含在cookies中的信息。因此即便这个Servlet程序可能同时要服务于多个客户,由相同的代码启动的这些logs,比如属于同一个logger,它们仍然能够被区分开来,因为不同的客户端请求具有不同的NDC stack。这与在客户请求期间把一个实例化的logger传递给所有要被执行的代码的复杂性形成了反差。

然而,一些复杂的应用程序,比如虚拟网络服务器,必须依据虚拟主机的上下文语言环境,以及发布请求的软体组件来作不同的log。最近的log4j发行版支持多阶层树。这一功能的加强允许每个虚拟主机拥有它自己的logger阶层版本。

性能

一个经常提出的争议就是logging的运算开销。这种关注是有道理的,因为即便是一个中等大小的应用程序至少也会产生几千个log输出。许多工作都花费在测量和改进logging性能上。Log4j声明它是快速和灵活的:速度第一,灵活性第二。

用户需要清楚地了解下面这些与性能相关的问题:

  1. Logging performance when logging is turned off.
  2. The performance of deciding whether to log or not to log when logging is turned on.
  3. Actually outputting log messages
  4. 当logging被完全关闭或只是set of levels被关闭,日志请求的开销是方法的调用和整数的比较。在一个233 MHz Pentium II机器上,这种开销通常在5 to 50 毫微秒范围内。  set of levels

    不过,方法的调用包含有参数的建造上的“隐闭”开销。

    例如下面的logger cat程序段中:

         logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));    
    不管message被日志记录与否,构造message参数的开销还是有的,比如说,把整数i 和数组entry[i]转化为String,连接中间字串。参数构造的这种开销可能很高,它依赖于所介入的参数数量有多少。

    为了避免这种参数构造开销,把以上的代码段改写为:

          if(logger.isDebugEnabled() {        logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));      }   

    如果排错功能不被使用,就不会有参数构造上的开销。但是,另一方面,如果 logger的排错功能被起用,就会有俩倍的开销用于评估logger是否被起用:一次是判断debugEnabled,一次是判断debug是否被启用。但这不是极重的负担,因为评估logger的时间只有整个log语句执行时间的1%

    在log4j中,把日志请求作为Logger类的实例。Logger是类而不是接口,这主要是为了减少程序调用的开销,但牺牲了接口所能带来的灵活性。

    有些用户使用预处理或compile-time技术来编译所有log语句。这样logging方面的性能是很好。但是,因为resulting application binary没有包含任何log语句,你不能对这个二进制程序起用logging。在我看来,这是为了小的性能增加而付出大的代价。

     

    本质上影响性能的因素是logger的层次关系。当logging功能被打开时,log4j仍然需要把log请求的级别去与request logger的级别作比较。不过,有些loggers 并没有指派的优先级别,但它可以从它的上一层logger那里继承优先级别。因此在继承优先级之前,logger可能需要搜索它的ancestors。

    Log4j在这方面做了很大的努力,以便使这种阶层的优先级别搜寻(hierarchy walk )尽可能的快速。例如,子代loggers仅仅只和它们现有的ancestors链接。在前面的BasicConfigurator示例中,叫做com.foo.Bar的logger 直接与 root logger链接,绕过了不存在的com或com.foo loggers。这极大地提高了优先级别搜寻的速度。

    阶层的优先级搜寻(walking the hierarchy )的开销在于它比logging完全关闭时要慢三倍。

    这里讲的是log输出的格式化和把log信息发送到目标所在地的开销。Log4j在这方面也下了大力气让格式化能尽快执行。对appenders也是一样。通常情况下,格式化语句的开销可能是100到300微秒的处理时间。确切数字请参看 org.apache.log4.performance.Logging

尽管log4j具有许多功能特性,但速度是第一设计目标。为了提高性能,一些 log4j的部件曾经被重写过许多次。即使这样,log4j的贡献者们不断提出新的优化办法。你应该很惊喜地发现当以SimpleLayout来配置时,性能测试显示使用 log4j日志和使用System.out.println日志同样快。

结论

Log4j是用Java编写的一个非常流行的logging开发包。它的一个显著特性之一是在loggers里运用了继承的概念。使用这种logger的层次关系,就可能准确地控制每一个log语句的输出。这样减少了log信息的输出量并降低了logging的开销。

Log4j API的优点之一是它的可管理性。一旦log语句被插入到代码中,他们就能被配置文件控制而无需重新编译源代码。Log信息的输出能够有选择地被起用或关闭,用户能够按照自己选择的格式将这些log信息输出到许多不同的输出设备中。Log4j软件包的设计是在代码中保留log语句的同时不造成很大的性能损失。

感谢

Many thanks to N. Asokan for reviewing the article. He is also one of the originators of the logger concept. I am indebted to Nelson Minar for encouraging me to write this article. He has also made many useful suggestions and corrections to this article. Log4j is the result of a collective effort. My special thanks go to all the authors who have contributed to the project. Without exception, the best features in the package have all originated in the user community.

一个ANT文件

<?xml version="1.0"  encoding="GB2312" ?>
<!--
    =======================================================================
      hello-ant 项目 ,学习ant工具的第2个build file.
      参照ant的jakarta-ant-1.6alpha的build.xml
      Copyright (c) 2002 The Neusoft Software Foundation.  All rights
      reserved.
    =======================================================================
-->
<!--
    文档结构为:
    <project>
        <property/>               全局变量的定义
        <property/>...
        <target name="1">         任务组(tasks)
            <javac></javac>       一项javac任务
            ...
            <oneTask></ontTask>   一项其它任务
        </target>
        <target name="2">
            <javac></javac>
            ...
            <oneTask></ontTask>
        </target>
    </project>
    project代表一个项目,
    default:运行到名称为"dist"的target(任务组)
    basedir:基准路径。
-->
<project default="dist" basedir=".">
<!--
    ===================================================================
      定义属性(property tasks)
      最好把用到的路径呀,名称呀都在这里定义成全局变量
      例:定义
          <property name="a" value="hello"/>
      以后就可以这样用它:
          <property name="b" value="${a}/b"/>
      现在:b=="hello/b"
    ===================================================================
-->
    <!--主要的系统环境属性-->
    <property environment="env"/><!--取window,unix...的环境变量-->
    <property name="java.home" value="${env.JAVA_HOME}"/>
    <property name="ant.home"  value="${env.ANT_HOME}"/>
    <!--主要的app环境属性-->
    <property name="app.name"      value="hello-ant"/>
    <property name="app.jar"       value="${app.name}.jar"/>
    <property name="app.copyright" value=" Copyright (c) 2002 The Neusoft Software Foundation.  All rights reserved."/>

    <!--app中src的属性-->
    <property name="src.dir"    value="src" />
    <property name="src.main"   value="${src.dir}/main"/>
    <property name="src.script" value="${src.dir}/script"/>
    <!--app用到的lib-->
    <property name="lib.dir" value="lib"/>
    <!--app的build目录中-->
    <property name="build.dir"      value="build" />
    <property name="build.classes"  value="${build.dir}/classes"/>
    <property name="build.docs"     value="${build.dir}/docs"/>
    <property name="build.docs.api" value="${build.docs}/api"/>
    <property name="build.lib"      value="${build.dir}/lib"/>
    <!--app的dist (distribution) 目录中-->
    <property name="dist.dir"      value="dist"/>
    <property name="dist.bin"      value="${dist.dir}/bin"/>
    <property name="dist.docs"     value="${dist.dir}/docs"/>
    <property name="dist.lib"      value="${dist.dir}/lib"/>
    <!--app的docs目录中-->
    <property name="docs.dir"      value="docs"/>
    <!--
    定义一组路径以后可以通过id重用这组路径 ,例:
    <javac srcdir="src/main" destdir="build/classes">
            <classpath refid="classpath"/>
    </javac>
    -->
    <path id="classpath">
        <!--本项目只有一个java,用不上classpath,这里只是做个例子-->
        <pathelement location="${build.classes}"/>
        <pathelement path="${java.home}/lib/tools.jar"/>
    </path>
<!--
    ===================================================================
      init 准备目录(File Tasks)
      主要的目录结构通常是不会变的,一起生成他们
    ===================================================================
-->
    <target name="init">
        <!--清除以前目录-->
        <delete dir="${build.dir}" failonerror="false" />
        <delete dir="${dist.dir}"  failonerror="false"/>
        <!--准备目录-->
        <mkdir dir="${build.dir}"/>
        <mkdir dir="${build.classes}"/>
        <mkdir dir="${build.docs}"/>
        <mkdir dir="${build.docs.api}"/>
        <mkdir dir="${build.lib}"/>
        <mkdir dir="${dist.dir}"/>
        <mkdir dir="${dist.bin}"/>
        <mkdir dir="${dist.lib}"/>
    </target>
<!--
    ===================================================================
      Build the code (Compile Tasks,File Tasks)
    ===================================================================
-->
    <target name="build" depends="init">
        <!--编译-->
        <javac srcdir="${src.main}" destdir="${build.classes}">
            <classpath refid="classpath"/>
        </javac>
    </target>
<!--
    ===================================================================
      打包文档(Archive Tasks)
      Create the project jars: xxx1.jar and xxx2.jar
    ===================================================================
-->
   <target name="jars" depends="build">
        <jar basedir="${build.classes}" jarfile="${build.lib}/${app.jar}"/>
    </target>
<!--
     ===================================================================
       Creates the API documentation
     ===================================================================
-->
    <target name="javadocs"
            depends="jars"
            description="--> creates the API documentation">
        <!--copy docs 手册... -->
        <copy todir="${build.docs}">
            <fileset dir="${docs.dir}"/>
        </copy>
        <javadoc packagenames="hello.ant.*"
                 sourcepath="${src.main}"
                 defaultexcludes="yes"
                 destdir="${build.docs.api}"
                 author="true"
                 version="true"
                 use="true"
                 windowtitle="Docs API">
             <doctitle><![CDATA[<h1>hello ant Docs API</h1>]]></doctitle>
             <bottom><![CDATA[<i>${app.copyright}</i>]]></bottom>
             <tag name="todo" scope="all" description="To do:" />
         </javadoc>
    </target>
<!--
     ===================================================================
       Create the distribution that can run (Archive Tasks)
       主要是从各目录中把该copy的copy上
     ===================================================================
-->
   <target name="dist" depends="javadocs">
        <!--copy bin 执行文件 -->
        <copy todir="${dist.bin}">
            <fileset dir="${src.script}/"/>
        </copy>
        <copy todir="${dist.docs}">
            <fileset dir="${build.docs}/"/>
        </copy>
        <!-- copy lib 文件 -->
        <copy todir="${dist.lib}">
            <fileset dir="${build.lib}/"/>
        </copy>
    </target>
<!--
     ===================================================================
      Cleans everything(File Tasks)
      例如可以删除build中的文件,留给你发挥吧
     ===================================================================
-->

一个导入oracle数据库的批处理文件。很好的bat范本!

@echo off
@
@
echo --------------------安装说明---------------------------------
echo  install orcl password
echo     install   执行的批处理命含
echo     orcl      Oracle 的sid 参数,没有此参数,不执行。是必选
echo     password  Oracle 的system用户的密码,没有此参数用 password
echo ---------------------------------------------------------------
echo 你确定要执行吗?此操作会删除 topicms 下的全部数据。
pause 请按任意键继续. . .
if "%1" == "" goto error
 
rem ------------------
rem 设置数据库的SID, 请跟情况进行修改!!!!!!!!!!!!
rem http://localhost:1158/em
rem ------------------
set sid=orcl
rem ------------------
rem 设置dba数据库的user/pass, 请跟情况进行修改!!!!!!!!!!!!
rem ------------------
set user=system
set pass=password
if NOT "%1" == "" set sid=%1
if NOT "%2" == "" set password=%2
 
rem ------------------
rem 数据库初始化
rem ------------------
sqlplus %user%/%pass%@%sid% @install-init.sql
sqlplus  topicms/topicms@%sid% @install_db.sql
 
rem ------------------
rem 导入文书模板数据
rem ------------------
rem  imp %user%/%pass%@%sid%  fromuser=topicms touser=topicms IGNORE=Y file=ws_init.dmp log=dblog.log
goto end
:error
echo 错误:无 Oracle SID 参数
:end
 
 
 
***************************************************************************
批处理结束
***************************************************************************
--- 引用的脚本文件 install-init.sql
--- 删除可能存在的用户,然后重新创建用户:topicms
prompt 删除topicms用户:
drop user topicms cascade;
prompt 创建topicms用户:
create user topicms identified by topicms;
prompt 给topicms用户授权:
grant connect, resource to topicms;
 
exit
***************************************************************************
 
rem 引用的脚本文件 install_db.sql
rem -------------------
rem 创建表结构
rem -------------------
@@topicms-schema.sql
 
rem -------------------
rem 创建代码表结构
rem -------------------
@@law_dm.sql
@@reg_dm.sql
@@sup_dm.sql
@@aaf_dm.sql
@@topprint.sql
@@topwrit-schema.sql
@@../common/reg_dm_init.sql
@@../common/aaf_dm_init.sql
@@../common/aaf_init.sql
@@../common/law_dm_init.sql
@@../common/sup_dm_init.sql

@@../common/reg_dm_init_pinyinku.sql
 
 
exit
 

CVS完全手册

             内容
概述
CVS环境设置
登录CVS服务器: 
cvs命令格式
CVS的日常使用
其他常用命令
CVS宏/keyword
CVS分支管理
CVS服务器的安装和配置
Watchers
WinCVS的安装和配置
CVSWEB的安装


概述
==================================

CVS是一个并行版本控制系统,它采用C/S模式,它的复杂度和功能性属于中等,是当今最流行的版本控制系统。它有两个基本的特点:
*保存修改记录:保存了所有文件的修改历史,并可以建立分支
*协作与并行:cvs不推荐使用lock-modify-unlock的串行的工作模式,而采用多人可以并行地修改同一个文件,而在提交时merge conflict;它更适合于大型的工作团体。
使用CVS的好处:
*文件集中管理,大家都可以方便的看到所有人员的最新文件,规范化了文件的管理
*可以查看以前任何的一个版本或修改历史
*可以同时维护多个版本和分支


CVS环境设置
==================================

先不要管CVS服务器的配置,我们先假设已经有一台配置好的服务器,要访问CVS,必需先设置环境变量CVSROOT
CVSROOT=:pserver:user@server#port:/path/to/cvsroot

*pserver是访问方式,口令认证的意思,这是最常用的方式,其他还有gserver,kserver,ext
*user是CVS服务器的用户名,
*server是CVS服务器的名称或者IP地址
*/path/to/cvsroot是你的CVS服务器的CVSROOT目录,根据你的CVS服务器设置做修改或者询问管理员
你可以把设置放到你的shell的profile里(.bash_profile,.profile等)这样就不用每次敲一长串命令了

高级功能:现在比较流行是使用ssh来加密口令和数据流
CVSROOT=:ext:user@server#port:/path/to/cvsroot
CVS_RSH=ssh
hints:
实际上没有CVSROOT也可以,你可以每次用cvs -d :pserver:user@server#port:/path/to/cvsroot来访问,而且它将忽略CVSROOT环境变量 ,也许你会笑我只有疯子才这么用,不过,cvs可以把每次使用的命令参数放到一个文件中,所以在~/.cvsrc中加入
cvs -d :pserver:user@server#port:/path/to/cvsroot
即可,它最大的好处是修改了立刻生效,而且它的优先级高于CVSROOT环境变量,到时候不要傻乎乎地来问我,我的环境变量真么不起作用了。


登录CVS服务器: 
==================================
$cvs login,这时候cvs会问你口令,请把你在CVS服务器上的口令敲进去
如果没有任何错误信息,恭喜你,成功了!
成功登录后将建立一个~/.cvspass文件,保存你的口令,以后就不用输入口令了.

cvs命令格式
==================================
cvs [global_opts] command [command_opts] [command_args]
Global options 属于左边cvs的,是全局的
command_opts   属于左边command的,是局部的
cvs --help-commands       查看命令列表
cvs -H command/cvs -help command      查看该命令的选项

hints:如果你每次使用一些命令都带同样的参数的话,可以把它们放到~/.cvsrc文件中去
update -c
diff -c
add -kb
cvs -Q


cvs global-option comand comand-option arguments

CVS的日常使用
==================================
CVS使用流程
   a checkout 尽当本地没有working copy时使用
   b staus 检查服务器上是否有新版本
   c update 如果有,则用update同步文件
   d 做你自己的修改,并保证正确
   e update 看是否有人修改了你的文件
   f 如果有冲突,合并冲突
   g commit 提交你的修改,如果因为又有人提交修改而失败,回到e步
   h 回到b步

1 cvs checkout module_name
-------------------------------------
module_name可以暂时理解为目录名,它会在本地但前目录下建立module_name目录,在把服务器上说有module_name目录下的文件copy到本地module_name目录下。
注意:第一次checkout后,就不是通过cvs checkout来同步文件了,而是要进入该目录下进行具体文件的版本同步(添加,修改,删除)操作。

2 cvs update filename
-------------------------------------
将文件同步到最新的版本:不指定文件名,cvs将同步所有子目录下的文件。
最好每天开始工作前或将自己的工作导入到CVS库里前都要做一次,并养成"先同步 后修改"的习惯,和Virvual SourceSafe不同,CVS里没有文件锁定的概念,所有的冲突是在commit之前解决,如果你修改过程中,有其他人修改并commit到了CVS库中,CVS会通知你文件冲突
<<<<<<< filename
 你文件上的内容
=======
  服务器上文件的内容
>;>;>;>;>;>;>; latest revision number in the repository

由你确认冲突内容的取舍。也可以多人协商解决,修改完成后去掉文件中的冲突标志

conflict:多人修改同一文件的同一区域这就叫冲突,它必须由人来解决,CVS不处理冲突,它只是告诉你存才冲突


3 cvs commit -m "write some comments here" file_name
------------------------------------
确认修改写入到CVS库里。
注意:CVS的很多动作都是通过cvs commit进行最后确认并修改的,最好每次只修改一个文件。在确认的前,还需要用户填写修改注释,以帮助其他开发人员了解修改的原因。如果不用写-m "comments"而直接确认`cvs commit file_name` 的话,cvs会自动调用系统缺省的文字编辑器(一般是vi)要求你写入注释。
注释的质量很重要:所以不仅必须要写,而且必须写一些比较有意义的内容:以方便其他开发人员能够很好的理解
不好的注释,很难让其他的开发人员快速的理解:比如: -m "bug fixed" 甚至 -m ""
好的注释,甚至可以用中文: -m "在用户注册过程中加入了Email地址校验"

修改某个版本注释:每次只确认一个文件到CVS库里是一个很好的习惯,但难免有时候忘了指定文件名,把多个文件以同样注释commit到CVS库里了,以下命令可以允许你修改某个文件某个版本的注释:
cvs admin -m 1.3:"write some comments here" file_name

4 查看状态
------------------------------------
cvs status filename
状态报告,类似这样: 
File: foo.c             Status: Up-to-date 
   Working revision:    1.1.1.1 'Some Date' 
   Repository revision: 1.2     /home/cvsroot/cvstest/foo.c,v 
   Sticky Tag:          (none) 
   Sticky Date:         (none) 
   Sticky Options:      (none) 

这里最重要的就是Status栏,这里总共可能有四种状态: 
Up-to-date: 表明你要到的文件是最新的. 
Locally Modified: 表明你曾经修改过该文件,但还没有提交,你的版本比仓库里的新. 
Needing Patch: 表明有个哥们已经修改过该文件并且已经提交了!你的版本比仓库里的旧. 
Needs Merge: 表明你曾经修改该文件,但是偏偏有个不识相的也修改了这个文件,而且还提交了!

5 查看修改历史和注释信息
------------------------------------
cvs log file_name


其他常用命令
==================================
1 添加文件和目录
------------------------------------
cvs add new_file_name
cvs add -kb new_file_name
cvs add dir_name

CVS一般只处理文本文件,它会扩展keyword(宏)并转换行结束符
对于图片,Word文档等非纯文本的项目,需要使用cvs add -kb选项,否则有可能出现文件被破坏的情况
然后确认修改并注释
cvs ci -m "write some comments here"  new_file_name

2 删除文件
------------------------------------
将某个源文件物理删除后
cvs remove file_name
然后确认修改并注释
cvs ci -m "write some comments here" file_name

注意:很多cvs命令都有缩写形式:commit=>;ci; update=>;up; checkout=>;co; remove=>;rm;

3.修改文件名
------------------------------------
移动文件:文件重命名
cvs里没有cvs move或cvs rename,因为这两个操作是先cvs remove old_file_name,然后cvs add new_file_name实现的。

4 目录结构同步
------------------------------------
如果在你checkout后,有人添加了新的文件或目录,你需要把他们取出来
cvs update -d

5 放弃本地的修改(undo)
------------------------------------
如果修改来了本地文件,不想提交,想重新取新文件
cvs update -C filename
它会先把你的本地文件改名

建议:建议大家把checkout的文件缺省为readonly,把"cvs -r"添加到~/.cvsrc文件中
     这样,你每次修改一个文件前,先cvs edit filename,提交后文件又变成readonly,
           如果你想放弃本地的修改,则cvs unedit filename,它会undo,而且文件又变成readonly


6 恢复到旧版本
------------------------------------
cvs update -j1.20 -j1.15 filenames
1.20时当前版本号,注意顺序不要反了,记住要commit,为了保证是但前版本号,最好先lock
注意:cvs update -r1.15 file.name,这里的-r不是版本号的意思,是给文件加了一个叫1.15的sticky tag
如果不小心已经加成STICK TAG的话:用cvs update -A 解决

7 文件比较
------------------------------------
cvs diff -c filename
cvs diff -c -r1.8 -r 1.5 filename

8 锁定与解锁文件
------------------------------------
为保证串行的修改文件,或修改二
cvs admin -l files
cvs admin -u files


==========================================================================
如果你只是一般性的使用cvs,到此为止就足够了。一个系统20%的功能往往能够满足80%的需求,CVS也不例外,以下是CVS最常用的功能,可能用到的还不到它全部命令选项的10%,更多的功能请在实际应用过程中体会,学习过程中应该是用多少,学多少,用到了再学也不迟。
==========================================================================

CVS宏/keyword
==================================
CVS缺省会对文件进行keyword(宏)替换,在文件中加入这些关键字是个良好的工作习惯
$Id$ 关键字是用文件名、版本、时间、作者 及代码性质替换,如果使用-l选项取出,在Exp后
面会加上登录用户的名称。除了$Id$关键字,RCS还支持下面常用的关键字:
$Log$ : 你所提供的修改日志信息。
$Author$ :存入该版本的作者。
$Locker$ : 该版本的加锁者
$State$ : 该版本的状态 Exp(试验版), Stabe(稳定版), Rel(发行版).缺省是Exp
$Date$ : 该版本存入的时间,使用UTC时间格式。
$Revision$ : 该版本的版本号
$RCSfile$ : RCS文件名
$Source$ : RCS全路径名
$Name$ : 取回该版本的符号名
$Header$ : 相当于$ Source $$ Revision$$Date$$Author $$State$$Locker$的组合


Sticky Tag
==================================
tag的作用是对多个连续变化的文件做一个快照来表示某一时刻的所有不停内部版本的文件,一般是项目到一定阶段,可以给所有文件统一指定一个阶段里程碑版本号,需要的时候可以一次导出这些版本不一的文件.标记的另外一个非常重要的作用是生成分支和合并分支.
1 cvs tag release_name  module_name
------------------------------------
release_name要简洁而含义丰富,由字母开头,加字母,数字,下划线和连字号组成,特别是不能含“.”
2 cvs checkout -r release_name module_name
------------------------------------
取出tag_name标志的文件
3 cvs update -A
------------------------------------
tag标识的文件是历史文件,不能修改,这样可在本地去除这个限制,让它和当前版本合并




CVS分支管理
=============================
CVS可以将历史划分成多个独立,并行和互不影响的分支,并去修改历史
1 标定里程碑
------------------------------------
cvs tag release_1_0 prj_dir_name

2 开始一个新的里程碑:
------------------------------------
cvs commit -r 2
标记所有文件开始进入2.x的开发
注意:CVS里的revsion和软件包的发布版本可以没有直接的关系。但所有文件使用和发布版本一致的版本号比较有助于维护。

3 建立分支
------------------------------------
在开发项目的2.x版本的时候发现1.x有问题,但2.x又不敢用,则从先前标记的里程碑:release_1_0导出一个分支release_1_0_b2
cvs rtag -b -r release_1_0 release_1_0_bugfixes prj_dir_name
-r修饰的是release_1_0,-b 修饰的是release_1_0_bugfixes,cvs的版本号将变为4位,以后每分一次支,版本号增加2位

4 分支并行开发
------------------------------------
一些人先在另外一个目录下导出release_1_0_bugfixes这个分支:解决1.0中的紧急问题,
cvs checkout -r release_1_0_bugfixes
分支是可以修改的,并自动提交到分支上去
而其他人员仍旧在项目的主干分支2.x上开发

5 tag分支
------------------------------------
在release_1_0_bugfixes上修正错误后,标记一个1.0的错误修正版本号
cvs tag release_1_0_bugfixes_p1

6 合并分支
------------------------------------
如果2.0认为这些错误修改在2.0里也需要,也可以在2.0的开发目录下合并release_1_0_patch_1中的修改到当前代码中:
cvs update -j release_1_0_bugfixes

7 再次合并分支
------------------------------------
如果又发现1.x新的bug,我在分支已经修改了,并标定了release_1_0_patch_2,我们同样希望把它合并到主干上来
cvs update -j release_1_0_bugfixes_p1 -j release_1_0_bugfixes
它的意思是把release_1_0_bugfixes_p1(tag)到release_1_0_bugfixes(分支)变化了的部分合并到当前文件(主干)
否则用6步的方法,则以前合并的内容会重新合并
注意:此时我们使用第5步的结果
教训:尽早频繁的tag,但同时不能导致tag泛滥,tag在不同的分支里可以同名

8 锁定分支
------------------------------------
cvs admin -l r_0_2 锁定r_0_2分支
cvs admin -l    锁定主分支

9 设置缺省分支
------------------------------------
cvs admin -b r_0_2 设定r_0_2为缺省分支
cvs admin -b    设定主分支

10 删除历史记录
  如果历史文件过多,或确定有几个阶段的稳定版本,我们可以删除一些历史文件,以保证cvs的性能
cvs admin -o rev1:rev2 filename 删除rev1到rev2的版本,含这两个版本
cvs admin -o rev1::rev2 filename 删除rev1到rev2的版本,不含这两个版本
如果省略rev1,表示删除本分支rev2之前的所有版本
如果省略rev2,表示删除本分支rev1之后的所有版本
cvs admin -o rev filename 删除rev这个版本的文件
注意:有tag的版本不能被删除,所以tag很重要,而且只能删单个文件


CVS服务器的安装和配置
=============================
1 下载源码
------------------------------------
  可从很多地方下载cvs,也可从官方ftp://ftp.gnu.org/gnu/cvs/下载

2 安装,同很多源码安装一样
------------------------------------
  gunzip cvs-1.10.6.tar.gz
  tar xvf cvs-1.10.6.tar
  cd cvs-1.10.6
  ./configure
  make
  make install

3 包的安装,如果找到具体操作系统的安装包,则见此操作系统的使用说明,比如linux为
------------------------------------
  rpm -ivh cvs-1.10.8-3.i386.rpm即可

4 建立Repository
------------------------------------
  groupadd cvs(要访问cvs的用户加入此组)
  useradd cvsroot
  mkdir /home/cvsroot
  cvs -d /home/cvsroot init
  chown -R cvsroot.cvs /home/cvsroot
  chmod -R ug+rwx /homecvsroot

5 配置/etc/services文件
------------------------------------
 添加cvspserver,如果有就不要加了
cvspserver 2401/tcp # cvs client/server operations
cvspserver 2401/udp # cvs client/server operations

6 配置inetd
------------------------------------
  编辑/etc/inetd.conf,加入
cvspserver stream tcp nowait root /usr/bin/cvs cvs --allow-root=/home/cvsroot pserver
  如果使用tcpwrappers
cvspserver stream tcp nowait root /usr/sbin/tcpd /usr/bin/cvs --allow-root=/usr/local/newrepos pserver
  如果使用xinetd,编辑/etc/xinetd.d/cvspserver
  service cvspserver
 {
disable = no
socket_type = stream
wait = no
user = root
env = HOME=
server = /usr/bin/cvs
server_args = -f --allow-root=/home/cvsroot pserver
  }
pserver表示是口令认证,如果要用ssh方式则应该是server
--allow-root是Repository的目录,可以有多个此选项来建立多个Repository

7 验证配置成功
  cvs login看时候能成功登录,有几个可能失败的地方
  用户是否属于cvs组和目录权限
  inetd是否正常配置和启动,--allow-root是否写对
8 导入项目
  把你的所有项目文件放入prj_dir
  cd prj_dir
 cvs import -m "this is a cvstest project" prj_dir v_0_0_1 start
  v_0_0_1是这个分支的总标记.没啥用(或曰不常用)
 start 是每次 import 标识文件的输入层次的标记,没啥用。


Watchers
=============================
cvs提供了watch的功能,来帮助大家了解谁在干什么,谁在对文件做什么操作,它发email通知,以协调大家的工作。
1 开启watch功能
  在CVSROOT/notify文件中加入下面行
  ALL mail %s -s "CVS notification"
2 设定外部email地址
  缺省是把通知信息发给本机的email,要发给别的地址,在CVSROOT/users加入如下内容
  hwz:kerlion@netease.com
3 watch文件的修改
  cvs watch add filename
  cvs watch remove filename
4 watch文件的修改
  watch功能是个大家协调的功能,大家必须按一个统一的工作流程来做,如果不cvs edit,cvs没有办法通知修改情况
  cvs edit filename
  修改文件
  cvs unedit filename
5 提醒大家使用watch功能
  cvs没有强制使用watch的功能,只有这个功能在用户提交完后自动把文件变为只读的,再使用cvs edit filename就可以修改它了
  cvs watch on
6 查看那人人再watch
  cvs watchers


WinCVS的安装和配置
=============================
1、WinCVS简介:
WinCVS是CVS的一个客户端软件,它运行在Windows上,用来在Windows上登录CVS服务器,然后进行一些CVS相关的操作与管理。由于当前很多的企业内部都采用Linux/Unix做服务器,而用Windows做客户端,所以,WinCVS与CVS服务器配合使用将组成最强有力的版本控制与管理的系统之一。
2、WinCVS的下载与安装;
  最新的WinCVS可以从http://sourceforge.net/project/showfiles.php?group_id=10072地址下载到,也可以在http://sourceforge.net/project 上下载到最新的或其它版本的WinCVS。
  下载到相应的版本后根据向导进行安装,已经要使用CVS的用户,安装这个WinCVS应该没什么问题吧!
3、配置WinCVS:
a、Admin->;Preferences…/general
   CVSROOT 按cvsroot格式填写
   Authentication:用来配置cvs服务器的认证方式一般只要选择默认的pserver方式就可以
       要注意的是必须与cvs服务器配置时所指定的认证方式一致
                   如果要用ssh server,确认在windows中安装了openssl,openssh
a、Admin->;Preferences…/Globals
  此项的配置主要是要注意这几选项:
  Checkout read-only:默认是选中的,建议不要修改,只是修改文件前需要先edit selection,提交后又自动变为只读
               这样有两个好处:
                一个是可以通过unedit selection来undo你做的修改
                二是可以符合watch功能的使用规范
  Prune empty directories: 不要选上,否则,会自动删除空目录;
4、登录服务器:
   选择Admin->;login,将出现如下对话框要求用户输入登录口令
   wincvs中命令的exit code 0表示正确执行,否则是失败,一般成功显示为
   *****CVS exited normally with code 0*****
5、winCVS的使用都有菜单,大家熟悉cvs的使用的话,用起来是很容易的,我就不多说了


CVSWEB的安装
=============================
CVSWEB就是CVS的WEB界面,可以大大提高程序员定位修改的效率:
使用的样例可以看:http://www.freebsd.org/cgi/cvsweb.cgi

CVSWEB的下载:CVSWEB从最初的版本已经演化出很多功能界面更丰富的版本,这个是个人感觉觉得安装设置比较方便的:
http://www.spaghetti-code.de/software/linux/cvsweb/

1 下载解包:
tar zxf cvsweb.tgz

2 把配置文件cvsweb.conf复制到apache的配置目录下
 cp cvsweb.conf /path/to/apache/conf
转到/path/to/apache/conf下并修改cvsweb.conf:
修改CVSROOT路径设置:
%CVSROOT = (
'Development' =>; '/path/to/cvsroot/dev', #<==修改指向本地的CVSROOT
'test' =>; '/path/to/cvsroot/test', #<==修改指向本地的CVSROOT
);
如果有多个cvsroot,这定缺省的cvsroot
$cvstreedefault = 'test';

3 其它个性化设置
 cvsweb.conf还有许多其它个性化设置,常见的有这些变量:
 $logo 图标设置
 $defaulttitle 标题设施
 $address 管理员email地址设置
 $long_intro 介绍文字
 $short_instruction  说明文字

4 把文件cvsweb.cgi复制到apache的cgi目录
 cp cvsweb.cgi /path/to/apache/cgi-bin
 转到/path/to/apache/cgi-bin修改cvsweb.cgi
修改cvsweb.cgi让CGI找到配置文件:
$config = $ENV{'CVSWEB_CONFIG'} || '/path/to/apache/conf/cvsweb.conf';

5 中文支持
 让cvsweb正确显示中文,找到sub html_header($)函数,
 然后在<html>;和<title>;之间插入一行,修改如下
 <html>;
 <meta http-equiv="Content-Type" content="text/html; charset=gb2312">;
 <title>;$title</title>;
6 复制所有的gif,png文件到apache的icons目录
7 增加访问控制
CVSWEB可不能随便开放给所有用户,因此需要使用WEB用户认证:
先生成 passwd:
/path/to/apache/bin/htpasswd -c cvsweb.passwd user

修改httpd.conf: 增加
<Directory "/path/to/apache/cgi-bin/cvsweb/">;
AuthName "CVS Authorization"
AuthType Basic
AuthUserFile /path/to/cvsweb.passwd
require valid-user
</Directory>;

增加文件夹右键打开dos功能

第一步:
 
将注册表:
HKEY_CLASSES_ROOT/Directory/shell
的默认键值改为“open”。
 
第二步:
在 “文件夹选项”-"文件类型" 中找到“文件夹”类型-“高级”  -“新建” 输入名称 "DOS" ,对应的命令cmd.exe
 
完成!
删除windows服务
删除windows服务
sc delete 服务名
注册表
HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services 下面找到删除即可
 

单例设计模式在Java中的应用(1)

在软件设计中,设计模式是针对某一普遍问题的通用解决方案。解决方案翻译成代码,然后在出现问题的时候,代码可以运用在不同的状况下。设计模式一书将模式划分为以下几个主要区域:创建、结构和行为。创建模式描述了对象是如何创建的,结构模式指导如何连接和合并对象。行为模式则描述了对象之间的通信机制。本文主要讲述单例模式。

 

在软件设计中,设计模式是针对某一普遍问题的通用解决方案。解决方案转换成代码,然后在出现问题的时候,代码可以运用在不同的状况下。设计模式一书将模式划分为以下几个主要区域:创建、结构和行为。创建模式描述了对象是如何创建的,结构模式指导如何连接和合并对象。行为模式则描述了对象之间的通信机制。本文主要讲述单例模式。

 

单例模式是创建模式中普遍采用的一种。使用单例模式可以确保某个类只会有一个实例被创建。单例模式是通过下面的思想来保证的:不让类以外的任何事物创建对象的实例。通常来讲,单例可以缩减内存的需求。实现方式也有很多种。

 

如果你知道将要创建的实例是一个子类,那么将父类声明为抽象类并提供一个方法来获得当前的实例。在AWT包中,Toolkit类就是一个典型的代表。Toolkit的构造器是public的。

public Toolkit()

并且类具有一个方法getDefaultToolkit()用于获得特定的子类。在这里,子类是和平台相关的。

public static Toolkit getDefaultToolkit()

Linux平台上,特定的子类是sun.awt.X11.XToolkit。然而,你并不需要知道这些细节,因为访问的时候我们是通过子类的抽象父类Toolkit实现的。

Collator类是单例模式中另一个例子,只是实现略有不同。它提供了两个getInstance()方法。无参数的版本得到默认localeCollator。也可以通过传递一个参数来获得指定localeCollator。通过同样的locale参数多次获得的Collator实例是相同的。Collator的构造器是protected类型的。在J2SE标准类库中,我们还可以找到很多这样的例子。

我们可能会认为限制一个类的构造器的访问会自动将这个类设计为单例模式的,但是并非如此。Calendar就是这样一个例子,Calendar的构造器是protected的,提供了一个getInstance()方法来获得这个类的实例,每次调用getInstance()都会创建一个新的实例。因此它不是单例模式的。

当创建自己的单例类的时候,确保只有一个实例被创建:

   public class MySingleton {

     private static final MySingleton INSTANCE =

       new MySingleton();

     private MySingleton() {

     }

     public static final MySingleton getInstance() {

       return INSTANCE;

     }

   }

静态方法getInstance()返回这个类的一个实例。注意即使这个实例需要是子类,也无须修改API

       一般来说,不需要提供一个getInstance()方法,因为INSTANCE变量可以声明为public的。但是,getInstance()方法可以提供更好的灵活性,尤其是以后系统的设计发生变化的时候。出色的虚拟机实现可以将getInstance()方法进行内联。

       编写一个好的单例模式的类并非这么简单,如果需要使得自己的单例类是可序列化的,那么必须提供一个readResolve()方法:

  /**

   * Ensure Singleton class

   */

  private Object readResolve() throws ObjectStreamException {

    return INSTANCE;

  }

提供readResolve()方法后,反序列化的时候将只有一个对象产生,无论调用了多少次getInstance()方法。如果不提供readResolve()方法,当反序列化的时候,每次都会创建一个新的对象实例。

       如果只需要使用一个单独的资源,并且需要共享这个单独资源的状态信息的时候,单例模式是非常有用的。在设计的时候就标记好单例模式的需求可以简化开发。然而,有些时候我们意识不到需要使用单例模式直到系统出现了性能问题,这个时候我们就需要重构代码/例如,你可能发现系统的性能在下滑,原因是程序中重复的创建了同一个类的很多对象,通过应用单例模式则可以很好的避免创建通常的对象。这可以减少系统用来创建对象的时间,也可以节省垃圾收集器用来释放这些实例的时间。

总的来说,如果不想创建一个类的多个实例的时候就使用单例模式。如果构造器中不需要其他的操作,那么就提供一个空的私有构造器,如果需要子类的话就提供一个protected类型的。否则,默认情况下,系统会提供一个公共的构造器。

       需要注意的是在一个给定的类装载器中单例模式保证是唯一的。如果在多个不同的企业容器中使用通常的类的时候,那么需要为每个容器提供一个实例。

       单例模式通常和工厂模式一起使用,象单例模式一样。工厂模式也是创建模式。它描述了如何扩展一个特殊的对象,更典型的情况是实现一个特殊的接口,做实际的对象创建工作。工厂模式的典型例子是Swing中的BorderFactor类。这个类有一系列的静态方法可以返回不同的Border对象。它掩藏了子类的实现细节,允许工厂直接调用构造器来获得接口的实现。例如:

 

Border line = BorderFactory.createLineBorder(Color.RED);

   JLabel label = new JLabel("Red Line");

   label.setBorder(line);

 

事实上,BorderFactor创建了一个LineBorderBorderFactor创建LineBorder的细节没有暴露给开发者。在特殊的例子中尼可以调用LineBorder构造器,但是在使用工厂模式的时候,你一般不能这么做。

       很多时候,单例模式的类实现返回一个对象用作工厂来创建另一个类的实例。例如PopupFactory创建Popup对象的时候:

调用PopupFactorygetSharedInstance()方法可以获得单例工厂:

PopupFactory factory = PopupFactory.getSharedInstance();

然后调用工厂的getPopup()方法来创建Popup对象,需要指定父组件,内容和位置:

Popup popup = factory.getPopup(owner, contents, x, y);

       工厂模式在安全上下文中使用很频繁。例如,下面将创建一个证书工厂,然后产生一个证书流。

 

FileInputStream fis = new FileInputStream(filename);

   CertificateFactory cf =

      CertificateFactory.getInstance("X.509");

   Collection c = cf.generateCertificates(fis);

 

虽然,工厂模式不一定要和单例模式一起使用,但是通常这两个模式在一起使用的时候比较频繁。

 

使用XDoclet生成hbm.xml

用XDoclet生成hbm.xml就是在.java文件里写入一些元数据,XDoclet会从这些数据以及类本身得到足够的信息来生成目标文件。当然,除了用于hibernate,XDoclet还可以用于web、ejb等等很多用途。

XDoclet要从sourceforge上下载,包含了很多jar包、文档和例子,我觉得文档做得还是不错的,查起来比较方便。要使用XDoclet,一般要通过ant来完成,也就是在ant脚本里加入XDoclet的内容。

由于eclipse已经包含了ant支持,因此我没有专门去下载一个ant回来,而是直接使用eclipse带的,版本是1.5.3。

创建一个名为build.xml的脚本(其实应该换个名,比如gen-hbm.xml,看起来比较明白),内容如下:

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

<project name="XDoclet Examples" default="hibernate" basedir=".">
    <property name="xdoclet.root.dir" value="c:/xdoclet-1.2.1"/>
    <property name="xdoclet.lib.dir" value="${xdoclet.root.dir}/lib"/>
    <path id="myclasspath">
        <fileset dir="${xdoclet.lib.dir}">
            <include name="*.jar"/>
        </fileset>
    </path>
     <taskdef
        name="hibernatedoclet"
        classname="xdoclet.modules.hibernate.HibernateDocletTask"
        classpathref="myclasspath"
        />
    <target name="hibernate" description="Generate mapping documents">

        <echo>+---------------------------------------------------+</echo>
        <echo>|                                                   |</echo>
        <echo>| R U N N I N G   H I B E R N A T E D O C L E T     |</echo>
        <echo>|                                                   |</echo>
        <echo>+---------------------------------------------------+</echo>

        <hibernatedoclet
            destdir="./src"
            excludedtags="@version,@author,@todo,@see"
            addedtags="@xdoclet-generated at ${TODAY},@copyright The XDoclet Team,@author XDoclet,@version ${version}"
            force="false"
            verbose="true">

            <fileset dir="./src">
                <include name="org/haree/struts/form/UserForm.java"/>
            </fileset>

            <hibernate version="2.0"/>

        </hibernatedoclet>
    </target>
</project>

 
原创粉丝点击