分析之前先理清楚几个概念

Log4J = Log For Java

SLF4J = Simple Logging Facade for Java

看到Facade首先想到的就是设计模式中的门面(Facade)模式,实际上SLF4J 就是一个装"门面"的java日志框架,它只提供一层抽象且通用的日志API供调用方写日志使用,而真正实现写日志功能的则是Log4J、logback等框架和从jdk1.4之后开始提供的java.util.logging包,而具体要使用谁就要看SLF4J中设置的策略,整体来看的话也确实是使用了门面模式

关于这几个日志框架的诞生和关系推荐看下这篇博客:https://blog.csdn.net/qq_32625839/article/details/80893550

Apache的commons-logging和SLF4J 一样,也是一个抽象的日志框架,使用得更广泛,下面通过几段源码分析下其内部的门面模式是怎样实现的

一般写日志之前都要先用下面的方法获取到log对象

Log log = LogFactory.getLog(clz.getName());

进入getLog方法
    public static Log getLog(String name) throws LogConfigurationException {
return getFactory().getInstance(name);
}

看来是先获取到log工厂对象再获取到log对象,进入getFactory方法

//获取到上下文中的类加载器
ClassLoader contextClassLoader = getContextClassLoaderInternal();
//先尝试从缓存中获取LogFactory
LogFactory factory = getCachedFactory(contextClassLoader);
if (factory != null) {
  return factory;
}

//如果存在commons-logging.properties配置文件且其中的use_tccl配置项为false,则使用thisClassLoader作为后续的类加载器,thisClassLoader就是当前类的加载器
  Properties props = getConfigurationFile(contextClassLoader, FACTORY_PROPERTIES);
  ClassLoader baseClassLoader = contextClassLoader;
  if (props != null) {
    String useTCCLStr = props.getProperty(TCCL_KEY);
    if (useTCCLStr != null) {
      if (Boolean.valueOf(useTCCLStr).booleanValue() == false) {
        baseClassLoader = thisClassLoader;
      }
    }
  }

