1.1描写properties文件。
这个描述文件一般放在可以放在两个地方:(1)WEB-INF/classes目录下,或者放在在/project_root/,也就是你的web_app的根目录下。利用这个控制日志纪录的配置,当然也可以通过xml文件配置,这里我们暂且说说properties的配置。
建立文件名log4j.properties文件。放在%tomca_home%/web_app/fcxtest/目录下。
文件内容如下:(fcxtest为自己建立的web app目录)

#--------------------------------
# 设定logger的root level为DEBUG,指定的输出目的地(appender)为A1
log4j.rootLogger=DEBUG, A1

# 设定调试信息的输出位置,此处设定输出为控制台
log4j.appender.A1=org.apache.log4j.ConsoleAppender

# 设定调试信息的输出位置,此处设定输出为fcxtest.log文件
# log4j.appender.A1=org.apache.log4j.RollingFileAppender
# log4j.appender.A1.File=fcxtest.log
# log4j.appender.A1.MaxFileSize=1000KB

# 设定制定的A1使用的PatternLayout.
# 有关ConversionPattern中的转意字符的含义参考说明
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%d %-5p [%t] %C{2} (%F:%L) - %m%n
#--------------------------------

1.2建立测试用的Servlet类
这个测试的com.fcxlog.LogShow类主要是显示如何使用log4j。

package com.fcxlog;

import javax.servlet.*;
import javax.servlet.http.*;

import org.apache.log4j.Logger;
import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.PropertyConfigurator;

public class LogShow extends javax.servlet.http.HttpServlet{

protected String configfile = "log4j.properties";

public void init() throws ServletException{
ServletContext sct = getServletContext();
System.out.println("[Log4j]: The Root Path: " + sct.getRealPath("/"));
//指定自己的properties文件
//以前使用的是BasicConfigurator.configure(),现在使用PropertyConfigurator替代
org.apache.log4j.PropertyConfigurator.configure(sct.getRealPath("/") + configfile);
}

public void service(javax.servlet.http.HttpServletRequest req,javax.servlet.http.HttpServletResponse res){

//初始化Logger,以前使用Category,现在改用Logger替代
//org.apache.log4j.Category log = org.apache.log4j.Category.getInstance(LogShow.class);
org.apache.log4j.Logger log = ora.apache.log4j.Logger.getLogger(LogShow.class);
log.info("调试信息");

}
}

1.3 测试了
至于如何测试,发布,就不说了。

在此说明:
(1)本篇可能错误不少,一方面是参考《Short introduction to log4j》着翻译了一点,有诸多言辞不妥之处,还望指正。一方面,凭借自己的理解,所以难免有不全的地方,还望各位补充。
(2)因为时间有限,每天只能写一点,本文主要是介绍有关Logger及Logger level相关概念的
(3)有关Log4j介绍(一),请参阅:http://www.javaren.com/bbs/cgi-bin/topic.cgi?forum=1&topic=12766&show=0#lastviewpost


概述:
本文主要是简要的介绍了Log4j的api,以及其一些特征和设计原理。它本身是一个开源的软件,允许开发者任意的操纵应用系统日志信息。Log4j的使用通过外部配置文件进行配置。

任何大型应用系统都有其自己的系统日志管理或跟踪的API,所以在1996年的时候,E.U. SEMPER项目组(http://www.semper.org/)也开发其自己的日志管理API,后来经过无数次的修改和补充,发展成了现在的log4j,一个给予java的日志管理工具包。有关最新的版本信息和源码,请访问http://jakarta.apache.org/log4j/docs/index.html
把纪录语句放在代码之中,是一种低端的调试方法,不过有时却不得不说是很有效,也是必需的。毕竟很多时候,调试器并不见得很适用。特别是在多线程应用程序(multithread application)或分布式应用程序(distributed application)

经验告诉我们,在软件开发生命周期中,日志系统是一个非常重要的组件。当然,日志系统也有其自身的问题,过多的日志纪录会降低系统运行的速度。

(一)Log4j的三个重要组件?? Loggers, Appenders, Layouts

这三个组件协同的工作,使得开发者能够依据信息类别和级别去纪录信息,并能够运行期间,控制信息记录的方式已经日志存放地点。

(二)记录器层次(Logger hierarchy)

几乎任何纪录日志的API得功能都超越了简单的System.out.print语句。允许有选择控制的输出日志信息,也就是说,某的时候,一些日志信息允许输出,而另一些则不允许输出。这就假设日志纪录信息之间是有分别的,根据开发者自己定义的选择标准,可以对日志信息加以分类。

纪录器的命名是依据实体的。下面有一段有点绕口的解释,我就直抄了,各位可以看看:(Name Hierarchy)A logger is said to be an ancestor of another logger if its name followed by a dot is a prefix of the descendant logger name. A logger is said to be a parent of a child logger if there are no ancestors between itself and the descendant logger.
(形象的解释,比如存在记录器 a.b.c,记录器a.b,记录器a。那么a就是a.b的ancestor,而a.b就是a.b.c的parent,而a.b.c就是a.b的child)

根纪录器(root logger)是记录器层次的顶端。它有两个独特点:(1)总是存在的(2)能够被重新找回。可以通过访问类的静态方法 Logger.getRootLogger 重新得到。其他的纪录器通过访问静态方法 Logger.getLogger 被实例话或被得到,这个方法将希望获得的记录器的名称作为参数。一些Logger类的方法描述如下:
public class Logger {
// Creation & 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);
}

记录器被赋予级别,这里有一套预定的级别标准:DEBUG, INFO, WARN, ERROR and FATAL ,这些是在 org.apache.log4j.Level 定义的。你可以通过继承Level类定义自己的级别标准,虽然并不鼓励这么做。
如果给定的记录器没有被赋予级别,则其会从离其最近的拥有级别的ancestor处继承得到。如果ancestor也没有被赋予级别,那么就从根记录器继承。所以通常情况下,为了让所有的记录器最终都能够被赋予级别,跟记录器都会被预先设定级别的。比如我们在操作properties文件中,会写这么一句:log4j.rootLogger=DEBUG, A1 。实际上就这就指定了root Logger和root Logger level。

Appenders
Log4j允许记录信息被打印到多个输出目的地,一个输出目的地叫做Appender。目前的Log4j存在的输出目的地包括:控制台(Console),文件(File),GUI Componennt,Remote Socket Server,JMS,NT Event Logger,and Remote Unix Syslog daemons。
多个Appender可以绑定到一个记录器上(Logger)。

通过方法 addAppender(Logger.addAppender) 可以将一个Appender附加到一个记录器上。每一个有效的发送到特定的记录器的记录请求都被转送到那个与当前记录器所绑定的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),换句话说,Appender的继承层次是附加在记录器继承层次上的。举个例子:如果一个Console Appender被绑定到根记录器(root Logger),那么所有的记录请求都可以至少被打印到Console。另外,把一个file Appender绑定到记录器C,那么针对记录器C(或C的子孙)的记录请求都可以至少发送到Console Appender和file Appender。当然这种默认的行为方式可以跟改,通过设定记录器的additivity flag(Logger.setAdditivity)为false,从而可以使得Appender的不再具有可加性(Additivity)。
下面简要介绍一下Appender Additivity。

Appender Additivity:记录器C所记录的日志信息将被发送到与记录器C以及其祖先(ancestor)所绑定的所有Appender。
但是,如果记录器C的祖先,叫做P,它的additivity flag被设定为false。那么,记录信息仍然被发送到与记录器C及其祖先,但只到达P这一层次,包括P在内的记录器的所有Appender。但不包括P祖先的。
通常,记录器的additivity flag的被设置为true。


Layouts
这一块主要是介绍输出格式的。PatternLayout,Log4j标准的分配器,可以让开发者依照conversion patterns去定义输出格式。Conversion patterns有点像c语言的打印函数。
参看配置文件的java properties,如下面的两行:
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%d %-5p [%t] %C{2} (%F:%L) - %m%n
第一行就指定了分配器,第二行则指定了输出的格式。
有关输出格式的定义可以参考/org/apache/log4j/PatternLayout.html
更基础的知识,可以到田贯升门诊室,另有一篇偏重基础的介绍~  
posted @ 2006-05-30 16:53 edsonjava 阅读(232) | 评论 (0)编辑 收藏
 
