SLF4J及MyBatis日志源码分析

slf4j与logback

slf4j是日志门面,通过它调用日志实现类
logback为日志实现类
通常用法

Logger logger = LoggerFactory.getLogger(A.class);
logger.info("msg");

就从此为入口,进行源码分析

// 根据名称从工厂获取日志实现类
public static Logger getLogger(String name) {
        ILoggerFactory iLoggerFactory = getILoggerFactory();
        return iLoggerFactory.getLogger(name);
}

// 获取日志工厂,INITIALIZATION_STATE初始值为0
public static ILoggerFactory getILoggerFactory() {
        if (INITIALIZATION_STATE == 0) {
            Class var0 = LoggerFactory.class;
            synchronized(LoggerFactory.class) {
                if (INITIALIZATION_STATE == 0) {
                    INITIALIZATION_STATE = 1;
                    performInitialization();
                }
            }
        }

        switch(INITIALIZATION_STATE) {
        case 1:
            return SUBST_FACTORY;
        case 2:
            throw new IllegalStateException("");
        case 3:
            return StaticLoggerBinder.getSingleton().getLoggerFactory();
        case 4:
            return NOP_FALLBACK_FACTORY;
        default:
            throw new IllegalStateException("");
        }
    }

// 执行实例化
private static final void performInitialization() {
        bind();
        if (INITIALIZATION_STATE == 3) {
            versionSanityCheck();
        }
 }

// 绑定日志实现
private static final void bind() {
        String msg;
        try {
            Set<URL> staticLoggerBinderPathSet = null;
            if (!isAndroid()) {
                staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
            }

            StaticLoggerBinder.getSingleton();
            INITIALIZATION_STATE = 3;
        } catch (NoClassDefFoundError var2) {
            INITIALIZATION_STATE = 4;
        } catch (NoSuchMethodError var3) {
        } catch (Exception var4) {
        }
    }

// 查找org/slf4j/impl/StaticLoggerBinder.class实现类
String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class"
static Set<URL> findPossibleStaticLoggerBinderPathSet() {
        LinkedHashSet staticLoggerBinderPathSet = new LinkedHashSet();

        try {
            ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();
            Enumeration paths;
            if (loggerFactoryClassLoader == null) {
                paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
            } else {
                paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);
            }

            while(paths.hasMoreElements()) {
                URL path = (URL)paths.nextElement();
                staticLoggerBinderPathSet.add(path);
            }
        } catch (IOException var4) {
        }

        return staticLoggerBinderPathSet;
    }

// 初始化
StaticLoggerBinder.getSingleton();
static {
        SINGLETON.init();
}

void init() {
	(new ContextInitializer(this.defaultLoggerContext)).autoConfig();
	this.contextSelectorBinder.init(this.defaultLoggerContext, KEY);
	this.initialized = true;
}

// 自动配置
public void autoConfig() throws JoranException {
        StatusListenerConfigHelper.installIfAsked(this.loggerContext);
        URL url = this.findURLOfDefaultConfigurationFile(true);
        if (url != null) {
            this.configureByResource(url);
        } else {
            Configurator c = (Configurator)EnvUtil.loadFromServiceLoader(Configurator.class);
            if (c != null) {
                try {
                    c.setContext(this.loggerContext);
                    c.configure(this.loggerContext);
                } catch (Exception var4) {
                    throw new LogbackException("");
                }
            } else {
                BasicConfigurator basicConfigurator = new BasicConfigurator();
                basicConfigurator.setContext(this.loggerContext);
                basicConfigurator.configure(this.loggerContext);
            }
        }
    }

// 查找logback.xml配置文件
public URL findURLOfDefaultConfigurationFile(boolean updateStatus) {
        ClassLoader myClassLoader = Loader.getClassLoaderOfObject(this);
        URL url = this.findConfigFileURLFromSystemProperties(myClassLoader, updateStatus);
        if (url != null) {
            return url;
        } else {
            url = this.getResource("logback-test.xml", myClassLoader, updateStatus);
            if (url != null) {
                return url;
            } else {
                url = this.getResource("logback.groovy", myClassLoader, updateStatus);
                return url != null ? url : this.getResource("logback.xml", myClassLoader, updateStatus);
            }
        }
    }