//获取org.apache.commons.logging.LogFactory系统配置项,若存在则使用配置的类新建一个工厂对象
String factoryClass = getSystemProperty(FACTORY_PROPERTY, null);
if (factoryClass != null) {
factory = newFactory(factoryClass, baseClassLoader, contextClassLoader);
} //利用java中的SPI机制根据META-INF/services/org.apache.commons.logging.LogFactory中的配置类新建工厂对象
if (factory == null) {
try {
final InputStream is = getResourceAsStream(contextClassLoader, SERVICE_ID);
     ...
     factory = newFactory(factoryClassName, baseClassLoader, contextClassLoader );
     ... //根据commons-logging.properties中的配置属性org.apache.commons.logging.LogFactory新建工厂对象

if (factory == null) {
    String factoryClass = props.getProperty(FACTORY_PROPERTY);
    factory = newFactory(factoryClass, baseClassLoader, contextClassLoader);

//如果以上都没成功创建工厂对象则直接使用默认的org.apache.commons.logging.impl.LogFactoryImpl类创建

factory = newFactory(FACTORY_DEFAULT, thisClassLoader, contextClassLoader);

//最后放入缓存

cacheFactory(contextClassLoader, factory);

一般上述过程中属性配置都不加的话默认的logFactory实现类就是LogFactoryImpl,然后进入到它的getInstance方法看下

public Log getInstance(String name) throws LogConfigurationException {
Log instance = (Log) instances.get(name);
if (instance == null) {
instance = newInstance(name);
instances.put(name, instance);
}
return instance;
}

进入newInstance

    protected Log newInstance(String name) throws LogConfigurationException {
Log instance;
try {
if (logConstructor == null) {
instance = discoverLogImplementation(name);
}
else {
Object params[] = { name };
instance = (Log) logConstructor.newInstance(params);
} if (logMethod != null) {
Object params[] = { this };
logMethod.invoke(instance, params);
} return instance;

进入discoverLogImplementation

    private Log discoverLogImplementation(String logCategory)
throws LogConfigurationException {
initConfiguration();
Log result = null;     //findUserSpecifiedLogClassName方法内部也是读取一些系统配置项,如果读取到了就根据配置的类名来创建日志对象
        String specifiedLogClassName = findUserSpecifiedLogClassName();
if (specifiedLogClassName != null) {
result = createLogFromClass(specifiedLogClassName,logCategory,true);
if (result == null) {
StringBuffer messageBuffer = new StringBuffer("User-specified log class '");
messageBuffer.append(specifiedLogClassName);
messageBuffer.append("' cannot be found or is not useable.");
informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_LOG4J_LOGGER);
informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_JDK14_LOGGER);
informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_LUMBERJACK_LOGGER);
informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_SIMPLE_LOGGER);
throw new LogConfigurationException(messageBuffer.toString());
}
return result;
}
     //如果上面没有创建成功则根据classesToDiscover数组的值作为类名依次创建日志对象,直到创建成功(不为空)就返回
     for(int i=0; i<classesToDiscover.length && result == null; ++i) {
result = createLogFromClass(classesToDiscover[i], logCategory, true);
} if (result == null) {
throw new LogConfigurationException
("No suitable Log implementation");
}
return result;
}

其中classesToDiscover数组的值是写死的

private static final String[] classesToDiscover = {
  "org.apache.commons.logging.impl.Log4JLogger",
  "org.apache.commons.logging.impl.Jdk14Logger",
  "org.apache.commons.logging.impl.Jdk13LumberjackLogger",
  "org.apache.commons.logging.impl.SimpleLog"
}

所以当我们的项目中引入的commons-logging和Log4j的jar包,其实不需要做任何配置,就会优先使用Log4JLogger做为实际的写日志实现类

整个过程中有两点需要注意的:

1.日志工厂对象和日志实现类对象都是先使用当前类或者thread context的classLoad将类加载进来再通过反射创建对象,这样的话如果有的插件使用自定义的classLoad加载,当插件内部打印日志时可能会出现无法创建日志对象或者使用了和预期不一致的日志对象

2.动态查找:对日志工厂实现类和日志实现类的动态查找基本上都是通过读取系统配置或者代码里写死的方式来查找,其实可以通过java SPI机制来实现

slf4J的实现方式与上述是完全不同的,它采用的是静态绑定,可以避免classLoad的问题,但在使用时要引入各种桥接包,如果引入了两个相反的桥接包就会导致循环依赖的StackOverflow

最新文章

  1. 记录最初工作下的笔记($.each)
  2. 分享一组矢量图标–UX图标字体库
  3. python enumerate函数用法
  4. 直接拿来用!最火的Android开源项目
  5. js实现加减乘除
  6. 2014-9-17二班----6 web project
  7. [转载]WCF序列化65536大小限制的问题
  8. centos更新163源并升级内核
  9. Java反编译器安装及各版本介绍
  10. ZOJ 2745 01-K Code(DP)(转)
  11. Python读写文件需要注意的地方 2015-03-31 23:19 69人阅读 评论(0) 收藏
  12. 几个学习Maven不错的网址
  13. CSS学习笔记05 display属性
  14. 从async await 报错Unexpected identifier 谈谈对上下文的理解
  15. Effective Java 第三版——11. 重写equals方法时同时也要重写hashcode方法
  16. Kafka的partions和replication-factor参数的理解
  17. linux系统自动备份打包部署脚本
  18. String、StringBuffer、StringBuilder的比较
  19. gevent实现异步
  20. 使用ASP.NET Core的User Secrets特性

热门文章

  1. 关于Lombok和自动生成get set方法
  2. 什么是JS跨域请求
  3. (转)VSCode调试go语言出现:exec: &quot;gcc&quot;: executable file not found in %PATH%
  4. 如何用ModelSim对Xilinx ISE产生的网表进行仿真
  5. JAVA超级简单的爬虫例子(1)
  6. 在vue项目中遇到关于对象的深浅拷贝问题
  7. c++学习书籍推荐《C标准库(英文版)》下载
  8. Spring Cloud Alibaba | Nacos配置管理
  9. Java编程思想:内部类中级部分
  10. re模块:模式匹配与正则表达式