前言
本文主要介绍的是spring5、spring4、mybatis日志源码中是具体如何加载具体的日志类,看完后你可以根据源码的加载顺序使用自己需要使用的日志。如有错误,欢迎指出
spring当中的日志
- log4j
- logback
- log4j2
- jul
- slf4j
- jcl
- 各种日志技术的关系和作用
通过源码来分析spring的日志技术
- 分别分析spring5和spring4的使用日志的区别
- 通过源码分析mybaits的日志技术
- spring和mybatis整合通过注解设置自定义的日志
主流的log技术名词
- log4j
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
可以不需要依赖第三方的技术,直接记录日志
- jcl
jakartaCommonsLoggingImpl
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.1</version>
</dependency>
jcl他不直接记录日志,他是通过第三方记录日志(jul),它是jdk自带的
如果使用jcl来记录日志,在没有log4j的依赖情况下,是用jul
如果有了log4j则使用log4j
jcl=Jakarta commons-logging ,是apache公司开发的一个抽象日志通用框架,本身不实现日志记录,但是提供了记录日志的抽象方法即接口(info,debug,error.......),底层通过一个数组存放具体的日志框架的类名,然后循环数组依次去匹配这些类名是否在app中被依赖了,如果找到被依赖的则直接使用,所以他有先后顺序。
上图为jcl中存放日志技术类名的数组,默认有四个,后面两个可以忽略,因为都是jdk1.3以前的。
上图81行就是通过一个类名去load一个class,如果load成功则直接new出来并且返回使用。
如果没有load到class这循环第二个,直到找到为止。
可以看到这里的循环条件必须满足result不为空,
也就是如果没有找到具体的日志依赖则继续循环,如果找到则条件不成立,不进行循环了。
总结:顺序log4j>jul
jul
java自带的一个日志记录的技术,直接使用
- log4j2
slf4j
slf4j他也不记录日志,通过绑定器绑定一个具体的日志记录来完成日志记录
绑定器、桥接器
问题
slf4j -->通过绑定器-->jcl -->通过桥接器--> jcl ====> 会导致内存溢出
- logback
- simple-log
各种日志技术的关系和作用
spring日志技术分析
spring5.*日志技术实现
spring5使用的spring的jcl(spring改了jcl的代码)来记录日志的,但是jcl不能直接记录日志,默认是使用JUL
记录,但是只要引入了log4j2
、slf4j
等,就会被重新赋值改变(在static静态块中)
源代码展示
org.apache.commons.logging.LogFactory类
里面被调用的最多的方法:
// 这个方法,调用本类的getLog()方法
public static Log getLog(Class<?> clazz) {
return getLog(clazz.getName());
}
public static Log getLog(String name) {
return LogAdapter.createLog(name);
}
org.apache.commons.logging.LogAdapter类
静态变量和静态代码块会在类的实例化之前被加载,具体是在类加载过程的初始化阶段,会在编译器生成的clinit方法中被调用,执行的顺序有源文件中出现的顺序一致
/**
* Create an actual {@link Log} instance for the selected API.
* 这个方法就是返回具体的实例
*/
public static Log createLog(String name) {
// 通过静态变量logApi具体的值来加载日志
switch (logApi) {
case LOG4J:
return Log4jAdapter.createLog(name);
case SLF4J_LAL:
return Slf4jAdapter.createLocationAwareLog(name);
case SLF4J:
return Slf4jAdapter.createLog(name);
default:
return JavaUtilAdapter.createLog(name);
}
}
// 核心代码
private static final String LOG4J_SPI = "org.apache.logging.log4j.spi.ExtendedLogger";
private static final String LOG4J_SLF4J_PROVIDER = "org.apache.logging.slf4j.SLF4JProvider";
private static final String SLF4J_SPI = "org.slf4j.spi.LocationAwareLogger";
private static final String SLF4J_API = "org.slf4j.Logger";
private static final LogApi logApi;
// 枚举类
private enum LogApi {LOG4J, SLF4J_LAL, SLF4J, JUL}
static {
// isPresent()方法,是判断这个类是否能够被加载到
if (isPresent(LOG4J_SPI)) {
if (isPresent(LOG4J_SLF4J_PROVIDER) && isPresent(SLF4J_SPI)) {
// log4j-to-slf4j bridge -> we'll rather go with the SLF4J SPI;
// however, we still prefer Log4j over the plain SLF4J API since
// the latter does not have location awareness support.
logApi = LogApi.SLF4J_LAL;
}
else {
// Use Log4j 2.x directly, including location awareness support
// 使用Log4j 2.x的版本
logApi = LogApi.LOG4J;
}
}
else if (isPresent(SLF4J_SPI)) {
// Full SLF4J SPI including location awareness support
logApi = LogApi.SLF4J_LAL;
}
else if (isPresent(SLF4J_API)) {
// Minimal SLF4J API without location awareness support
logApi = LogApi.SLF4J;
}
else {
// java.util.logging as default
// 默认使用JUL
logApi = LogApi.JUL;
}
}
spring4.*日志技术实现
spring4当中依赖的是原生的jcl,采用循环优先的原则。
mybatis的日志技术实现
你提供哪个,就使用你提供的。如果没有提供,就会使用JCL
,然后JCL会去找到实现类
初始化
静态代码块会在类的加载过程中的初始化阶段执行
org.apache.ibatis.logging.LogFactory
常被调用的方法
public static Log getLog(Class<?> aClass) {
return getLog(aClass.getName());
}
public static Log getLog(String logger) {
try {
// 通过静态变量logConstructor来获得具体的
return logConstructor.newInstance(logger);
} catch (Throwable t) {
throw new LogException("Error creating logger for logger " + logger + ". Cause: " + t, t);
}
}
// 静态变量
private static Constructor<? extends Log> logConstructor;
static {
// 在初始化阶段执行,一旦logConstructor不为空,后面的就不会被执行
tryImplementation(LogFactory::useSlf4jLogging);
tryImplementation(LogFactory::useCommonsLogging);
tryImplementation(LogFactory::useLog4J2Logging);
tryImplementation(LogFactory::useLog4JLogging);
tryImplementation(LogFactory::useJdkLogging);
tryImplementation(LogFactory::useNoLogging);
}
关键代码 if (logConstructor == null),没有找到实现则继续找
private static void tryImplementation(Runnable runnable) {
// 当为空时,才执行方法
if (logConstructor == null) {
try {
runnable.run();
} catch (Throwable t) {
// ignore
}
}
}
public static synchronized void useSlf4jLogging() {
setImplementation(org.apache.ibatis.logging.slf4j.Slf4jImpl.class);
}
private static void setImplementation(Class<? extends Log> implClass) {
try {
Constructor<? extends Log> candidate = implClass.getConstructor(String.class);
// 为什么不直接返回这里的Log对象?
// 这里主要的目的是看能不能得到,用户还需要自定义一些东西,所以不能返回这个,应该使用getLog()方法返回的对象
Log log = candidate.newInstance(LogFactory.class.getName());
if (log.isDebugEnabled()) {
log.debug("Logging initialized using '" + implClass + "' adapter.");
}
// 赋值,只要前面没有抛异常
logConstructor = candidate;
} catch (Throwable t) {
throw new LogException("Error setting Log implementation. Cause: " + t, t);
}
}
// getLog方法
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);
}
}
具体实现类
mybatis提供很多日志的实现类,用来记录日志,取决于初始化的时候load到的class
上图红色箭头可以看到JakartaCommonsLoggingImpl中引用了jcl 的类,如果在初始化的时候load到类为JakartaCommonsLoggingImpl
那么则使用jcl 去实现日志记录,但是也是顺序的,顺序参考源码
Spring和Mybatis整合设置自定义的的日志
// spring的Appconfig中
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
// 所以可以通过这样设置自己想要使用的日志
org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
// 这个方法里面调用的是org.apache.ibatis.logging.LogFactory的useCustomLogging()
configuration.setLogImpl(Log4jImpl.class);
factoryBean.setConfiguration(configuration);
factoryBean.setDataSource(dataSource());
return factoryBean.getObject();
}
// useCustomLogging方法
public static synchronized void useCustomLogging(Class<? extends Log> clazz) {
setImplementation(clazz);
}