Java日志框架(第五章)

7 篇文章 0 订阅
2 篇文章 0 订阅

5.第五章Slf4j

 

5.1日志门面概述

5.1.1门面模式(外观模式)

        我们先谈一谈GOF23中设计模式其中之一。

        门面模式(Facade Pattern),也称之为外观模式,其核心为:外部与一个子系统的通信必须通过一个统一的外观对象进行,使得子系统更易于使用。

        外观模式主要是体现了Java中的一种好的封装性,更简单的说,就是对外提供的接口要尽可能的简单。

5.1.2日志门面

        前面介绍的几种日志框架,每一种日志框架都有自己单独的API,要使用对应的框架就要使用其对应的API,这就大大的增加了应用程序代码对于日志框架的耦合性。

        为了解决这个问题,就是在日志框架和应用程序之间架设一个沟通的桥梁,对于应用程序来说,无论底层的日志框架如何变,都不需要有任何感知,只要门面服务做的足够好,随意换另外一个日志框架,应用程序不需要修改任意一行代码,就可以直接上线。

5.1.3常见的日志框架及日志门面

        常见的日志实现:JUL、Log4j、Logback、Log4j2

        常见的日志门面:JCL、Slf4j

        出现顺序:Log4j-->JUL-->JCL-->Slf4j-->Logback-->Log4j2

5.2Slf4j日志门面

5.2.1Slf4j简介

        简单日志门面(Simple Logging Facade Pattern For Java)Slf4j主要是为了给Java日志访问提供一套标准、规范的API框架,其主要的意义在于提供接口,具体实现可以交由其他日志框架,例如Log4j和Logback等。当然Slf4j自己也有功能较为简单的实现,但是一般很少用到。对于一般的Java项目而言,日志框架会选择Slf4j作为门面,配置具体的实现框架(Log4j、Logback等),中间使用桥接器完成桥接。所以我们可以得出Slf4j最重要的两个功能就是对于日志框架的绑定以及日志框架的桥接。

5.2.2Slf4j桥接技术

        通常,我们依赖的某些组件依赖于Slf4j以外的日志API。我们可能还假设这些组件在不久的将来不会切换到Slf4j,为了处理这种情况,Slf4j附带了几个桥接模块,这些模块会将对Log4j,JCL(JCL内也有对日志的实现),JUL API的调用重定向为行为,就好像对Slf4j API进行的操作一样。

5.3案例分析

        首先我们还是创建一个mavne项目,引入需要的依赖

    <dependencies>
        <!-- 单元测试依赖 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        
        <!-- slf4j核心依赖 -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.25</version>
        </dependency>
        <!-- slf4j自带的简单日志实现 -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>1.7.25</version>
        </dependency>
    </dependencies>

        本次我们创建的为Slf4j包下的Logger,如下图

