2. 日志模块(上)

日志需求分析

无论对于业务系统还是中间件来说,日志都是必不可少的基础功能。完善、清晰地日志可以帮助我们观测系统运行的状态,并且快速定位问题。现在让我们站在 MyBatis 框架开发者的角度,来简单做一下日志功能的需求分析:

  1. 作为一个成熟的中间件,日志功能是必不可少的。那么,MyBatis 是要自己实现日志功能,还是集成现有的日志呢?MyBatis 没有选择重复造轮子,而是直接集成了第三方日志框架。
  2. 第三方的日志框架种类繁多,常用的如 slf4j、log4j2、logback 等等,而且每种框架的日志级别定义、打印方式、配置格式都不尽相同。MyBatis 作为底层的中间件,每个依赖 MyBatis 的业务系统都可能使用不同的日志组件,那 MyBatis 如何进行兼容呢?如果业务方引入了多个日志框架,MyBatis 按照什么优先级进行选择?
  3. 在 MyBatis 的核心处理流程中,包括 SQL 拼接、SQL 执行、结果集映射等关键步骤,都是需要打印日志的,如果在各处都显式地进行 log.info(“xxx”) 打印肯定不太合适,那么如何将日志打印优雅地织入到核心流程中?

Adapter Pattern 适配器模式

