MENU

spring和mybatis的日志

September 6, 2020 • Read: 2193 • 后端

前言

本文主要介绍的是spring5、spring4、mybatis日志源码中是具体如何加载具体的日志类,看完后你可以根据源码的加载顺序使用自己需要使用的日志。如有错误,欢迎指出

spring当中的日志

  1. log4j
  2. logback
  3. log4j2
  4. jul
  5. slf4j
  6. jcl
  • 各种日志技术的关系和作用
  • 通过源码来分析spring的日志技术

    • 分别分析spring5和spring4的使用日志的区别
  • 通过源码分析mybaits的日志技术
  • spring和mybatis整合通过注解设置自定义的日志

主流的log技术名词

  1. log4j
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

可以不需要依赖第三方的技术,直接记录日志

  1. 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中被依赖了,如果找到被依赖的则直接使用,所以他有先后顺序。

image-20200821094203236.png

上图为jcl中存放日志技术类名的数组,默认有四个,后面两个可以忽略,因为都是jdk1.3以前的。

image-20200821094555439.png

上图81行就是通过一个类名去load一个class,如果load成功则直接new出来并且返回使用。

如果没有load到class这循环第二个,直到找到为止。

image-20200821094610697.png

可以看到这里的循环条件必须满足result不为空,

也就是如果没有找到具体的日志依赖则继续循环,如果找到则条件不成立,不进行循环了。

总结:顺序log4j>jul

  1. jul

    java自带的一个日志记录的技术,直接使用

  2. log4j2
  1. slf4j

    slf4j他也不记录日志,通过绑定器绑定一个具体的日志记录来完成日志记录

    绑定器、桥接器

问题
slf4j -->通过绑定器-->jcl -->通过桥接器--> jcl ====> 会导致内存溢出
  1. logback
  2. simple-log

各种日志技术的关系和作用

image-20200821094649714.png

spring日志技术分析

spring5.*日志技术实现

spring5使用的spring的jcl(spring改了jcl的代码)来记录日志的,但是jcl不能直接记录日志,默认是使用JUL记录,但是只要引入了log4j2slf4j等,就会被重新赋值改变(在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

image-20200821094838982.png

上图红色箭头可以看到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);
}
Last Modified: October 10, 2020