1.1准备工作
一。Tomcat已正确配置与使用。
二。软件下载:log4j------http://www.apache.org/dist/jakarta/log4j/jakarta-log4j-1.2.8.zip

1.2. Log4j简介

在强调可重用组件开发的今天,除了自己从头到尾开发一个可重用的日志操作类外,Apache为我们提供了一个强有力的日志操作包-Log4j。
Log4j是Apache的一个开放源代码项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件、甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等;我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。
此外,通过Log4j其他语言接口,您可以在C、C++、.Net、PL/SQL程序中使用Log4j,其语法和用法与在Java程序中一样,使得多语言分布式系统得到一个统一一致的日志组件模块。而且,通过使用各种第三方扩展,您可以很方便地将Log4j集成到J2EE、JINI甚至是SNMP应用中。本文介绍的Log4j版本是1.2.8,怎样通过一个配置文件来灵活地进行配置,主要的应用平台是Tomcat4.

1.3。Log4j的配置。

首先到jakarta下载一个log4j的组件。把jakarta-log4j-1.2.8\dist\lib下的log4j-1.2.8.jar文件copy到classpath指定的目录下!可以是Tomcat的common\lib目录下,也可以是你需要用到log4j的application下的lib目录。
1.4在Application目录下的web.xml文件加入以后代码

log4j
com.apache.jakarta.log4j.Log4jInit

log4j
/WEB-INF/log4j.properties

1


这段代码的意思是说,在Tomcat启动时加载com.apache.jakarta.log4j.Log4jInit这个名叫Log4jInit.class这个类文件。其中Log4jInit.class的源代码如下

package com.apache.jakarta.log4j;
import org.apache.log4j.PropertyConfigurator;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class Log4jInit extends HttpServlet {

public void init() {
String prefix = getServletContext().getRealPath("/");
String file = getInitParameter("log4j");
// if the log4j-init-file is not set, then no point in trying
System.out.println("................log4j start");
if(file != null) {
PropertyConfigurator.configure(prefix+file);
}
}
public void doGet(HttpServletRequest req, HttpServletResponse res) {
}
}
这段代码很简单,可以看出,在加载的过程中,程序会读取/WEB-INF/log4j.properties这个文件
这个文件就是本文的重点,也就是log4j的配置文件。

# Set root logger level to DEBUG and its only appender to A1
#log4j中有五级logger
#FATAL 0
#ERROR 3
#WARN 4
#INFO 6
#DEBUG 7
#配置根Logger,其语法为:
#log4j.rootLogger = [ level ] , appenderName, appenderName, …
log4j.rootLogger=INFO, A1 ,R
#这一句设置以为着所有的log都输出
#如果为log4j.rootLogger=WARN, 则意味着只有WARN,ERROR,FATAL
#被输出,DEBUG,INFO将被屏蔽掉.
# A1 is set to be a ConsoleAppender.
#log4j中Appender有几层如控制台、文件、GUI组件、甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等
#ConsoleAppender输出到控制台
log4j.appender.A1=org.apache.log4j.ConsoleAppender
# A1 使用的输出布局,其中log4j提供4种布局. org.apache.log4j.HTMLLayout(以HTML表格形式布局)
#org.apache.log4j.PatternLayout(可以灵活地指定布局模式),
#org.apache.log4j.SimpleLayout(包含日志信息的级别和信息字符串),
#org.apache.log4j.TTCCLayout(包含日志产生的时间、线程、类别等等信息)

log4j.appender.A1.layout=org.apache.log4j.PatternLayout
#灵活定义输出格式 具体查看log4j javadoc org.apache.log4j.PatternLayout
#d 时间 ....
log4j.appender.A1.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss} [%c]-[%p] %m%n
#R 输出到文件 RollingFileAppender的扩展,可以提供一种日志的备份功能。
log4j.appender.R=org.apache.log4j.RollingFileAppender
#日志文件的名称
log4j.appender.R.File=log4j.log
#日志文件的大小
log4j.appender.R.MaxFileSize=100KB
# 保存一个备份文件
log4j.appender.R.MaxBackupIndex=1

log4j.appender.R.layout=org.apache.log4j.TTCCLayout
#log4j.appender.R.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss} [%c]-[%p] %m%n

配置以这里就差不多了,如果你想更深入了解配置文件的各个细节,可以去查看docs。还有,在文章的最后面我们提供配置文件中一些主要的语法。下面我们来看看怎样在程序中使用log4j.

1.4 Log4j的使用。
使用Log4j,第一步就是获取日志记录器,这个记录器将负责控制日志信息。其语法为:
public static Logger getLogger( String name),
必须在使用前要把这个类导入
import org.apache.log4j.Logger;

name一般是类文件的名字,如下:
static Logger logger = Logger.getLogger ("".class.getName () ) ;

您就可以轻松地使用不同优先级别的日志记录语句插入到您想记录日志的任何地方,其语法如下:
logger.debug ( Object message ) ;
logger.info ( Object message ) ;
logger.warn ( Object message ) ;
logger.error ( Object message ) ;

为什么这里要分级别的呢?试想一下,我们在写程序的时候,为了调试程序,会在很多会出错的地方加入大量的logger.info();信息。当然程序调试完毕,我们不需要这些输出信息了,那怎么办呢?以前的做法是把每个程序中的logger.info删除,但这是不现实的,如果程序不大还可以,但如果程序很多,做这些事情就很烦人了。但因为log4j分级别了,当我们不需要输出这样调试时用到的log.info()时,我们可以把输出的级别调高,如调到warn,或error级别,这样info级别及以下的级别就不会出输出了,是不是很方便的呢?

其实除了这种使用方式,log4j还有其它的使用方面,不需要配置文件,直接在程序中定义输入出级别,层次等信息,如果要了解这方法的使用,可以参考文档。

1.5。附注:
以下是配置文件的一些重要的语法
定义配置文件

其实您也可以完全不使用配置文件,而是在代码中配置Log4j环境。但是,使用配置文件将使您的应用程序更加灵活。

Log4j支持两种配置文件格式,一种是XML格式的文件,一种是Java特性文件(键=值)。下面我们介绍使用Java特性文件做为配置文件的方法:

配置根Logger,其语法为:

log4j.rootLogger = [ level ] , appenderName, appenderName, …
其中,level 是日志记录的优先级,分为OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL或者您定义的级别。Log4j建议只使用四个级别,优先级从高到低分别是ERROR、WARN、INFO、DEBUG。通过在这里定义的级别,您可以控制到应用程序中相应级别的日志信息的开关。比如在这里定义了INFO级别,则应用程序中所有DEBUG级别的日志信息将不被打印出来。
appenderName就是指定日志信息输出到哪个地方。您可以同时指定多个输出目的地。

配置日志信息输出目的地Appender,其语法为

log4j.appender.appenderName = fully.qualified.name.of.appender.class
log4j.appender.appenderName.option1 = value1

log4j.appender.appenderName.option = valueN
其中,Log4j提供的appender有以下几种:
org.apache.log4j.ConsoleAppender(控制台),
org.apache.log4j.FileAppender(文件),
org.apache.log4j.DailyRollingFileAppender(每天产生一个日志文件),org.apache.log4j.RollingFileAppender(文件大小到达指定尺寸的时候产生一个新的文件),
org.apache.log4j.WriterAppender(将日志信息以流格式发送到任意指定的地方)

配置日志信息的格式(布局),其语法为:

log4j.appender.appenderName.layout = fully.qualified.name.of.layout.class
log4j.appender.appenderName.layout.option1 = value1

log4j.appender.appenderName.layout.option = valueN
其中,Log4j提供的layout有以下几种:
org.apache.log4j.HTMLLayout(以HTML表格形式布局),
org.apache.log4j.PatternLayout(可以灵活地指定布局模式),
org.apache.log4j.SimpleLayout(包含日志信息的级别和信息字符串),
org.apache.log4j.TTCCLayout(包含日志产生的时间、线程、类别等等信息)

posted @ 2006-05-30 16:30 edsonjava 阅读(169) | 评论 (0)编辑 收藏
 
log4j在web端的使用技巧!

log4j的好处在于:

1.

