2005年5月21日

什么是分析?什么是设计?

所谓的分析,就是对问题的调查,着重点在于业务过程;所谓的设计,就是问题的逻辑解决方案,设计可以通过软件和硬件来实现。

在面向对象分析和设计面世之前,流行的是结构化分析和设计。结构化分析和设计主要是依据软件的功能或者过程来划分,将一个复杂过程分为以系列有层次的子过程。

比方,对于一个图书管理的软件来说,可以分为:进库管理 出库管理 报表打印 图书检索等等子系统。

而从面向对象分析与设计来说,该系统可以分解成为几个对象: 书、目录、书库、管理员等等 。

 所以,面向对象的分析和设计强调的是以对象为尺度而不是以功能为尺度。

12:59 | 评论 (2)

2005年5月20日

为了满足可插接性,JAAS是可堆叠的。在单一登录的情况下,一组安全模块可以堆叠在一起,然后被其他的安全机制按照堆叠的顺序被调用。JAAS的实现者根据现在一些流行的安全结构模式和框架将JASS模型化。例如可堆叠的特性同Unix下的可堆叠验证模块(PAM,Pluggable Authentication Module)框架就非常相似。从事务的角度看,JAAS类似于双步提交(Two-Phase Commit,2PC)协议的行为。JAAS中安全配置的概念(包括策略文件(Police File)和许可(Permission))来自于J2SE 1.2。JAAS还从其他成熟的安全框架中借鉴了许多思想。
JAAS提供了一些LoginModule的参考实现代码,比如JndiLoginModule。开发人员也可以自己实现LoginModule接口以满足实际应用中的需要。
一、登陆模块(LoginModule)

LoginModule 是参与 JAAS 认证过程所需的方法的接口。因为可能要到执行其它登录过程时才知道特定登录过程是成功还是失败,所以用两阶段提交过程来确定是否成功。下列方法由 LoginModule 对象实现:

  • initialize( subject, callbackHandler, sharedState, options) 初始化 LoginModule。(注:对 sharedState 和 options 的讨论超出了本文的范围。)
  • login() 设置任何必需的回调,调用 CallbackHandler 来处理它们,并将返回的信息(即用户名和密码)与允许值进行比较。如果匹配,则登录模块成功,尽管仍可能因为另一个登录模块不成功而异常终止它,这取决于 login.config 文件中的设置。
  • commit() 作为两阶段提交过程的一部分被调用以确定是否成功。如果根据 login.config 文件中指定的约束,所有登录模块都是成功的,那么新的 Principal 随同用户名一起创建,并被添加到 Subject 的主体集。
  • abort(),如果总体登录未成功,则调用它;如果发生异常终止,必须清除内部的 LoginModule 状态。
  • logout() 被调用以除去 Subject 的主体集中的 Principal 并执行其它内部状态清除。

下面两页说明了两个登录模块。第一个是 AlwaysLoginModule,类似于WebSite中的guest访问。第二个是 PasswordLoginModule,仅当用户标识和密码与某些Database的值相匹配时,它才会成功。


AlwaysLoginModule  

AlwaysLoginModule 认证将始终成功,所以实际上它仅用于通过 NameCallback 函数获取用户名。假设其它登录模块都成功,AlwaysLoginModule 的 commit() 方法将创建一个带用户名的新 PrincipalImpl 对象并将它添加到 Subject 的 Principal 集中。注销将除去 Subject 的 Principal 集中的 PrincipalImpl。
import java.security.*;
import javax.security.auth.*;
import javax.security.auth.spi.*;
import javax.security.auth.callback.*;
import javax.security.auth.login.*;
import java.io.*;
import java.util.*;

// This is a JAAS Login Module that always succeeds.  While not realistic, 
// it is designed to illustrate the bare bones structure of a Login Module 
// and is used in examples that show the login configuration file 
// operation.

public class AlwaysLoginModule implements LoginModule {

     private Subject subject;
     private Principal principal;
     private CallbackHandler callbackHandler;
     private String username;
     private boolean loginSuccess;
     //
     // Initialize sets up the login module.  sharedState and options are
     // advanced features not used here
     public void initialize(Subject sub, CallbackHandler cbh,
       Map sharedState, Map options) {

       subject = sub;
       callbackHandler = cbh;
       loginSuccess = false;
     }
     //
     // The login phase gets the userid from the user
     public boolean login() throws LoginException {
       //
       // Since we need input from a user, we need a callback handler
       if (callbackHandler == null) {
         throw new LoginException( "No CallbackHandler defined");

       }
       Callback[] callbacks = new Callback[1];
       callbacks[0] = new NameCallback("Username");
       //
       // Call the callback handler to get the username
       try {
         System.out.println( "\nAlwaysLoginModule Login" );
         callbackHandler.handle(callbacks);
         username = ((NameCallback)callbacks[0]).getName();
       } catch (IOException ioe) {
         throw new LoginException(ioe.toString());
       } catch (UnsupportedCallbackException uce) {
         throw new LoginException(uce.toString());
       }
       loginSuccess = true;
       System.out.println();
       System.out.println( "Login: AlwaysLoginModule SUCCESS" );
       return true;
     }
     //
     // The commit phase adds the principal if both the overall authentication
     // succeeds (which is why commit was called) as well as this particular
     // login module
     public boolean commit() throws LoginException {
       //
       // Check to see if this login module succeeded (which it always will

       // in this example)
       if (loginSuccess == false) {
         System.out.println( "Commit: AlwaysLoginModule FAIL" );
         return false;
       }
       //
       // If this login module succeeded too, then add the new principal
       // to the subject (if it does not already exist)
       principal = new PrincipalImpl(username);
       if (!(subject.getPrincipals().contains(principal))) {
         subject.getPrincipals().add(principal);
       }
       System.out.println( "Commit: AlwaysLoginModule SUCCESS" );
       return true;
     }
     //
     // The abort phase is called if the overall authentication fails, so
     // we have to clean up the internal state
     public boolean abort() throws LoginException {

       if (loginSuccess == false) {
         System.out.println( "Abort: AlwaysLoginModule FAIL" );
         principal = null;
         return false;
       }
       System.out.println( "Abort: AlwaysLoginModule SUCCESS" );
       logout();

       return true;
     }
     //
     // The logout phase cleans up the state
     public boolean logout() throws LoginException {

       subject.getPrincipals().remove(principal);
       loginSuccess = false;
       principal = null;
       System.out.println( "Logout: AlwaysLoginModule SUCCESS" );
       return true;
      }
}


