Mybatis内部提供了日志模块,用于记录框架内部异常与调试信息;日志实现使用了经典的适配器模式,mybatis自己本身不提供日志功能,而是依赖其他的日志框架来实现;内部支持的优先级是:

SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING

由于各个厂商提供的日志api,日志级别等都不尽相同,所以mybatis对所有支持的日志框架做了封装,自定义了日志打印级别:

error (错误) > warn (警告) > debug(调试信息)>trace

mybatis 里定了了统一的日志接口,Log接口并定义了一系列的接口(目标接口)

  1. /**
  2. * @author Clinton Begin
  3. */
  4. public interface Log {
  5. boolean isDebugEnabled();
  6. boolean isTraceEnabled();
  7. void error(String s, Throwable e);
  8. void error(String s);
  9. void debug(String s);
  10. void trace(String s);
  11. void warn(String s);
  12. }

提供的实现类(适配器):
image.png
日志集成核心类 org.apache.ibatis.logging.LogFactory

  1. public final class LogFactory {
  2. /**
  3. * Marker to be used by logging implementations that support markers.
  4. */
  5. public static final String MARKER = "MYBATIS";
  6. private static Constructor<? extends Log> logConstructor;
  7. static {
  8. // 在LogFactory的静态方法里按照特定的顺序,寻找并加载对于的第三方日志组件,
  9. tryImplementation(LogFactory::useSlf4jLogging);
  10. tryImplementation(LogFactory::useCommonsLogging);
  11. tryImplementation(LogFactory::useLog4J2Logging);
  12. tryImplementation(LogFactory::useLog4JLogging);
  13. tryImplementation(LogFactory::useJdkLogging);
  14. tryImplementation(LogFactory::useNoLogging);
  15. }
  16. private LogFactory() {
  17. // disable construction
  18. }
  19. /*
  20. * 获取Log类对象。这个是为了实例化一个Log对象
  21. */
  22. public static Log getLog(Class<?> aClass) {
  23. return getLog(aClass.getName());
  24. }
  25. public static Log getLog(String logger) {
  26. try {
  27. return logConstructor.newInstance(logger);
  28. } catch (Throwable t) {
  29. throw new LogException("Error creating logger for logger " + logger + ". Cause: " + t, t);
  30. }
  31. }
  32. /*
  33. * tryImplementation 方法里会先判断是否已经加载了日志组件,如果已经加载了则不会继续处理;
  34. * 并且这个方法做了异常的吞没,如果没有对于的日志组件也不会影响下一个组件的加载流程;
  35. */
  36. private static void tryImplementation(Runnable runnable) {
  37. if (logConstructor == null) {
  38. try {
  39. runnable.run();
  40. } catch (Throwable t) {
  41. // ignore
  42. }
  43. }
  44. }
  45. public static synchronized void useSlf4jLogging() {
  46. setImplementation(org.apache.ibatis.logging.slf4j.Slf4jImpl.class);
  47. }
  48. /*
  49. * 这个方法主要功能是去加载提供的实现类,这些实现类里引用了第三方的类,
  50. * 如果项目中没有提供对于的jar包则该方法会抛错,然后异常会传递到 tryImplementation方法;
  51. * 如果实例化特制类成功,则会将对于的类构造器赋值给 logConstructor,以便后续实例化
  52. */
  53. private static void setImplementation(Class<? extends Log> implClass) {
  54. try {
  55. // 创建日志对象
  56. Constructor<? extends Log> candidate = implClass.getConstructor(String.class);
  57. Log log = candidate.newInstance(LogFactory.class.getName());
  58. if (log.isDebugEnabled()) {
  59. log.debug("Logging initialized using '" + implClass + "' adapter.");
  60. }
  61. // 报存构造器
  62. logConstructor = candidate;
  63. } catch (Throwable t) {
  64. throw new LogException("Error setting Log implementation. Cause: " + t, t);
  65. }
  66. }
  67. }

下面以 org.apache.ibatis.logging.log4j.Log4jImpl 为例做分析

  1. public class Log4jImpl implements Log {
  2. private static final String FQCN = Log4jImpl.class.getName();
  3. private final Logger log;
  4. public Log4jImpl(String clazz) {
  5. log = Logger.getLogger(clazz);
  6. }
  7. @Override
  8. public boolean isDebugEnabled() {
  9. return log.isDebugEnabled();
  10. }
  11. @Override
  12. public boolean isTraceEnabled() {
  13. return log.isTraceEnabled();
  14. }
  15. @Override
  16. public void error(String s, Throwable e) {
  17. log.log(FQCN, Level.ERROR, s, e);
  18. }
  19. @Override
  20. public void error(String s) {
  21. log.log(FQCN, Level.ERROR, s, null);
  22. }
  23. @Override
  24. public void debug(String s) {
  25. log.log(FQCN, Level.DEBUG, s, null);
  26. }
  27. @Override
  28. public void trace(String s) {
  29. log.log(FQCN, Level.TRACE, s, null);
  30. }
  31. @Override
  32. public void warn(String s) {
  33. log.log(FQCN, Level.WARN, s, null);
  34. }
  35. }

这个类适配的 org.apache.log4j日志组件,在类里有一个成员变量。相当于适配器模式里面对象的适配器模式。Log4jImpl这个类相当于是将log4j的功能翻译成了Mybatis的功能,做了api的适配

拓展:Mybatis是如何做到日志可以自定义扩展的呢?

从上面的源码分析我们已经看出了,Mybatis内置了

  • SLF4J
  • Apache Commons Logging
  • Log4j2
  • Log4j2
  • JDK Logging

几种开源的实现作为日志扩展。默认使用的是 “Slf4j ” 如果我们需要自己指定也是可以的,我们只需要在配置文件中加上:

  1. <configuration>
  2. <settings>
  3. <setting name="logImpl" value="SLF4J"/>
  4. </settings>
  5. </configuration>

mybatis的启动加载的源码中会自动的加载,解析配置文件,

  1. private void parseConfiguration(XNode root) {
  2. try {
  3. propertiesElement(root.evalNode("properties"));
  4. Properties settings = settingsAsProperties(root.evalNode("settings"));
  5. loadCustomVfs(settings);
  6. // 4、根据<logImpl>标签获取日志的实现类,我们可以用到很多的日志的方案,包括LOG4J,LOG4J2
  7. loadCustomLogImpl(settings);
  8. ...
  9. }
  10. }
  11. /**
  12. * 在这里我们可以指定日志的实现方式
  13. * @param props mybatis-config.xml中setting的properties的配置文件中logImpl实现方式
  14. */
  15. private void loadCustomLogImpl(Properties props) {
  16. // 根据配置文件中的配置的日志的名称,解析出使用日志的实现类
  17. Class<? extends Log> logImpl = resolveClass(props.getProperty("logImpl"));
  18. // 方法设置为我们自定义的类,跟踪此方法,找到最后的实现为
  19. configuration.setLogImpl(logImpl);
  20. }
  21. public void setLogImpl(Class<? extends Log> logImpl) {
  22. if (logImpl != null) {
  23. this.logImpl = logImpl;
  24. // 查找字符串参数的构造器
  25. LogFactory.useCustomLogging(this.logImpl);
  26. }
  27. }
  28. // 最后到了最关键的 LogFactory# useCustomLogging方法,这个方法如果在加载配置文件阶段就指定了
  29. // 那么我们自定义的日志实现类就会生效
  30. public static synchronized void useCustomLogging(Class<? extends Log> clazz) {
  31. setImplementation(clazz);
  32. }

https://juejin.cn/post/7022138295177969694