通过修改配置文件,就可以决定log信息输出到何处(console,文件,...),是否输出。
这样,在系统开发阶段可以打印详细的log信息以跟踪系统运行情况,而在系统稳定后可以关闭log输出,从而在能跟踪系统运行情况的同时,又减少了垃圾代码(System.out.println(...)等)。

2.

使用log4j,需要整个系统有一个统一的log机制,有利于系统的规划。

log4j的使用本身很简单。但合理地规划一个系统的统一log机制需要周全的考虑。

其他关于log4j的信息参看log4j自带的文档。

PART II 配置文件详细解释
先看一个配置文件的例子:

1.

配置文件的例子

log4j.rootLogger=DEBUG
#将DAO层log记录到DAOLog,allLog中
log4j.logger.DAO=DEBUG,A2,A4
#将逻辑层log记录到BusinessLog,allLog中
log4j.logger.Businesslog=DEBUG,A3,A4

#A1--打印到屏幕上
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%-5p [%t] %37c %3x - %m%n

#A2--打印到文件DAOLog中--专门为DAO层服务log4j.appender.A2=org.apache.log4j.DailyRollingFileAppender
log4j.appender.A2.file=DAOLog
log4j.appender.A2.DatePattern='.'yyyy-MM-dd
log4j.appender.A2.layout=org.apache.log4j.PatternLayout
log4j.appender.A2.layout.ConversionPattern=[%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n

#A3--打印到文件BusinessLog中--专门记录逻辑处理层服务log信息log4j.appender.A3=org.apache.log4j.DailyRollingFileAppender
log4j.appender.A3.file=BusinessLog
log4j.appender.A3.DatePattern='.'yyyy-MM-dd
log4j.appender.A3.layout=org.apache.log4j.PatternLayout
log4j.appender.A3.layout.ConversionPattern=[%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n

#A4--打印到文件alllog中--记录所有log信息log4j.appender.A4=org.apache.log4j.DailyRollingFileAppender
log4j.appender.A4.file=alllog
log4j.appender.A4.DatePattern='.'yyyy-MM-dd
log4j.appender.A4.layout=org.apache.log4j.PatternLayout
log4j.appender.A4.layout.ConversionPattern=[%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n

2.

Appender的使用
一个Appender代表log信息要写向的一个地方。log4j可使用的Appender有很多类型,这里只考虑3种:ConsoleAppender,FileAppender,DailyRollFileAppender
2.1
 ConsoleAppender
如果使用ConsoleAppender,那么log信息将写到Console。就是直接把信息打印到System.out上了。
2.2 
FileAppender
使用FileAppender,那么log信息将写到指定的文件中。这应该是比较经常使用到的情况。
相应地,在配置文件中应该指定log输出的文件名。如下配置指定了log文件名为demo.txt
log4j.appender.A2.File=demo.txt
注意将A2替换为具体配置中Appender的别名。
2.3
 DailyRollingAppender
使用FileAppender可以将log信息输出到文件中,但是如果文件太大了读起来就不方便了。这时就可以使用DailyRollingAppender。DailyRollingAppender可以把Log信息输出到按照日期来区分的文件中。如下配置文件就会每天产生一个log文件,每个log文件只记录当天的log信息:

  1. log4j.appender.A2=org.apache.log4j.DailyRollingFileAppender
  2. log4j.appender.A2.file=demo
  3. log4j.appender.A2.DatePattern='.'yyyy-MM-dd
  4. log4j.appender.A2.layout=org.apache.log4j.PatternLayout
  5. log4j.appender.A2.layout.ConversionPattern=%m%n


3.

Layout的配置
Layout指定了log信息输出的样式。
详细信息请查看PatternLayout的javadoc。
例子1:显示日期和log信息
  1. log4j.appender.A2.layout=org.apache.log4j.PatternLayout
  2. log4j.appender.A2.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} %m%n

打印的信息是:
2002-11-12 11:49:42,866 SELECT * FROM Role WHERE 1=1 order by createDate desc

例子2:显示日期,log发生地方和log信息
  1. log4j.appender.A2.layout=org.apache.log4j.PatternLayout
  2. log4j.appender.A2.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} %l "#" %m%n
  3. 2002-11-12 11:51:46,313 cn.net.unet.weboa.system.dao.RoleDAO.select(RoleDAO.java:409) "#" SELECT * FROM Role WHERE 1=1 order by createDate desc 

例子3:显示log级别,时间,调用方法,log信息
  1. log4j.appender.A2.layout=org.apache.log4j.PatternLayout
  2. log4j.appender.A2.layout.ConversionPattern=[%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%nlog信息:[DEBUG] 2002-11-12 12:00:57,376 method:cn.net.unet.weboa.system.dao.RoleDAO.select(RoleDAO.java:409)
  3. SELECT * FROM Role WHERE 1=1 order by createDate desc 


PART 3 log4j的使用
log4j使用步骤有3个:
3.1
.根据配置文件初始化log4j
配置文件如PART 2所叙述。现在讲的是如何在程序中配置log4j。
log4j可以使用3中配置器来初始化:BasicConfigurator,DOMConfigurator,PropertyConfigurator
这里用的是PropertyConfigurator。使用PropertyConfigurator适用于所有的系统。
如下的语句
PropertyConfigurator.configure("log4j.properties");
就以log4j.properties为配置文件初始化好了log4j环境。
注意一点:这个语句只需要在系统启动的时候执行一次。例如:在unet webOA项目中可以这么用:
在ActionServlet的init()方法中调用一次。
public class ActionServlet extends HttpServlet{
/*** Initialize global variables*/
public void init() throws ServletException {
// 初始化Action资源
try{initLog4j();...}
catch(IOException e)
{throw new ServletException("Load ActionRes is Error");}}
protected void initLog4j(){PropertyConfigurator.configure("log4j.properties");}
}
//end class ActionServlet

3.2 
在需要使用log4j的地方获取Logger实例
如下是RoleDAO类中的使用例子:
static Logger log = Logger.getLogger("DAO");
注意这里使用"DAO"标识符,那么对应的在配置文件中对应的配置信息如下:

#定义DAO Logger
log4j.logger.DAO=DEBUG,A2
#设置Appender A2的属性
log4j.appender.A2=org.apache.log4j.DailyRollingFileAppender
log4j.appender.A2.file=demolog4j.appender.A2.DatePattern='.'yyyy-MM-ddlog4j.appender.A2.layout=org.apache.log4j.PatternLayout
log4j.appender.A2.layout.ConversionPattern=%-5p %d{yyyy-MM-dd HH:mm:ss} %l%n%m%n
public class RoleDAO extends BaseDBObject{...static Logger log = Logger.getLogger("DAO");
public BeanCollection selectAll() throws SQLException{
StringBuffer sql = new StringBuffer(SQLBUF_LEN);sql.append("SELECT * FROM " + tableName + " order by roldId");
//System.out.println(sql.toString());log.debug(sql);
}
}

3.3 
使用Logger对象的debug,info,fatal...方法
log.debug("it is the debug info");

附件1:log4j的一个bug
当这样使用时,DailyRollingFileAppender不能正确使用:
public Class RoleDAO(){
static Logger log = Logger.getLogger("DAO");
//在每一次new RoleDAO对象的时候都执行一次configure()操作
public RoleDAO(TransactionManager transMgr) throws SQLException{...PropertyConfigurator.configure("log4j.properties");}
public void select(){...//使用log4j进行log记录log.debug("...");
}
}

 1. 不需要显式编码去初始化 log4j,只要你的配置文件放在 CLASSPATH的目录下就可以了.
 2.用common log而不要直接用log4j的 Logger logger = XXX, 例子如下:
   在类 ThisIsMyClass 的代码中:
   private static Log log = LogFactory.getLog(ThisIsMyClass.class);

   log4j 就会自动用步骤1所述方法初始化.

   用ThisIsMyClass.class作为参数去取得一个Log对象,
   好处就是可以更灵活地在 log4j 配置文件中配置相应的 appender 和 logger 
posted @ 2006-05-30 16:23 edsonjava 阅读(317) | 评论 (0)编辑 收藏
 
基于 Java 开发的论坛,最有名的当属 Jive ,国产的 Jute 也很不错。好是好,但需要花费银子才能用。对于企业,应该考虑使用Jute,因为其提高良好的技术支持。对于个人,或许就不值得了。这里推荐开源项目 mvnForum, 基于 J2EE technology (Jsp/Servlet).

mvnFourm is free, opensource and released under the terms of the GNU General Public License. It means that you could use it free of charge to build your own discussion communities.

一、简介

mvnForum 基于Jsp/Servlet开发,支持Jsp 1.2 和 Servlet 2.3,安装和使用都非常简单。

    主要特征:

  • 基于 MVC 架构
  • 内建数据库连接池
  • 多种数据库 (DB2, MySQL, Oracle 8i/9i, Sql Server, postgreSQL, hsqldb, Interbase/Firebird, SAPDB)
  • 国际化 (支持14种语言: English, 简体, 繁体等等)
  • Jakarta Common Logging
这里可以查看论坛功能和全部特征。

二、安装环境

  参考
服务器:tomcat 4.1.27 http://jakarta.apache.org/tomcat/index.html
数据库:PostgreSQL 7.3.3 http://www.postgresql.org

三、安装

1、下载mvnForum 1.0.0 RC 1,如果需要源码,可以下载mvnForum 1.0.0 RC 1 Source
2、将mvnforum-1.0.0-rc1.zip截压缩到mvnforum-1.0.0-rc1目录。把mvnforum-1.0.0-rc1/webapp目录复制到tomcat/webapps/目录下,然后将webapp改名为mvnforum(可根据需要改成任意的名称)。接着把mvnforum-1.0.0-rc1/driver/postgresql.jar复制到tomcat/webapps/mvnforum/WEB-INF/lib/目录下。
3、建立论坛数据库表。首先在PostgreSQL创建一数据库mvnForum,执行命令如下:
CREATEDB -E unicode mvnForum
接着执行mvnforum-1.0.0-rc1/sql/mvnForum_postgresql.sql文件,创建表,执行命令如下:
#psql mvnForum
mvnForum=#\i mvnForum_postgresql.sql
4、修改tomcat/webapps/mvnforum/WEB-INF/class/mvncore_db_DBOptions.properties文件,这个文件配置与数据库相关的资料。设置内容如下:
DRIVER_CLASS_NAME = org.postgresql.Driver
DATABASE_URL = jdbc:postgresql://192.168.0.10:5432/mvnForum
DATABASE_USER = postgres
DATABASE_PASSWORD =
5、基本安装完成,启动Tomcat,通过http://localhost:8080/mvnforum/index.jsp就可以访问论坛了。
6、管理员管理,http://localhost:8080/mvnforum/mvnforumadmin/index。用户名和密码都是admin,管理员登陆后可以增加版面和管理用户了。

posted @ 2006-05-30 15:28 edsonjava 阅读(259) | 评论 (0)编辑 收藏
 
<object id="player" height="64" width="260" classid="CLSID:6BF52A52-394A-11d3-B153-00C04F79FAA6">
    <param NAME="AutoStart" VALUE="-1">
    <!--是否自动播放-->
    <param NAME="Balance" VALUE="0">
    <!--调整左右声道平衡,同上面旧播放器代码-->
    <param name="enabled" value="-1">
    <!--播放器是否可人为控制-->
    <param NAME="EnableContextMenu" VALUE="-1">
    <!--是否启用上下文菜单-->
    <param NAME="url" VALUE="http://download.xaonline.com/music/huangpy/lover/HPY01.wma">
    <!--播放的文件地址-->
    <param NAME="PlayCount" VALUE="1">
    <!--播放次数控制,为整数-->
    <param name="rate" value="1">
    <!--播放速率控制,1为正常,允许小数,1.0-2.0-->
    <param name="currentPosition" value="0">
    <!--控件设置:当前位置-->
    <param name="currentMarker" value="0">
    <!--控件设置:当前标记-->
    <param name="defaultFrame" value="">
    <!--显示默认框架-->
    <param name="invokeURLs" value="0">
    <!--脚本命令设置:是否调用URL-->
    <param name="baseURL" value="">
    <!--脚本命令设置:被调用的URL-->
    <param name="stretchToFit" value="0">
    <!--是否按比例伸展-->
    <param name="volume" value="50">
    <!--默认声音大小0%-100%,50则为50%-->
    <param name="mute" value="0">
    <!--是否静音-->
    <param name="uiMode" value="mini">
    <!--播放器显示模式:Full显示全部;mini最简化;None不显示播放控制,只显示视频窗口;invisible全部不显示-->
    <param name="windowlessVideo" value="0">
    <!--如果是0可以允许全屏,否则只能在窗口中查看-->
    <param name="fullScreen" value="0">
    <!--开始播放是否自动全屏-->
    <param name="enableErrorDialogs" value="-1">
    <!--是否启用错误提示报告-->
    <param name="SAMIStyle" value>
    <!--SAMI样式-->
    <param name="SAMILang" value>
    <!--SAMI语言-->
    <param name="SAMIFilename" value>
    <!--字幕ID-->
</object>

1.avi格式

<object id="video" width="400" height="200" border="0" classid="clsid:CFCDAA03-8BE4-11cf-B84B-0020AFBBCCFA">
<param name="ShowDisplay" value="0">
<param name="ShowControls" value="1">
<param name="AutoStart" value="1">
<param name="AutoRewind" value="0">
<param name="PlayCount" value="0">
<param name="Appearance value="0 value=""">
<param name="BorderStyle value="0 value=""">
<param name="MovieWindowHeight" value="240">
<param name="MovieWindowWidth" value="320">
<param name="FileName" value="file:///D|/work/vod/Mbar.avi">
<embed width="400" height="200" border="0" showdisplay="0" showcontrols="1" autostart="1" autorewind="0" playcount="0" moviewindowheight="240" moviewindowwidth="320" filename="file:///D|/work/vod/Mbar.avi" src="Mbar.avi">
</embed>
</object>

2.mpg格式

<object classid="clsid:05589FA1-C356-11CE-BF01-00AA0055595A" id="ActiveMovie1" width="239" height="250">
<param name="Appearance" value="0">
<param name="AutoStart" value="-1">
<param name="AllowChangeDisplayMode" value="-1">
<param name="AllowHideDisplay" value="0">
<param name="AllowHideControls" value="-1">
<param name="AutoRewind" value="-1">
<param name="Balance" value="0">
<param name="CurrentPosition" value="0">
<param name="DisplayBackColor" value="0">
<param name="DisplayForeColor" value="16777215">
<param name="DisplayMode" value="0">
<param name="Enabled" value="-1">
<param name="EnableContextMenu" value="-1">
<param name="EnablePositionControls" value="-1">
<param name="EnableSelectionControls" value="0">
<param name="EnableTracker" value="-1">
<param name="Filename" value="../../../mpeg/halali.mpg" valuetype="ref">
<param name="FullScreenMode" value="0">
<param name="MovieWindowSize" value="0">
<param name="PlayCount" value="1">
<param name="Rate" value="1">
<param name="SelectionStart" value="-1">
<param name="SelectionEnd" value="-1">
<param name="ShowControls" value="-1">
<param name="ShowDisplay" value="-1">
<param name="ShowPositionControls" value="0">
<param name="ShowTracker" value="-1">
<param name="Volume" value="-480">
</object>


3.smi格式

<OBJECT id=RVOCX classid=clsid:CFCDAA03-8BE4-11cf-B84B-0020AFBBCCFA width=240
height=180>
<param name="_ExtentX" value="6350">
<param name="_ExtentY" value="4763">
<param name="AUTOSTART" value="-1">
<param name="SHUFFLE" value="0">
<param name="PREFETCH" value="0">
<param name="NOLABELS" value="-1">
<param name="SRC" value="rm.rm">
<param name="CONTROLS" value="ImageWindow">
<param name="CONSOLE" value="console1">
<param name="LOOP" value="0">
<param name="NUMLOOP" value="0">
<param name="CENTER" value="0">
<param name="MAINTAINASPECT" value="0">
<param name="BACKGROUNDCOLOR" value="#000000"><embed src="real.smi" type="audio/x-pn-realaudio-plugin" console="Console1" controls="ImageWindow" height="180" width="240" autostart="true"></OBJECT>

4.rm格式

<OBJECT ID=video1 CLASSID="clsid:CFCDAA03-8BE4-11cf-B84B-0020AFBBCCFA" HEIGHT=288 WIDTH=352>

<param name="_ExtentX" value="9313">
<param name="_ExtentY" value="7620">
<param name="AUTOSTART" value="0">
<param name="SHUFFLE" value="0">
<param name="PREFETCH" value="0">
<param name="NOLABELS" value="0">
<param name="SRC" value="rtsp://203.207.131.35/vod/dawan-a.rm";>
<param name="CONTROLS" value="ImageWindow">
<param name="CONSOLE" value="Clip1">
<param name="LOOP" value="0">
<param name="NUMLOOP" value="0">
<param name="CENTER" value="0">
<param name="MAINTAINASPECT" value="0">
<param name="BACKGROUNDCOLOR" value="#000000"><embed SRC type="audio/x-pn-realaudio-plugin" CONSOLE="Clip1" CONTROLS="ImageWindow" HEIGHT="288" WIDTH="352" AUTOSTART="false">

</OBJECT>

5.wmv格式

<object id="NSPlay" width=200 height=180 classid="CLSID:22d6f312-b0f6-11d0-94ab-0080c74c7e95" codebase="http://activex.microsoft.com/activex/controls/mplayer/en/nsmp2inf.cab#Version=6,4,5,715" standby="Loading Microsoft Windows Media Player components..." type="application/x-oleobject" align="right" hspace="5">
<param name="AutoRewind" value=1>
<param name="FileName" value="xxxxxx.wmv">
<param name="ShowControls" value="1">
<param name="ShowPositionControls" value="0">
<param name="ShowAudioControls" value="1">
<param name="ShowTracker" value="0">
<param name="ShowDisplay" value="0">
<param name="ShowStatusBar" value="0">
<param name="ShowGotoBar" value="0">
<param name="ShowCaptioning" value="0">
<param name="AutoStart" value=1>
<param name="Volume" value="-2500">
<param name="AnimationAtStart" value="0">
<param name="TransparentAtStart" value="0">
<param name="AllowChangeDisplaySize" value="0">
<param name="AllowScan" value="0">
<param name="EnableContextMenu" value="0">
<param name="ClickToPlay" value="0">
</object>

posted @ 2006-05-30 12:51 edsonjava 阅读(201) | 评论 (0)编辑 收藏
 

java中提供了io类库,可以轻松的用java实现对文件的各种操作。下面就来说一下如何用java来实现这些操作。
 
新建目录

  1. <%@ page contentType="text/html;charset=gb2312"%>
  2. <%
  3. //String URL = request.getRequestURI();
  4. String filePath="C:\\测试\\";
  5. filePath=filePath.toString();//中文转换
  6. java.io.File myFilePath=new java.io.File(filePath);
  7. if(!myFilePath.exists())
  8. myFilePath.mkdir();
  9. %>

新建文件

  1. <%@ page contentType="text/html;charset=gb2312"%>
  2. <%@ page import="java.io.*" %>
  3. <%
  4. String filePath="c:/测试/newFile.txt";
  5. filePath=filePath.toString();
  6. File myFilePath=new File(filePath);
  7. if(!myFilePath.exists())
  8. myFilePath.createNewFile();
  9. FileWriter resultFile=new FileWriter(myFilePath);
  10. PrintWriter myFile=new PrintWriter(resultFile);
  11. String content ="这是测试数据";
  12. String strContent = content.toString();
  13. myFile.println(strContent);
  14. resultFile.close();
  15. %>


删除文件

  1. <%@ page contentType="text/html;charset=gb2312"%>
  2. <%
  3. String filePath="c://测试//newFile.txt";
  4. filePath=filePath.toString();
  5. java.io.File myDelFile=new java.io.File(filePath);
  6. if(myDelFile.exists())
  7. {  
  8.     myDelFile.delete();
  9.     out.println(filePath+"删除成功!!!");
  10. }
  11. else
  12. {
  13.     out.println(filePath+"该文件不存在");
  14. }
  15. %>

文件拷贝

  1. <%@ page contentType="text/html; charset=gb2312" %>
  2. <%@ page import="java.io.*" %>
  3. <%
  4. int bytesum=0;
  5. int byteread=0;
  6. //file:读到流中
  7. InputStream inStream=new FileInputStream("c://测试//newFile.txt");
  8. FileOutputStream fs=new FileOutputStream( "c://测试//copyFile.txt");
  9. byte[]  buffer =new  byte[1444];
  10. int length;
  11. while ((byteread=inStream.read(buffer))!=-1)
  12.  {
  13.    out.println("<DT><B>"+byteread+"</B></DT>");
  14.    bytesum+=byteread;
  15.    out.println(bytesum);
  16.    fs.write(buffer,0,byteread);
  17.  }
  18. inStream.close();
  19. %>

整个文件夹拷贝

  1. <%@ page contentType="text/html;charset=gb2312"%>
  2. <%@ page import="java.io.*" %>
  3. <%String url1="C:/aaa";
  4.   String url2="d:/java/";
  5.   (new File(url2)).mkdirs();
  6.  File[] file=(new File(url1)).listFiles();
  7.  for(int i=0;i<file.length;i++){
  8.   if(file[i].isFile()){
  9.    file[i].toString();
  10.    FileInputStream input=new FileInputStream(file[i]);
  11.    FileOutputStream output=new FileOutputStream(url2+"/"+(file[i].getName()).toString());
  12.    byte[] b=new byte[1024*5];
  13.     int len;
  14.     while((len=input.read(b))!=-1){
  15.     output.write(b,0,len);
  16.     }
  17.     output.flush();
  18.     output.close();
  19.     input.close();
  20.   }
  21.  }
  22. %>

文件下载

  1. <%@ page contentType="text/html; charset=gb2312"%>
  2. <%@ page import="java.io.*" %>
  3. <%
  4.   String fileName = "newFile.txt".toString();
  5.   //读到流中
  6.   InputStream inStream=new FileInputStream("c://测试//newFile.txt");
  7.   //设置输出的格式
  8.   response.reset();
  9.   response.setContentType("text/plain");
  10.   response.addHeader("Content-Disposition","attachment; filename=\"" + fileName + "\"");
  11.   //循环取出流中的数据
  12.   byte[] b = new byte[100];
  13.   int len;
  14.   ServletOutputStream outStream = response.getOutputStream();
  15.   while((len=inStream.read(b)) >0)
  16.   outStream.write(b,0,len);
  17.   outStream.flush();
  18.   outStream.close();
  19.   inStream.close();
  20. %>

数据库字段中的文件下载

  1. <%@ page contentType="text/html;charset=gb2312"%>
  2. <%@ page import="java.util.*,java.sql.*,java.io.*"%>
  3. <%
  4.     String id = request.getParameter("id");
  5.     if(id==null)
  6.     {   throw new Exception ("没有找到图片");
  7.     }
  8.     else
  9.     {
  10.        try
  11.        {
  12. com.gzrealmap.lib.jdbc.JDBCUtil  SqlBean= com.gzrealmap.lib.jdbc.JDBCUtil.getInstance();
  13.                SqlBean.connect();
  14.                String sql = "select * from innernews where id = '"+79+"'";
  15.                ResultSet rs = SqlBean.queryforUpdate(sql);
  16.                rs.next();
  17.                //String fileNamedb = rs.getString("imageName");
  18.                String file= rs.getString("acc");
  19.                //String fileName = new String(fileNamedb.getBytes(),"iso-8859-1");
  20.                String fileName = "a.jpg";
  21.                 response.setHeader("Content-Disposition",  "inline; filename=\"" + fileName + "\"");    
  22.                String filter = fileName.substring(fileName.lastIndexOf("."));
  23.               
  24.                if(filter.equals(".txt"))
  25.                {
  26.                    response.setContentType("text/plain");
  27.                }
  28.                else if(filter.equals(".doc")||filter.equals(".dot"))
  29.                {
  30.                    response.setContentType("application/msword");
  31.                }
  32.                else
  33.                {
  34.                  response.setContentType("image/jpeg;charset=GB2312");
  35.                }
  36.                ServletOutputStream o = response.getOutputStream();
  37.                //o.write(file);
  38.                out.println(file);
  39.                //o.flush();
  40.                //o.close();
  41.                SqlBean.disconnect();
  42.        }
  43.         catch(Exception ex)
  44.        {
  45.            out.println(ex.getMessage());
  46.        }
  47.     }  
  48. %>

把网页保存成文件

  1. <%@ page contentType="text/html;charset=gb2312"%>
  2. <%@ page import="java.text.*,java.util.*,java.net.*,java.io.*"%>
  3. <%
  4.  URL stdURL = null;
  5.  BufferedReader stdIn = null;
  6.  PrintWriter stdOut = null;
  7.  try {
  8.   stdURL = new URL("<a href="http://www.163.com/">http://www.163.com</a>");
  9.  }
  10.  catch (MalformedURLException e) {
  11.    throw e;
  12.  }
  13. try {
  14.     //将字节流转变成为字符流
  15.     stdIn = new BufferedReader(new InputStreamReader(stdURL.openStream()));
  16.     String theFileName = "c://测试//163.html";
  17.     stdOut = new PrintWriter(new BufferedWriter(new FileWriter(theFileName.toString())));
  18.  }
  19.  catch (IOException e) {
  20.  }
  21.  /***把URL指定的页面以流的形式读出,写成指定的文件***/
  22.  try {
  23.     String strHtml = "";
  24.    while((strHtml = stdIn.readLine())!=null) {
  25.    stdOut.println(strHtml);
  26.    }
  27.  }
  28.  catch (IOException e) {
  29.    throw e;
  30.  }
  31.  finally {  
  32.    try {
  33.      if(stdIn != null)
  34.        stdIn.close();
  35.      if(stdOut != null)
  36.        stdOut.close();
  37.        }
  38.    catch (Exception e) {
  39.      System.out.println(e);
  40.    }
  41.  }
  42. %>

直接下载网上的文件

  1. <%@ page contentType="text/html;charset=gb2312"%>
  2. <%@ page import="java.io.*"%>
  3. <%@ page import="java.net.*"%>
  4. <%
  5.   int bytesum=0;
  6.   int byteread=0;
  7.   URL url = new URL("<a href="http://pimg.163.com/sms/micheal/logo.gif">http://pimg.163.com/sms/micheal/logo.gif</a>");
  8.   URLConnection conn = url.openConnection();
  9.   InputStream inStream = conn.getInputStream();
  10.   /**
  11.   String theFileName = "c:/测试/logo.gif";
  12.   theFileName = theFileName.toString();
  13.   File myFilePath=new File(theFileName);
  14.   if(!myFilePath.exists())
  15.   myFilePath.createNewFile();
  16.   **/
  17.   FileOutputStream fs=new FileOutputStream("c:/测试/logo2.gif");
  18.   byte[]  buffer =new  byte[1444];
  19.     while ((byteread=inStream.read(buffer))!=-1)
  20.     {
  21.        out.println("<DT><B>"+byteread+"</B></DT>");
  22.        bytesum+=byteread;
  23.        //System.out.println(bytesum);
  24.        fs.write(buffer,0,byteread);
  25.      }
  26. %>

按行读文件

  1. <%@ page contentType="text/html; charset=gb2312" %>
  2. <%@ page import="java.io.*" %>
  3. <%
  4. FileReader myFileReader=new FileReader("c:/哈哈.txt");
  5. BufferedReader myBufferedReader=new BufferedReader(myFileReader);
  6. String myString=null;
  7. String resultString=new String();
  8. while((myString=myBufferedReader.readLine())!=null) {
  9. resultString=resultString+myString+"
  10. ";
  11. }
  12. out.println(resultString);
  13. myFileReader.close();
  14. %>

对word文档的处理(上传与下载)

  1. <%@ page contentType="application/msword" %>
  2. <!-- 以上这行设定本网页为excel格式的网页 -->
  3. <%
  4.    response.setHeader("Content-disposition","inline; filename=test1.doc"); //线上浏览方式
  5.   // response.setHeader("Content-disposition","attachment; filename=test1.doc");//下载方式
  6.    //以上这行设定传送到前端浏览器时的档名为test1.doc
  7.    //就是靠这一行,让前端浏览器以为接收到一个word档
  8. %>
  9. //然后输出动态内容就可以得到一个word文档了

1,打开:
1)文件头上加:

  1. <%@ page  contentType="application/msword"%> 
  2. xml文件里:
  3. <mime-mapping>
  4.         <extension>doc</extension>
  5.         <mime-type>application/msword</mime-type>
  6. </mime-mapping>

2)可以用js,以下代码来自引用:

]]>




 
2,下载:

  1. <%@ page contentType="text/html;charset=gb2312" import= "java.io.*"%>
  2. <%// 得到文件名字和路径
  3.   String filename = "jsp.doc";
  4.   String filepath = "C:\\";
  5.   // 设置响应头和下载保存的文件名
  6.   response.setContentType("APPLICATION/OCTET-STREAM");
  7.   response.setHeader("Content-Disposition","attachment; filename=\"" + filename + "\"");
  8.   // 打开指定文件的流信息
  9.   java.io.FileInputStream fileInputStream = new java.io.FileInputStream(filepath + filename);
  10.   //FileOutputStream out  = new FileOutputStream(filepath+"测试\\" + filename);
  11.   // 写出流信息
  12.   int i;
  13.   while ((i=fileInputStream.read()) != -1) {
  14.    out.write(i);
  15.   }
  16.   fileInputStream.close();
  17.   out.close()
  18.  %>