Logger

         通过观察Logger类我们可以了解到Slf4j具有trace、debug、info、warn、error五个级别

                trace:日志追踪信息
                debug:日志调试信息
                info:日志关键信息(默认打印级别)
                warn:日志警告信息
                error:日志错误信息

        在没有任何日志框架集成的基础之上,Slf4j使用的就是自带的框架,并且slf4j-simple也必须以单独的依赖的形式导入进来,经过测试,Slf4j自带日志实现输出如下

        在集成其他日志框架之前,我们先来了解一下Slf4j与其他日志框架之前的关系结构,如下图(官网地址:https://www.slf4j.org/images/concrete-bindings.png) 

        根据上图,我们了解到Slf4j日志门面共有3种情况对日志实现进行绑定

        1.在没有绑定任何日志实现的基础上,日志不能够绑定实现任何功能,值得注意的是,slf4j-simple是Slf4j官网提供的简单日志实现,我们需要引入依赖,其会自动绑定到Slf4j门面上,如果不引入依赖,Slf4j核心依赖是不提供任何实现的。

        2.Logback和simple(包括nop),都是Slf4j门面之后出现的日志实现,所以API完全遵循Slf4j进行设计,因此我们只需要引入要使用的日志实现依赖,即可完成与Slf4j的无缝衔接,值得注意的是,nop虽然划分到实现中,但是它是指不实现日志记录(具体情况,下面会做出讲解)

        3. Log4j和JUL,都是Slf4j门面之前出现的日志实现,所有API不遵循Slf4j进行设计,因此需要通过适配桥接的技术,完成与日志门面的衔接

        接下来,我们试着在原有依赖的基础上,引入Logback日志实现的依赖,并且我们将Logback依赖添加到simple依赖的下方,执行日志输出。

        <!-- logback依赖 -->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>

         从测试结果我们了解到,Slf4j发现了两个日志框架实现,并且日志输出使用的simple日志实现,我们继续将两个日志实现的依赖调换位置,继续测试

         我们根据结果发现,Slf4j使用的日志实现变为了Logback,因此得出结论,如果有多个日志实现的话,默认使用先导入的日志实现,值得一提的是,当我们将simple依赖注释,只留下Logback日志实现时,Slf4j使用的就是Logback日志实现,并且没有了多余的提示信息,所以在实际应用时,我们一般情况下,仅仅只做一种日志实现的集成就可以了。

        通过上述集成测试,我们发现虽然底层的日志实现变了,但是源代码完全没有改变,这就是日志门面给我们带来的最大的好处,在底层真实记录日志的时候,我们不需要应用去做任何的了解,应用只需要去记Slf4j的API就可以了。

        接下来我们来讲解一下slf4j-nop,首先我们需要引入nop的实现依赖,根据上面进行的Slf4j关系图及上述集成测试,我们了解到slf4j-nop与Logback属于一类情况,只需依赖便可以与Slf4j无缝衔接,并且要想是nop发挥作用,由集成依赖的顺序而定,所以必须将slf4j-nop依赖放在所有日志实现的上方,进行日志的输出测试。

        <!-- slf4j-nop依赖 -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-nop</artifactId>
            <version>1.7.25</version>
        </dependency>

         根据测试结果我们发现Slf4j使用的日志实现时slf4j-nop,并且不实现日志的记录

        接下来我们来绑定Log4j日志实现,由上面内容的讲解我们也了解到,Log4j是在Slf4j之前出品的日志框架实现,所以没有遵循Slf4j的API规范,因此如果我们要想将Log4j与Slf4j进行衔接,就需要绑定一个适配器slf4j-log4j12。

        <!-- log4j适配器依赖 -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.25</version>
        </dependency>
        <!-- log4j依赖 -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>

        值得注意的是,上面我们了解到Slf4j与多个日志实现衔接是由日志实现的引入顺序决定的,因此如果我们将Logback依赖放置到Log4j依赖与Log4j适配器之前时,Slf4j使用的是Logback的日志实现,如下图

         上面我们既然引入绑定Log4j的日志实现,我们就需要在resources下创建Log4j的配置文件来控制日志的输出形式,我们简单配置一个Log4j的控制台输出。

         接下来我们进行JUL日志实现的绑定,因为JUL是JDK内置的日志实现,并且JUL与Log4j属于一类情况,需要添加适配器才可以与Slf4j进行衔接,我们只需要引入slf4j-jdk14依赖即可完成JUL日志实现的绑定。

        <!-- JUL适配器依赖 -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-jdk14</artifactId>
            <version>1.7.25</version>
        </dependency>

         由上面的测试结果,我们发现,绑定多个日志实现时,会出现警告信息,接下来,我们通过源码来查看其原理(看看Slf4j的执行原理)

        首先从LoggerFactory的getLogger()方法入手,进入该方法,我们发现

         进入重载的方法,我们可以看到下图方法,该方法用来取得logger工厂实现的方法。

        进入该方法,我们可以看到以双重检查锁的方法做判断,执行performInitialization(); 工厂的初始化方法,进入该方法,看到bind(); 就是用来绑定日志具体实现的方法。

         进入bind()方法,因为当前有可能会出现N多个日志框架实现,看到下图代码

         进入到findPossibleStaticLoggerBinderPathSet()方法,看到创建了一个有序不可重复的集合对象,声明了枚举类的路径,经过if else 判断,以获取系统中都有哪些日志实现。

         我们主要观察常量STATIC_LOGGER_BINDER_PATH,通过常量我们会找到类StaticLoggerBinder,这个类是以静态的方式绑定Logger实现的类。

        我们进入slf4j-JDK14适配器的StaticLoggerBinder,如下图,说明slf4j-jdk14使用的是JUL的Logger

        继续回到findPossibleStaticLoggerBinderPathSet()方法进行观察。

        while循环如果还有其他的日志实现,便将路径添加到集合中,回到bind()方法,我们发现reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);该方法表示对于绑定多实现的处理。

         在该方法中我们就可以看到测试时控制台输出的提示信息打印操作,如下图

 5.4需求分析

         假设目前有这样一个需求,我们项目一直以来使用的是Log4j日志框架,但是随着技术和需求的更新换代,Log4j已然不能够满足我们的系统需求,我们现在需要将系统中的日志实现重构为Slf4j+Logback的组合实现,在不触碰源代码的情况下,如何将这个问题解决掉。

        解决方案:

        首先我们将所有的日志框架依赖注释掉,只留下Log4j依赖,并使用Log4j的Logger进行日志输出。

         既然我们要使用Slf4j+Loback替代掉Log4j,所以我们需要在pom文件中引入Slf4j和Logback依赖,将Log4j日志依赖注释掉。

        添加好后我们会发现没有了Log4j环境的支持,编译报错,这个时候,我们需要使用桥接器来解决这个问题,桥接器解决的是项目中日志重构的问题,当前系统中存在之前的日志API,可以通过桥接器转换到slf4j的实现。

        添加桥接器依赖,桥接器加入之后,代码编译就不报错了。

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>log4j-over-slf4j</artifactId>
            <version>1.7.25</version>
        </dependency>

         根据测试结果,我们发现日志的输出格式为Logback,证明了现在使用的是Slf4j+Logback实现。

        在重构之后,就会为我们造成一种假象,使用的命名是Log4j包下的日志组件资源,但是真正日志的实现,却是使用了Logback的日志,这就是桥接器给我们带来的效果,有一点需要注意,在桥接器加入之后,适配器就没有必要加入了,桥接器和适配器不能同时导入依赖,桥接器配置在适配器上方,则运行报错,不能同时出现,桥接器配置在适配器下方,则不会执行桥接器,没有任何意义。

        接下来我们通过对其底层原理的分析来证明配置桥接器后,底层就是使用Slf4j实现的日志,我们通过Logger的getLogger()方法来分析,进入方法,进入Log4jLoggerFactory,我们可以看到Logger newInstance = new Logger(name); 新建logger对象,进入构造方法并进入父类方法。

         在父类的Category构造方法中我们可以看到slf4jLogger = LoggerFactory.getLogger(name);,而这里使用的LoggerFactory来自于org.slf4j。

5.5总结

        1.在真实的生产环境当中,Slf4j只绑定一个日志实现框架就可以了。

        2.在Slf4j绑定多个日志框架时,默认使用绑定依赖引入的第一个日志实现,并且会产生多个日志实现的警告提示信息。

        3.Slf4j与多个日志实现的绑定情况有三种,一种是没有任何日志实现引入的情况,Slf4j不具备实现日志记录的功能,一种是对于Logback、Simple、nop均符合Slf4j基础上进行的设计,因此直接引入依赖便可与Slf4j进行绑定,并且值得注意的是,nop并没有实现日志记录的功能,第三种情况则是通过适配器进行Slf4j与日志实现的衔接,例如Log4j、JUL,在引入日志实现依赖的同时,还需要引入其相应的适配器依赖,才可以与Slf4j进行绑定使用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值