11:02 | 评论 (0)

摘要:

Java Authentication Authorization Service(JAAS,Java验证和授权API)提供了灵活和可伸缩的机制来保证客户端或服务器端的Java程序。JAAS强调的是通过验证谁在运行代码及其权限来保护系统免受用户的攻击。JAAS还能将一些标准的安全机制,例如Solaris NIS(网络信息服务)、Windows NT、LDAP(轻量目录存取协议),Kerberos等通过一种可配置的方式集成到系统中。

我们将分验证和授权两部分来讲述JAAS。

验证和授权概述

验证是用户或计算设备用来验证身份的过程。授权是根据请求用户的身份允许访问和操作一段敏感软件的过程。这两个概念密不可分。没有授权,就无需知道用户的身份。没能认证,就不可能区分可信和不可信用户,更不可能安全地授权访问许多系统部分。

不一定要标识或认证个别实体;在某些情况下,可以通过分组,对给定组中的所有实体授予某种权限来进行认证。在某些情况下,个别认证是系统安全性必不可少的环节。

认证与授权的另一个有趣方面是,一个实体在系统中可以有几个角色。例如,用户可以同时是公司职工(表示他需要对公司的电子邮件有访问权)和该公司的会计师(表示他需要对公司财务系统有访问权)。

有两种控制访问敏感代码的基本方法:

  • 声明性授权可以由系统管理员执行,他配置系统的访问权(即,声明谁可以访问系统中的哪些应用程序)。通过声明性授权,可以添加、更改或取消用户访问特权,而不影响底层应用程序代码。
  • 程序性授权使用 Java 应用程序代码来做授权决定。当授权决定需要更复杂的逻辑和决定(超出了声明性授权的能力范围)时,程序性授权是必需的。因为程序性授权被构建到应用程序代码中,所以更改程序性授权时要求重写应用程序的部分代码。

JAAS验证技术
几乎每个程序员都有为系统开发过登录模块,可能你的登录模块是建立在数据库的基础上,也有可能使用的是NT的用户验证,或者使用的是LDAP目录。如果有一种方法可以在不改变代码的基础上支持上面提到的所有安全机制,对于程序员来说是一件幸运的事。

你可以使用JAAS实现上面的目标。通过在应用程序和底层的验证和授权机制之间加入一个抽象层,JAAS可以简化涉及到Java Security的程序开发。图一给出了JAAS程序的整体框架图。应用程序级的代码主要处理LoginContext。在LoginContext下面是一组动态配置的LoginModules。

图一 JAAS概览

JAAS 实现了“可插入认证模块(Pluggable Authentication Module(PAM))”框架的 Java 版本。PAM 的主要用途是允许应用程序开发人员在开发时写入标准认证接口,并将使用哪些认证技术(以及如何使用它们)的决策留给系统管理员。

认证技术是在登录模块中实现的,这些登录模块是在编写了应用程序之后部署的,并且在称为登录配置文件(本教程中名为 login.config)的文本文件中指定。login.config 文件不仅可以指定要调用哪些模块,而且还可以指定总体认证成功的条件。 PAM 使新的认证技术或技巧能更方便地添加到现有应用程序中。同样,可以通过更新 login.config 文件来更改认证策略,而不是重写整个应用程序。

JDK 1.4 提供了下列 PAM 模块: 

  • com.sun.security.auth.module.NTLoginModule
  • com.sun.security.auth.module.NTSystem 
  • com.sun.security.auth.module.JndiLoginModule 
  • com.sun.security.auth.module.KeyStoreLoginModule 
  • com.sun.security.auth.module.Krb5LoginModule 
  • com.sun.security.auth.module.SolarisSystem 
  • com.sun.security.auth.module.UnixLoginModule 
  • com.sun.security.auth.module.UnixSystem

开发人员也可以自己实现LoginModule接口,比如使用关系数据库保存用户的登录信息。

为了满足可插接性,JAAS是可堆叠的。在单一登录的情况下,一组安全模块可以堆叠在一起,然后被其他的安全机制按照堆叠的顺序被调用。

Subject和Principal

Subject 是一种 Java 对象,它表示单个实体,如个人。一个 Subject 可以有许多个相关身份,每个身份都由一个 Principal 对象表示。那么,比方说一个 Subject 表示要求访问电子邮件系统和财务系统的雇员。该 Subject 将有两个 Principal,一个与用于电子邮件访问的雇员的用户标识关联,另一个与用于财务系统访问的用户标识关联。

除了包含一组 Principal 外,Subject 还可以包含两组凭证:公用和专用。credential 是密码、密钥和令牌等。对公用和专用凭证集的访问是由 Java 特权控制的。

Subject 对象有几个方法,其中一些方法如下:

  • subject.getPrincipals() 返回一组 Principal 对象。因为结果是 Set,所以适用操作 remove()、add() 和 contains()。
  • subject.getPublicCredentials() 返回一组与 Subject 相关的公用可访问凭证。
  • subject.getPrivateCredentials() 返回一组与 Subject 相关的专用可访问凭证。

