3.4 单例模式在日志管理中的实际应用

作为非常简单的单例模式,其实现方法就是定义一个该类的静态变量,然后再定义一个获取该静态变量的静态方法。单例模式在程序开发中有着广泛的应用,比如数据库连接池,日志管理等都会使用到,学习设计模式最好的办法就是看好的代码,Log4j是Apache的一个开放源代码项目,得益于它良好的架构设计,开发人员可以随意控制日志语句的输出,输出的目的地可以是控制台,文件,邮件,数据库等。在Log4j中就用到了单例模式,当然Log4j中还是用了很多的设计模式。
先来看一下Log4j的使用方法
对于一个日志管理工具来说,需要考虑的内容主要有3个:

  • 日志的输出格式
  • 日志的输出目的地
  • 日志的优先级

Log4j有3个主要的组件和上述内容相对应:Layouts(布局),Appenders(输出源)和Loggers(记录器)。Log4j可以自定义日志输出的级别,从低到高依此有:DEBUG,INFO,WARN,ERROR,FATAL,这些级别分别用来制定输出日志的重要程度:如果定义了输入的级别为WARN,则只有等于或高于这个级别的(WARN,ERROR,FATAL)的日志才能输出,日志输出中还有两个关键字:
- ALL:输出所有的日志
- OFF:关闭所有的日志输出
要使用Log4j,首先需要下载Log4j的JAR包,然后将其放在程序的CLASSPATH中。首通过XML或Properties配置Log4j的日志输出位置,输出格式,输出优先级等信息,然后在程序中获取Logger,编写日志输出信息即可。
Log4j有两种定义方式:一种是采用Porperties,另一种是采用XML。 具体的使用方法是:首先在程序的开始位置定义Logger,如下:
public static logger logger = Logger.getLogger(myClass.getName());
然后在需要日志的地方,添加相应的代码即可,如下所示:
logger.debug(“输出日志”);
从上面Log4j的使用方法可以看出,在使用Log4j时,首先要通过调用public static Logger logger.getLogger(String name)或者 public static Logger Logger.getLogger(Class calzz)获得或创建一个Logger,然后在需要的地方,添加相应的代码即可,典型的使用Log4j来记录日志的示意代码如下:

//***Computer.java************
package Computer;
import org.apache.log4j.PropertyConfigurator;
import org.apache.log4j.Logger;
import org.apache.log4j.Priority;
Public class Computer{
    //定义Logger
    static Logger logger = Logger.getLogger(Computer.class.getName());
    logger.info("输出希望的日志");
}
}

从上面的代码可以看出,在使用Log4j管理日志时,必须要定义一个静态的变量Logger,就是这个静态的变量,从而保证所有的日志都能够输出到一个日志文件里,这就是单例模式的应用,如果不定义为静态变量,会出现什么状况?这里通过一组示意代码来展示采用单例模式和不采用单例模式的区别。
模拟一个日志管理类,该日志管理类不采用单例模式,用来负责日志的输出。它的源码如下:

//**LogManager.java******************
import java.io.*;
import java.util.*;
public class LogManager{
//默认的日志文件的路径和文件名称
    private String DefalutLogFilePathName = "c:\\single.log";
    //唯一的实例
    private LogManager logManager;
    //属性配置文件的输入流
    private InputStream fin;
    private Properties pro;
    private PrintWriter out;
    private String LogFileName;
    private LogManager(){ outInit(); }//私有化构造函数,只能通过调用公共方法获取实例

//保持日志,并加上保持日期
//out put the message information
//@param message infomation
public static synchronized void log(String message){
    logManager = new LogManager();
    if (out != null){
        out.println(new java.util.Date() + “:” + message);
    }
}
//保存异常,并加上日期
public static synchronized void log(Exception ex){
    logManager = new LogManager();
    if(out != null){
        out.println(new java.util.Date() + ":");
        ex.printStackTrace(out);
    }
}
/**输出文件流的init
private void outInit(){
    if (logFileName == null){
        //从属性文件中类获取日志文件的路径
        logFileName = getlogFileName();
        try{
            if (out == null){
                out = new PrintWriter(new FileWriter(logFileName,true),true);
            }
        }catch(IOException ex){
                System.out.println("无法打开日志文件:" + logFileName);
                ex.printStackTrace();
                out = null  
        }
    }
}
//根据配置文件来获取日志文件的位置
private String getlogFileName(){
    try{
        if (pro == null){
            pro = new Properties();
            //获取属性配置文件 log.properties
            fin = getClass().getResourceAsStream("log.properties");
            //载入配置文件
            pro.load(fin);
            //关闭属性文件
            fin.close();
        }
    }catch(IOException ex){ 
        System.err.println("无法打开属性配置文件:log.properties");
        ex.printStackTrace();
    }
    //获取文件路径,如果没有,则使用默认值
    return pro.getProperty("logfile",DefaultLogFilePathName);
}
//关闭日志文件
public void destory(){
    try{
        this.logManager = null;
        if (out != null){
            this.out.close();
        }
        if (fin != null) this.fin.close();
    }catch(IOException ex){
        ex.printStackTrace();
    }
}

如果调用上述的日志管理类,则新产生的日志文件都会覆盖原来的日志文件。这是因为每次都是创建一个新的该日志管理类的实例,而不是调用已经存在的实例,上述代码如果采用单例模式实现,示意代码如下:

//**LogManager.java***********
import java.io.*;
import java.util.*;
public class LogManager{
    //默认日志文件的路径和文件名称
    private static final String DefaultLogFilePathName = "c:\\simple.log";
    //唯一的实例
    private static LogManager logManager;
    //属性配置文件的输入流
    private static InputStream fin;
    private static properties pro;
    private static PrintWriter out;
    private static String logFileName;
    private LogManager(){ outInit(); }
    //保存日志,并加上日期
    public static synchronized void log(String message){
        if (logManager == null || (out == null)) logManager = new LogManager();
        out.println(new java.util.Date + ":" + message)
    }
    //保存错误,并加上日期
    public static synchronized void log(IOException ex){
        if (logManager == null || (out == null)) logManager = new LogManager();
        ex.PrintStackTrace(out);
    }
    //初始化输出日志的输出流
    public void outInit(){
        if (logFileName == null){
            logFileName = getlogFileName();
            try{
                if (out == null){
                    out = new PrintWriter(new FileWriter(logFileName,true),true);
                }
            }catch(IOException ex){
                System.out.println("无法创建日志输出流" + logFileName);
                ex.printStackTrace();
                out = null;
            }
        }
    }
    //获取日志的名称
    public String getlogFileName(){
        try{
            if (pro == null){
                pro = new Properties();
                //获取属性配置文件 log.properties
                fin = getClass().getResourceAsStream("log.properties")
                //载入配置文件
                pro.load(fin);
                //关闭配置文件
                fin.Close();
            }
        }catch(IOException ex){
            System.err.println("无法从配置文件中获取日志的名称 log.properties");
            ex.printStackTrace();
        }
        //获取从配置文件中读取的日志名称,如果了没有则返回默认名称DefaultlogFilePathName
        return pro.getproperties("logFile",DefaultlogFilePathName);
    }
    //关闭日志管理
    public void destory(){
        try{
            this.logManager = null;
            if (out != null) this.out.close();
            if (fin != null) this.fin.close();
        }catch(IOException ex){
            ex.printStackTrace();
        }

    } 
}

运行上述代码,可以看到该日志输出终于正常工作了,这就是单例模式的好处,只产生一个该类的实例。上述代码只是模拟Log4j的示意代码,目的是让读者清楚采用单例模式和不采用单例模式的区别。实际上在Log4j中,也有一个和前面LogManager类似的功能,示意代码如下:

//**LogManager.java********
package org.apache.log4j;
//相关的日志类
import org.apache.log4j.spi.LoggerRepository;
import org.apache.log4j.spi.LoggerFactory;
import org.apache.log4j.spi.RepositorySelector;
import org.apache.log4j.spi.DefaultRepositorySelector;
...
Public class LogManager{
    //定义全局变量
    static public final String DEFAULT_CONFIGURATION_FILE = "log4j.properties";
    static final public String DEFAULT_XML_CONFIGURATION_FILE = "log4j.xml";
    static final public String DEFAULT_CONFIGURATION_KEY = "log4j.configuration";
    static final public String CONFIGURATION_CLASS_KEY = "log4j.configuratorClass";
    public static final String DEFAULT_INIT_OVERRIDE_KEY = "log4j.defaultInitOverride";
    static private Object guard = null;
    static private RepositorySelector repositorySelector;
    //单例模式
    public static Logger getLogger(Class clazz){\\
        //Delegate the actual manufacturing of the logger to the logger repository
        return repositorySelector.getLoggerRepository().getLogger(clazz.getName());
    }
    public static Logger getLogger(String name,LoggerFactory factory){
        return repositorySelector.getLoggerRepository().getLogger(name,factory);    
    }
    //退出日志
    public static Logger exists(final String name){
        return getLoggerRepository().exists(name);  
    }
    //关闭日志类
    public static void shutdown(){
        repositiorySelector.getLoggerRepository().shutdown();
    }
    //获取当前日志
    public static Enumeration getCurrentLoggers(){
        return getLoggerRepository().getCurrentLoggers();   
    }
}

猜你喜欢

转载自blog.csdn.net/xiaohaopei/article/details/82229168
3.4