spring5新特性日志体系

spring5新特性日志体系

主流的log技术名词
1.jul

java自带的一个日志记录的技术,直接使用
java.util.logging.Logger

2.log4j
//log4j依赖

	log4j
  	log4j
   	1.2.17


//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

log4j特点:
可以不需要依赖第三方的技术,直接记录日志。

3.jcl
//jcl依赖

   commons-logging
   commons-logging
   1.2


//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

通过分析jcl的代码可以得到:
jcl本身不实现日志记录,但是提供了记录日志的抽象方法即接口(info,debug,error…)
底层通过一个数组存放具体的日志框架的类名,然后循环数组依次去匹配这些类名是否在项目中被依赖了,如果找到被依赖的则直接使用,所以他有先后顺序。
下图为jcl中存放日志技术类名的数组,默认有四个,后面两个可以忽略。
spring5新特性日志体系_第1张图片
上面的代码81行就是通过一个类名去load一个class,如果load成功则直接new出来并且返回使用。如果没有load到class这循环第二个,直到找到为止。
可以看到这里的循环条件必须满足result不为空,也就是如果没有找到具体的日志依赖则继续循环,如果找到则条件不成立,不进行循环了。
总结:顺序log4j>jul

  • 虽然Log4JLogger是jcl的jar包中的类,但是该Log4JLogger类,依赖了log4j的类,当你没有引入log4j的依赖的时候,在创建Log4JLogger类,就会失败。
  • 下图是在没有引入log4j依赖的情况下,Log4JLogger类的情况图:
  • spring5新特性日志体系_第2张图片
  • 因为jul是java自带的日志类,所以在java环境下,jcl就算在创建log4j失败的情况下,也会去创建Jdk14Logger。可以看到Jdk14Logger是依赖了jul的。下图是Jdk14Logger的类图:
  • spring5新特性日志体系_第3张图片

jcl特点:
他不直接记录日志,他是通过第三方记录日志(jul)。
jcl是一个接口,默认有4个log实现类。

4.slf4j

slf4j他也不记录日志,通过绑定器绑定一个具体的日志记录来完成日志记录
官网:https://www.slf4j.org/

//slf4j依赖

   org.slf4j
   slf4j-api
   1.7.25

  • 在只添加了slf4j依赖,而没有添加任意一个绑定器,日志是不会打印的。控制台会输出warn信息,如下图:
  • spring5新特性日志体系_第4张图片
//slf4j依赖

   org.slf4j
   slf4j-api
   1.7.25




    org.slf4j
    slf4j-jdk14
    1.7.25













  • slf4j提供了很多的绑定器,有log4j,jul,jcl。
  • 当引入了jul绑定器之后,slf4j就能打印日志了,如下图:
  • spring5新特性日志体系_第5张图片

总结:

  1. slf4j需要打印日志,就一定需要引入绑定器。slf4j提供了很多的绑定器,有jul,jcl,log4j等。
  2. slf4j如果引入了jcl绑定器,因为jcl也是一个接口,jcl会加载log4j,jul。
  1. 如果你想使用log4j,也需要引入log4j的依赖,log4j的配置文件
  2. 如果你不引入log4j的依赖,就默认使用jul
  1. slf4j如果引入了log4j绑定器,需要log4j的配置文件(这个时候不用引入log4j的依赖了,因为该绑定器已经帮我们引入了)
  • 问题:有这样的一个业务系统:
  • 项目A,是使用了slf4j打印日志,然后通过slf4j绑定器,绑定到jul,然后使用jul打印日志。
  • 项目A使用了spring框架,但是spring框架使用jcl打印日志,spring引入了log4j依赖,使得spring框架是用log4j打印日志。
  • 这样的话,项目A就出现了多个日志框架打印日志了,那就很混乱,现在要求项目A只能使用一种日志框架技术,这个要怎么处理?
//项目A的依赖
//log4j

   log4j
    log4j
    1.2.17


//jcl

    commons-logging
    commons-logging
    1.2