// 返回日志工厂实现类,进而获取Logger实例输出日志
// 到这里绑定结束,INITIALIZATION_STATE = 3
// 根据条件判断,获取绑定的工厂实现类
StaticLoggerBinder.getSingleton().getLoggerFactory();
public ILoggerFactory getLoggerFactory() {
        if (!this.initialized) {
            return this.defaultLoggerContext;
        } else if (this.contextSelectorBinder.getContextSelector() == null) {
            throw new IllegalStateException("contextSelector cannot be null. See also http://logback.qos.ch/codes.html#null_CS");
        } else {
            return this.contextSelectorBinder.getContextSelector().getLoggerContext();
        }
    }

总结:每个slf4j日志实现类都存在
org/slf4j/impl/StaticLoggerBinder.class
里面持有工厂实现类的单例,比如logback中的defaultLoggerContext,如果存在多个实现类,由JVM根据类加载次序,决定加载哪一个,然后再进行日志工厂的配置,比如
(new ContextInitializer(this.defaultLoggerContext)).autoConfig();
再回到开头
Logger logger = LoggerFactory.getLogger(A.class);
返回的就是logback日志工厂的实例

mybatis日志模块

org.apache.ibatis.logging.LogFactory

// 按序加载日志实现类
static {
        tryImplementation(new Runnable() {
            public void run() {
                LogFactory.useSlf4jLogging();
            }
        });
        tryImplementation(new Runnable() {
            public void run() {
                LogFactory.useCommonsLogging();
            }
        });
        tryImplementation(new Runnable() {
            public void run() {
                LogFactory.useLog4J2Logging();
            }
        });
        tryImplementation(new Runnable() {
            public void run() {
                LogFactory.useLog4JLogging();
            }
        });
        tryImplementation(new Runnable() {
            public void run() {
                LogFactory.useJdkLogging();
            }
        });
        tryImplementation(new Runnable() {
            public void run() {
                LogFactory.useNoLogging();
            }
        });
    }

private static void tryImplementation(Runnable runnable) {
        if (logConstructor == null) {
            try {
                runnable.run();
            } catch (Throwable var2) {
            }
        }
    }

以slf4j为例

public static synchronized void useSlf4jLogging() {
        setImplementation(Slf4jImpl.class);
    }

private static void setImplementation(Class<? extends Log> implClass) {
        try {
            Constructor<? extends Log> candidate = implClass.getConstructor(String.class);
            Log log = (Log)candidate.newInstance(LogFactory.class.getName());
            if (log.isDebugEnabled()) {
                log.debug("Logging initialized using '" + implClass + "' adapter.");
            }

            logConstructor = candidate;
        } catch (Throwable var3) {
            throw new LogException("Error setting Log implementation.  Cause: " + var3, var3);
        }
    }

// 创建slf4j日志对象Logger,并适配到mybatis日志对象Log
public Slf4jImpl(String clazz) {
        Logger logger = LoggerFactory.getLogger(clazz);
        if (logger instanceof LocationAwareLogger) {
            try {
                logger.getClass().getMethod("log", Marker.class, String.class, Integer.TYPE, String.class, Object[].class, Throwable.class);
                this.log = new Slf4jLocationAwareLoggerImpl((LocationAwareLogger)logger);
                return;
            } catch (SecurityException var4) {
            } catch (NoSuchMethodException var5) {
            }
        }

        this.log = new Slf4jLoggerImpl(logger);
    }

// 创建Slf4jImpl对象,这样mybatis的Log就完成了到slf4j的适配
public static Log getLog(String logger) {
        try {
            return (Log)logConstructor.newInstance(logger);
        } catch (Throwable var2) {
            throw new LogException("Error creating logger for logger " + logger + ".  Cause: " + var2, var2);
        }
    }

总结:mybatis日志模块其实就是对各日志实现类适配

mybatis日志输出

在这里插入图片描述
mybatis通过动态代理,拦截Connection,Statement,PrepareStatement,ResultSet对象进行日志输出,关键代码:

SqlSession session = new SqlSession();
session.selectList(statement);

// 1 DefaultSqlSession
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        MappedStatement ms = this.configuration.getMappedStatement(statement);
        return this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    }

// 2 BaseExecutor
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        BoundSql boundSql = ms.getBoundSql(parameter);
        CacheKey key = this.createCacheKey(ms, parameter, rowBounds, boundSql);
        return this.query(ms, parameter, rowBounds, resultHandler, key, boundSql);
    }

// 3 SimpleExecutor
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        Statement stmt = null;

        List var9;
        try {
            Configuration configuration = ms.getConfiguration();
            StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
            stmt = this.prepareStatement(handler, ms.getStatementLog());
            var9 = handler.query(stmt, resultHandler);
        } finally {
            this.closeStatement(stmt);
        }

        return var9;
    }

// 4 SimpleExecutor
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
        Connection connection = this.getConnection(statementLog);
        Statement stmt = handler.prepare(connection, this.transaction.getTimeout());
        handler.parameterize(stmt);
        return stmt;
    }

// 5 BaseExecutor
protected Connection getConnection(Log statementLog) throws SQLException {
        Connection connection = this.transaction.getConnection();
        return statementLog.isDebugEnabled() ? ConnectionLogger.newInstance(connection, statementLog, this.queryStack) : connection;
    }

通过一条执行链路跟踪下来,Connection确实被动态代理了:
ConnectionLogger.newInstance(connection, statementLog, this.queryStack)
具体代码如下:

public final class ConnectionLogger extends BaseJdbcLogger implements InvocationHandler {
    private final Connection connection;

    private ConnectionLogger(Connection conn, Log statementLog, int queryStack) {
        super(statementLog, queryStack);
        this.connection = conn;
    }

    public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
        try {
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, params);
            } else {
                PreparedStatement stmt;
                if ("prepareStatement".equals(method.getName())) {
                    if (this.isDebugEnabled()) {
                        this.debug(" Preparing: " + this.removeBreakingWhitespace((String)params[0]), true);
                    }

                    stmt = (PreparedStatement)method.invoke(this.connection, params);
                    stmt = PreparedStatementLogger.newInstance(stmt, this.statementLog, this.queryStack);
                    return stmt;
                } else if ("prepareCall".equals(method.getName())) {
                    if (this.isDebugEnabled()) {
                        this.debug(" Preparing: " + this.removeBreakingWhitespace((String)params[0]), true);
                    }

                    stmt = (PreparedStatement)method.invoke(this.connection, params);
                    stmt = PreparedStatementLogger.newInstance(stmt, this.statementLog, this.queryStack);
                    return stmt;
                } else if ("createStatement".equals(method.getName())) {
                    Statement stmt = (Statement)method.invoke(this.connection, params);
                    stmt = StatementLogger.newInstance(stmt, this.statementLog, this.queryStack);
                    return stmt;
                } else {
                    return method.invoke(this.connection, params);
                }
            }
        } catch (Throwable var5) {
            throw ExceptionUtil.unwrapThrowable(var5);
        }
    }

    public static Connection newInstance(Connection conn, Log statementLog, int queryStack) {
        InvocationHandler handler = new ConnectionLogger(conn, statementLog, queryStack);
        ClassLoader cl = Connection.class.getClassLoader();
        return (Connection)Proxy.newProxyInstance(cl, new Class[]{Connection.class}, handler);
    }

    public Connection getConnection() {
        return this.connection;
    }
}

从代码中可以看出,在代理类中对日志进行输出,从源码还可以发现,ConnectionLogger 中调用了PreparedStatementLogger,PreparedStatementLogger中调用了ResultSetLogger,从Connection开始,逐层进行代理,分别完成各自对象的日志输出。

本文从slf4j日志原理,mybatis日志适配,再到SQL执行日志通过关键对象的动态代理进行输出,作了一个简易的分析,从整个源码分析,你会发现这当中用了很多设计模式,比如工厂模式、代理模式、适配器模式,大家好好体会这些设计模式的运用,希望本文对大家有帮助。

参考资料:
https://blog.csdn.net/kxcfzyk/article/details/38613861

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值