Principal对象代表了Subject对象的身份。它们实现了java.security.Principal接口和java.io.Serializable接口。在Principal类中,最重要的方法是getName()。该方法返回一个身份名称。在Subject对象中包含了多个Principal对象,因此它可以拥有多个名称。由于登录名称、身份证号和Email地址都可以作为用户的身份标识,可见拥有多个身份名称的情况在实际应用中是非常普遍的情况。

Principal 不是持久性的,所以每次用户登录时都必须将它们添加到 Subject。Principal 作为成功认证过程的一部分被添加到 Subject。同样,如果认证失败,则从 Subject 中除去 Principal。不管认证成功与否,当应用程序执行注销时,将除去所有 Principal。

10:57 | 评论 (0)

2005年3月24日

Apache Commons是一个非常有用的工具包,解决各种实际的通用问题,下面是一个简述表,详细信息访问http://jakarta.apache.org/commons/index.html

BeanUtils
Commons-BeanUtils 提供对 Java 反射和自省API的包装

Betwixt
Betwixt提供将 JavaBean 映射至 XML 文档,以及相反映射的服务.

Chain
Chain 提供实现组织复杂的处理流程的“责任链模式”.

CLI
CLI 提供针对命令行参数,选项,选项组,强制选项等的简单API.

Codec
Codec 包含一些通用的编码解码算法。包括一些语音编码器, Hex, Base64, 以及URL encoder.

Collections
Commons-Collections 提供一个类包来扩展和增加标准的 Java Collection框架

Configuration
Commons-Configuration 工具对各种各式的配置和参考文件提供读取帮助.

Daemon
一种 unix-daemon-like java 代码的替代机制

DBCP
Commons-DBCP 提供数据库连接池服务

DbUtils
DbUtils 是一个 JDBC helper 类库,完成数据库任务的简单的资源清除代码.

Digester
Commons-Digester 是一个 XML-Java对象的映射工具,用于解析 XML配置文件.

Discovery
Commons-Discovery 提供工具来定位资源 (包括类) ,通过使用各种模式来映射服务/引用名称和资源名称。.

EL
Commons-EL 提供在JSP2.0规范中定义的EL表达式的解释器.

FileUpload
FileUpload 使得在你可以在应用和Servlet中容易的加入强大和高性能的文件上传能力

HttpClient
Commons-HttpClient 提供了可以工作于HTTP协议客户端的一个框架.

IO
IO 是一个 I/O 工具集

Jelly
Jelly是一个基于 XML 的脚本和处理引擎。 Jelly 借鉴了 JSP 定指标签,Velocity, Cocoon和Xdoclet中的脚本引擎的许多优点。Jelly 可以用在命令行, Ant 或者 Servlet之中。

Jexl
Jexl是一个表达式语言,通过借鉴来自于Velocity的经验扩展了JSTL定义的表达式语言。.

JXPath
Commons-JXPath 提供了使用Xpath语法操纵符合Java类命名规范的 JavaBeans的工具。也支持 maps, DOM 和其他对象模型。.

Lang
Commons-Lang 提供了许多许多通用的工具类集,提供了一些java.lang中类的扩展功能

Latka
Commons-Latka 是一个HTTP 功能测试包,用于自动化的QA,验收和衰减测试.

Launcher
Launcher 组件是一个交叉平台的Java 应用载入器。 Commons-launcher 消除了需要批处理或者Shell脚本来载入Java 类。.原始的 Java 类来自于Jakarta Tomcat 4.0 项目

Logging
Commons-Logging 是一个各种 logging API实现的包裹类.

Math
Math 是一个轻量的,自包含的数学和统计组件,解决了许多非常通用但没有及时出现在Java标准语言中的实践问题.

Modeler
Commons-Modeler 提供了建模兼容JMX规范的 Mbean的机制.

Net
Net 是一个网络工具集,基于 NetComponents 代码,包括 FTP 客户端等等。

Pool
Commons-Pool 提供了通用对象池接口,一个用于创建模块化对象池的工具包,以及通常的对象池实现.

Primitives
Commons-Primitives提供了一个更小,更快和更易使用的对Java基本类型的支持。当前主要是针对基本类型的 collection。.

Validator
The commons-validator提供了一个简单的,可扩展的框架来在一个XML文件中定义校验器 (校验方法)和校验规则。支持校验规则的和错误消息的国际化。

10:20 | 评论 (0)

2005年3月11日

草稿中。。

13:50 | 评论 (0)

在现实世界中的系统经常不得不同时处理多个客户端请求。在这样的一个典型的多线程的系统中,不同的线程将处理不同的客户端。一个常见的区分每个客户端所输出的Logging的方法是为每个客户端实例化一个新的独立的Logger。这导致Logger的大量产生,管理的成本也超过了logging本身。
 
嵌套诊断环境NDC 
在多用户并发的环境下,通常是由不同的线程分别处理不同的客户端请求。此时要在日志信息中区分出不同的客户端,区分两个客户的日志输出的常用方法是单独为每个客户端实例化新类别。但是,该方法会增加类别数量,并增加日志记录的管理开销。

Log4J巧妙地使用了Neil Harrison提出的“NDC(嵌套诊断环境)”机制来解决这个问题。Neil Harrison(请参阅参考资料)想出了一个简便方法,可以唯一标记从同一个客户机交互启动的每一个日志记录请求。Log4J为同一类别的线程生成一个Logger,多个线程共享使用,而它仅在日志信息中添加能够区分不同线程的信息,该信息可能是发出请求的主机名、IP地址或其它任何可以用于标识该请求的信息。这样,由于不同的客户端处理线程具有不同的NDC堆栈,即使这个Servlet同时生成多个线程处理不同的请求,这些日志信息仍然可以区分出来,就好像Log4J为每一个线程都单独生成了一个Logger实例一样。