posted @ 2006-05-30 12:46 edsonjava 阅读(365) | 评论 (0)编辑 收藏
 
     摘要: Write an image of a given format ...  阅读全文
posted @ 2006-05-30 12:39 edsonjava 阅读(371) | 评论 (1)编辑 收藏
 
     摘要: Read an image ...  阅读全文
posted @ 2006-05-30 10:30 edsonjava 阅读(348) | 评论 (0)编辑 收藏
 
 
回复此消息
前言
Jive是一个广受欢迎的开放的源码的论坛项目,它有很多值得我们学习的地方。这篇文章谈的就是Jive缓存机制的实现,希望对大家有所帮助。
简介
我们知道,在两个存取速度差距很大的对象(比如数据库和内存)之间,通常要加一个缓存,来匹配二者的速度。因此,缓存机制在我们实际项目中还是经常遇到的。同样Jive也使用缓存来加快贴子的显示。如果你正试图编写一个类似的东西,不妨研究一下Jive源码,可能对你大有帮助。
在Jive2.1.2中,涉及jive的缓存机制的java类大致可以分为以下四个部分(为了简化起见,本文只讨论帖子的缓存机制的实现,用户名和权限的存取虽然也用到了的缓存,但其实现机制与前者类似,因此不再赘述):
第一部分:提供HashMap,LinkedListedlist等数据结构以便实现缓存机制,其中HashMap是JDK提供的,其Key类型为Object。你可以在com.jivesoftware.util包中找到他们,包括的类有Cache类, LinkedList类,LinkedListNode类和Casheable接口,CacheObject类,CacheableBoolean类,CacheableInt类,CacheableLong类,CacheableLongArray类,CacheableString类,CacheSizes类,CacheTimer类;
第二部分:提供LongHashMap,LongLinkedListedlist等数据结构以便实现缓存机制,与第一部分不同的是,它的HashMap是自己编写的,其Key类型为Long,因此被冠以LongHashMap的名称。你同样可以在com.jivesoftware.util包中找到他们,包括的类有LongHashMap类,LongCache类, LongCacheObject类,
LongLinkedList类和LongLinkedListNode类;还有第一部分中的Casheable接口,它的各种数据类型的实现以及CacheSizes类和CacheTimer类,也可归于这部分,它们可看作是第一部分和第二部分的交集;
第三部分:调用底层数据结构以提供论坛对象的缓存,你可以在com.jivesoftware.forum.database包中找到他们,包括的类主要有DatabaseCacheManager类,DbForumFactory类,DbForum类,DbForumThread类,DbForumMessage 类,DatabaseCache类,ForumCache类, ForumThreadCache类,ForumMessageCache类; 
第四部分:向Jsp页面提供访问接口,同样在com.jivesoftware.forum.database包中,包括类有ForumThreadBlockIterator和ForumMessageBlockIterator类,第三部分的DbForum类,DbForumThread类和DbForumMessage 类也可以包括进来,实际上,这三个类是第三和第四部分联系的纽带,在com.jivesoftware.util包中还有一个LongList类,它用来将ForumThreadBlockIterator和ForumMessageBlockIterator转化成Long型数组,因此也应算在这部分;
因此缓存机制也可以划分为三层,即第一、二部分的底层数据结构、第三部分的中间层和第四部分的上层访问接口,下面分别讨论:
底层数据结构
  jive缓存机制的原理其实很简单,就是把所要缓存的对象加到HashMap哈希映射表中,用两个LinkedListedlist双向链表分别维持着缓存对象和每个缓存对象的生命周期,如果一个缓存对象被访问到,那么就把它放到链表的最前面,然后不定时的把要缓存对象的对象加入链表中,把过期对象删除,如此反复。我们比较一下第一和第二部分就可以发现,他们的代码几乎完全相同。差别就在第二部分的哈希映射表没有采用JDK提供的类,而是采用了作者自己编写的一个类,他将原来哈希映射表的Key的类型由Object改为Long,这样做虽然在一定程度上加快了缓存的速度并减小了缓存的大小,但无形之中也减低了程序的稳定性和可读性,因此不推荐大家仿效。值得一提的是,在Jive1.0.2版中,所有Forum,Thread,Message的ID和他们的内容的缓存都是用第一部分的Java类实现的,在升级到后面的版本时,其内容采用了第二部分的Java类实现,但其ID仍用第一部分的Java类实现,这也是Jive容易混淆的一个地方。好了,我们先来看第一部分的Java类实现。
