简单日志门面(SimpleLoggingFacadeForJava)SLF4J主要是为了给Java日志访问提供一套标准、规范的API框架,其主要意义在于提供接口,具体的实现可以交由其他日志框架,例如log4j和logback等。当然slf4j自己也提供了功能较为简单的实现,但是一般很少用到。对于一般的Java项目而言,日志框架会选择slf4j-api作为门面,配上具体的实现框架(log4j、logback等)。
SLF4J官方网站:https://www.slf4j.org/
使用SLF4J来做一个小案例
步骤一:引入坐标
<!--slf4j门面依赖-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.27</version>
</dependency>
<!--slf4j自带的简单日志实现-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.27</version>
</dependency>
步骤二:编写代码进行测试
@Test
public void normalTest(){
//和log4j类似,传入类名获取logger对象
Loggerlogger=LoggerFactory.getLogger(Slf4jTest.class);
//slf4j默认只有五个等级,调用logger对象进行各个等级的输出
logger.error("error");
logger.warn("warn");
logger.info("info");
logger.debug("debug");
logger.trace("trace");
//使用占位符来进行日志输出
logger.warn("{}今年{}岁了","小明",15);
//slf4j也支持我们将异常作为参数传入
logger.error("出现异常:",newNullPointerException());
}
步骤三:运行代码,观察结果
SLF4J绑定其他日志实现
SLF4J的出现时间其实是相对较晚的,所以即使SLF4J
提出了很好的接口规范,但问题在于以前的日志实现并没有实现后面才提出来的日志规范,在这种情况下,SLF4J
使用适配器模式的设计思想来解决了这个问题。我们可以来简单了解一下SLF4J的解决方案。
使用适配器模式解决接口和实现不对应的问题
假如存在LogImpl类,类中方法如下:
class LogImpl{
public void mylog(){
System.out.println("logImplislogging...");
}
}
现在存在一个接口Logable
interface Logable{
void log();
}
使用Logable
接口的方法调用LogImpl
方法可以怎么办呢?其实很简单,引入一个适配器即可:
class LogAdaptor extends LogImpl implements Logable{
public void log(){
mylog();
}
}
SLF4J
正是使用的这种方法来解决原先的日志框架没有实现其接口规范的问题。
使用SLF4J的绑定来做一个小案例
步骤一:引入坐标依赖
<!--slf4j门面依赖-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.27</version>
</dependency>
<!--log4j适配log4j的依赖包-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.27</version>
</dependency>
<!--log4j具体实现-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
步骤二:在resources目录下配置log4j.properties
文件
#指定日志的输出级别与输出端
log4j.rootLogger=WARN,Console
#控制台输出配置
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=%d[%t]%-5p[%c]-%m%n
步骤三:编写测试代码
@Test
public void normalTest(){
Loggerlogger=LoggerFactory.getLogger(Slf4jTest.class);
logger.error("error");
logger.warn("warn");
logger.info("info");
logger.debug("debug");
logger.trace("trace");
//使用占位符来进行日志输出
logger.warn("{}今年{}岁了","小明",15);
//slf4j也支持我们将异常作为参数传入
logger.error("出现异常:",newNullPointerException());
}
步骤四:运行代码,观察结果
使用桥接来维护以前的日志框架
依赖的某些组件使用了SLF4J以外的日志记录API,且这些组件在不久的将来也不会切换到SLF4J。为了解决这种情况,SLF4J附带了几个桥接模块,这些模块将对log4j,JCL和java.util.loggingAPI的调用重定向,就好像它们是对SLF4J的API一样。
简单来说就是,对于项目的依赖包使用到了其他日志框架这种情况,很明显我们在没有源码的情况下无法手动将他们的日志框架进行切换,所以SLF4J
使用了桥接的设计方式来解决这种问题
使用桥接模式解决旧接口和新接口不一致的问题
public class LogImpl{
public void mylog(){
System.out.println("logImplislogging...");
}
}
public class UserService{
public void login(){
newLogImpl().mylog();
//省略具体方法
}
}
public interface Logable{
void log();
}
class MyLog implementsLogable{
public void log(){
System.out.println("MyLogislogging...");
}
}
使用SLF4J的桥接包来做一个小案例
步骤一:引入依赖
<!--slf4j提供log4j的桥接包-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
<version>1.7.27</version>
</dependency>
<!--slf4j的门面依赖-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.27</version>
</dependency>
<!--slf4j接口的实现包-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.27</version>
</dependency>
步骤二:编写测试代码
public class Slf4jBridgeTest{
@Test
public void bridgeTest(){
Loggerlogger=Logger.getLogger(Slf4jBridgeTest.class);
logger.error("error");
logger.warn("warn");
logger.info("info");
logger.trace("trace");
logger.debug("debug");
logger.fatal("fatal");
}
}
步骤三:运行代码,观察结果
使用日志门面的优势
面向接口编程。无论底层实现怎么改变,我们都不需要更改任何项目中的日志代码,比如项目一开始是使用log4j作为日志实现,处于性能考虑想要将项目中的日志框架改为log4j2或者logback,那么此时我们只需要改一下pom文件的依赖就行,其他代码完全不需要改变。
对于历史代码有较好的适配依赖进行处理。
使用SLF4J的注意事项
SLF4J不依赖于任何特殊的类装载。实际上,每个SLF4J绑定在编译时都是硬连线的,使用一个且只有一个特定的日志记录框架。
同对象的桥接包不可以和绑定包一起引入(例如jcl-over-slf4j.jar和slf4j-jcl.jar不能同时部署),否则会出现循环依赖
所有的桥接都只对Logger日志记录器对象有效,如果程序中调用了内部的配置类或者是Appender,Filter等对象,将无法产生效果。