slf4j介绍以及实现原理窥探
一、概述
slf4j(全称是Simple Loging Facade For Java)是一个为Java程序提供日志输出的统一接口,并不是一个具体的日志实现方案,就好像我们经常使用的JDBC一样,只是一种规则而已。因此单独的slf4j是不能工作的,它必须搭配其他具体的日志实现方案,比如apache的org.apache.log4j.Logger,jdk自带的java.util.logging.Logger等等。
其中对与jar包:
slf4j-log4j12-x.x.x.jar是使用org.apache.log4j.Logger提供的驱动
slf4j-jdk14-x.x.x.jar是使用java.util.logging提供的驱动
slf4j-simple-x.x.x.jar直接绑定System.err
slf4j-jcl-x.x.x.jar是使用commons-logging提供的驱动
logback-classic-x.x.x.jar是使用logback提供的驱动
二、slf4j优势
1.与客户端很好的解耦
比如:我们发现了一位大牛开发了一个非常好而且又刚好能够满足自己需求的类库,类库里使用了apache的org.apache.log4j.Logger,然而你自己的程序在开发的时候使用的是jdk自带的java.util.logging.Logger,那么现在忧伤的问题来了:如果你想要使用,你是不是需要同时支持log4j和jdk两种日志系统?这样的话,你就需要添加两个实现同样功能的jar包并且维护两套日子配置,你是不是需要耗费更多的精力来进行维护?此时宝宝心里苦,宝宝不说。
2.节省内存
log4j这些传统的日志系统里面并没有占位符的概念,当我们需要打印信息的时候,我们需要如下方式进行使用。
- package com.hafiz.zhang;
- import org.apache.log4j.Logger;
- /**
- * @author hafiz.zhang
- * @date 16/5/12 18:01
- */
- public class TestLog4j {
- private static final Logger LOGGER = Logger.getLogger(TestLog4j.class);
- public static void main(String[] args) {
- String message = "服务器出错啦.";
- LOGGER.info("Error message is : " + message);
- }
- }
查看源码,我们发现了log4j的info函数有两种方式可供选择:
- public void info(Objectmessage)
- public void info(Objectmessage, Throwable t)
第一个参数是要输出的信息,假设我们要输出的是一个字符串,并且字符串中包含变量,则Objectmessage参数就必须使用字符串相加操作,就比如上面测试代码的14行一样。姑且不说字符串相加是一个比较消耗性能的操作,字符串是一个不可变对象,一旦创建就不能被修改,创建的字符串会保存在String池中,占用内存。更糟糕的是如果配置文件中配置的日志级别是ERROR的话,这行info日志根本不会输出,则相加得到的字符串对象是一个非必须对象,白白浪费了内存空间。这时候有人会说了,那我可以这样写啊:
- package hafiz.zhang;
- import org.apache.log4j.Logger;
- /**
- * @author hafiz.zhang
- * @date 16/5/12 18:04
- */
- public class TestLog4j {
- private static final Logger LOGGER = Logger.getLogger(TestLog4j.class);
- public static void main(String[] args) {
- String message = "服务器出错啦.";
- if (LOGGER.isInfoEnabled()) {
- LOGGER.info("Error message is: " + message);
- }
- }
- }
这样不就解决了白白浪费内存的问题了吗?没错,这是一个变通方案,但是这样的代码太繁琐,不直观!
下面再来看看slf4j的打日志的方式:(爽爆了)
- package com.hafiz.zhang;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- /**
- * @author hafiz.zhag
- * @date 15/8/26 21:54
- */
- public class TestLog4j {
- private static final Logger LOGGER = LoggerFactory.getLogger(TestLog4j.class);
- public static void main(String[] args) {
- String message = "服务器出错啦.";
- LOGGER.info("Error message is: {}", message);
- }
- }
看到没有,打日志的时候使用了{}占位符,这样就不会有字符串拼接操作,减少了无用String对象的数量,节省了内存。并且记住,在生产最终日志信息的字符串之前,这个方法会检查一个特定的日志级别是不是打开了,这不仅降低了内存消耗而且预先降低了CPU去处理字符串连接命令的时间。这里是使用SLF4J日志方法的代码,来自于slf4j-log4j12-1.6.1.jar中的Log4j的适配器类Log4jLoggerAdapter。
三、slf4j的使用方法以及实现原理
上面我们提到了slf4j是不能够独立工作的,要想使用我们必须带上其他的具体日志实现方案,下面我们就以log4j为例进行使用slf4j,我们需要做的工作如下:(下面的xxx表示jar包具体版本号)
1.将slf4j-api-xxx.jar加入工程classpath中
2.将slf4j-log4jxx-xxx.jar加入工程classpath中
3.将log4j-xxx.jar加入工程classpath中
4.将log4j.properties(log4j.xml)文件加入工程classpath中(与spring继承 还能使用自定义文件位置的方式指定,后续博客中我会介绍)
注:如果项目是maven项目,则前三步就变成一步,在pom.xml文件中添加以下依赖。(如果没有更高版本的slf4j-api和log4j要求,则只添加第一条依赖就可以,因为slf4j-log4j12依赖会包含slf4j-api和log4j依赖)
- <dependency>
- <groupId>org.slf4j</groupId>
- <artifactId>slf4j-log4j12</artifactId>
- <version>1.6.4</version>
- </dependency>
- <dependency>
- <groupId>org.slf4j</groupId>
- <artifactId>slf4j-api</artifactId>
- <version>1.6.4</version>
- </dependency>
- <dependency>
- <groupId>log4j</groupId>
- <artifactId>log4j</artifactId>
- <version>1.2.16</version>
- </dependency>
slf4j工作原理窥探
首先,slf4j-api作为slf4j的接口类,使用在程序代码中,这个包提供了一个Logger类和LoggerFactory类,Logger类用来打日志,LoggerFactory类用来获取Logger;slf4j-log4j是连接slf4j和log4j的桥梁,怎么连接的呢?我们看看slf4j的LoggerFactory类的getLogger函数的源码:
- /**
- * Return a logger named according to the name parameter using the statically
- * bound {@link ILoggerFactory} instance.
- *
- * @param name
- * The name of the logger.
- * @return logger
- */
- public static Logger getLogger(String name) {
- ILoggerFactory iLoggerFactory = getILoggerFactory();
- return iLoggerFactory.getLogger(name);
- }
- /**
- * Return a logger named corresponding to the class passed as parameter, using
- * the statically bound {@link ILoggerFactory} instance.
- *
- * @param clazz
- * the returned logger will be named after clazz
- * @return logger
- */
- public static Logger getLogger(Class clazz) {
- return getLogger(clazz.getName());
- }
- /**
- * Return the {@link ILoggerFactory} instance in use.
- *
- * <p>
- * ILoggerFactory instance is bound with this class at compile time.
- *
- * @return the ILoggerFactory instance in use
- */
- public static ILoggerFactory getILoggerFactory() {
- if (INITIALIZATION_STATE == UNINITIALIZED) {
- INITIALIZATION_STATE = ONGOING_INITILIZATION;
- performInitialization();
- }
- switch (INITIALIZATION_STATE) {
- case SUCCESSFUL_INITILIZATION:
- return StaticLoggerBinder.getSingleton().getLoggerFactory();
- case NOP_FALLBACK_INITILIZATION:
- return NOP_FALLBACK_FACTORY;
- case FAILED_INITILIZATION:
- throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
- case ONGOING_INITILIZATION:
- // support re-entrant behavior.
- // See also http://bugzilla.slf4j.org/show_bug.cgi?id=106
- return TEMP_FACTORY;
- }
- throw new IllegalStateException("Unreachable code");
- }
查找到现在,我们发现LoggerFactory.getLogger()首先获取一个ILoggerFactory接口,然后使用该接口获取具体的Logger。获取ILoggerFactory的时候用到了一个StaticLoggerBinder类,仔细研究我们会发现StaticLoggerBinder这个类并不是slf4j-api这个包中的类,而是slf4j-log4j包中的类,这个类就是一个中间类,它用来将抽象的slf4j变成具体的log4j,也就是说具体要使用什么样的日志实现方案,就得靠这个StaticLoggerBinder类。
再看看slf4j-log4j包种的这个StaticLoggerBinder类创建ILoggerFactory长什么样子:
- /**
- * The ILoggerFactory instance returned by the {@link #getLoggerFactory}
- * method should always be the same object
- */
- private final ILoggerFactory loggerFactory;
- private StaticLoggerBinder() {
- loggerFactory = new Log4jLoggerFactory();
- try {
- Level level = Level.TRACE;
- } catch (NoSuchFieldError nsfe) {
- Util
- .report("This version of SLF4J requires log4j version 1.2.12 or later. See also http://www.slf4j.org/codes.html#log4j_version");
- }
- }
- public ILoggerFactory getLoggerFactory() {
- return loggerFactory;
- }
- public String getLoggerFactoryClassStr() {
- return loggerFactoryClassStr;
- }
可以看到slf4j-log4j中的StaticLoggerBinder类创建的ILoggerFactory其实是一个org.slf4j.impl.Log4jLoggerFactory,这个类的getLogger函数代码如下:
- /*
- * (non-Javadoc)
- *
- * @see org.slf4j.ILoggerFactory#getLogger(java.lang.String)
- */
- public Logger getLogger(String name) {
- Logger slf4jLogger = null;
- // protect against concurrent access of loggerMap
- synchronized (this) {
- slf4jLogger = (Logger) loggerMap.get(name);
- if (slf4jLogger == null) {
- org.apache.log4j.Logger log4jLogger;
- if(name.equalsIgnoreCase(Logger.ROOT_LOGGER_NAME)) {
- log4jLogger = LogManager.getRootLogger();
- } else {
- log4jLogger = LogManager.getLogger(name);
- }
- slf4jLogger = new Log4jLoggerAdapter(log4jLogger);
- loggerMap.put(name, slf4jLogger);
- }
- }
- return slf4jLogger;
- }
就在其中创建了真正的org.apache.log4j.Logger,也就是我们需要的具体的日志实现方案的Logger类。就这样,整个绑定过程就完成了。
slf4j介绍以及实现原理窥探的更多相关文章
- 第一章-Flink介绍-《Fink原理、实战与性能优化》读书笔记
Flink介绍-<Fink原理.实战与性能优化>读书笔记 1.1 Apache Flink是什么? 在当代数据量激增的时代,各种业务场景都有大量的业务数据产生,对于这些不断产生的数据应该如 ...
- SharePoint Client Object Model API 介绍以及工作原理解析
CSOM和ServerAPI 的对比 SharePoint从2010开始引入了Client Object Model的API(后文中用CSOM来代替),从名字来看,我们可以简单的看出,该API是面向客 ...
- Java日志框架:slf4j作用及其实现原理
简单回顾门面模式 slf4j是门面模式的典型应用,因此在讲slf4j前,我们先简单回顾一下门面模式, 门面模式,其核心为外部与一个子系统的通信必须通过一个统一的外观对象进行,使得子系统更易于使用.用一 ...
- keepalived介绍及工作原理
keepalived介绍keepalived观察其名可知,保持存活,在网络里面就是保持在线了,也就是所谓的高可用或热备,它集群管理中保证集群高可用的一个服务软件,其功能类似于heartbeat,用来防 ...
- Elasticsearch-基础介绍及索引原理分析(转载)
最近在参与一个基于Elasticsearch作为底层数据框架提供大数据量(亿级)的实时统计查询的方案设计工作,花了些时间学习Elasticsearch的基础理论知识,整理了一下,希望能对Elastic ...
- Docker OpenvSwitch 介绍 or 工作原理
Docker OpenvSwitch Network 介绍 什么是OpenVSwich OpenvSwich Network:属于第三方网络项目,可以理解为是一个标准的交换机协议. OpenvSwic ...
- Docker Swarm 介绍 or 工作原理
Docker Swarm 介绍 Swarm 简介 Swarm是Docker公司自研发的容器集群管理系统,Swarm在早期是作为一个独立服务存在,在Docker Engine v1.12中集成了Swar ...
- Appium介绍及工作原理
一.Appium介绍 Appium是一个开源.跨平台的测试框架,可以用来测试原生及混合的移动端应用.Appium支持IOS.Android及FirefoxOS平台.Appium使用WebDriver的 ...
- Elasticsearch-基础介绍及索引原理分析
介绍 Elasticsearch 是一个分布式可扩展的实时搜索和分析引擎,一个建立在全文搜索引擎 Apache Lucene(TM) 基础上的搜索引擎.当然 Elasticsearch 并不仅仅是 L ...
随机推荐
- c++的一些陷阱(1)
class String { public: String(]) { strcpy(p,pp); } ~String() { delete[] p; } char& operator[](in ...
- js 用途
嵌入动态文本于HTML页面.[4] 对浏览器事件做出响应.[4] 读写HTML元素.[4] 在数据被提交到服务器之前验证数据.[4] 检测访客的浏览器信息.[4] 控制cookies,包括创 ...
- java.lang.NoClassDefFoundError:org/apache/commons/lang/exception/NestableRuntimeException错误的解决
java.lang.NoClassDefFoundError 是运行时jvm找不到对应类.这种情况是少包的导致的.根据提示语添加对应的jar包就可以. 感叹一下:maven真是一个伟大的东西,在包的依 ...
- c# 反射类字段
//在wpf中动态绘制Grid布局控件中值 需要来动态获取类中的字段数来自动生成Grid列数或者行数, public class models { public Label name { get; s ...
- js 获取据当前时间n天前的时间
<script type="text/javascript"> function getLastDate() { var date = new Date(); ; va ...
- 10月30日上午MySQL数据库的修改(从网页上实现对数据库的更改)
从网页页面上对数据库进行更改,连接着之前做的增加.删除.查询. 1.先做一个修改页面 <body> <!--这个页面需要让用户看到一些数据,所以不是一个纯php页面,页面效果和增加页 ...
- Easymake
Easymake太好用了,偷懒神器,强力推荐. 一.根目录下的easymake.mk就是makefile模板,我们自定义的makefile只需要包含这个模板即可 include ../../easym ...
- php多进程总结
本文部分来自网络参考,部分自己总结,由于一直保存在笔记中,并没有记录参考文章地址,如有侵权请通知删除.最近快被业务整疯了,这个等抽时间还需要好好的整理一番. 多进程--fork 场景:日常任务中, ...
- junit学习笔记
junit编程规范 测试方法上必须使用@Test进行修饰 测试方法必须使用public void 进行修饰,不能带任何的参数 新建一个源代码目录 测试类的包应该和被测试类保持一致 测试单元中的每个方法 ...
- Pandas-数据整理
Pandas包对数据的常用整理功能,相当于数据预处理(不包括特征工程) 目录 丢弃值 drop() 缺失值处理 isnull() & notnull() dropna() fillna() 值 ...