为什么要写?
工作中,可能我们本系统中用的是logback框架,然而当我们引入别人的A包时,包中的日志打印框架为log4j,导致包冲突,因此学习一下,记录一下。
什么是log4j、logback、slf4j-simple框架?
它们都是日志框架,拥有具体实现。
- log4j是apache实现的一个开源日志组件。
- logback同样是由log4j的作者设计完成的,拥有更好的特性,用来取代log4j的一个日志框架。
- slf4j提供的一个简单实现。
什么是slf4j?
slf4j是日志门面,只是实现了这些日志框架的一些通用接口,具体实现是那些框架所实现的。
- logback是直接实现了slf4j接口,因此不消耗内存和计算开销。
- log4j不是对slf4j的原生实现,所以slf4j在调用log4j时,需要一个适配器。
- slf4j-api:是日志接口
- slf4j-simple:是slf4j提供的一个简单实现。
在任何一个项目中,都不允许单独使用日志框架。(阿里巴巴开发手册已强调)
假设项目中已经使用了log4j,而我们此时加载了一个类库,而这个类库依赖另一个日志框架。这个时候我们就需要维护两个日志框架,这是一个非常麻烦的事情。而使用了slf4j就不同了,由于应用调用的抽象层的api,与底层日志框架是无关的,因此可以任意更换日志框架。
所涉及的模式-门面(外观)模式
定义
通过创建一个统一的类,用来包装子系统中一个或多个复杂的类,客户端可以通过调用外观类的方法来调用内部子系统中所有方法
原本客户端是直接通过调用各个子系统的,通过门面模式,创建一个外观类,使得客户端直接调用外观类,来间接去操作相关子系统。
主要作用
- 实现客户端与子系统之间的松耦合(子系统的修改,不会影响到客户端)
- 降低原有系统的复杂度(例如:各个电器之间的开关按钮,如果客户端需要关闭整个房间里的电器,那么原来要调用所有电器的off方法,而现在只需要调用外观类的off方法即可)
- 提高客户端便捷性,使客户端无需关心子系统的工作细节,通过外观类调用。
解决的问题
- 1.避免了系统与系统之间的高度耦合。
- 2.使得复杂的子系统使用起来更简单。
优缺点
优点:
- 实现了子系统与客户端之间的松耦合关系
1.只是提供了一个访问子系统的统一入口,并不影响用户直接使用子系统类;
2.减少了与子系统的关联对象,实现了子系统与客户之间的松耦合关系,松耦合使得子系统的组件变化不会影响到它的客户;
- 外观模式对客户屏蔽了子系统组件,从而简化了接口,减少了客户处理的对象数目并使子系统的使用更加简单
1.引入外观角色之后,用户只需要与外观角色交互;
2.用户与子系统之间的复杂逻辑关系由外观角色来实现
- 降低原有系统的复杂度和系统中的编译依赖性,并简化了系统在不同平台之间的移植过程
因为编译一个子系统一般不需要编译所有其他的子系统。一个子系统的修改对其他子系统没有任何影响,而且子系统内部变化也不会影响到外观对象。
缺点:
- 在不引入抽象外观类的情况下,增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”。
- 不能很好地限制客户使用子系统类,如果对客户访问子系统类做太多的限制则减少了可变性和灵活性
阅读源码
pom添加依赖:
<!--slf4j只负责提供接口,日志门面-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.5</version>
</dependency>
<!--logback日志框架-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<!--log4j日志框架-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!--log4j适配转换-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.21</version>
</dependency>
<!--slf4j简单实现-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.25</version>
</dependency>
当我们只引入了slf4j-api包,执行日志打印操作控制台会报错。
明确告知我们没有日志实现类。
引入三个日志框架后,执行结果如下:
SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/Users/yinxin/.m2/repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/Users/yinxin/.m2/repository/org/slf4j/slf4j-log4j12/1.7.21/slf4j-log4j12-1.7.21.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/Users/yinxin/.m2/repository/org/slf4j/slf4j-simple/1.7.25/slf4j-simple-1.7.25.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [ch.qos.logback.classic.util.ContextSelectorStaticBinder]
19:57:20.867 [main] ERROR com.lion.LogBean - print log
开始阅读
//得到日志具体类
public static Logger logger = LoggerFactory.getLogger(LogBean.class);
进入到里面
public static Logger getLogger(Class clazz) {
return getLogger(clazz.getName());
}
将className获取,作为参数传入
public static Logger getLogger(String name) {
ILoggerFactory iLoggerFactory = getILoggerFactory();
return iLoggerFactory.getLogger(name);
}
/** ILoggerFactory实例绑定是在类编译时期完成的 */
public static ILoggerFactory getILoggerFactory() {
if (INITIALIZATION_STATE == UNINITIALIZED) {
INITIALIZATION_STATE = ONGOING_INITIALIZATION;
performInitialization();
}
switch (INITIALIZATION_STATE) {
case SUCCESSFUL_INITIALIZATION:
return StaticLoggerBinder.getSingleton().getLoggerFactory();
case NOP_FALLBACK_INITIALIZATION:
return NOP_FALLBACK_FACTORY;
case FAILED_INITIALIZATION:
throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
case ONGOING_INITIALIZATION:
// support re-entrant behavior.
// See also http://bugzilla.slf4j.org/show_bug.cgi?id=106
return TEMP_FACTORY;
}
throw new IllegalStateException("Unreachable code");
}
getILoggerFactory()获取到一个工厂类,这个ILoggerFactory是一个接口,可以看到每个日志框架都有一个工厂类实现了这个接口。
getILoggerFactory()说的是,如果未初始化工厂类,那么进行初始化,如果成功初始化,那么调用StaticLoggerBinder.getSingleton().getLoggerFactory()
来进行获取工厂类。
我们看一下performInitialization()方法
private static String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class";
private final static void performInitialization() {
bind();
if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {
versionSanityCheck();
}
}
private final static void bind() {
try {
Set staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
// the next line does the binding
StaticLoggerBinder.getSingleton();
INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
reportActualBinding(staticLoggerBinderPathSet);
emitSubstituteLoggerWarning();
} catch (NoClassDefFoundError ncde) {
String msg = ncde.getMessage();
if (messageContainsOrgSlf4jImplStaticLoggerBinder(msg)) {
INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION;
Util.report("Failed to load class \"org.slf4j.impl.StaticLoggerBinder\".");
Util.report("Defaulting to no-operation (NOP) logger implementation");
Util.report("See " + NO_STATICLOGGERBINDER_URL
+ " for further details.");
} else {
failedBinding(ncde);
throw ncde;
}
} catch (java.lang.NoSuchMethodError nsme) {
String msg = nsme.getMessage();
if (msg != null && msg.indexOf("org.slf4j.impl.StaticLoggerBinder.getSingleton()") != -1) {
INITIALIZATION_STATE = FAILED_INITIALIZATION;
Util.report("slf4j-api 1.6.x (or later) is incompatible with this binding.");
Util.report("Your binding is version 1.5.5 or earlier.");
Util.report("Upgrade your binding to version 1.6.x.");
}
throw nsme;
} catch (Exception e) {
failedBinding(e);
throw new IllegalStateException("Unexpected initialization failure", e);
}
}
private static Set findPossibleStaticLoggerBinderPathSet() {
// use Set instead of list in order to deal with bug #138
// LinkedHashSet appropriate here because it preserves insertion order during iteration
Set 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 ioe) {
Util.report("Error getting resources from path", ioe);
}
return staticLoggerBinderPathSet;
}
进入bind方法,findPossibleStaticLoggerBinderPathSet方法是用来获取StaticLoggerBinder.class对象集合的,然后执行reportMultipleBindingAmbiguity方法,这个方法是打印警告信息如果在类路径上发现了多个StaticLoggerBinder。紧接着实例化StaticLoggerBinder对象,指定真正所用到的,这个看类加载器喜欢哪个加载哪个吧。reportActualBinding方法打印告知选择的是哪一个StaticLoggerBinder对象,然后返回。通过该类来得到一致具体的工厂类,通过这个工厂类来得到具体的日志类,从而让我们的客户端可以打印输出。
带你弄清混乱的JAVA日志体系!
http://ju.outofmemory.cn/entry/320663
https://www.cnblogs.com/bigdataZJ/p/springboot-log.html