最近一段时间,学习了java日志管理方面的内容,用过很久,但是没有系统的学习相关。 趁这个机会,将各种日志相关的内容,以及框架,和集成使用方面的内容拉通学习一下。
我将从以下的方面进行笔记的整理:
1.log4j1与log4j2在web集成方面的区别;
2.日志框架标准与实现的区别,及举例;面向框架的spring-jcl以及slf4j标准,在web方面的使用记录;
3.slf4j与其它各种日志框架的集成使用;
4.slf4j日志框架自动发现的实现原理,及其源码解读;
5.各种日志框架(不包括log4j2)自动加载配置文件的原理;
6.如何进行日志框架的无缝迁移;
log4j1与log4j2在web集成方面的区别
我们应当知道,javaweb项目与一般的java项目是有区别的。 这些区别可以体现在: 启动的逻辑不同;环境上下文不同;用户交互方式不同等等。 因为web项目很多很多的框架可以使用,针对不同的目标类型,不同的逻辑构成。有些是不同的逻辑组件,有些是相同的业务逻辑组件不同的实现。 所以可复用组件,或者说标准尤为重要。 日志组件就是这样的一种存在。
web环境搭建就没有什么必要介绍了。 以前记录过一篇:地址在这里
看一下log4j1的使用:
依赖:
使用:
配置:
这样一个log4j的日志就可以使用了。 不需要其它任何额外的配置。
在看一看log4j2的使用(基于web项目):
依赖:
使用:
配置:
注册web上下文:
这样,log4j2就可以在项目中使用了。
实际上,这里的log4j2采用的是spring-jcl标准。 于是这就引出第二个问题了。
2.日志框架标准与实现的区别,及举例;面向框架的spring-jcl以及slf4j标准,在web方面的使用记录:
先说第一个问题:标准与实现
至此,我们实际上已经接触了很多对这样的逻辑关系的组件:
比如:
jdbc标准,由j2ee标准提出,对应的实现有:mysql的实现,sql-server的实现,oracle的实现。 其中,mysql的实现: mysql-connector.jar,由mysql官方提供。
servlet标准,由j2ee标准提出,对应的实现有: tomcat服务器容器,jetty容器,websocket,websphere等。 其中典型的tomcat由apache基金会提供并开源。
orm标准,由j2ee标准提出,对应的是是现有: mybatis,hibernate。
datasource标准,由jdk定义,对应的实现有: dbcp2实现,spring的相关实现,druid的实现。 其中druid由阿里巴巴提供。
类似于这样的标准与实现还有很多很多。。
要说它们的本质,那就是一堆接口,与一堆实现了这些接口的类组成。 当然在这个途中进行了大量的设计模式的使用,性能的考量,相应的扩展等。
日志框架的标准与实现也是这样。
第二个问题:spring-jcl:
spring-jcl的源码较少,因此我们不妨将它的源码类结构贴上:
可见,它实际上整合的是apache的commons-loging。 只不过,进行了一些适配。 其中的关键是:LogFactory抽象类,以及LOG接口标准。 它的核心源码片段:
以上给是该类初始化的静态块方法,它会根据上下文就进行适配,需注意它们实际上是有优先级关系的。 原理很简单,就是用类加载器去加载相应日志框架的spi,若找到,则判定为当前系统采用该日志框架。 否则依次进行迭代,直至采用jul。 因为它位于jdk中,无论如何都会有该日志框架的。
通过源码可以看到,它提供了三个大类型的日志框架实现,分别是: log4j2日志框架实现(因为该spi类只存在Log4j2中);slf4j日志框架标准;jul(java util logging)日志实现。 它们具有一定的优先级关系。
至于为什么是Log4j2,可以给出源码验证:
ok,这是spring-jcl作为日志标准为基础的逻辑思路。 我上文使用的slf4j并没有说它是日志实现,而是标准。 为什么这么说呢? 这就是接下来的一个问题:
slf4j标准:
先看一看slf4j的项目结构:
这也很好的印证了spring-jcl的动态发现,选择日志实现框架。
slf4j的标准是由这里的Logger接口提供的。 至于具体的实现,将在后面的问题中进行讨论。
最后,来看一看在web项目的中的使用:
涉及到使用,那么必须的是标准的实现。 由于spring-jcl默认优先级的关系,log4j2我们必须先注释掉,然后才能使用slf4j。 但是我们知道,这是一个标准,如何使用呢?
通过搜集相关的资料,我们了解到,slf4j对于主流的日志框架实现都提供了适配,或者基于该标准进行开发。
如:slf4j-simple,它的依赖:
它是一个实现类,实现了slf4j的logger接口。
slf4j-jdk4,它的依赖是:
他提供的是基于slf4j的logger接口,针对java.util,logging实现的适配类。 相应的实现由Jdk提供实现,无需依赖其它的第三方。
slf4j-jcl,它的依赖:
它提供的是基于slf4j的logger接口的,针对commons-logging实现的适配类,它需要依赖jcl的实现:
slf4j-log4j12,它的依赖:
它提供的是基于slf4j的logger接口的,针对log4j实现的适配类,它需要依赖log4j的实现,这里使用的是log4j1:
logbak,它的依赖:
它提供的是基于slf4j的Logger接口的实现类。
以上所说的适配类,以及实现类可以通过查看继承关系得到验证:
注意,这里有一个有趣的现象,spring-jcl 作为日志框架为其它日志实现提供支持,包括对slf4j提供支持。 spring-jcl是基于commons-logging实现的; 而作为slf4j日志标准来说,它同时也提供了对jcl的集成支持。 这就感觉有那么一丝丝怪异,形成了一个类似环形的结构。。 不过,这只是感觉上来说,spring-jcl中的那个日志工厂与commons-logging的日志工厂应该并不相同,只不过他们提供了相同的包结构。如果是这样的话, 在一定程度上可能会存在潜在的bug。 具体怎样等以后有兴趣了在看看看吧。
如何使用:
针对这些slf4j标准的实现,它的原理与spring-jcl优点相似,就是在运行时动态的寻找实现。 因此,这里举一个例子,其它的类推即可,至于适配的那种,则需要根据本身框架实现来进行配置使用即可。 比如,就拿Simple来说:
第一步,当然是引入相关依赖;
第二步,建立配置文件:
如:
第三步,在相应的位置使用日志框架,它会自动扫描,动态寻找依赖,如spring-jcl:
运行,即可。 其它的日志使用类似!! 包括logback。 这样的话,简单的问题背后难免引起我们的思考。 因此这就是接下来思考的问题了。
slf4j日志框架自动发现的实现原理,及其源码解读:
这个问题实际上可以与spring-jcl类比着看。 先回顾一下spring-jcl是如何实现自动选择实现的: 它实际上是通过类加载器去加载特定的类,如果加载成功,则认为使用该框架作为实现,否则就一路迭代,选择其它的实现。 直到最终会确定一个一个实现类。 这个思路有点类似于责任链传递模式。
那么slf4j是如何实现的呢?
我主要从两个方面入手分析了这个问题: 第一,如何确定有哪些实现模块在当前项目中(如何发现); 第二,如何加载实现项目的启动类(如何实现)。
第一个问题: 我是通过查看资料,与查看源代码进行分析的,过程如下:
这是位于slf4j的日志工厂的其中一个静态方法,通过源码逻辑,可以分析出,它实际上是完成了获得实现模块的功能。 这个操作通过类加载器实现:
该类加载器是继承于URLClassLoader。 它的名称为: 平行类加载器。 当然前面有一些关于类加载机制的双亲委派机制。 这是jvm部分的基础知识,也是java的核心知识。 很早以前看过了。(大概昨年春节的时候看过源码,也从此开启奋斗之旅。)。 需要注意,在web容器中的类加载器机制中,并没有继续采用双亲委派机制了。 通过运行时调试,我们可以分析出,获得资源的这个实际实现是由tomcat容器的一个类加载器实现的:
(为了实验的目的,我特意放了两个依赖进来。)
由于在idea中我并没有采用内置容器,所以具体的实现细节源码没有查看了。 至此,第一个问题就解决了。
第二个问题:
记得我们之前看过基于Slf4j的实现类,其中除了一些适配类,实现类外,有一个类没有去描述过它,因为它既不是抽象类,也不是其他类的适配器类。 并且这个类位于slf4j-api的项目中。
看一看它的源码片段:
在slf4j的抽象工厂中有如下的一些关于它的操作:
它的核心方法代码片段:
自此,第二个问题也解决的差不多了。 它的实现方法是: 抽象+ 代理
各种日志框架(不包括log4j2)自动加载配置文件的原理:
这个问题,各个日志框架实现类都有各自的实现。 仍然是采用类比的思想,挑一个简单的来看看,复杂的就不看了。 要说简单,那自然就是slf4j-simple了。
它的核心业务类: SimpleLogger。
具有如下的一些代码片段:
其它自动加载配置文件的原理估计也差不多。 就不看了。
那么就剩最后一个问题了:
如何进行日志框架的无缝迁移:
实际上,这个问题在上面的分析过程中已经解决了。 只要是基于标准实现的,要替换实现,我们要做的仅仅是替换相应的依赖即可,并且这个过程用户不需要其它额外的配置(当然,对于不同实现之间的必要的适配还是需要做的)。 关于日志使用的部分,就告一段落。