//slf4j

    org.slf4j
    slf4j-api
    1.7.25




    org.slf4j
    slf4j-jdk14
    1.7.25

  • 就引入上面的依赖jar包,项目就会有两种日志输出,如下图所示:
  • spring5新特性日志体系_第6张图片

解决方案:

  1. 可以通过修改slf4j的绑定器,直接改用slf4j的log4j绑定器。

因为使用slf4j绑定到log4j,只需要简单的引入一个依赖即可。

  1. 使用slf4j的桥接器,将spring使用jcl打印日志这步切断,将jclj桥接到slf4j,然后再走项目A的日志打印。

因为使用jcl桥接到slf4j,只需要简单的引入一个依赖即可。

//增加下面这个依赖即可:


    org.slf4j
    jcl-over-slf4j
    1.7.25

  • 当使用了jcl-over-slf4j桥接器之后,可以使得jcl桥接到slf4j,然后再走slf4j这边的日志输出。从而实现将spring框架的日志输出,改为了slf4j–>jul
  • spring5新特性日志体系_第7张图片
  • 改进后的日志打印流程图如下:
  • spring5新特性日志体系_第8张图片

各种日志技术的关系和作用

  • spring5新特性日志体系_第9张图片

spring5新特性日志体系

Spring4当中依赖jcl,即Spring4当中采用的日志技术是jcl:commons-logging,即默认使用jul;加入log4j依赖和配置,即可切换为log4j
Spring5当中也是使用了jcl:spring-jcl,是重写为了jul框架。spring5使用的spring的jcl(spring改了jcl的代码)来记录日志的,但是jcl不能直接记录日志,采用循环优先的原则。

spring-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日志技术,需要加入的依赖和配置文件。


	org.springframework
	spring-context
	5.0.9.RELEASE




    org.apache.logging.log4j
    log4j-core
    2.8.2





   
       
           
       
   
   
       
           
       
   

  • 要将spring5改用log4j来记录日志,怎么实现?
  • 1.引入slf4j依赖,然后slf4j绑定log4j,并添加log4j的配置文件。


  	org.springframework
    spring-context
    5.0.9.RELEASE




   	org.slf4j
   	slf4j-api
   	1.7.25




    org.slf4j
    slf4j-log4j12
    1.7.5

扩展:

Mybatis日志体系

  • mybatis的日志技术实现
  • spring5新特性日志体系_第10张图片
#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 implClass) {
  try {
	    Constructor 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实现
  • spring5新特性日志体系_第11张图片
  • log4j2实现
  • spring5新特性日志体系_第12张图片
  • jul实现
  • spring5新特性日志体系_第13张图片

mybaits缓存问题:
1.mybaits在整合spring框架的时候,一级缓存会失效,原因是mybatis一级缓存是基于sqlSession的,整合了spring之后,spring会管理sqlSession,在查询完之后,会帮我们关闭sqlSession,所以导致缓存失效了。

2.mybatis的二级缓存。开启也是很简单在对应的mapper接口中加上@CacheNamespace注解即可。
备注:mybatis的二级缓存会有一个很大的坑。
因为mybatis的二级缓存是基于命名空间来实现了。当在不同的mapper接口操作了同一张表,这个就会有问题了,A接口更新了数据,B接口两次获取的数据都会是一样的。

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
  • spring5新特性日志体系_第14张图片
  • 跟踪isLoggable()方法,可以看到下图,level.intValue()就是上面传递进去的Level.FINE是500,levelValue是800
  • spring5新特性日志体系_第15张图片
  • 这里跟踪一下levelValue的值,可以看到levelValue = Level.INFO.intValue() , INFO的值是800
  • spring5新特性日志体系_第16张图片
  • spring5新特性日志体系_第17张图片
  • 跟踪mybatis打印sql日志的源码,是判断了isDebugEnabled()返回的值,所以mybatis在使用jul的时候,是不能打印sql日志的。
  • spring5新特性日志体系_第18张图片

你可能感兴趣的:(spring源码分析,mybatis,log技术总结)