03、JCL(日志门面)

举报
长路 发表于 2022/11/28 08:34:28 2022/11/28
【摘要】 文章目录前言一、认识JCL1.1、JCL概述1.2、第三方jar包1.3、认识Log接口与LogFactory抽象类二、JCL实际使用2.1、应用JUL(jdk自带日志)2.2、应用Log4j(第三方)三、源码分析3.1、Log接口实现类分析3.2、JCL原理分析(动态加载Log实现类)总结参考资料 前言 本篇博客主要介绍的是日志门面技术JCL,现在大多使用slf4j来作为日志门面了,不过还是需

@[toc]


前言

本篇博客主要介绍的是日志门面技术JCL,现在大多使用slf4j来作为日志门面了,不过还是需要学习了解一下。其他日志框架内容可见日志专栏。

所有博客文件目录索引(包含日志框架系列学习):博客目录索引(持续更新)



一、认识JCL

1.1、JCL概述

JCL(全称为Jakarta Commons Logging):是Apache提供的一个通用日志API,它为多个Java日志框架实现(如JULLog4j等)提供了一个统一的接口Log,其自身也提供一个日志实现SimpleLog(一般不使用它)。

  • 2014年的时候被Apache淘汰了(具有缺陷,当时只考虑了主流的应用框架,对于未来新出现框架不提供相应的实现类实现,除非自己修改源码进行完善),最新的版本为1.2(即最终版本,停止维护),后期又推出了更加优秀的日志门面框架。

JCL包含两个基本抽象类:Log类(通用接口)、LogFactory(负责创建Log实例)。

首先看下面这个图

image-20210306143007198

这个图只是个抽象的举例,在JCL中提供了一个通用接口Log(包含多种日志级别方法),该Log接口有多个实现类其中的实现方法分别使用了不同日志框架的日志方法,这种方式解决了什么问题呢?

  • 能够让我们在系统进行升级时更好的切换使用日志框架,例如通过JCL日志门面我们默认使用的是JUL,若是想要替换使用Log4j,则只需要导入Log4jjar包并提供配置文件,不用修改方法即可使用。


1.2、第三方jar包

想要使用JCL,我们需要引入第三方jar包,下面给出pom.xml的坐标:

<dependency>
    <groupId>commons-logging</groupId>
    <artifactId>commons-logging</artifactId>
    <version>1.2</version>
</dependency>


1.3、认识Log接口与LogFactory抽象类

Log接口

image-20210306144610695

  • 可以看到Log接口中都是对应的日志等级方法。
  • 右边则是Log接口的继承图,红框中的都是它的实现类,其中Jdk14Logger(其中Logger实例使用的JUL)、Log4JLogger(使用的是Log4j)

LogFactory抽象类

image-20210306145010459

  • 我们需要关注其getLog()方法,获取Log实例是通过该工厂类来获取的。


二、JCL实际使用

2.1、应用JUL(jdk自带日志)

首先引入jar包,接着通过使用LogFactory获取到Log实例,调用日志方法即可:

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class LogTest {
    public static void main(String[] args) {
        //获取Log实例
        Log log = LogFactory.getLog(LogTest.class);
        log.info("info");
    }
}

image-20210306145322944

  • 通过输出的内容我们可以判定是JUL默认输出的格式内容。

说明:在不导入其他指定日志框架时(如Log4j),通过工厂类获取到的Log实例其实就是Jdk14Logger实例(方法实际上调用的就是JUL的日志方法),我们之后看源码即可知晓。



2.2、应用Log4j(第三方)

想要从JUL切换到Log4j,我们只需要简单一步,导入Log4jjar包即可,引入坐标:

<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.12</version>
</dependency>

添加配置文件log4j.properties,若是maven项目放置到resource目录下:

# rootLogger日志等级为trace,输出到屏幕上
log4j.rootLogger = trace,console

# console
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern= [%-5p]%r %l %d{yyyy-MM-dd HH:mm:ss:SSS} %m%n

测试程序

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class LogTest {
    public static void main(String[] args) {
        //获取Log实例
        Log log = LogFactory.getLog(LogTest.class);
        log.info("info");
    }
}

image-20210306150004337

可以看到使用相同的方法,随着我们的jar包引入切换使用为Log4j的日志方法,是不是很妙。之后我们看其中源码即可知道究竟是如何实现了的。



三、源码分析

3.1、Log接口实现类分析

