本篇文章为大家展示了MyBatis日志为什么可以兼容其他的日志框架,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。
从策划到设计制作,每一步都追求做到细腻,制作可持续发展的企业网站。为客户提供成都网站设计、网站建设、外贸网站建设、网站策划、网页设计、域名注册、网站空间、网络营销、VI设计、 网站改版、漏洞修补等服务。为客户提供更好的一站式互联网解决方案,以客户的口碑塑造优易品牌,携手广大客户,共同发展进步。前言
日志,在我们开发中是一个非常重要的话题,良好的日志打印可以帮助我们快速的定位问题,可能现在我们开发用到最多的日志框架就是slf4j了,但是日志还有其他很多优秀的框架,比如:Apache Common Log,Log4j,java.util.logging等。MyBatis作为一款优秀的ORM框架,定义了一套统一的日志接口供应用层调用,而底层却利用适配器模式兼容了我们上面所列出来的常用日志框架。
MyBatis日志分类
在介绍MyBatis的全局配置文件的时候,我们提到setting内有一个属性logImpl,可以配置的选项有:SLF4J,LOG4J,LOG4J2,JDK_LOGGING,COMMONS_LOGGING,STDOUT_LOGGING,NO_LOGGING。
这就说明MyBatis支持六种日志类型(NO_LOGGING是不打印日志)。我们看一下MyBatis的日志模块也可以很明显的看出六种日志类型:
它们的对应关系为:
日志属性 | 对应日志模块包名 | 实现方式 |
---|---|---|
SLF4J | slf4j | 使用SLF4J日志框架实现 |
LOG4J | log4j | 使用Log4J日志框架实现(1.x版本) |
LOG4J2 | log4j2 | 使用Log4J日志框架实现(2.x版本) |
JDK_LOGGING | jdk14 | 使用java.util.logging实现 |
COMMONS_LOGGING | commons | 使用Apache Commons Logging实现 |
STDOUT_LOGGING | stdout | 使用System类实现 |
NO_LOGGING | nologging | 不打印日志 |
PS:需要注意的是,SLF4J并不是一个具体的日志框架,也就是我们不能单独只配置SLF4J而不引入其他任何具体的日志框架。
简单谈谈SLF4J
SLF4J:简单日记门面。(英文全称为simple logging Facade for Java),这个是用来为各种日志框架提供一个简单的统一的接口,这样使得我们在切换日志框架的时候可以直接替换jar包就可以了,而无需修改源代码。
logback我想大家都用过,logback是一个实现了具体日志打印的框架,但是MyBatis上面列出来的分类并没有支持logback,它又为什么能够打印呢?这就是SLF4J的作用了,因为logback也实现了SLF4J提供的接口,所以我们需要将logback和SLF4J结合配置使用才行。而后面的介绍中也可以看到,MyBatis中如果我们不指定日志种类的时候,优先选择的就是SLF4J,这正是因为SLF4J可以和其他许多日志框架一起结合来使用。
那么假如我们指定了日志类型为SLF4J,但是不引入其他任何实现呢?
答案就是MyBatis不会打印任何日志出来,下图就是只配置了SLF4J而没有引入其他任何实现的警告信息:
可以看到这里提示我们SLF4J没有任何实现,而后面的sql语句和参数这些信息也没有打印出来。
MyBatis日志实现原理
日志的解析
老规矩,我们还是先找到加载mybatis-config配置文件中的解析日志的源码:
这里首先会根据我们配置的属性作为别名去TypeAliasRegistry类中查找对应的类,如果不存在这个别名,那就会把我们配置的属性直接通过Class.forName去查找日志类,所以看到这里就明白我们可以自定义日志类,只要实现Log接口就行,然后配置我们自己的类名就行了。虽然别名都存在TypeAliasRegistry类里面,但是我们前面介绍MyBatis配置文件的时候,列出了TypeAliasRegistry类中默认初始化的别名,并没有看到日志相关类的别名,那么日志的别名又是在哪里配置的呢?我们打开Configuration类:
可以看到Configuration的构造方法里面也初始化了一些别名注册到TypeAliasRegistry类了。
接下来我们看看读取到日志类之后调用了setLogImpl做了什么事情:
调用了LogFactory类的方法。
LogFactory
LogFactory工厂是负责创建日志对象对应的适配器。
LogFactory的静态代码块内按顺序初始化了所有内置的日志
再看一下tryImplementation方法,如果logConstructor不为空,说明当前还没有加载到日志适配器,那就继续执行run()方法,也就是继续执行useXXXLogging方法,而所有的useXXXLogging方法都是调用了setImplementation方法。
下面这里如果加载成功之后就会对logConstructor进行赋值,那么后续的方法就不会再执行run()方法, 而如果抛出异常,因为已经被捕获了,所以就会继续往后执行静态代码块内的方法。
从上面的LogFactory中我们可以看到,初始化的时候就会默认初始化一个日志适配器,所以如果我们引用了相关日志所需要的类,那么就会按照static代码块内的顺序进行选择一个合适的日志适配器。
继续回到上面的Configuration里面,这里拿到我们配置的日志信息之后,会直接调用useCustomLogging方法,也就是绕过了上面的logConstructor == null这个判断,而直接调用了setImplementation方法,所以假如我们配置了日志信息,那么会覆盖初始化的日志适配器。
PS:假如我们配置了一个不存在的日志类,那么因为它是直接调用setImplementation方法,所以异常就会被抛出来,不抛异常的方法是tryImplementation而不是setImplementation。
jdbc log
MyBatis的日志包下面还有一个包时jdbc,这个我们还没有介绍,那么jdbc包下面的类又有什么用呢?我们先看一下类图关系:
很明显,MyBatis将日志拆分成了ConnectionLogger,PreparedStatementLogger,ResultSetLogger,StatementLogger四种类型分开处理,它们都继承了BaseJdbcLogger类,而且实现了InvocationHandler接口,也很明显,这里用到了JDK动态代理。
任意点开ConnectionLogger可以发现,它是用来代理Connection对象的:
其他三个那很明显,分别是用来代理PreparedStatement,ResultSet,Statement这三个对象的。也就是说MyBatis中日志最终的打印是通过JDK动态代理来实现的,而且不同的执行过程分成了四个对象来分别负责对应的日志打印。
我们继续看一下ConnectionLogger的invoke方法,可以看到,这里就是打印了一句日志:
上面日志打印出来的效果就是我们下面红框中的日志:
等等,差点被忽悠了,这代码里面并没有打印“==>”,打印出来的这个符号又是怎么来的呢?
那就需要进入debug方法里面继续看一看,这个debug方法是在抽象类BaseJdbcLogger里面实现的,所以我们还需要看看BaseJdbcLogger类的debug方法。
可以看到这里打印的时候拼接了一个前缀:
PS:queryStack是查询层数,如果没有嵌套查询则queryStack=1
上述内容就是MyBatis日志为什么可以兼容其他的日志框架,你们学到知识或技能了吗?如果还想学到更多技能或者丰富自己的知识储备,欢迎关注创新互联行业资讯频道。