我们先来看LinkedListNode类的源码:
public class LinkedListNode {
public LinkedListNode previous;
    public LinkedListNode next;
    public Object object;
    public long timestamp;

public LinkedListNode(Object object, LinkedListNode next,
            LinkedListNode previous)
    {
        this.object = object;
        this.next = next;
        this.previous = previous;
    }
public void remove() {
        previous.next = next;
        next.previous = previous;
    }
public String toString() {
        return object.toString();
    }
}
很明显,这是一个双向链表的节点类,previous,next分别记录前后节点的指针,object用于记录所需缓存的对象,timestamp用于记录当前节点被创建时的时间戳。当该时间戳超过该节点的生存周期时,它就会被remove()方法删除掉。就是由LinkedListNode构成了LinkedList链表,而LinkedList类中只是实现了getFirst(),getLast(),addFirst(),addLast(),clear()等链表的基本方法,没有其他内容。
再来看Cacheable接口和它的一个实现类CacheableInt:

public interface Cacheable {
    public int getSize();
}
public class CacheableInt implements Cacheable {
    private int intValue;
    public CacheableInt(int intValue) {
        this.intValue = intValue;
    }
    public int getInt() {
        return intValue;
    }
    public int getSize() {
        return CacheSizes.sizeOfObject() + CacheSizes.sizeOfInt();
    }
}
从上面的代码可以看到Cacheable接口只有一个方法getSize(),它要求继承类实现该方法汇报占用缓存的大小,以便实施管理。Integer,Boolean,Long,LongArray,String类型都有其对应的类,CacheSizes类则把各种类型的长度封装起来,便于修改和移植。那么为什么CacheableInt. getSize()得到的是sizeOfObject()+sizeOfInt()呢,聪明的读者一定想到了,因为任何都继承自Object,计算空间时当然也要把它给算上。
还有一个CacheObject类,它是缓存的基本元素,我们来看代码:
public final class CacheObject {
public Cacheable object;
      public int size;
      public LinkedListNode lastAccessedListNode;
public LinkedListNode ageListNode;

