spring-jcl 模块源码分析
简介
spring-jcl是spring用于处理日志打印的模块,被spring-core所依赖:
jcl全称是Jakarta Commons Logging
,是apache提供的日志门面(功能同slf4j),日志门面利用设计模式中的门面模式提供统一的日志接口,实际的日志实现可以任意更换。
不过jcl支持的日志实现有限,已经被淘汰了,目前日志门面一般使用slf4j
源码分析
spring-jcl是从jcl改造而来,使用它提供的工厂方法时会调用它提供的LogAdapter.creatLog()
public abstract class LogFactory { //外部接口 获取Log实例 这个Log接口跟commons-loggings的一样 public static Log getLog(Class<?> clazz) { return getLog(clazz.getName()); } public static Log getLog(String name) { //调用提供的适配器方法 return LogAdapter.createLog(name); }
LogAdapter根据logApi属性分别使用各种实现创建Log,再后面会加载实际的实现类,通过实际的实现类进行打日志。因此,走到某个分支的时候,其实现类必须在类路径上,否则肯定就ClassNotFound了
package org.apache.commons.logging; import java.io.Serializable; //注意 这些类jul的 import java.util.logging.LogRecord; //注意 这些类是log4j的 import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.spi.ExtendedLogger; import org.apache.logging.log4j.spi.LoggerContext; //注意 这些类是slf4j的 import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.spi.LocationAwareLogger; //检测日志优先级:log4j2 -> sl4j,都没有则使用jul /** * Spring's common JCL adapter behind {@link LogFactory} and {@link LogFactoryService}. * Detects the presence of Log4j 2.x / SLF4J, falling back to {@code java.util.logging}. * * @author Juergen Hoeller * @since 5.1 */ final class LogAdapter { 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; static { //先判断是否有log4j2实现 if (isPresent(LOG4J_SPI)) { //如果有log4j2 同时 又有log4j到slf4j的桥接器和slf4j的绑定实现 那么日志就使用slf4j if (isPresent(LOG4J_SLF4J_PROVIDER) && isPresent(SLF4J_SPI)) { //使用slf4j // 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 { //否则就直接使用llog4j2 // Use Log4j 2.x directly, including location awareness support logApi = LogApi.LOG4J; } } else if (isPresent(SLF4J_SPI)) { //如果没有log4j2 有slf4j的绑定实现则使用slf4j的绑定实现 // Full SLF4J SPI including location awareness support logApi = LogApi.SLF4J_LAL; } else if (isPresent(SLF4J_API)) { //如果只有slf4j的api那么使用slf4j // Minimal SLF4J API without location awareness support logApi = LogApi.SLF4J; } else { //默认情况下 使用jdk的日志实现 // java.util.logging as default logApi = LogApi.JUL; } } private LogAdapter() { } /** * Create an actual {@link Log} instance for the selected API. * @param name the logger name */ public static Log createLog(String name) { //根据类路径上的日志门面及日志实现情况 判断使用哪种日志 这里的XxxAdapter会加载它们对应的实现类创建具体的log实例 在使用这里的Log打印日志时 实际调用实际的实现 如log4j的 下面分析下Log4jAdapter.createLog() switch (logApi) { case LOG4J: return Log4jAdapter.createLog(name); case SLF4J_LAL: return Slf4jAdapter.createLocationAwareLog(name); case SLF4J: return Slf4jAdapter.createLog(name); default: // Defensively use lazy-initializing adapter class here as well since the // java.logging module is not present by default on JDK 9. We are requiring // its presence if neither Log4j nor SLF4J is available; however, in the // case of Log4j or SLF4J, we are trying to prevent early initialization // of the JavaUtilLog adapter - e.g. by a JVM in debug mode - when eagerly // trying to parse the bytecode for all the cases of this switch clause. return JavaUtilAdapter.createLog(name); } } //判断类是否在类路径上的工具方法 private static boolean isPresent(String className) { try { Class.forName(className, false, LogAdapter.class.getClassLoader()); return true; } catch (ClassNotFoundException ex) { return false; } } //不同日志实现的枚举 private enum LogApi {LOG4J, SLF4J_LAL, SLF4J, JUL} //xxxAdapter分别创建对应的内部类实例 内部类实例分别调用对应实现的包内的api获取对应logger进行日志打印 private static class Log4jAdapter { public static Log createLog(String name) { return new Log4jLog(name); //直接创建这个内部类的一个实例 } } private static class Slf4jAdapter { //... } private static class JavaUtilAdapter { //... } @SuppressWarnings("serial") private static class Log4jLog implements Log, Serializable { private static final String FQCN = Log4jLog.class.getName(); private static final LoggerContext loggerContext = LogManager.getContext(Log4jLog.class.getClassLoader(), false); private final ExtendedLogger logger; public Log4jLog(String name) { LoggerContext context = loggerContext; if (context == null) { // Circular call in early-init scenario -> static field not initialized yet context = LogManager.getContext(Log4jLog.class.getClassLoader(), false); } this.logger = context.getLogger(name); } //使用logger打印日志 这个logger就是log4j2的logger //... } @SuppressWarnings("serial") private static class Slf4jLog<T extends Logger> implements Log, Serializable { protected final String name; protected transient T logger; public Slf4jLog(T logger) { this.name = logger.getName(); this.logger = logger; } // 使用slf4j打印日志 //... protected Object readResolve() { return Slf4jAdapter.createLog(this.name); } } // 这个Slf4jLocationAwareLog继承了Slf4jLog @SuppressWarnings("serial") private static class Slf4jLocationAwareLog extends Slf4jLog<LocationAwareLogger> implements Serializable { //... } @SuppressWarnings("serial") private static class JavaUtilLog implements Log, Serializable { private String name; private transient java.util.logging.Logger logger; public JavaUtilLog(String name) { this.name = name; this.logger = java.util.logging.Logger.getLogger(name); } //... } //... }
总结
spring-jcl从jcl改造而来,使用了同样的Log接口,重写了LogFactory,这里面通过根据类路径上的日志实现情况加载对应的日志实现,从而实现在使用spring框架的时候,可以动态调整应用程序中使用的日志实现。
如使用log4j2,添加log4j2的依赖即可
如果使用logback,添加logback依赖即可(slf4j的直接实现)
---
本文来自博客园,作者:Bingmous,转载请注明原文链接:https://www.cnblogs.com/bingmous/p/17923036.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?