spring-jcl 模块源码分析

发布时间 2023-12-23 14:40:04作者: Bingmous

简介

spring-jcl是spring用于处理日志打印的模块,被spring-core所依赖:
image

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的直接实现)

日志详细配置参考:https://www.cnblogs.com/bingmous/p/15786789.html