      public CacheObject(Cacheable object, int size) {
        this.object = object;
        this.size = size;
}
}
lastAccessedListNode记录着一个缓存节点的Key,是构成lastAccessedList链表的基本元素,在lastAccessedList链表中,经常被访问到的节点总是在最前面。而ageListNode记录着缓存节点的加入时间,是构成ageList链表的基本元素,而ageList链表则是按时间先后排序,先加入的节点总是在最后面。lastAccessedListNode和ageListNode本来可以写成两个类,毕竟lastAccessedListNode并不需要ageListNode的成员变量timestamp,但是为了简化程序,Jive把他们写成了一个类,这也是容易混淆的一个地方。
现在来看缓存机制中最关键的一个类Cache的部分代码,主要是add()和get()方法:
public class Cache implements Cacheable {
protected static long currentTime = CacheTimer.currentTime;
protected HashMap cachedObjectsHash;
protected LinkedList lastAccessedList;
protected LinkedList ageList;
//缓存元素的最大尺寸128kbit,可修改

protected int maxSize =  128 * 1024; 
//整个缓存的大小
protected int size = 0;
//缓存元素的最大保存时间,用Cache(long maxLifetime)初始化
protected long maxLifetime = -1;
//记录cache的命中次数和未命中次数
protected long cacheHits, cacheMisses = 0L; 
……
//向哈希表中添加一个关键字为Key的缓存对象object
public synchronized void add(Object key, Cacheable object) {
        //先把原来的对象remove掉
        remove(key);
int objectSize = object.getSize();
        //如果对象太大,则不加入缓冲存
        if (objectSize > maxSize * .90) {
            return;
        }
        size += objectSize;
        //新建一个缓存对象,并放入哈希表中
        CacheObject cacheObject = new CacheObject(object, objectSize);
        cachedObjectsHash.put(key, cacheObject);
        // 把缓存元素的Key放到lastAccessed List链表的最前面
        LinkedListNode lastAccessedNode = lastAccessedList.addFirst(key);
        cacheObject.lastAccessedListNode = lastAccessedNode;
        //把缓存元素的Key放到ageList链表的最前面,并记下当前时间
        LinkedListNode ageNode = ageList.addFirst(key);
        ageNode.timestamp = System.currentTimeMillis();
        cacheObject.ageListNode = ageNode;
// 在cullCache()中,先调用deleteExpiredEntries()把过期对象删掉,如果缓存还是太满,则掉用remove(lastAccessedList.getLast().object)把lastAccessedList中不常访问的对象删掉
        cullCache();
    }
//在哈希表中得到一个关键字为Key的缓存对象object
public synchronized Cacheable get(Object key) {
        // 清理过期对象
        deleteExpiredEntries();

        CacheObject cacheObject = (CacheObject)cachedObjectsHash.get(key);
        if (cacheObject == null) {
            //没找到则未命中次数加一
            cacheMisses++;
            return null;
        }

        //找到则命中次数加一
        cacheHits++;

        //将该缓存对象从lastAccessedList链表中取下并插入到
        //链表头部
        cacheObject.lastAccessedListNode.remove();
        lastAccessedList.addFirst(cacheObject.lastAccessedListNode);

        return cacheObject.object;
    }
到这里第一部分的Java类实现就说完了,正如上文提到的那样,第二部分的Java类实现与第一部分基本上没有什么差别,因此就不再赘述。下面给出第二部分的类图,以供读者参考。
[img]http://www.javaresearch.org/members/{tomjava}
中间层
     中间层是联系上层访问接口和低层数据结构的纽带。它的主要功能就是根据ID(对应于数据库中的编号)到缓存中去找相应的对象,如果缓存中有该对象就直接得到,没有则去读数据库生成一个新的对象,再把该对象放入缓存中,以便下次访问时能直接得到。下面是相关类的类图:
[img]http://www.javaresearch.org/members/{tomjava}
(注:Forum表示论坛,Thread表示论坛贴子的线索,Message表示论坛贴子,它们的关系是这样的 :Forum包括数条Thread,Thread包括数条Message。)
由上图可见,DbForum类,DbForumThread类和DbForumMessage 类的实例对象都包含一个 DbForumFactory类的实例对象factory。DbForum,DbForumThread和DbForumMessage被DbForumFactory生产出来,同时他们也通过DbForumFactory来访问缓存。而在DbForumFactory中则包含一个DatabaseCacheManager类的实例对象cacheManager,它负责管理所有的缓存对象,如图,这些缓存对象就是ForumCache类, ForumThreadCache类和ForumMessageCache类的实例。ForumCache类, ForumThreadCache类和ForumMessageCache类继承自同一个抽象类DatabaseCache,而在DatabaseCache类中,有一个LongCache型的成员变量cache,就这样,中间层就和低层的数据结构结合起来了。
我们以thread线索对象的获得为例来说明中间层是如何运作的,请看代码摘要:
DbForum.java
public class DbForum implements Forum, Cacheable {
    。。。 。。。
public ForumThread getThread(long threadID)
            throws ForumThreadNotFoundException
    {
        return factory.getThread(threadID, this);
    }
。。。 。。。
}

DbForumFactory.java
public class DbForumFactory extends ForumFactory {
    。。。 。。。
protected DbForumThread getThread(long threadID, DbForum forum) throws
            ForumThreadNotFoundException
    {
        DbForumThread thread = cacheManager.threadCache.get(threadID);
        return thread;
}
。。。 。。。
}

ForumThreadCache.java
public class ForumThreadCache extends DatabaseCache {
    。。。 。。。
public DbForumThread get(long threadID)
            throws ForumThreadNotFoundException
{ //缓存中寻找以threadID为编号的DbForumThread对象
DbForumThread thread = (DbForumThread)cache.get(threadID);
        if (thread == null) {
      //如果在缓存中找不到该对象
            //新建一个以threadID为编号的DbForumThread对象
thread = new DbForumThread(threadID, factory);
//将新建对象加入缓存
            cache.add(threadID, thread);
        }
        return thread;
}
   。。。 。。。
}

DbForumThread.java
public class DbForumThread implements ForumThread, Cacheable {
   。。。 。。。
protected DbForumThread(long id, DbForumFactory factory)
            throws ForumThreadNotFoundException
    {
        this.id = id;
        this.factory = factory;
//读取数据库,其中id对应数据库中的jiveThreadProp表中的threadID字段
        loadFromDb();
        isReadyToSave = true;
}
   。。。 。。。
}
   从上面的代码我们可以看到,当我们调用DbForum类 的getThread(long threadID)方法去获得一个编号为threadID的线索对象时,我们实际上调用的是DbForumFactory类中的getThread(long threadID, DbForum forum)方法,而它则是调用ForumThreadCache类的方法来完成任务的。那么ForumThreadCache类里get(long threadID)方法有做了什么呢?代码已经很清楚了,就是根据threadID到缓存中去找相应的线索对象,如果缓存中有该对象就直接得到,没有则新建一个DbForumThread对象,再把该对象放入缓存中。看到这里,有点让人奇怪,好象程序中根本没有连接数据库的语句。再看DbForumThread类的代码,终于恍然大悟,原来Jive在新建一个DbForumThread对象就已经用loadFromDb()方法把数据读出来了;另一方面,如果在缓存中找了DbForumThread对象,程序根本就不会新建DbForumThread对象,因而就好象没有数据库的操作,这实际上就是我们通过缓存机制所要达到的目的。
    Message帖子对象的获得与Thread对象的获得类似,因此就不再重复了。从上面我们可以看到,只要我们得到了论坛线索的编号threadID,我们就可以得到对应的线索对象,不管它是从缓存中来,还是从数据库中来。那么threadID是如何从Jsp页面传到中间层的呢?让我们来看上层访问接口的运行机制吧。
上层访问接口
    上层访问接口的主要功能连接Jsp页面和中间层,换句话说,就是把Jsp页面中要调用的Thread、Message对象的ID传递到中间层。下面给出访问Thread的相关类的类图(访问Message机制类似,故省略)。其中的forum.jsp是显示论坛内容的页面,在这里,我们把forum.jsp看成是一个特殊的类,它里面有一个ForumThreadIterator类的实例变量threads和DbForum类的实例变量forum,故它和ForumThreadIterator类及DbForum类的关系应是关联关系。
[img]http://www.javaresearch.org/members/{tomjava}
先来看forum.jsp和DbForum 类的部分代码:
forum.jsp
<%
// ResultFilter结果过滤类
ResultFilter filter = new ResultFilter();
    filter.setStartIndex(start);
    filter.setNumResults(range);
//调用Dbforum的threads()方法,获得ForumThreadIterator对象实例
ForumThreadIterator threads = forum.threads(filter);
。。。 。。。
while (threads.hasNext()) {
        //对thead进行遍历
        ForumThread thread = (ForumThread)threads.next();
        //得到thread的ID
        long threadID = thread.getID();
        //得到线索的根帖子rootMessage
        ForumMessage rootMessage = thread.getRootMessage();
        //得到帖子主题和作者等信息
        String subject = rootMessage.getSubject();
        User author = rootMessage.getUser();
。。。 。。。
}
%>
DbForum.java
public class DbForum implements Forum, Cacheable {
。。。 。。。
public ForumThreadIterator threads(ResultFilter resultFilter) {
    //生成SQL语句
        String query = getThreadListSQL(resultFilter, false);
        //得到threadID块
long [] threadBlock = getThreadBlock(query.toString(),
 resultFilter.getStartIndex());
        。。。 。。。
        //返回ForumThreadBlockIterator对象
        return new ForumThreadBlockIterator(threadBlock, query.toString(),
                startIndex, endIndex, this.id, factory);
}
protected long[] getThreadBlock(String query, int startIndex) {
        int blockID = startIndex / THREAD_BLOCK_SIZE;
        int blockStart = blockID * THREAD_BLOCK_SIZE;  
        String key = query + blockID;     
 //根据Key的值到缓存中取得ThreadID的数组
        CacheableLongArray longArray = 
(CacheableLongArray)threadListCache.get(key);
        //在缓存中则返回
        if (longArray != null) {
            long [] threads = longArray.getLongArray();
            return threads;
        }
        // 否则到数据库中取ThreadID的块,以数组形式返回
        else {
            LongList threadsList = new LongList(THREAD_BLOCK_SIZE);
            Connection con = null;
            Statement stmt = null;
            。。。数据库操作 。。。
            }
            long [] threads = threadsList.toArray();
            //将 ThreadID的块加入缓存
threadListCache.add(key, new CacheableLongArray(threads));
            return threads;
        }
     }
。。。 。。。
}
在forum.jsp中有一个ResultFilter类的实例resultFilter,它给出页面显示Thread的起始位置和数量,它作为参数传入forum.threads()中,用于构造相关的SQL语句。当调用forum.threads(filter)时,程序将生成的SQL语句传入到getThreadBlock()方法中去得到一个threadID的块,也就是一组threadID。之所以要读threadID块,是因为显示论坛时并不是显示一条线索就行了,而是一下显示十几条,这样做可以避免反复读数据库,再说threadID不是thread对象,并不怎么占空间。
应该说,使用了块以后,减轻了数据库的访问量,因而论坛的效率有了很大的提高。然而好戏还在后头,Jive又把块放入了缓存中。在getThreadBlock()方法里,Jive用Cache类的实例对象threadListCache来缓存threadID块,而关键字就是SQL语句加上blockID,也就是说,主要SQL语句和blockID相同,就可以在缓存中取出相同的threadID块。当然,缓存中找不到,还是要到数据库中读出来加入缓存的。这样论坛的效率又得到了进一步的提升。
ForumThreadBlockIterator类继承自ForumThreadIterator抽象类,而ForumThreadIterator类又实现了Iterator接口,因此得到ForumThreadBlockIterator的实例对象threads后,就可以在用threads.next()方法对它进行编历了。ForumThreadBlockIterator类的代码想都可以想得到,就是逐个读取ThreadID,然后根据ThreadID返回Thread对象,由此上层访问接口就和中间层衔接起来了。
小结
Jive的缓存机制值得我们学习的地方有很多,比如读取线索时不是读一条而是读一个block;显示线索的起始位置和数量用专门的一个类来管理,动态生成SQL语句;用一个专门的类来负责管理缓存;把论坛缓存对象的功能抽象出来形成一个缓存的抽象类DatabaseCache,让它去跟低层数据结构联系起来。这些都体现了面向对象的设计原则即提高软件的可维护性和可复用性。
同时,Jive也告诉我们,要想编好程序,只懂条件语句和循环语句可不行;必须选择好数据结构,掌握好面向对象的设计原则,熟悉设计模式思想方法,这样才能编写出强壮高效的代码。
posted @ 2006-05-23 00:23 edsonjava 阅读(416) | 评论 (0)编辑 收藏
仅列出标题
共7页: 上一页 1 2 3 4 5 6 7