java
自带的一个日志记录的技术,直接使用。
java.util.logging.Logger
//log4j依赖
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>1.2.17version>
dependency>
//log4配置文件
log4j.rootLogger=info, stdout
#mybatis的sql级别(结果的日志级别为TRACE,SQL 语句的日志级别为DEBUG)
log4j.logger.com.log.dao=TRACE
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n
//log4j测试类
public static void main(String[] args) {
Logger log4j = Logger.getLogger("log4j");
log4j.info("log4j");
}
//输出
2019-09-03 11:17:06,273 INFO [log4j] - log4j
//jcl依赖
<dependency>
<groupId>commons-logginggroupId>
<artifactId>commons-loggingartifactId>
<version>1.2version>
dependency>
//jcl测试类
public static void main(String[] args) {
Log jcl = LogFactory.getLog("jcl");
jcl.info("jcl");
}
//输出
(1):2019-09-03 11:23:54,470 INFO [jcl] - jcl
(2):九月 03, 2019 11:24:43 上午 jcl main
信息: jcl
这个jcl
什么情况下会有不同的输出?
1.当项目有logj4
的依赖的时候,就会输出(1)信息。
2.当项目没有log4j
依赖的时候,就会使用java
自带的日志技术jul
输出(2)信息。
jcl源码分析:
#LogFactory
public static Log getLog(String name) throws LogConfigurationException {
//通过Factory获取instance实例
return getFactory().getInstance(name);
}
#LogFactoryImpl
public Log getInstance(String name) throws LogConfigurationException {
//一开始没有缓存,所以为null
Log instance = (Log) instances.get(name);
if (instance == null) {
//这里是重点,创建logger实例
instance = newInstance(name);
//放入到缓存中
instances.put(name, instance);
}
return instance;
}
protected Log newInstance(String name) throws LogConfigurationException {
Log instance;
try {
if (logConstructor == null) {
//重要代码,发现log实现类
instance = discoverLogImplementation(name);
}
else {
Object params[] = { name };
instance = (Log) logConstructor.newInstance(params);
}
if (logMethod != null) {
Object params[] = { this };
logMethod.invoke(instance, params);
}
return instance;
}
}
private Log discoverLogImplementation(String logCategory)
throws LogConfigurationException {
if (isDiagnosticsEnabled()) {
logDiagnostic("Discovering a Log implementation...");
}
initConfiguration();
//需要返回的对象
Log result = null;
//查看用户是否指定要使用的日志实现
String specifiedLogClassName = findUserSpecifiedLogClassName();
if (specifiedLogClassName != null) {
if (isDiagnosticsEnabled()) {
logDiagnostic("");
}
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.");
// Mistyping or misspelling names is a common fault.
// Construct a good error message, if we can
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;
}
if (isDiagnosticsEnabled()) {
logDiagnostic("");
}
//如果用户没有指定日志实现类,jcl使用默认的实现类(4个),然后遍历依次创建对应的log实现类。
for(int i=0; i<classesToDiscover.length && result == null; ++i) {
result = createLogFromClass(classesToDiscover[i], logCategory, true);
}
if (result == null) {
throw new LogConfigurationException("");
}
return result;
}
//jcl内部默认的4个log实现类。
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"
};
//根据class创建log对象
private Log createLogFromClass(String logAdapterClassName,
String logCategory,
boolean affectState)
throws LogConfigurationException {
Object[] params = { logCategory };
Log logAdapter = null;
Constructor constructor = null;
Class logAdapterClass = null;
ClassLoader currentCL = getBaseClassLoader();
for(;;) {
logDiagnostic("Trying to load '" + logAdapterClassName + "' from classloader " + objectId(currentCL));
try {
Class c;
try {
//通过class.forName加载log类
c = Class.forName(logAdapterClassName, true, currentCL);
} catch (ClassNotFoundException originalClassNotFoundException) {
//表示找不到类(因为用户可能指定类log的实现类)
try {
c = Class.forName(logAdapterClassName);
} catch (ClassNotFoundException secondaryClassNotFoundException) {
break;
}
}
//拿到c的构造方法对象。然后通过构造对象来创建对象。(有参构造方法)
constructor = c.getConstructor(logConstructorSignature);
Object o = constructor.newInstance(params);
if (o instanceof Log) {
logAdapterClass = c;
logAdapter = (Log) o;
break;
}
handleFlawedHierarchy(currentCL, c);
} catch (NoClassDefFoundError e) {
String msg = e.getMessage();
logDiagnostic("");
break;
} catch (ExceptionInInitializerError e) {
String msg = e.getMessage();
logDiagnostic("");
break;
} catch (LogConfigurationException e) {
throw e;
} catch (Throwable t) {
handleThrowable(t);
handleFlawedDiscovery(logAdapterClassName, currentCL, t);
}
if (currentCL == null) {
break;
}
currentCL = getParentClassLoader(currentCL);
}
//返回创建的对象
return logAdapter;
}
jcl
本身不实现日志记录,但是提供了记录日志的抽象方法即接口(info,debug,error…)
底层通过一个数组存放具体的日志框架的类名,然后循环数组依次去匹配这些类名是否在项目中被依赖了,如果找到被依赖的则直接使用,所以他有先后顺序。
下图为jcl
中存放日志技术类名的数组,默认有四个,后面两个可以忽略。
上面的代码81
行就是通过一个类名去load
一个class
,如果load
成功则直接new
出来并且返回使用。如果没有load
到class
这循环第二个,直到找到为止。
可以看到这里的循环条件必须满足result
不为空,也就是如果没有找到具体的日志依赖则继续循环,如果找到则条件不成立,不进行循环了。
总结:顺序log4j>jul
虽然Log4JLogger
是jcl
的jar
包中的类,但是该Log4JLogger
类,依赖了log4j
的类,当你没有引入log4j
的依赖的时候,在创建Log4JLogger
类,就会失败。
下图是在没有引入log4j
依赖的情况下,Log4JLogger
类的情况图:
因为jul
是java
自带的日志类,所以在java
环境下,jcl
就算在创建log4j
失败的情况下,也会去创建Jdk14Logger
。可以看到Jdk14Logger
是依赖了jul
的。下图是Jdk14Logger
的类图:
(jul)
。jcl
是一个接口,默认有4
个log
实现类。slf4j
他也不记录日志,通过绑定器绑定一个具体的日志记录来完成日志记录
官网:https://www.slf4j.org/
//slf4j依赖
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-apiartifactId>
<version>1.7.25version>
dependency>
在只添加了slf4j
依赖,而没有添加任意一个绑定器,日志是不会打印的。控制台会输出warn
信息,如下图:
//slf4j依赖
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-apiartifactId>
<version>1.7.25version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-jdk14artifactId>
<version>1.7.25version>
dependency>
slf4j
提供了很多的绑定器,有log4j,jul,jcl
。
当引入了jul
绑定器之后,slf4j
就能打印日志了,如下图:
slf4j
需要打印日志,就一定需要引入绑定器。slf4j
提供了很多的绑定器,有jul,jcl,log4j
等。
slf4j
如果引入了jcl
绑定器,因为jcl
也是一个接口,jcl
会加载log4j,jul
。
如果你想使用log4j
,也需要引入log4j
的依赖,log4j
的配置文件
如果你不引入log4j
的依赖,就默认使用jul
slf4j
如果引入了log4j
绑定器,需要log4j
的配置文件(这个时候不用引入log4j
的依赖了,因为该绑定器已经帮我们引入了)
项目A
,是使用了slf4j
打印日志,然后通过slf4j
绑定器,绑定到jul
,然后使用jul
打印日志。
项目A
使用了spring
框架,但是spring
框架使用jcl
打印日志,spring
引入了log4j
依赖,使得spring
框架是用log4j
打印日志。
这样的话,项目A
就出现了多个日志框架打印日志了,那就很混乱,现在要求项目A
只能使用一种日志框架技术,这个要怎么处理?
//项目A的依赖
//log4j
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>1.2.17version>
dependency>
//jcl
<dependency>
<groupId>commons-logginggroupId>
<artifactId>commons-loggingartifactId>
<version>1.2version>
dependency>
//slf4j
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-apiartifactId>
<version>1.7.25version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-jdk14artifactId>
<version>1.7.25version>
dependency>
就引入上面的依赖jar
包,项目就会有两种日志输出,如下图所示:
可以通过修改slf4j
的绑定器,直接改用slf4j
的log4j
绑定器。
因为使用slf4j
绑定到log4j
,只需要简单的引入一个依赖即可。
使用slf4j
的桥接器,将spring
使用jcl
打印日志这步切断,将jclj
桥接到slf4j
,然后再走项目A
的日志打印。
因为使用jcl
桥接到slf4j
,只需要简单的引入一个依赖即可。
//增加下面这个依赖即可:
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>jcl-over-slf4jartifactId>
<version>1.7.25version>
dependency>
当使用了jcl-over-slf4j
桥接器之后,可以使得jcl
桥接到slf4j
,然后再走slf4j
这边的日志输出。从而实现将spring
框架的日志输出,改为了slf4j–>jul
Spring4
当中依赖jcl
,即Spring4
当中采用的日志技术是jcl:commons-logging
,即默认使用jul
;加入log4j
依赖和配置,即可切换为log4j
Spring5
当中也是使用了jcl:spring-jcl
,是重写为了jul
框架。spring5
使用的spring
的jcl
(spring
改了jcl
的代码)来记录日志的,但是jcl
不能直接记录日志,采用循环优先的原则。
#AbstractApplicationContext
protected final Log logger = LogFactory.getLog(getClass());
#LogFactory(spring-jcl包下)
public static Log getLog(Class<?> clazz) {
return getLog(clazz.getName());
}
public static Log getLog(String name) {
switch (logApi) {
//log4j2
case LOG4J:
return Log4jDelegate.createLog(name);
//slf4j
case SLF4J_LAL:
return Slf4jDelegate.createLocationAwareLog(name);
case SLF4J:
return Slf4jDelegate.createLog(name);
default:
//默认是jul
return JavaUtilDelegate.createLog(name);
}
}
//默认是jul
private static LogApi logApi = LogApi.JUL;
//静态代码块,在类初始化的时候执行
static {
ClassLoader cl = LogFactory.class.getClassLoader();
try {
// Try Log4j 2.x API(尝试加载log4j2)
cl.loadClass("org.apache.logging.log4j.spi.ExtendedLogger");
logApi = LogApi.LOG4J;
}
catch (ClassNotFoundException ex1) {
try {
// Try SLF4J 1.7 SPI(尝试加载slf4j)
cl.loadClass("org.slf4j.spi.LocationAwareLogger");
logApi = LogApi.SLF4J_LAL;
}
catch (ClassNotFoundException ex2) {
try {
// Try SLF4J 1.7 API(尝试加载slf4j)
cl.loadClass("org.slf4j.Logger");
logApi = LogApi.SLF4J;
}
catch (ClassNotFoundException ex3) {
// Keep java.util.logging as default(如果都没有,就保持使用默认的jul)
}
}
}
}
从上面spring5
的源码可以看到,spring5
使用的日志是spring-jcl
,默认是jul
,然后会依次加载log4j2,slf4j
。在都加载不到的情况下,就使用默认的jul
日志技术了。
因为spring5
使用的是log4j2
,所以在加入了log4j
的依赖和配置文件,是不生效的。
spring5
使用log4j2
日志技术,需要加入的依赖和配置文件。
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.0.9.RELEASEversion>
dependency>
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-coreartifactId>
<version>2.8.2version>
dependency>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
Console>
Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="Console"/>
Root>
Loggers>
Configuration>
spring5
改用log4j
来记录日志,怎么实现?1.引入slf4j
依赖,然后slf4j
绑定log4j
,并添加log4j
的配置文件。
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.0.9.RELEASEversion>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-apiartifactId>
<version>1.7.25version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-log4j12artifactId>
<version>1.7.5version>
dependency>
#LogFactory
private static void tryImplementation(Runnable runnable) {
//关键代码 logConstructor == null 没有找到实现则继续找
if (logConstructor == null) {
try {
runnable.run();
} catch (Throwable t) {
// ignore
}
}
}
tryImplementation(new Runnable() {
@Override
public void run() {
useSlf4jLogging();
}
});
public static synchronized void useSlf4jLogging() {
setImplementation(org.apache.ibatis.logging.slf4j.Slf4jImpl.class);
}
private static void setImplementation(Class<? extends Log> implClass) {
try {
Constructor<? extends Log> candidate = implClass.getConstructor(String.class);
Log log = candidate.newInstance(LogFactory.class.getName());
if (log.isDebugEnabled()) {
log.debug("Logging initialized using '" + implClass + "' adapter.");
}
logConstructor = candidate;
} catch (Throwable t) {
throw new LogException("Error setting Log implementation. Cause: " + t, t);
}
}
具体实现类
mybatis
提供很多日志的实现类,用来记录日志,取决于初始化的时候load
到的class
jcl
实现jul
实现mybatis
打印sql
日志分析
mybaits
加载日志技术的顺序是:slf4j–>jcl—>log4j2—>log4j–>jul—>nolog
1.在spring4+mybatis+log4j
的情况会有sql
日志输出。
因为spring4
使用的是jcl
,jcl
在引入log4j
依赖下,就会使用了log4j
技术打印sql
日志。
2.在spring5+mybatis+log4j
的情况不会有sql
日志输出。
因为spring5
使用的是spring-jcl
(本质也是jcl
),spring-jcl
默认使用了jul
(不再使用log4j
而是用log4j2
,在上面有详细说明)。
由于mybatis
加载日志的顺序,jcl
是先与log4j
,所以该情况下导致了mybatis
使用了jcl
技术。
那么问题来了?为什么mybatis
在使用jul
的情况下,没有打印sql
日志?
当使用了jul
的时候,jul
默认的日志级别是 INFO
原生的jdk
的logging
的日志级别是FINEST、FINE、INFO、WARNING、SEVERE
分别对应我们常见的trace、debug、info、warn、error
通过下图可以看到jdk14
的isDebugEnabled()
方法,是使用了Level.FINE
,这个值是500
跟踪isLoggable()
方法,可以看到下图,level.intValue()
就是上面传递进去的Level.FINE
是500
,levelValue是800
这里跟踪一下levelValue
的值,可以看到levelValue = Level.INFO.intValue()
, INFO
的值是800
跟踪mybatis
打印sql
日志的源码,是判断了isDebugEnabled()
返回的值,所以mybatis
在使用jul
的时候,是不能打印sql
日志的。
1.mybaits
在整合spring
框架的时候,一级缓存会失效,原因是mybatis
一级缓存是基于sqlSession
的,整合了spring
之后,spring
会管理sqlSession
,在查询完之后,会帮我们关闭sqlSession
,所以导致缓存失效了。
2.mybatis
的二级缓存。开启也是很简单在对应的mapper
接口中加上@CacheNamespace
注解即可。
备注:mybatis
的二级缓存会有一个很大的坑。
因为mybatis
的二级缓存是基于命名空间来实现了。当在不同的mapper
接口操作了同一张表,这个就会有问题了,A
接口更新了数据,B
接口两次获取的数据都会是一样的。
文章来源:https://blog.csdn.net/qq_24101357/article/details/100514448