Spring5 новая система журналов функций
Технические термины основного журнала
1.jul
Технология ведения журнала, которая поставляется с java, используйте ее напрямую java.util.logging.Logger
2.log4j
//log4j依赖
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</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
Особенности log4j: Вы можете записывать журналы напрямую, не полагаясь на сторонние технологии.
3.jcl
//jcl依赖
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</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, для вывода (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, мы можем получить: Сам Jcl не реализует логирование, а предоставляет абстрактный метод логирования, то есть интерфейс (info, debug, error......) Нижний уровень хранит имена классов конкретных фреймворков журналов в массиве, а затем перебирает массив, чтобы определить, зависят ли эти имена классов в проекте. На следующем рисунке показан массив имен классов технологии журналов в jcl. По умолчанию их четыре, а последние два можно игнорировать.Строка 81 приведенного выше кода предназначена для загрузки класса через имя класса. Если загрузка прошла успешно, он будет непосредственно новым и возвращен к использованию. Если для класса нет нагрузки, этот цикл повторяется до тех пор, пока он не будет найден. Видно, что условие цикла здесь должно удовлетворять тому, чтобы результат не был пустым, то есть, если не будет найдена конкретная зависимость журнала, цикл продолжится, если она найдена, условие не будет выполнено, и цикл не будет выполняться. . Резюме: заказ log4j>jul
- Хотя Log4JLogger является классом в пакете jcl jar, класс Log4JLogger зависит от класса log4j.Если вы не введете зависимость от log4j, создание класса Log4JLogger завершится ошибкой.
- На следующем рисунке показана ситуация с классом Log4JLogger без введения зависимостей log4j:
- Поскольку jul — это класс журнала, поставляемый с java, в среде java jcl создаст Jdk14Logger, даже если ему не удастся создать log4j. Вы можете видеть, что Jdk14Logger зависит от jul. На следующем рисунке представлена диаграмма классов Jdk14Logger:
Особенности JCL: Он не входит напрямую, он входит через третье лицо (июль). Jcl — это интерфейс, и по умолчанию существует 4 класса реализации журнала.
4.slf4j
slf4j не записывает журналы и привязывает определенную запись журнала через биндер для завершения журнала. Официальный сайт:www.slf4j.org/
//slf4j依赖
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
- Когда добавляется только зависимость slf4j без добавления какого-либо связующего, журнал не будет распечатан. Консоль выведет предупреждающую информацию, как показано ниже:
//slf4j依赖
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<!--slf4jbind绑定器,将slf4j绑定到jul-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>1.7.25</version>
</dependency>
<!--slf4jbind绑定器,将slf4j绑定到log4j-->
<!--<dependency>-->
<!-- <groupId>org.slf4j</groupId>-->
<!-- <artifactId>slf4j-log4j12</artif
<!-- <version>1.7.5</version>-->
<!--</dependency>-->
<!--slf4jbind绑定器,将slf4j绑定到jcl-->
<!--<dependency>-->
<!-- <groupId>org.slf4j</groupId>-->
<!-- <artifactId>slf4j-jcl</artifactI
<!-- <version>1.7.25</version>-->
<!--</dependency>-->
- slf4j предоставляет множество биндеров, включая log4j, jul и jcl.
- Когда вводится jul binder, slf4j может распечатать журнал, как показано ниже:
Суммировать:
- Если slf4j нужно распечатать журналы, он должен ввести связующее. slf4j предоставляет множество биндеров, таких как jul, jcl, log4j и т. д.
- Если slf4j вводит связыватель jcl, поскольку jcl также является интерфейсом, jcl загрузит log4j и jul.
- Если вы хотите использовать log4j, вам также необходимо ввести зависимости log4j, файлы конфигурации log4j
- Если вы не вводите зависимости log4j, используйте jul по умолчанию.
- Если slf4j вводит биндер log4j, то требуется конфигурационный файл log4j (на этот раз нет необходимости вводить зависимость log4j, т.к. биндер уже введен за нас)
- Проблема: Существует такая бизнес-система:
- Проект A использует slf4j для печати журналов, затем привязывается к jul через биндер slf4j, а затем использует jul для печати журналов.
- В проекте A используется среда Spring, но среда Spring использует jcl для печати журналов, а Spring вводит зависимости log4j, так что среда Spring использует log4j для печати журналов.
- В этом случае в проекте А появится несколько фреймворков для печати логов, что очень сбивает с толку.Теперь в проекте А требуется использовать только одну технологию фреймворков.Как с этим быть?
//项目A的依赖
//log4j
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
//jcl
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
//slf4j
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<!--slf4jbind绑定器,将slf4j绑定到jul-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>1.7.25</version>
</dependency>
- Как только будет введен указанный выше пакет зависимостей jar, проект будет иметь два выхода журнала, как показано на следующем рисунке:
решение:
- Вы можете напрямую использовать привязку log4j для slf4j, изменив привязку slf4j.
Поскольку slf4j используется для привязки к log4j, необходимо просто ввести зависимость.
- Используйте мост slf4j, отрежьте шаг пружины с помощью jcl для печати журнала, соедините jclj с slf4j, а затем перейдите к печати журнала проекта A.
Поскольку jcl используется для соединения с slf4j, необходимо просто ввести зависимость.
//增加下面这个依赖即可:
<!--slf4j桥接器,将jcl桥接到slf4j-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.25</version>
</dependency>
- Когда используется мост jcl-over-slf4j, jcl можно подключить к slf4j, а затем можно использовать вывод журнала на стороне slf4j. Таким образом, вывод журнала среды Spring изменяется на slf4j-->jul
- Блок-схема улучшенной печати журнала выглядит следующим образом:
Взаимосвязь и роль различных технологий каротажа
Spring5 новая система журналов функций
Spring4 опирается на jcl, то есть технология логирования, используемая в Spring4, это jcl: commons-logging, то есть по умолчанию используется jul; добавив зависимость и конфигурацию log4j, можно переключиться на log4j Spring 5 также использует jcl: spring-jcl, который переписан на фреймворк jul. jcl Spring используется spring5 (весна изменила код 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 и требует добавления зависимостей и файлов конфигурации.
<!-- spring5的依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.9.RELEASE</version>
</dependency>
<!-- log4j2依赖 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.8.2</version>
</dependency>
<!-- log4j2.xml配置文件 -->
<?xml version="1.0" encoding="UTF-8"?>
<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>
- Как использовать log4j для ведения журнала в spring5?
- 1. Введите зависимость slf4j, затем привяжите log4j к slf4j и добавьте файл конфигурации log4j.
<!-- spring5的依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.9.RELEASE</version>
</dependency>
<!--slf4j依赖(可省略)-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<!--slf4jbind绑定器,将slf4j绑定到log4j(已经包含了slf4j和log4j的依赖)-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.5</version>
</dependency>
Расширение:
Лог-система Mybatis
- Реализация лог-технологии в mybatis
#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 предоставляет множество классов реализации журнала для ведения журнала, в зависимости от класса, загруженного во время инициализации.
- jcl-реализация
- реализация log4j2
- июль реализация
проблема с кэшем mybaits: 1. Когда mybaits интегрирует структуру Spring, кеш первого уровня будет недействителен. Причина в том, что кеш первого уровня mybatis основан на sqlSession. После интеграции Spring Spring будет управлять sqlSession. После завершения запроса он поможет нам закрыть sqlSession, поэтому это приводит к тому, что Cache недействителен.
2.Кэш второго уровня mybatis. Также очень просто добавить аннотацию @CacheNamespace в соответствующий интерфейс сопоставления. Примечание. Во вторичном тайнике Mybatis будет большая яма. Потому что кеш второго уровня mybatis реализован на основе пространства имен. Когда одна и та же таблица работает на разных интерфейсах преобразователя, возникает проблема: интерфейс A обновляет данные, а данные, полученные интерфейсом B, будут дважды одинаковыми.
Анализ лога SQL печати mybatis
Порядок загрузки технологии журнала mybaits: slf4j-->jcl--->log4j2--->log4j-->jul--->nolog 1. В случае spring4+mybatis+log4j будет вывод журнала sql.
Поскольку spring4 использует jcl, jcl будет использовать технологию log4j для печати журналов sql, когда будет введен log4j.
2. В случае spring5+mybatis+log4j не будет вывода журнала sql.
Поскольку spring5 использует spring-jcl (по сути, jcl), spring-jcl по умолчанию использует jul (больше не используйте log4j, а log4j2, что подробно описано выше). Из-за порядка, в котором mybatis загружает журнал, jcl идет первым с log4j, поэтому в этом случае mybatis использует технологию jcl.
Так вот вопрос? Почему mybatis не печатает журнал sql при использовании jul?
При использовании jul уровень журнала jul по умолчанию — INFO. Уровни журнала собственного ведения журнала jdk: FINEST, FINE, INFO, WARNING, Severe, соответствующие нашей общей трассировке, отладке, информации, предупреждению, ошибке соответственно.
- Как видно из рисунка ниже, метод isDebugEnabled() jdk14 использует Level.FINE, а значение равно 500.
- Отслеживая метод isLoggable(), вы можете увидеть рисунок ниже: level.intValue() — это переданный выше уровень Level.FINE — 500, levelValue — 800.
- Отследите здесь значение levelValue, вы можете видеть, что levelValue = Level.INFO.intValue() , значение INFO равно 800.
- Отслеживание исходного кода mybatis для печати журнала sql означает оценку значения, возвращаемого isDebugEnabled(), поэтому, когда mybatis использует jul, он не может распечатать журнал sql.