在Log4J中是通过org.apache.log4j.NDC实现这种机制的。使用NDC的方法也很简单,步骤如下: 
1. 在进入一个环境时调用NDC.push(String),然后创建一个NDC; 
2. 所做的日志操作输出中包括了NDC的信息; 
3. 离开该环境时调用NDC.pop方法; 
4. 当从一个线程中退出时调用NDC.remove方法,以便释放资源。 

下面是一个模拟记录来自不同客户端请求事件的例子,代码如下: 

import org.apache.log4j.Logger;
import org.apache.log4j.NDC;
public class TestNDC {
 static Logger log = Logger.getLogger(TestNDC.class.getName());
 public static void main(String[] args) {
  log.info("Make sure %x is in your layout pattern!");
  // 从客户端获得IP地址的例子
  String[] ips = {"192.168.0.10","192.168.0.27"};
  for (int i = 0; i  {
   // 将IP放进 NDC中
   NDC.push(ips[i]);
   log.info("A NEW client connected, who's ip should appear in this log message.");
   NDC.pop();
  }
  NDC.remove();
  log.info("Finished.");
 }
}


注意配置文件中的布局格式中一定要加上%x。系统输出如下: 
INFO   - Make sure %x is in your layout pattern!
INFO  192.168.0.10 - A NEW client connected, who's ip should appear in this log 
message.
INFO  192.168.0.27 - A NEW client connected, who's ip should appear in this log 
message.
INFO   - Finished.

在Web应用中使用 
在Web应用中,应该在哪儿对Log4J进行配置呢?首先要明确,Log4J必须在应用的其它代码执行前完成初始化。因为Servlet是在Web服务器启动时立即装入的,所以,在Web应用中一般使用一个专门的Servlet来完成Log4J的配置,并保证在web.xml的配置中,这个Servlet位于其它Servlet之前。下面是一个例子,代码如下: 

package org.javaresearch.log4j;
import java.io.*;
import javax.servlet.*;
import org.apache.log4j.*;
public class Log4JInit extends HttpServlet {
 public void init() throws ServletException {
  String prefix = getServletContext().getRealPath("/");
  String file = getServletConfig().getInitParameter("log4j-config-file");
  // 从Servlet参数读取log4j的配置文件 
  if (file != null) {
   PropertyConfigurator.configure(prefix + file);
  }
 }
 public void doGet(HttpServletRequest request,HttpServletResponse response)throws 
IOException, ServletException {}
 public void doPost(HttpServletRequest request,HttpServletResponse response)throws 
IOException, ServletException {}
}


  log4jinit 
   org.javaresearch. log4j.Log4JInit        
   
    log4j-config-file  
   /properties/log4j.properties 
   

   1
  


注意:上面的load-on-startup应设为1,以便在Web容器启动时即装入该Servlet。log4j.properties文件放在根的properties子目录中,也可以把它放在其它目录中。应该把.properties文件集中存放,这样方便管理。 

Use log4j in J2EE

    在J2EE 服务器中使用log4j包需要注意的是:1)基于J2EE服务器jar包加载机制的特殊性,必须将log4j的jar包拷贝到%JAVAHOME\jre\lib\ext\目录下。仅仅放到CLASSPATH中是不起作用的。2)因为J2EE服务器对文件操作进行了严格的控制,所以Log文件时只能写到%J2EE_HOME%\logs目录下,除非修改server.policy文件指定别的可写目录。下面我举这个很简单的例子说明一下如何在J2EE服务器中使用上log4j开发工具包,例子中涉及的文件如下:

bonus.html



Bonus Calculation


ACTION="BonusAlias">


Enter social security Number:




Enter Multiplier:











上面的bonus.html接受用户输入的社会保险号和投保金额。
BonusServlet.java
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import javax.naming.*;
import javax.rmi.PortableRemoteObject;
import Beans.*;
public class BonusServlet extends HttpServlet {
CalcHome homecalc;
public void init(ServletConfig config)
throws ServletException{
//Look up home interface
try{
InitialContext ctx = new InitialContext();
Object objref = ctx.lookup("calcs");
homecalc =(CalcHome)PortableRemoteObject.narrow(
objref,CalcHome.class);
} catch (Exception NamingException) {
NamingException.printStackTrace();
}
}
public void doGet (HttpServletRequest request,HttpServletResponse response)
throws ServletException, IOException {
String socsec = null;
int multiplier = 0;
double calc = 0.0;
PrintWriter out;
response.setContentType("text/html");
String title = "EJB Example";
out = response.getWriter();
out.println("");
try{
Calc theCalculation;
//Get Multiplier and Social Security Information
String strMult = request.getParameter("MULTIPLIER");
Integer integerMult = new Integer(strMult);
multiplier = integerMult.intValue();
socsec = request.getParameter("SOCSEC");
//Calculate bonus
double bonus = 100.00;
theCalculation = homecalc.create();
calc = theCalculation.calcBonus(multiplier, bonus);
} catch(Exception CreateException){
CreateException.printStackTrace();
}
//Display Data
out.println("

Bonus Calculation

");
out.println("

Soc Sec: " + socsec + "

");
out.println("

Multiplier: " + multiplier + "

");
out.println("

Bonus Amount: " + calc + "

");
out.println("");
out.close();
}
public void destroy() {
System.out.println("Destroy");
}
}


BonusServlert.java得到从bonus.html中传过来的社会保险号和金额,并调用EJB程序对数据进行处理并将结果显示给客户端。

EJB 的Home接口:

package Beans;
import java.rmi.RemoteException;
import javax.ejb.CreateException;
import javax.ejb.EJBHome;
public interface CalcHome extends EJBHome {
Calc create() throws CreateException,RemoteException;
}

EJB 的Remote 接口:
package Beans;
import javax.ejb.EJBObject;
import java.rmi.RemoteException;
public interface Calc extends EJBObject {
public double calcBonus(int multiplier,double bonus)
throws RemoteException;
}

EJB 的业务处理逻辑:
package Beans;
import java.rmi.RemoteException;
import javax.ejb.SessionBean;
import javax.ejb.SessionContext;
public class CalcBean implements SessionBean {
public double calcBonus(int multiplier,double bonus) {
double calc = (multiplier*bonus);
return calc;
}
//These methods are described in more
//detail in Lesson 2
public void ejbCreate() { }
public void setSessionContext(
SessionContext ctx) { }
public void ejbRemove() { }
public void ejbActivate() { }
public void ejbPassivate() { }
public void ejbLoad() { }
public void ejbStore() { }
}

    成功发布后就可以在代码中加入Log4j的测试代码了。想详细了解上面的应用程序的发布步骤请叁照我最近写的一篇文章“Say ‘Hello World’ to EJB”。接下来我们在EJB中使用Log4j包(由于Servlet 中使用Log4j的方法和前面提到的例子中的使用方法相同,这里不在重复)。
1. 首先在EJB的Remote接口中增加名为logtest的方法声明:
package Beans;
import javax.ejb.EJBObject;
import java.rmi.RemoteException;
public interface Calc extends EJBObject {
public double calcBonus(int multiplier,double bonus)
throws RemoteException;
public void logtest() throws RemoteException;
}

2.在 EJB的业务处理Bean中实现logtest()方法:
package Beans;
import java.rmi.RemoteException;
import javax.ejb.SessionBean;
import javax.ejb.SessionContext;
import org.apache.log4j.Category;
import org.apache.log4j.Logger;
import org.apache.log4j.*;
import org.apache.log4j.NDC;

public class CalcBean implements SessionBean {
static Logger logger = Logger.getLogger(CalcBean.class.getName());
public double calcBonus(int multiplier,double bonus) {
logger.debug("Entering calcBeans() method"); 
logtest();
double calc = (multiplier*bonus);
logger.debug("Exitint calcBeans() method");
return calc;
}
public void logtest(){
logger.debug("Enter logtest method");
NDC.push("Client #45890"); 
logger.debug("Exiting logtest method.");
}
//These methods are described in more
//detail in Lesson 2
public void ejbCreate() { 
PropertyConfigurator.configure("F:/example.properties"); 
}
public void setSessionContext(
SessionContext ctx) { }
public void ejbRemove() { }
public void ejbActivate() { }
public void ejbPassivate() { }
public void ejbLoad() { }
public void ejbStore() { }
}


这里假设log配置文件为F:盘下的example.properties文件。
接下来就是配置文件的设置问题了。
Example.properties
# Attach appender A1 to root. Set root level to Level.DEBUG.
log4j.rootLogger=DEBUG, A1

# A1 is set to be a FileAppender sending its output to
# a log file. However, only error messages and above will be printed
# in A1 because A1's threshold is set to Level.ERROR.

# The fact that the root level is set to Prority.DEBUG only influences
# log requests made to the root logger. It has no influence on the
# *appenders* attached to root.
# Appender A1 writes to the file "test" in J2EE's allow write DIR ex logs direstory.
log4j.appender.A1=org.apache.log4j.FileAppender
log4j.appender.A1.File=E:/j2sdkee1.3/logs/test.log

# Truncate 'test' if it aleady exists.
log4j.appender.A1.Append=false

# Appender A1 uses the PatternLayout.
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%5r %-5p [%t] %c{2} - %m%n
# Set the level of the logger named "Beans.CalcBean" to 
# Level.DEBUG(also INFO WARN ERROR etc.),auto attach appender A1.
log4j.category.Beans.CalcBean=debug


这样重新发布这个EJB就回在%J2EE_HOME%\logs 目录下生成名为test.log的log文件。

13:48 | 评论 (0)

2005年3月1日

背景
对于有经验的开发者来说,日志记录的重要性显而易见。例如程序中的异常处理和安全性都依赖于Logging的功能来帮助履行它们的指责。应用程序中的日志记录主要基于三个目的:监视代码中变量的变化情况,周期性的记录到文件中供其他应用进行统计分析工作;跟踪代码运行时轨迹,作为日后审计的依据;担当集成开发环境中的调试器的作用,向文件或控制台打印代码的调试信息。经验表明日志记录是开发周期中的重要组成部分。

最简单的做法就是在代码中嵌入许多的打印语句,但是这样打印语句会充斥代码的主体,显然不是一个好方法。因此,使用成熟的框架例如Log4j,则会更具灵活性。

Log4j简介
Log4j 框架是用 Java 语言编写的标准日志记录框架。作为 Jakarta 项目的一部分,它在 Apache 软件许可证(Apache Software License)下分发,以速度和灵活性为中心概念:Log4j 环境是完全可配置的,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件、甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等;我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。

Log4j由三个重要的部件构成:记录器(Loggers)、输出源(Appenders)和布局(Layouts)。

记录器按照布局中指定的格式把日志信息写入一个或多个输出源。输出源可以是控制台、文本文件、XML文件或Socket,甚至还可以把信息写入到Windows事件日志或通过电子邮件发送。我们可以通过配置文件来部署这些组件。

其实您也可以完全不使用配置文件,而是在代码中配置Log4j环境。但是,使用配置文件将使您的应用程序更加灵活。本文从描述 log4j 体系结构的主要组件着手。然后是描述基本用法和配置的简单示例。

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

一、 配置记录器。

Log4j允许程序员定义多个记录器,每个记录器有自己的名字。但有一个记录器叫根记录器,它永远存在,且不能通过名字检索或引用,在配置文件中,可以如下定义根记录器:

  log4j.rootLogger = [ level ] , appenderName, appenderName, … 

Level是记录器的级别,它是日志记录的优先级,分为OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL或者您定义的级别。Log4j建议只使用四个级别:ERROR、WARN、INFO、DEBUG:

  DEBUG  <  INFO  <  WARN  <  ERROR  <  FATAL

右边的级别比左边的高。如果一条log信息的级别,大于等于记录器的级别值,那么记录器就会记录它。例如level被设置为INFO级别,那么应用程序中所有的DEBUG的日志信息将不被打印出来。通过在这里定义的级别,您可以控制到应用程序中相应级别的日志信息的开关。

 appenderName是输出源的名字,它指定日志信息输出到哪个地方。您可以为一个记录器指定多个输出源。

在一些配置文件中,你可能会看到下面的语句:

  log4j.rootCategory = [ level ] , appenderName, appenderName, … 

在早期的Log4j版本中,org.apache.Category实现了记录器的功能,为了提高向后兼容性,Logger扩展了Category,因此rootCategory和rootLogger是可以互换的,但最后Category将从类库中删除,因此请使用Logger类。

除了根记录器之外,log4j允许程序员定义多个记录器,每个记录器有自己的名字:

  log4j.logger.loggerName = [ level ] , appenderName, appenderName, … 

在Log4J中Logger是具有层次关系的,Log4j支持配置的记录器之间的“父子关系”,记录器之间通过名字来表明隶属关系(或家族关系),它们有一个共同的根,位于最上层,其它Logger遵循类似包的层次:记录器a.b,与记录器a.b.c之间是父子关系,而记录器a与a.b.c之间是祖先与后代的关系。例如:

    static Logger root = Logger.getRootLogger();
       static Logger log1 = Logger.getLogger("cc");
       static Logger log2 = Logger.getLogger("cc.ejb");
       static Logger log3 = Logger.getLogger("cc.ejb.my.TestApp");
 
上面代码中,log1是log2的父亲,是log3的祖先,而root是所有log1、log2、log3的祖先,它们都从root中继承。所以,一般情况下,仅需要配置好rootLogger,其它子记录器都会从中继承rootLogger的配置。如果修改了rootLogger的配置,其它所有的子记录器也会继承这种变化。这样就大大地方便了配置。
 
如果一个应用中包含了上千个类都需要日志,那么我们是否需要配置上千个Logger呢?我们通过一个简单的办法来解决这个问题: 用每一个java类文件名(包含该类的包名)定义一个记录器,这是一种有用并且直观的记录器实例名的定义方式。例如在配置文件中定义了一个com.foo的记录器:
  log4j.logger.com.foo=WARN
在com.foo中的一个java类bar,我们通过其本类的名字获得一个记录器“com.foo.Bar”:

package com.foo;
class Bar{
static Logger log=Logger.getLogger(bar.Class.getName());
.....
}

由于记录器com.foo.Bar 没有指定的级别,它从com.foo(在配置文件中其级别设置成WARN) 继承级别。并且这样我们就能方便的从大量log信息中判断出它们各自的来源。当然了,这不是硬性规定的,实际上Log4j没有对设置记录器的实例名做什么限制,程序员可以根据自己的喜好随意定义。

二、日志信息输出源Appender
log4j 还允许日志记录请求打印到多个输出目的地,按 log4j 的叫法是输出源。一个记录器可以有多个输出源。一条log信息如果可被这个记录器处理,则该记录器会把这条信息送往每个它所拥有的输出源,以及层次结构中更高级的输出源。例如,根记录器以控制台作为输出源,则所有可被纪录的日志都将至少打印到控制台。

配置日志信息输出源,其语法为:

  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(将日志信息以流格式发送到任意指定的地方) 
  • org.apache.log4j.SocketAppender (Socket)
  • org.apache.log4j.NtEventLogAppender   (NT的Event Log) 
  • org.apache.log4j.JMSAppender   (电子邮件)

请注意,可以通过覆盖缺省行为,这样就不再附加累积的输出源:

  log4j.additivity.loggerName=false

注意,不要把一个输出源附加到多个记录器上,否则会得到“Attempted to append to closed appender named xxx”的信息。


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

  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(包含日志产生的时间、线程、类别等等信息)

如果采用了PatternLayout, 则Log4J采用类似C语言中的printf函数的打印格式格式化日志信息,打印参数如下:

  • %m 输出代码中指定的消息
  • %p 输出优先级,即DEBUG,INFO,WARN,ERROR,FATAL 
  • %r 输出自应用启动到输出该log信息耗费的毫秒数 
  • %c 输出所属的类目,通常就是所在类的全名 
  • %t 输出产生该日志事件的线程名 
  • %n 输出一个回车换行符,Windows平台为“\r\n”,Unix平台为“\n”
  • %d 输出日志时间点的日期或时间,默认格式为ISO8601,也可以在其后指定格式,比如:%d{yyy MMM dd HH:mm:ss,SSS},输出类似:2002年10月18日 22:10:28,921 
  • %l 输出日志事件的发生位置,包括类目名、发生的线程,以及在代码中的行数。举例:Testlog4.main(TestLog4.java:10) 

四、例子
下面是一个完整的Log4j配置文件,这个配置文件指定了两个输出源stdout和R。前者把日志信息输出到控制台,后者是一个轮转日志文件。最大的文件是100KB,当一个日志文件达到最大尺寸时,Log4J会自动把example.log重命名为example.log.1,然后重建一个新的example.log文件,依次轮转。

log4j.rootLogger=debug, stdout, R

log4j.appender.stdout=org.apache.log4j.FileAppender
log4j.appender.stdout.File=System.out
log4j.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%n

log4j.appender.R=org.apache.log4j.RollingFileAppender
log4j.appender.R.File=example.log

log4j.appender.R.MaxFileSize=100KB
# Keep one backup file
log4j.appender.R.MaxBackupIndex=1

log4j.appender.R.layout=org.apache.log4j.PatternLayout
log4j.appender.R.layout.ConversionPattern=%p %t %c - %m%n

log4j.logger.cc.ejb.my=error,out

log4j.appender.out=org.apache.log4j.ConsoleAppender
log4j.appender.out.layout=org.apache.log4j.PatternLayout
log4j.appender.out.layout.ConversionPattern=%p %t %c - %m%n

log4j.logger.cc.ejb.my.son=debug
log4j.additivity.cc.ejb.my.son=false

在代码中使用Log4j 

一、得到记录器
使用Log4j,第一步就是获取日志记录器,这个记录器将负责控制日志信息。其语法为: 

  public static Logger getLogger( String name) 

通过指定的名字获得记录器,如果必要的话,则为这个名字创建一个新的记录器。Name一般取本类的名字,比如: 

  static Logger logger = Logger.getLogger ( ServerWithLog4j.class.getName () )

二、读取配置文件 
当获得了日志记录器之后,第二步将配置Log4j环境,其语法为:

  //自动快速地使用缺省Log4j环境。

BasicConfigurator.configure ();
//读取使用Java的特性文件编写的配置文件

  PropertyConfigurator.configure ( String configFilename);
//读取XML形式的配置文件
  DOMConfigurator.configure ( String filename );

三、插入记录信息(格式化日志信息)
当上两个必要步骤执行完毕,您就可以轻松地使用不同优先级别的日志记录语句插入到您想记录日志的任何地方,其语法如下:

  Logger.debug ( Object message ) ;
  Logger.info ( Object message ) ;
  Logger.warn ( Object message ) ;
  Logger.error ( Object message ) ;

四、例子

我们通过下面这个简单的例子,来演示在程序如何使用Log4j,您可以修改配置文件以得到不同日志信息。

package cc.ejb.my;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
import my.son.Foo;
public class TestApp {

    static Logger logger=Logger.getLogger(TestApp.class.getName());

    public static void main(String[] args) {
       
        PropertyConfigurator.configure("log4j.properties");
        logger.info("Applcaiton Starts");
        logger.warn("Bar Starts");
        Bar bar=new Bar();
        logger.error("Bar Errors");
        bar.doIt();
        logger.warn("Bar  Exits");
        logger.info("Foo Starts");
        Foo foo=new Foo();
        logger.error("Foo Errors");
        foo.doit();
        logger.warn("Foo exits ");
        logger.info("Applcaition Exits");
       
    }

}

class Bar
{
    static Logger logger = Logger.getLogger(Bar.class.getName());
    public void doIt() {
      logger.debug("Did it again!");
    }
  }

package cc.ejb.my.son;

import org.apache.log4j.Logger;

public class Foo {
    private Logger log=Logger.getLogger(Foo.class.getName());
    public Foo() {
        log.info("Foo Initialzie");
    }
    public void doit()
    {
        log.debug("Do it in Foo");
    }

}

14:47 | 评论 (1)

2005年2月24日

Java 5.0中增强的for/in 与“普通”for 之间的最基本区别是,您不必使用计数器(通常称为 i 或 count)或 Iterator。例1是一个很典型的for循环。


public void testForLoop(PrintStream out) throws IOException {
  List list = getList();  // initialize this list elsewhere
  
  for (Iterator i = list.iterator(); i.hasNext(); ) {
    Object listElement = i.next();
    out.println(listElement.toString());
  }
}

在例2中我们用for/in改写了上面的例子。

public void testForInLoop(PrintStream out) throws IOException {
  List list = getList();  // initialize this list elsewhere
  
  for (Object listElement : list) {
    out.println(listElement.toString());    
 }
}

for/in 循环的基本语法如下所示:

for(声明:表达式)
{
   语句
}

声明是一个变量,例如 Object listElement。这个变量应该有自己的类型,这样,它就可以与将遍历的列表、数组或集合中的每一个项兼容。在例2中,list 包含一些对象,因此这些对象就是 listElement 的类型。

表达式的结果应当是可以遍历的。表达式可以是一个变量(如例 2 所示)或者是一个方法调用(例如 getList()),亦或是包含布尔逻辑或三元运算符的复杂表达式。只要它返回一个数组或集合,for/in就能很好的工作。

事实上,java编译器实际上把这个for/in 语句变成了一个更加编译器友好的 for 循环。

使用数组
利用for/in处理数组也一样容易。对于使用 for 以及计算器或索引变量的场合,现在就可以使用 for/in(当然,前提是您正在使用 Tiger)。例3 显示了一个简单的示例:

public void testArrayLooping(PrintStream out) throws IOException {
  int[] primes = new int[] { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29 };
  
  // Print the primes out using a for/in loop
  for (int n : primes) {
    out.println(n);
  }
}

处理集合
使用 for/in 对集合进行遍历没有任何需要特殊处理或者复杂的地方,它工作起来,与您刚才看到的处理列表和集合的方式一样。

for/in与泛型
现在我们要利用到 Tiger 的另一项特性 —— 泛型,泛型让 for/in 变得更加强大。

记得 for/in 语句的声明部分创建了一个变量,它代表要遍历的集合中每个项目的类型。在数组中,类型非常明确,因为类型是强类型的,int[] 只能包含整数,所以在循环中没有类型转换。在您通过泛型使用类型化列表时,也可以做到这一点:

List wordlist = new ArrayList();
Set wordset = new HashSet();

for (String word : wordlist) {
  System.out.print(word + " ");
}


在这例子中,类型转换还没有完全消失。但是,这些工作正逐步转交给编译器完成。在编译的时候,所有这些类型都会被检测,您可能得到相应的错误信息。

类与 for/in 的集成
迄今为止,我只是针对 Java 事先打包的类和类型(array、list、map、set 和其他集合)进行遍历。这一节要处理的只是允许 for/in 构造使用您自己的对象所涉及的一些概念与步骤。

例5 是java.util.interator接口:

package java.util;

public interface Iterator {

  public boolean hasNext();
  
  public E next();
  
  public void remove();
}


现在为了利用 for/in,需要添加另一个接口 java.lang.Iterable。该接口如例6 所示:
package java.lang;

public interface Iterable {
  public java.util.Iterator iterator();
}


请注意,Iterable 位于 java.lang 之中,而不是位于java.util 中。为了让您的对象或类能与 for/in 一起工作,对象和类需要实现 Iterable 接口。可以通过下面两种方式来实现:
扩展现有的、已经实现了 Iterable(因此也就已经支持 for/in)的集合类。 
手动处理遍历,定义自己的 Iterable 实现。
扩展现有的集合是利用 for/in 的捷径。
import java.util.LinkedList;
import java.util.List;

public class GuitarManufacturerList extends LinkedList {

  public GuitarManufacturerList() {
    super();
  }

  public boolean add(String manufacturer) {
    if (manufacturer.indexOf("Guitars") == -1) {
      return false;
    } else {
      super.add(manufacturer);
      return true;
    }
  }
}  

因为 LinkedList 已经可以使用 for/in,所以,不需要特殊的代码,就可以在 for/in 中使用这个新类。
在某些不常见的情况下,您必须自己处理这些事情。例9 演示了如何自己处理遍历。以下这个类提供了文本文件的包装器,在遍历它的时候,它将列出文件中的每行内容。

import java.util.Iterator;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

/**
 * This class allows line-by-line iteration through a text file.
 *   The iterator's remove() method throws UnsupportedOperatorException.
 *   The iterator wraps and rethrows IOExceptions as IllegalArgumentExceptions.
 */
public class TextFile implements Iterable {

  // Used by the TextFileIterator below
  final String filename;
  
  public TextFile(String filename) {
    this.filename = filename;
  }
  
  // This is the one method of the Iterable interface
  public Iterator iterator() {
    return new TextFileIterator();
  }
  
  // This non-static member class is the iterator implementation
  class TextFileIterator implements Iterator {
  
    // The stream being read from
    BufferedReader in;
    
    // Return value of next call to next()
    String nextline;
    
    public TextFileIterator() {
      // Open the file and read and remember the first line
      //   Peek ahead like this for the benefit of hasNext()
      try {
        in = new BufferedReader(new FileReader(filename));
        nextline = in.readLine();
      } catch (IOException e) {
        throw new IllegalArgumentException(e);
      }
    }
    
    // If the next line is non-null, then we have a next line
    public boolean hasNext() {
      return nextline != null;
    }
    
    // Return the next line, but first read the line that follows it
    public String next() {
      try {
        String result = nextline;
        
        // If we haven't reached EOF yet...
        if (nextline != null) {
          nextline = in.readLine();    // Read another line
          if (nextline == null)
            in.close();                // And close on EOF
        }
        
        // Return the line we read last time through
        return result;
        
      } catch (IOException e) {
        throw new IllegalArgumentException(e);
      }
    }
    
    // The file is read-only; we don't allow lines to be removed
    public void remove() {
      throw new UnsupportedOperationException();
    }
  }
  
  public static void main(String[] args) {
    String filename = "TextFile.java";
    if (args.length > 0)
      filename = args[0];
      
    for (String line : new TextFile(filename))
      System.out.println(line);
  }
}


其中大部分工作是实现 Iterator,然后通过 iterator() 方法返回它。其他的事情就非常简单了。但是,您可以看到,与扩展一个现成的类来完成同样的工作相比,手动实现 Iterable 接口需要做的工作多得多。

不能做什么
 for/in 也有自身的局限性。原因是 for/in 设置的方式,特别是因为它没有显式地使用 Iterator,所以使用这个新构造时,有些事情是您不能做的。

定位
最明显的显然是不能确定您在列表或数组(或者定制对象)中的位置。

public void determineListPosition(PrintStream out, String[] args)
    throws IOException {
    
    List wordList = new LinkedList();
    
    // Here, it's easy to find position
    for (int i=0; i      wordList.add("word " + (i+1) + ": '" + args[i] + "'");
    }
    
    // Here, it's not[/i] possible to locate position
    for (String word : wordList) {
      out.println(word);
    }
}


在这里,没有任何类型的计数器变量(或者 Iterator),也不存在任何侥幸。如果需要定位,就得用“普通”的 for。

删除项目
另外一个限制是项目删除。在列表遍历期间无法删除项目:

public void removeListItems(PrintStream out, String[] args)
    throws IOException {
    
    List wordList = new LinkedList();
    
    // Assign some words
    for (int i=0; i      wordList.add("word " + (i+1) + ": " '" + args[i] + "'");
    }
    
    // Remove all words with "1" in them. Impossible with for/in!
    for (Iterator i = wordList.iterator(); i.hasNext(); ) {
      String word = (String)i.next();
      if (word.indexOf("1") != -1) {
        i.remove();
      }
    }
    
    // You can print the words using for/in
    for (String word : wordList) {
      out.println(word);
    }
}

从整体来看,这些不算什么限制,只是什么时候使用 for、什么时候使用 for/in 的一个准则。for/in 是一项很方便的功能,它能让代码更清晰、更简洁。

13:21 | 评论 (2)

2 3 

Copyright 龙二少爷.