我们知道Log接口有多个实现类,其实现类的方法即调用的是各个日志框架的方法,我们深入源码查看一下JUL、Log4j的日志方法是如何被应用到JCL的实现类中的:

Jdk14Logger实现类

image-20210306150922178

我们注重看其中的构造器以及部分日志等级方法:

//注意这里引入的类是JDK自带的日志JUL的Logger类
import java.util.logging.Level;
import java.util.logging.Logger;

public class Jdk14Logger implements Log, Serializable {
    
    protected static final Level dummyLevel = Level.FINE;
    protected transient Logger logger = null;//默认为null
	
    public Jdk14Logger(String name) {
        this.name = name;
        //调用了自身的getLogger()方法
        logger = getLogger();//见1
    }
    
    //1、获取Logger实例方法
    public Logger getLogger() {
        if (logger == null) {
            //重要:这里调用的是java.util.logging.Logger(即JUL)的获取实例方法
            logger = Logger.getLogger(name);
        }
        return logger;
    }
    
    //info日志等级调用
    public void info(Object message) {
        //实际上就是调用的JUL的INFO日志等级的Log方法
        log(Level.INFO, String.valueOf(message), null);
    }
    
    //fatal:我们之前在Log4j中看到是最高等级,这里也用来表示最高的意思
    public void fatal(Object message, Throwable exception) {
        //实际上就是调用JUL的SERVER日志等级(最高)的Log方法
        log(Level.SEVERE, String.valueOf(message), exception);
    }
}

看到这里我们其实心里就清楚了,原来就是外面套了个方法呀,通过继承Log接口来让Log接口统一管理各个日志框架,从而达到切换日志框架不改变代码的操作。那对于JCLLog4j的方法我们也差不多知晓了是如何实现了的。


Log4JLogger实现类

image-20210306151256164

  • 老样子都实现了Log接口的日志等级方法

直接看其中构造器以及相关方法还有静态代码块:

//注意这里使用的是Log4j的日志
import org.apache.log4j.Logger;
import org.apache.log4j.Priority;
import org.apache.log4j.Level;

public class Log4JLogger implements Log, Serializable {

        //默认为null
		private transient volatile Logger logger = null;
    	private static final String FQCN = Log4JLogger.class.getName();
		
        //类被加载时进行初始化
        static {
            if (!Priority.class.isAssignableFrom(Level.class)) {
                throw new InstantiationError("Log4J 1.2 not available");
            }
            
            Priority _traceLevel;
            try {
            } catch(Exception ex) {
                _traceLevel = Level.DEBUG;
            }
            traceLevel = _traceLevel;
        }
    
        //无参构造器
        public Log4JLogger() {
            name = null;
        }
    
        //有参构造器
        public Log4JLogger(String name) {
            this.name = name;
            //通过调用自身类的getLogger()获取
            this.logger = getLogger();//见1
        }
    
       //1、通过org.apache.log4j.Logger的getLogger()来获取实例
       public Logger getLogger() {
            Logger result = logger;
            if (result == null) {
                synchronized(this) {
                    result = logger;
                    if (result == null) {
                        logger = result = Logger.getLogger(name);
                    }
                }
            }
            return result;
        }
        
        //fatal等级方法就是调用的Log4j的FATAL等级,是一致的
        public void fatal(Object message) {
            getLogger().log(FQCN, Level.FATAL, message, null);
        }
}
  • 我们可以看到当获取该实现类的实例对象时,做了获取Log4jLogger实例操作,其中的日志等级方法中实际就是调用的Log4j的日志方法。

注意:看其中的静态方法就有一个Level.DEBUG;获取操作,我们可以试想一下若是没有引入Log4j的jar包,那么该操作也不会实现即可报错,也是一种证实开发者没有导入Log4j的操作(这里只是我的设想)。



3.2、JCL原理分析(动态加载Log实现类)

分析下面的第7行程序段:

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class LogTest {
    public static void main(String[] args) {
        //获取Log实例
        Log log = LogFactory.getLog(LogTest.class);
        log.info("info");
    }
}

源码分析

public abstract class LogFactory {
    
    //1、实际调用了其实现类的方法
    public static Log getLog(Class clazz) throws LogConfigurationException {
        //获取LogFactoryImpl
        return getFactory().getInstance(clazz);//见2
    }
}

public class LogFactoryImpl extends LogFactory {
    