我们要在系统中集成多个第三方组件,每个组件具有相似的功能,但是接口定义各不相同,而我们自己的系统希望以统一地方式对组件进行调用。这么典型的使用场景,第一时间就可以想到 Adapter Pattern 适配器模式。
我们先来复习一下经典适配器模式的 UML 图:
Adapter Pattern
(图片来源:https://refactoring.guru/design-patterns/adapter)

适配器模式的作用:将一个接口转换成满足客户端期望的另一个接口,使得接口不兼容的那些类可以一起工作。

Adapter 模式主要包含了以下角色:

  • Client:客户端,即我们自己的业务系统;
  • Client Interface:目标接口,定义了统一的、所有第三方组件都需要遵循的规范;
  • Service:需要集成的第三方组件,它包含了我们需要的功能,但是因为接口定义不匹配,所以无法直接使用;
  • Adapter:即最核心的适配器,它实现了 Client Interface 接口,并且对于 Service 进行了包装。这样一来,Adapter 就成为了既符合业务接口规范,同时又具备了期望的功能的组件,可以直接在项目中使用

集成第三方日志框架

了解了适配器模式之后,我们来看下 MyBatis 是怎么把它灵活运用于日志模块中的。

首先,MyBatis 定义了 Log 接口,并指定了四种日志级别:

/*** MyBatis日志接口定义* 指定了trace、debug、warn和error四种日志级别*/
public interface Log {boolean isDebugEnabled();boolean isTraceEnabled();void error(String s, Throwable e);void error(String s);void debug(String s);void trace(String s);void warn(String s);}

可以看出,这其实是所有主流日志框架所支持的级别的交集。

接下来,MyBatis 为常用的日志框架都进行了 Adapter 的实现。这里以常用的 slf4j 为例:

/*** slf4j日志框架的Adapter实现* 该Adapter实现了Log接口,并且内部包装了slf4j的Logger对象以完成实际的日志打印功能*/
class Slf4jLoggerImpl implements Log {private final Logger log;public Slf4jLoggerImpl(Logger logger) {log = logger;}@Overridepublic boolean isDebugEnabled() {return log.isDebugEnabled();}@Overridepublic boolean isTraceEnabled() {return log.isTraceEnabled();}@Overridepublic void error(String s, Throwable e) {log.error(s, e);}@Overridepublic void error(String s) {log.error(s);}@Overridepublic void debug(String s) {log.debug(s);}@Overridepublic void trace(String s) {log.trace(s);}@Overridepublic void warn(String s) {log.warn(s);}}

该 Adapter 实现了 Log 接口,并且内部包装了 slf4j 的 org.slf4j.Logger 对象以完成实际的日志打印功能,是一种经典的适配器实现。

这样一来,日志适配器的整体结构就比较清晰了,我简单画一张图类比一下:
日志适配器
这里的对应关系为:

Adapter 模式MyBatis 实现
Client InterfaceLogger 接口
Serviceorg.slf4j.Logger 组件
AdapterSlf4jLoggerImpl 适配器

有了日志适配器,就可以在 MyBatis 中实现日志打印的功能了。但是第三方的日志框架众多,如果业务方引入了多个框架,MyBatis 应该如何决策该使用哪一个呢?我们来看下 MyBatis 中 LogFactory 日志工厂的实现:

/*** 日志工厂,通过getLog()方法获取日志实现类*/
public final class LogFactory {public static final String MARKER = "MYBATIS";private static Constructor<? extends Log> logConstructor;//按照顺序依次尝试加载Log实现类//优先级为:slf4j -> commons-logging -> log4j2 -> log4j -> jdk-logging -> no-loggingstatic {tryImplementation(LogFactory::useSlf4jLogging);tryImplementation(LogFactory::useCommonsLogging);tryImplementation(LogFactory::useLog4J2Logging);tryImplementation(LogFactory::useLog4JLogging);tryImplementation(LogFactory::useJdkLogging);tryImplementation(LogFactory::useNoLogging);}private LogFactory() {// disable construction}public static Log getLog(Class<?> clazz) {return getLog(clazz.getName());}public static Log getLog(String logger) {try {return logConstructor.newInstance(logger);} catch (Throwable t) {throw new LogException("Error creating logger for logger " + logger + ".  Cause: " + t, t);}}...省略非必要代码
}

可以看到,在 LogFactory 的静态代码块中,按照指定的顺序尝试加载 Log 实现类,具体的优先级为:slf4j -> commons-logging -> log4j2 -> log4j -> jdk-logging -> no-logging 。如果加载成功,则不再继续加载。这样就实现了主流日志框架的选择。从 MyBatis 的选择中也可以看出,slf4j 确实是日志框架的首选。

最后,可以稍微留意一下,日志适配器中有一个 no-logging,它对应的是 NoLoggingImpl 类,它是一个空的实现,里面什么都没做。这其实是一种 Null Object Pattern(空对象模式),它也实现了目标接口,但是内部实际上是 Do Noting,这样能够以统一的方式使用目标组件,并且省去了很多判空操作。

/*** 空日志适配器* Null Object模式*/
public class NoLoggingImpl implements Log {public NoLoggingImpl(String clazz) {// Do Nothing}@Overridepublic boolean isDebugEnabled() {return false;}@Overridepublic boolean isTraceEnabled() {return false;}@Overridepublic void error(String s, Throwable e) {// Do Nothing}@Overridepublic void error(String s) {// Do Nothing}@Overridepublic void debug(String s) {// Do Nothing}@Overridepublic void trace(String s) {// Do Nothing}@Overridepublic void warn(String s) {// Do Nothing}}

好了,到这里 MyBatis 的日志功能已经实现了。但是作为有追求的程序员,我们不能只满足于实现业务需求,还应该考虑提升代码的可扩展性,在面对新需求的时候可以尽可能少地修改现有代码。 那么 MyBatis 是如何实现优雅地打印日志的呢?我们下节再来分析。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/9275.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【REST规范】JAX-RS有哪些实现

JAX-RS是JAVA EE6 引入的一个新技术。 JAX-RS即Java API for RESTful Web Services&#xff0c;是一个Java 编程语言的应用程序接口&#xff0c;支持按照表述性状态转移&#xff08;REST&#xff09;架构风格创建Web服务。JAX-RS使用了Java SE5引入的Java注解来简化Web服务的客…

YUV 8bit转10bit

在有些场景中&#xff0c;我们经常会使用到YUV 8 bit转10bit的场景。 比如YUV420p 8bit 转 P010,P010le,YUV420p10le。 首先说ffmpeg 8 bit 转 10bit. 对于ffmpeg的P010le 和P010be 分别代表小端和大端&#xff0c;那么它转化的时候非常简单&#xff0c;就是8 bit再增加 8bit&…

一篇读懂React、vue框架的生命周期函数

当涉及到前端框架时&#xff0c;React 和 Vue.js 是两个非常受欢迎的选择。它们都提供了强大的工具和功能&#xff0c;帮助开发者构建交互式的、可扩展的应用程序。在这两个框架中&#xff0c;生命周期函数是一个重要的概念&#xff0c;它们允许我们在组件的不同阶段执行特定的…

https安全传输原理:

内容来自思学堂&#xff1a; 信息裸奔——>对称加密——>非对称加密——>非对称和对称加密——>权威第三方机构CA数字签名

Mockito的使用案例

流水线的单元测试 代码没有覆盖到&#xff0c;使用的是Mockito测试框架&#xff0c;原来是Mockito没有正确使用 package com.hmdp;import com.hmdp.controller.BlogController; import com.hmdp.entity.Blog; import com.hmdp.service.IBlogService; import com.hmdp.service.…

Spring学习(三)(类注解和方法注解)

目录 1. 存储Bean对象 1.1 配置扫描路径 1.2 添加注解存储Bean对象 1.2.1 Controller(控制器存储) 1.2.2 Service&#xff08;服务存储&#xff09; 1.3 这么多注解&#xff1f;&#xff1f;&#xff1f;为什么&#xff1f;&#xff1f; 1.3.1 类注解时间的关系 1.4 方法…

J2EE自定义mvc【框架配置及功能】

目录 一、配置步骤 二、配置框架前三步 导入相应的jar 导入相应的Class 导入xml文件 三、优化基本操作&#xff08;增删改&#xff09; 1、基础优化 编写实体类 编写BookDao类 优化BookDao JUnit测试 2、后台优化 3、前端优化 一、配置步骤 将框架打成jar包&…

【Hello mysql】 数据库库操作

Mysql专栏&#xff1a;Mysql 本篇博客简介&#xff1a;介绍数据库的库操作 库的操作 创建数据库创建数据库案例字符集和校验规则查看系统默认字符集和校验规则查看数据库支持的字符集和校验规则 校验规则对于数据库的影响操纵数据库查看数据库显示创建语句修改数据库数据库删除…

集成运放电路计算(全)

自记&#xff1a; 常用运放电路计算与分析 1、运放的符号表示 2、集成运算放大器的技术指标 (1) 开环差模电压放大倍数(开环增益)大 Ao(Ad)Vo/(V-V-)107-1012倍; (2) 共模抑制比高 KCMRR100db以上; (3) 输入电阻大 ri>1MW, 有的可达100MW以上; (4) 输出电阻小 ro 几W-几十…

Leetcode-每日一题【234.回文链表】

题目 给你一个单链表的头节点 head &#xff0c;请你判断该链表是否为回文链表。如果是&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,2,1]输出&#xff1a;true 示例 2&#xff1a; 输入&#xff1a;head…

日历与时钟

目录 公历 黑色星期五 生物韵律 公历 在公历中&#xff0c;当年份为4的整数倍&#xff0c;但不是100的整数倍时&#xff0c;会出现闰年的现象。 y40 mod(y,4) 0 && mod(y,100)||mod(y,400)0 输出当时的年、月、日、时、分、秒 f%6d %6d %6d %6d %6d %9.3f\n cclock …

面向对象五大基本原则

面向对象五大基本原则 更多精彩 先案例后讲解&#xff0c;这里是代码教父&#xff0c;今天讲解面向对象的五大基本原则&#xff1a; 单一职责原则&#xff08;The Single Responsibility Principle&#xff09;开闭原则&#xff08;The Open-Closed Principle&#xff09;里氏…