    protected Constructor logConstructor = null;
    //该数组中包含了对应的全限定类名
    private static final String[] classesToDiscover = {
            LOGGING_IMPL_LOG4J_LOGGER,//"org.apache.commons.logging.impl.Log4JLogger"
            "org.apache.commons.logging.impl.Jdk14Logger",
            "org.apache.commons.logging.impl.Jdk13LumberjackLogger",
            "org.apache.commons.logging.impl.SimpleLog"
    };
    
    //2、获取Log实例
    public Log getInstance(Class clazz) throws LogConfigurationException {
        //这里传入类的名称
        return getInstance(clazz.getName());//见3
    }
    
    //3、其中获取到Log实例
    public Log getInstance(String name) throws LogConfigurationException {
        Log instance = (Log) instances.get(name);
        if (instance == null) {
            //通过类名获取到Log实例
            instance = newInstance(name);//见4
            instances.put(name, instance);
        }
        return instance;
    }
    
    //4、获取新的实例
    protected Log newInstance(String name) throws LogConfigurationException {
        Log instance;
        try {
            //默认为null
            if (logConstructor == null) {
                //重要:查找Log实现类,及获取Log的单个实现类
                instance = discoverLogImplementation(name);//见5
            }
            else {
                Object params[] = { name };
                instance = (Log) logConstructor.newInstance(params);
            }
            if (logMethod != null) {
                Object params[] = { this };
                logMethod.invoke(instance, params);
            }
            //返回获取到的Log实例即可
            return instance;
        } catch (LogConfigurationException lce) {
		....
    }
        
    //5、该方法通过数组的顺序来进行实现类实例
    private Log discoverLogImplementation(String logCategory)
        throws LogConfigurationException {
        
        ...//无关紧要的先省略
        
        if (isDiagnosticsEnabled()) {
            logDiagnostic(
                "No user-specified Log implementation; performing discovery" +
                " using the standard supported logging implementations...");
        }
        //核心部分:从数组中进行遍历,可见classesToDiscover数组内容(上面),顺序依次为Log4JLogger、Jdk14Logger...
        //其中的result == null则是判断是否获取到了实例,若获取到退出for循环
        for(int i=0; i<classesToDiscover.length && result == null; ++i) {
            result = createLogFromClass(classesToDiscover[i], logCategory, true);
        }

        if (result == null) {
            throw new LogConfigurationException
                        ("No suitable Log implementation");
        }
		//返回指定实例
        return result;
    }
  • 第38行:调用方法获取到Log的实现类,确定使用的日志框架。
  • 第73行:核心代码,根据多种框架实现类的权限定类名去查找获取Log实例的。

说明:我们在调用LogFactory.getLog()方法时获取了JCLLog接口的实现类实例,在实现类中的构造器里获取到了真正对应的日志框架Logger实例。

这样的话我们之前的第二部分就解释的通为什么默认获取到的是JUL,在添加了jar包之后则为Log4j了!!!



总结

1、JCL日志门面中Log是通用接口,LogFactory.getLog()用于获取对应Log的实现类。

2、Log接口(包含了主要的日志等级方法)的实现类主要的是两个,Jdk14Logger(即JUL,不导任何包默认)和Log4JLogger(即Log4j,导入Log4j则切换)。

3、对于获取Log实例是在LogFactory.getLog()中执行获取的,执行顺序见如下核心代码

private static final String[] classesToDiscover = {
    LOGGING_IMPL_LOG4J_LOGGER,//"org.apache.commons.logging.impl.Log4JLogger"
    "org.apache.commons.logging.impl.Jdk14Logger",
    "org.apache.commons.logging.impl.Jdk13LumberjackLogger",
    "org.apache.commons.logging.impl.SimpleLog"
};

//核心部分:从数组中进行遍历,可见classesToDiscover数组内容(上面),顺序依次为Log4JLogger、Jdk14Logger...
//其中的result == null则是判断是否获取到了实例,若获取到退出for循环
for(int i=0; i<classesToDiscover.length && result == null; ++i) {
    result = createLogFromClass(classesToDiscover[i], logCategory, true);
}

4、在获取到指定Log实例时,即使用构造器会进行各个实现类中的Logger实例的赋值获取(通过对应的日志框架),其中的日志等级方法调用的是对应框架的日志等级方法。



参考资料

[1] 视频:2020年Java进阶教程,全面学习多种java日志框架

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。