嗨玩手游网

梦到掉牙、考试挂科、找不到厕所?你做噩梦的原因解析来了......

每晚我们大约会做5个梦,地球上70亿人24小时内共同创造了350亿个梦。

这些梦有不少是噩梦,无论是梦到被追赶,还是梦到牙掉了,我们都会被吓得不轻甚至直接吓醒。

[Photo/Pexels]

弗洛伊德在《梦的解析》中说,梦是人们压抑愿望的伪装表现形式(disguised fulfillments of repressed wishes),那么这些常见的噩梦都表达了什么寓意呢?

All of these dreams mirror fundamental patterns of human behavior. Some believe these dream patterns reflect who we actually are, what we really need and what we believe.

所有这些梦都反映了人类行为的基本模式。有些人认为这些梦反映了我们是谁,我们真正需要什么以及我们相信什么。

[Photo/Pexels]

Teeth falling out

掉牙

Dreams about your teeth can reflect your anxieties about your appearance and how others perceive you. Such dreams may stem from a fear of rejection, embarrassment or feeling unattractive.

梦到牙齿通常反映出一个人的外貌焦虑,这种梦可能来源于害怕被拒绝,尴尬或者觉得自己不好看。

As teeth are used to bite, tear and chew, dreams about losing your teeth can stem from a sense of powerlessness which means you may be experiencing self-confidence issues.

由于牙齿是用来咬、撕扯和咀嚼东西的,失去牙齿的梦可能源于无能为力的感觉,这意味着你可能正经历自信的问题。

Being chased

被追赶

Being chased suggests you are running away from something that is causing you fear or anxiety in waking life.

梦到被追赶说明你正在逃离现实生活中让你感到害怕或者焦虑的东西。

The chaser can also represent an aspect of yourself. Your own feelings of anger, jealousy or fear can manifest itself as the threatening figure.

追逐者也可以代表自己的一个方面。你自己的愤怒、嫉妒或恐惧感外化成有威胁的人物。

[Photo/Pexels]

Unable to find a toilet

找不到厕所

Having trouble finding a toilet means you may be finding it difficult to express your needs in a certain situation. It can represent feelings of your personal needs not being met by always putting others first.

梦到找不到厕所意味着你觉得在某种情况下自己很难表达自己的需求。你可能总是把自己的优先级往后靠。

You may feel that you are lacking time for personal issues and need more privacy, self-care or self-expression.

你可能会觉得你缺乏时间处理个人问题,需要更多的隐私、自我关心或自我表达。

Naked in public

公共场合没穿衣服

Being naked in a dream symbolizes not being able to find yourself, uncertainty, or being wrongly accused.

梦到没穿衣服表现出一种不确定性,或者被误解。

If you are not the naked person in your dream, but you see a nude person and you are sickened by it, it means you are worried about exposing that person.

如果你不是梦中那个没穿衣服的人,但你看到一个的人而且被恶心到了,这意味着你担心揭穿那个人。

[Photo/Pexels]

Unprepared for an exam

考试没准备

Exam dreams can be so real that we actually wake up convinced we just failed an important test.

考试噩梦通常非常真实,以至于我们醒了以后真的觉得自己挂科了。

At least 1 in every 5 people will experience an exam dream in their lives. Exam dreams are a reflection of your lack of confidence and inability to advance to the next stage in life.

每5个人中,至少有一个人一生中会梦到考试。考试梦反映了自信缺乏,无法进入人生的下一阶段。

Falling

坠落

If you fall anywhere and you are overcome by fear, it signifies insecurity and anxiety about a situation.

梦到坠落并且害怕说明正在为某事焦虑不已。

Enjoying the feeling of falling suggests that you are not afraid of changes.

但是如果在梦里享受坠落的过程,说明毫不惧怕改变。

Being late

迟到

Dreaming that you are late represents your worry and anxiety about taking a different direction in your life, or that you are trying to get things done but you feel that you are running out of time.

梦见自己迟到代表着你对人生发生改变时的担忧和焦虑,或者你正试图完成一些事情,但你觉得时间不多了。

Your unconscious might be telling you that it is never too late to do things you want in your life.

你无意识中可能在告诉自己做你想做的事永远不会太晚。

Bug infestation

虫子成群

Dreaming about a bug infestation can have two interpretations. One is symbolic of feelings of guilt, minor irritations, or worries that are sneaking up on you.

梦到虫子有两种解释,一种是愧疚感、愠怒或是心里的担忧。

On the other hand, dreams about bugs are also associated with health problems, which is why many people reported having increasingly common nightmares about bugs during the pandemic.

另一种涉及到健康问题,流行病时期人们就会更频繁地梦到虫子。

对梦境的解析也不一定完全科学可靠,但它的确反应了我们日常生活中的一些状态,经常做噩梦可能是潜意识在提醒你需要调整好自己的状态。

你最近做噩梦了吗?梦到什么了呢?

编辑:焦洁 商桢

实习生:向静雅

来源:Dreams Puffy

来源:中国日报双语新闻

每日记一个英语单词:failed

failed失败

I heard you failed your English exam.

我听说你英语考试考砸了。

顿悟!新手都能学懂的SpringBoot源码分析

概述

日志是一个系统必不可缺少的东西,记录了系统运行时的点点滴滴,便于我们了解自己系统的运行状态,在我们使用 Spring Boot时,默认就已经提供了日志功能,使用Logback作为默认的日志框架。那么,接下来我们依赖来看看 Spring Boot是如何初始化好日志系统的。关于Spring的知识点总结了一个图谱,分享给大家:

为什么Spring Boot默认的日志框架是Logbasck呢?

因为在spring-boot-starter模块中引入spring-boot-starter-logging模块,该Starter引入了logback-classic依赖。

Log日志体系

在我们的日常工作中,可能看到项目中依赖的跟日志相关的jar包有很多,例如commons-logging、log4j、log4j2、sl4j和logback等等,眼花缭乱。经常会碰到各种依赖冲入的问题,非常烦恼,例如这几个问题:

1.Failed to load class org.slf4j.impl.StaticLoggerBinder,没找到日志实现,如果你觉得你已经添加了对应的日志实现依赖了,那应该检查一下版本是否兼容

2.Multiple bindings,找到了多个日志实现,也可能是版本问题,slf4j会找其中一个作为日志实现 如果想要正确地使用它们,有必要先理清它们之间的关系,我们可以来看看Log的发展史,首先从 Java Log 的发展历程开始说起:

1.1.log4j(作者Ceki Gülcü)出来后,被开发者们广泛的应用(注意,这里是直接使用),当初是 Java 日志事实上的标准,并成为了 Apache 的项目

2.2.Apache 要求把 log4j 并入到 jdk,SUN 表示拒绝,并在 jdk1.4 版本后增加了 JUL(java.util.logging);

3.3.毕竟是 JDK 自带的,JUL 也被很多人使用。同时还有其他的日志组件,如 SimpleLog 等。这个时候如果有人想换成其他日志组件,如 log4j 换成 JUL,因为 API 完全不同,就需要改动代码,当然很多人不愿意呀;

4.4.Apache 见此,开发了 JCL(Jakarta Commons Logging),即 commons-logging-xx.jar。它只提供一套通用的日志接口 API,并不提供日志的实现。很好的设计原则嘛,依赖抽象而非实现。这样一来,我们的应用程序可以在运行时选择自己想要的日志实现组件;

5.5.这样看上去也挺美好的,但是log4j的作者觉得JCL不好用,自己开发出一套slf4j,它跟JCL类似,本身不替供日志的具体实现,只对外提供接口或门面。目的就是为了替代JCL。同时,还开发出logback,一个比log4j 拥有更高性能的组件,目的是为了替代log4j;

6.6.Apache 参考了 logback,并做了一系列优化,推出了一套 log4j2 日志框架。 对于性能没什么特别高要求的使用 Spring Boot 中默认的 logback 就可以了,如果想要使用 log4j2 可以参考我的 《MyBatis 使用手册》 这篇文章,有提到过。

回顾

回到前面的 《SpringApplication 启动类的启动过程》 这篇文章,Spring Boot 启动应用的入口和主流程都是在 SpringApplication#run(String.. args) 方法中。

在启动 Spring 应用的整个过程中,到了不同的阶段会发布不同类型的事件,例如最开始会发布一个 应用正在启动 的事件,对于不同类型的事件都是通过 EventPublishingRunListener 事件发布器来发布,里面有一个事件广播器,封装了几个 ApplicationListener 事件,如下:

#Application Listenersorg.springframeworkntext.ApplicationListener=\org.springframework.boot.ClearCachesApplicationListener,\org.springframework.boot.builder.ParentContextCloserApplicationListener,\org.springframework.boot.CloudFoundryVcapEnvironmentPostProcessor,\org.springframework.bootntext.FileEncodingApplicationListener,\org.springframework.bootntextnfig.AnsiOutputApplicationListener,\org.springframework.bootntextnfig.ConfigFileApplicationListener,\org.springframework.bootntextnfig.DelegatingApplicationListener,\org.springframework.bootntext.logging.ClasspathLoggingApplicationListener,\org.springframework.bootntext.logging.LoggingApplicationListener,\org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener

其中有一个 LoggingApplicationListener 对象,监听到不同事件后,会对日志系统进行一些相关的初始化工作

提示:Spring Boot 的 LoggingSystem 日志系统的初始化过程有点绕,嵌套的方法有点多,可参考序号耐心查看

LoggingApplicationListener

org.springframework.bootntext.logging.LoggingApplicationListener,Spring Boot 事件,用于初始化日志系统

onApplicationEvent方法

onApplicationEvent(ApplicationEvent 方法,处理监听到的事件

@Overridepublic void onApplicationEvent(ApplicationEvent event) { // 应用正在启动的事件 if (event instanceof ApplicationStartingEvent) { onApplicationStartingEvent((ApplicationStartingEvent) event); } // Environment 环境已准备事件 else if (event instanceof ApplicationEnvironmentPreparedEvent) { onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event); } // 应用已准备事件 else if (event instanceof ApplicationPreparedEvent) { onApplicationPreparedEvent((ApplicationPreparedEvent) event); } // Spring 上下文关闭事件 else if (event instanceof ContextClosedEvent && ((ContextClosedEvent) event).getApplicationContext().getParent() == null) { onContextClosedEvent(); } // 应用启动失败事件 else if (event instanceof ApplicationFailedEvent) { onApplicationFailedEvent(); }}

对于不同的事件调用不同的方法,事件的发布顺序也就是上面从上往下的顺序

1. onApplicationStartingEvent方法

处理应用正在启动的事件

private void onApplicationStartingEvent(ApplicationStartingEvent event) { // <1> 创建 LoggingSystem 对象 // 指定了类型则使用指定的,没有则尝试创建对应的对象,ClassLoader 中有对应的 Class 对象则创建(logback > log4j2 > java logging) this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader()); // <2> LoggingSystem 的初始化前置处理 this.loggingSystem.beforeInitialize();}

过程如下:

1.1.创建LoggingSystem对象,指定了类型则使用指定的,没有则尝试创建对应的对象,ClassLoader中有对应的Class对象则创建(logback > log4j2 > java logging)

2.2.调用LoggingSystem 的 beforeInitialize()方法,初始化前置处理

2. onApplicationEnvironmentPreparedEvent方法

处理环境已准备事件

private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) { // <1> 如果还未明确 LoggingSystem 类型,那么这里继续创建 LoggingSystem 对象 if (this.loggingSystem == null) { this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader()); } // <2> 初始化 LoggingSystem 对象,创建日志文件,设置日志级别 initialize(event.getEnvironment(), event.getSpringApplication().getClassLoader());}

过程如下:

1.1.如果还未明确LoggingSystem类型,那么这里继续创建LoggingSystem对象

2.2.调用initialize(..) 方法,初始化LoggingSystem对象,创建日志文件,设置日志级别

3. initialize 方法

protected void initialize(ConfigurableEnvironment environment, ClassLoader classLoader) { // <1> 根据 Environment 环境通过 LoggingSystemProperties 往 System 进行一些日志配置 new LoggingSystemProperties(environment)ly(); // <2> 根据 Environment 环境配置的日志名称和路径创建一个日志文件 // 默认情况没有配置,这个对象也为 null,而是在打印第一个日志的时候会创建(如果不存在的话) this.logFile = LogFile.get(environment); if (this.logFile != null) { // <3> 往 System 添加日志文件的名称和路径 this.logFilelyToSystemProperties(); } // <4> 创建一个日志分组对象 this.loggerGroups = new LoggerGroups(DEFAULT_GROUP_LOGGERS); // <5> 初始化早期的 Spring Boot 日志级别(Debug 或者 Trace) initializeEarlyLoggingLevel(environment); // <6> 初始化 LoggingSystem 对象 initializeSystem(environment, this.loggingSystem, this.logFile); // <7> 初始化最终的 Spring Boot 日志级别,逐个设置 Environment 配置的日志级别 initializeFinalLoggingLevels(environment, this.loggingSystem); // <8> 向 JVM 注册一个钩子,用于在 JVM 关闭时关闭日志系统 registerShutdownHookIfNecessary(environment, this.loggingSystem);}

初始过程如下:

1.1.根据Environment环境通过LoggingSystemProperties往System进行一些日志配置

2.2.根据Environment环境配置的日志名称和路径创建一个日志文件,默认情况没有配置,这个对象也为null,而是在打印第一个日志的时候会创建(如果不存在的话)

// LogFile.javapublic static LogFile get(PropertyResolver propertyResolver) { // 获取 `logging.file` 指定的日志文件名称,也可以通过 `logging.file` 指定 String file = getLogFileProperty(propertyResolver, FILE_NAME_PROPERTY, FILE_PROPERTY); // 获取 `logging.file.path` 指定的日志文件保存路径,也可以通过 `logging.path` 指定 String path = getLogFileProperty(propertyResolver, FILE_PATH_PROPERTY, PATH_PROPERTY); // 创建一个日志文件 if (StringUtils.hasLength(file) || StringUtils.hasLength(path)) { return new LogFile(file, path); } return null;}

3.3.往System添加日志文件的名称和路径

4.4.创建一个日志分组对象

5.5.初始化早期的 Spring Boot 日志级别(Debug 或者 Trace)

private void initializeEarlyLoggingLevel(ConfigurableEnvironment environment) { if (this.parseArgs && this.springBootLogging == null) { if (isSet(environment, "debug")) { this.springBootLogging = LogLevel.DEBUG; } if (isSet(environment, "trace")) { this.springBootLogging = LogLevel.TRACE; } }}

初始化LoggingSystem对象

private void initializeSystem(ConfigurableEnvironment environment, LoggingSystem system, LogFile logFile) { LoggingInitializationContext initializationContext = new LoggingInitializationContext(environment); // <1> 找到 `loggingnfig` 指定的配置文件路径 String logConfig = environment.getProperty(CONFIG_PROPERTY); // <2> 如果没配置文件,则不指定配置文件初始化 LoggingSystem 对象 // 使用约定好的配置文件,或者使用默认配置 if (ignoreLogConfig(logConfig)) { systemitialize(initializationContext, null, logFile); } // <3> 否则,指定配置文件初始化 LoggingSystem 对象 else { try { systemitialize(initializationContext, logConfig, logFile); } catch (Exception ex) { // 抛出异常 } }}

初始化最终的Spring Boot日志级别,逐个设置Environment配置的日志级别

向JVM注册一个钩子,用于在JVM关闭时关闭日志系统

可以看到需要通过LoggingSystem日志系统对象来初始化,后面会讲到

4. onApplicationPreparedEvent方法

处理应用已准备事件

private void onApplicationPreparedEvent(ApplicationPreparedEvent event) { // 往底层 IoC 容器注册几个 Bean:LoggingSystem、LogFile 和 LoggerGroups ConfigurableListableBeanFactory beanFactory = event.getApplicationContext().getBeanFactory(); if (!beanFactoryntainsBean(LOGGING_SYSTEM_BEAN_NAME)) { beanFactory.registerSingleton(LOGGING_SYSTEM_BEAN_NAME, this.loggingSystem); } if (this.logFile != null && !beanFactoryntainsBean(LOG_FILE_BEAN_NAME)) { beanFactory.registerSingleton(LOG_FILE_BEAN_NAME, this.logFile); } if (this.loggerGroups != null && !beanFactoryntainsBean(LOGGER_GROUPS_BEAN_NAME)) { beanFactory.registerSingleton(LOGGER_GROUPS_BEAN_NAME, this.loggerGroups); }}

LoggingSystem

org.springframework.boot.logging.LoggingSystem 抽象类,Spring Boot 的日志系统对象,每个日志框架,都会对应一个实现类。如下图所示:

public abstract class LoggingSystem {private static final Map<String, String> SYSTEMS;static {Map<String, String> systems = new LinkedHashMap<>();systems.put("ch.qos.logbackre.Appender", "org.springframework.boot.logging.logback.LogbackLoggingSystem");systems.put("org.apache.logging.log4jre.impl.Log4jContextFactory","org.springframework.boot.logging.log4j2.Log4J2LoggingSystem");systems.put("java.util.logging.LogManager", "org.springframework.boot.logging.java.JavaLoggingSystem");SYSTEMS = Collections.unmodifiableMap(systems);}}

1.1 get 方法

创建一个 LoggingSystem 日志系统对象,如下:

public static LoggingSystem get(ClassLoader classLoader) { // <1> 从系统参数 `org.springframework.boot.logging.LoggingSystem` 获得 LoggingSystem 类型 String loggingSystem = System.getProperty(SYSTEM_PROPERTY); // <2> 如果非空,说明配置了,那么创建一个该类型的 LoggingSystem 实例对象 if (StringUtils.hasLength(loggingSystem)) { if (NONE.equals(loggingSystem)) { return new NoOpLoggingSystem(); } return get(classLoader, loggingSystem); } // <3> 否则,没有配置,则通过顺序依次尝试创建对应类型的 LoggingSystem 实例对象 // logback > log4j2 > java logging return SYSTEMS.entrySet().stream().filter((entry) -> ClassUtils.isPresent(entry.getKey(), classLoader)) .map((entry) -> get(classLoader, entry.getValue())).findFirst() .orElseThrow(() -> new IllegalStateException("No suitable logging system located"));}

过程如下:

1.1.从系统参数org.springframework.boot.logging.LoggingSystem获得LoggingSystem类型

2.2.如果非空,说明配置了,那么创建一个该类型的LoggingSystem实例对象

3.3.否则,没有配置,则通过顺序依次尝试创建对应类型的LoggingSystem实例对象,也就是在static代码块中初始化好的集合,logback > log4j2 > java logging

1.2 beforeInitialize方法

初始化的前置操作,抽象方法,交由子类实现

/** * Reset the logging system to be limit output. This method may be called before * {@link #initialize(LoggingInitializationContext, String, LogFile)} to reduce * logging noise until the system has been fully initialized. */public abstract void beforeInitialize();2. initialize方法

初始化操作,空方法,由子类来重写

/** * Fully initialize the logging system. * @param initializationContext the logging initialization context * @param configLocation a log configuration location or {@code null} if default * initialization is required * @param logFile the log output file that should be written or {@code null} for * console only output */public void initialize(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile) {}

AbstractLoggingSystem

org.springframework.boot.logging.AbstractLoggingSystem 抽象类,继承 LoggingSystem 抽象类,作为一个基类

2.1 initialize方法

重写父类的 initialize(..) 方法,提供模板化的初始化逻辑,如下:

@Overridepublic void initialize(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile) { // <1> 有自定义的配置文件,则使用指定配置文件进行初始化 if (StringUtils.hasLength(configLocation)) { initializeWithSpecificConfig(initializationContext, configLocation, logFile); return; } // <2> 无自定义的配置文件,则使用约定配置文件进行初始化 initializeWithConventions(initializationContext, logFile);}

有指定的配置文件,则调用 initializeWithSpecificConfig(..) 方法, 使用指定配置文件进行初始化

没有自定义的配置文件,则调用 initializeWithConventions(..) 方法,使用约定配置文件进行初始化

2.1.1 initializeWithSpecificConfig方法

initializeWithSpecificConfig(LoggingInitializationContext, String, LogFile) 方法,使用指定配置文件进行初始化

private void initializeWithSpecificConfig(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile) { // <1> 获得配置文件的路径(可能有占位符) configLocation = SystemPropertyUtils.resolvePlaceholders(configLocation); // <2> 加载配置文件到日志系统中,抽象方法,子类实现 loadConfiguration(initializationContext, configLocation, logFile);}

先获取配置文件的路径(可能有占位符),然后调用 loadConfiguration(..) 抽象方法,加载配置文件到日志系统中

/** * Load a specific configuration. * @param initializationContext the logging initialization context * @param location the location of the configuration to load (never {@code null}) * @param logFile the file to load or {@code null} if no log file is to be written */protected abstract void loadConfiguration(LoggingInitializationContext initializationContext, String location, LogFile logFile);2.1.2 initializeWithConventions方法

initializeWithConventions(LoggingInitializationContext, LogFile) 方法,使用约定配置文件进行初始化

private void initializeWithConventions(LoggingInitializationContext initializationContext, LogFile logFile) { // <1> 尝试获得约定配置文件,例如 log4j2 约定的是 log4j2.xml String config = getSelfInitializationConfig(); // <2> 如果找到了约定的配置文件 if (config != null && logFile == null) { // self initialization has occurred, reinitialize in case of property changes // <2.1> 自定义初始化,子类实现 reinitialize(initializationContext); return; } // <3> 尝试获取约定的配置文件(带有 `-spring` ),例如 log4j2 对应是 log4j2-spring.xml if (config == null) { config = getSpringInitializationConfig(); } // <4> 获取到了 `-spring` 配置文件,则加载到日志系统中,抽象方法,子类实现 if (config != null) { loadConfiguration(initializationContext, config, logFile); return; } // <5> 加载默认配置,抽象方法,子类实现 loadDefaults(initializationContext, logFile);}

过程如下

调用 getSelfInitializationConfig() 方法,尝试获得约定配置文件,例如 log4j2 约定的是 log4j2.xml

protected String getSelfInitializationConfig() { return findConfig(getStandardConfigLocations());}protected abstract String[] getStandardConfigLocations();private String findConfig(String[] locations) { for (String location : locations) { ClassPathResource resource = new ClassPathResource(location, this.classLoader); if (resource.exists()) { return "classpath:" + location; } } return null;}

如果找到了约定的配置文件,则调用 reinitialize(..) 抽象方法,自定义初始化,子类实现

protected void reinitialize(LoggingInitializationContext initializationContext) { }

调用 getSpringInitializationConfig(..) 方法,尝试获取约定的配置文件(带有 -spring ),例如 log4j2 对应是 log4j2-spring.xml

protected String getSpringInitializationConfig() { return findConfig(getSpringConfigLocations());}protected String[] getSpringConfigLocations() { String[] locations = getStandardConfigLocations(); for (int i = 0; i < locations.length; i++) { String extension = StringUtils.getFilenameExtension(locations[i]); locations[i] = locations[i].substring(0, locations[i].length() - extension.length() - 1) + "-spring." + extension; } return locations;}private String findConfig(String[] locations) { for (String location : locations) { ClassPathResource resource = new ClassPathResource(location, this.classLoader); if (resource.exists()) { return "classpath:" + location; } } return null;}

获取到了 -spring 配置文件,则调用 loadConfiguration(..) 抽象方法,加载到日志系统中,子类实现

protected abstract void loadConfiguration(LoggingInitializationContext initializationContext, String location, LogFile logFile); 还没有找到到指定的配置文件,那么调用 loadDefaults(..) 抽象方法,加载默认配置,子类实现

protected abstract void loadDefaults(LoggingInitializationContext initializationContext, LogFile logFile); 整个过程就是尝试获取到各个日志框架约定好的配置文件名称,如果存在这个配置文件,则加载到日志系统中,否则使用默认的配置

Slf4JLoggingSystem org.springframework.boot.logging.Slf4JLoggingSystem,继承 AbstractLoggingSystem 抽象类,基于 Slf4J 的 LoggingSystem 的抽象基类

1.2.1 beforeInitialize方法

初始化的前置操作

@Overridepublic void beforeInitialize() { super.beforeInitialize(); // <1> 配置 JUL 的桥接处理器,桥接到 slf4j configureJdkLoggingBridgeHandler();}

先调用父类的 beforeInitialize() 方法,然后调用 configureJdkLoggingBridgeHandler() 方法,配置 JUL 的桥接处理器,桥接到 slf4j

private void configureJdkLoggingBridgeHandler() { try { // <1> 判断 JUL 是否桥接到 SLF4J 了 if (isBridgeJulIntoSlf4j()) { // <2> 移除 JUL 桥接处理器 removeJdkLoggingBridgeHandler(); // <3> 重新安装 SLF4JBridgeHandler SLF4JBridgeHandlerstall(); } } catch (Throwable ex) { // Ignore. No java.util.logging bridge is installed. }}

过程如下:

1.判断JUL是否桥接到slf4j了

protected final boolean isBridgeJulIntoSlf4j() { // 存在 SLF4JBridgeHandler 类,且 JUL 只有 ConsoleHandler 处理器被创建 return isBridgeHandlerAvailable() && isJulUsingASingleConsoleHandlerAtMost();}

3.移除JUL桥接处理器

private void removeJdkLoggingBridgeHandler() { try { // 移除 JUL 的 ConsoleHandler removeDefaultRootHandler(); // 卸载 SLF4JBridgeHandler SLF4JBridgeHandler.uninstall(); } catch (Throwable ex) { // Ignore and continue }}private void removeDefaultRootHandler() { try { Logger rootLogger = LogManager.getLogManager().getLogger(""); Handler[] handlers = rootLogger.getHandlers(); if (handlers.length == 1 && handlers[0] instanceof ConsoleHandler) { rootLogger.removeHandler(handlers[0]); } } catch (Throwable ex) { // Ignore and continue }}

3.重新安装 SLF4JBridgeHandler

2.3 loadConfiguration方法

重写AbstractLoggingSystem父类的方法,加载指定的日志配置文件到日志系统中

@Overrideprotected void loadConfiguration(LoggingInitializationContext initializationContext, String location, LogFile logFile) { Assert.notNull(location, "Location must not be null"); if (initializationContext != null) { // 将 Environment 中的日志配置往 System 中配置 applySystemProperties(initializationContext.getEnvironment(), logFile); }}

实际上就是将Environment中的日志配置往System中配置

LogbackLoggingSystem

org.springframework.boot.logging.logback.LogbackLoggingSystem,继承Slf4JLoggingSystem抽象类,基于 logback的LoggingSystem实现类

1.2.2 beforeInitialize方法

重写LoggingSystem的方法,初始化前置操作

@Overridepublic void beforeInitialize() { // <1> 获得 LoggerContext 日志上下文 LoggerContext loggerContext = getLoggerContext(); // <2> 如果 LoggerContext 已有 LoggingSystem,表示已经初始化,则直接返回 if (isAlreadyInitialized(loggerContext)) { return; } // <3> 调用父方法 super.beforeInitialize(); // <4> 添加 FILTER 到其中,因为还未初始化,不打印日志 loggerContext.getTurboFilterList().add(FILTER);}

过程如下:

1.调用getLoggerContext() 方法,获得LoggerContext日志上下文

private LoggerContext getLoggerContext() { ILoggerFactory factory = StaticLoggerBinder.getSingleton().getLoggerFactory(); // 这里会校验 factory 是否为 LoggerContext 类型 return (LoggerContext) factory;}

2.如果LoggerContext已有LoggingSystem,表示已经初始化,则直接返回

private boolean isAlreadyInitialized(LoggerContext loggerContext) { return loggerContext.getObject(LoggingSystem.class.getName()) != null;}

3.调用父方法

4.添加FILTER到其中,因为还未初始化,不打印日志

private static final TurboFilter FILTER = new TurboFilter() { @Override public FilterReply decide(Marker marker, ch.qos.logback.classic.Logger logger, Level level, String format, Object[] params, Throwable t) { // 一律拒绝 return FilterReply.DENY; }};

getStandardConfigLocations方法

重写AbstractLoggingSystem的方法,获取logback标准的配置文件名称

@Overrideprotected String[] getStandardConfigLocations() { return new String[] { "logback-test.groovy", "logback-test.xml", "logback.groovy", "logback.xml" };}

2.2 initialize 方法

重写LoggingSystem的方法,初始化操作

@Overridepublic void initialize(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile) { // <1> 获得 LoggerContext 日志上下文 LoggerContext loggerContext = getLoggerContext(); // <2> 如果 LoggerContext 已有 LoggingSystem,表示已经初始化,则直接返回 if (isAlreadyInitialized(loggerContext)) { return; } // <3> 调用父方法 superitialize(initializationContext, configLocation, logFile); // <4> 移除之前添加的 FILTER,可以开始打印日志了 loggerContext.getTurboFilterList().remove(FILTER); // <5> 标记为已初始化,往 LoggerContext 中添加一个 LoggingSystem 对象 markAsInitialized(loggerContext); if (StringUtils.hasText(System.getProperty(CONFIGURATION_FILE_PROPERTY))) { getLogger(LogbackLoggingSystem.class.getName()).warn("Ignoring '" + CONFIGURATION_FILE_PROPERTY + "' system property. Please use 'loggingnfig' instead."); }}

过程如下:

1.调用getLoggerContext() 方法,获得LoggerContext日志上下文

private LoggerContext getLoggerContext() { ILoggerFactory factory = StaticLoggerBinder.getSingleton().getLoggerFactory(); // 这里会校验 factory 是否为 LoggerContext 类型 return (LoggerContext) factory;}

2.如果LoggerContext已有LoggingSystem,表示已经初始化,则直接返回

private boolean isAlreadyInitialized(LoggerContext loggerContext) { return loggerContext.getObject(LoggingSystem.class.getName()) != null;}

3.调用父方法

4.移除之前添加的FILTER,可以开始打印日志了

5.调用markAsInitialized(..) 方法,标记为已初始化,往LoggerContext中添加一个LoggingSystem对象

2.4 loadConfiguration方法

重写AbstractLoggingSystem的方法,加载指定的日志配置文件到日志系统中

@Overrideprotected void loadConfiguration(LoggingInitializationContext initializationContext, String location, LogFile logFile) { // <1> 调用父方法 super.loadConfiguration(initializationContext, location, logFile); LoggerContext loggerContext = getLoggerContext(); // <2> 重置 LoggerContext 对象 // 这里会添加一个 LevelChangePropagator ,当日志级别被修改时会立即生效,而不用重启应用 stopAndReset(loggerContext); try { // <3> 读取配置文件并解析,配置到 LoggerContext 中 configureByResourceUrl(initializationContext, loggerContext, ResourceUtils.getURL(location)); } catch (Exception ex) { throw new IllegalStateException("Could not initialize Logback logging from " + location, ex); } // <4> 判断是否发生错误,有的话抛出 IllegalStateException 异常 List<Status> statuses = loggerContext.getStatusManager().getCopyOfStatusList(); StringBuilder errors = new StringBuilder(); for (Status status : statuses) { if (status.getLevel() == Status.ERROR) { errorsend((errors.length() > 0) ? String.format("%n") : ""); errorsend(status.toString()); } } if (errors.length() > 0) { throw new IllegalStateException(String.format("Logback configuration error detected: %n%s", errors)); }}

过程如下:

1.调用父方法

2.重置LoggerContext对象,这里会添加一个LevelChangePropagator,当日志级别被修改时会立即生效,而不用重启应用 private void stopAndReset(LoggerContext loggerContext) { // 停止 loggerContext.stop(); // 重置 loggerContext.reset(); // 如果有桥接器 if (isBridgeHandlerInstalled()) { // 添加一个日志级别的,能够及时更新日志级别 addLevelChangePropagator(loggerContext); }}private void addLevelChangePropagator(LoggerContext loggerContext) { LevelChangePropagator levelChangePropagator = new LevelChangePropagator(); levelChangePropagator.setResetJUL(true); levelChangePropagator.setContext(loggerContext); loggerContext.addListener(levelChangePropagator);}

3.读取配置文件并解析,配置到LoggerContext中

private void configureByResourceUrl(LoggingInitializationContext initializationContext, LoggerContext loggerContext, URL url) throws JoranException { if (url.toString().endsWith("xml")) { JoranConfigurator configurator = new SpringBootJoranConfigurator(initializationContext); configurator.setContext(loggerContext); configurator.doConfigure(url); } else { new ContextInitializer(loggerContext)nfigureByResource(url); }}

4.判断是否发生错误,有的话抛出IllegalStateException异常

reinitialize方法

实现类AbstractLoggingSystem的方法,重新初始化

@Overrideprotected void reinitialize(LoggingInitializationContext initializationContext) { // 重置 getLoggerContext().reset(); // 清空资源 getLoggerContext().getStatusManager().clear(); // 加载指定的配置文件,此时使用约定的配置文件 loadConfiguration(initializationContext, getSelfInitializationConfig(), null);}

loadDefaults方法

实现类AbstractLoggingSystem的方法,没有指定的配置文件,也没有约定的配置文件,那么加载默认的配置到日志系统

@Overrideprotected void loadDefaults(LoggingInitializationContext initializationContext, LogFile logFile) { LoggerContext context = getLoggerContext(); // <1> 重置 LoggerContext 对象 // 这里会添加一个 LevelChangePropagator ,当日志级别被修改时会立即生效,而不用重启应用 stopAndReset(context); // <2> 如果开启 debug 模式则添加一个 OnConsoleStatusListener boolean debug = Boolean.getBoolean("logback.debug"); if (debug) { StatusListenerConfigHelper.addOnConsoleListenerInstance(context, new OnConsoleStatusListener()); } // <3> 往 LoggerContext 中添加默认的日志配置 LogbackConfigurator configurator = debug ? new DebugLogbackConfigurator(context) : new LogbackConfigurator(context); Environment environment = initializationContext.getEnvironment(); context.putProperty(LoggingSystemProperties.LOG_LEVEL_PATTERN, environment.resolvePlaceholders("${logging.pattern.level:${LOG_LEVEL_PATTERN:%5p}}")); context.putProperty(LoggingSystemProperties.LOG_DATEFORMAT_PATTERN, environment.resolvePlaceholders( "${logging.patternformat:${LOG_DATEFORMAT_PATTERN:yyyy-MM-dd HH:mm:ss.SSS}}")); context.putProperty(LoggingSystemProperties.ROLLING_FILE_NAME_PATTERN, environment .resolvePlaceholders("${logging.pattern.rolling-file-name:${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz}")); // <4> 创建 DefaultLogbackConfiguration 对象,设置到 configurator 中 // 设置转换规则,例如颜色转换,空格转换 new DefaultLogbackConfiguration(initializationContext, logFile)ly(configurator); // <5> 设置日志文件,按天切割 context.setPackagingDataEnabled(true);}

过程如下:

1.重置LoggerContext对象,这里会添加一个LevelChangePropagator,当日志级别被修改时会立即生效,而不用重启应用

private void stopAndReset(LoggerContext loggerContext) { // 停止 loggerContext.stop(); // 重置 loggerContext.reset(); // 如果有桥接器 if (isBridgeHandlerInstalled()) { // 添加一个日志级别的,能够及时更新日志级别 addLevelChangePropagator(loggerContext); }}private void addLevelChangePropagator(LoggerContext loggerContext) { LevelChangePropagator levelChangePropagator = new LevelChangePropagator(); levelChangePropagator.setResetJUL(true); levelChangePropagator.setContext(loggerContext); loggerContext.addListener(levelChangePropagator);}

2.如果开启debug模式则添加一个OnConsoleStatusListener

3.往LoggerContext中添加默认的日志配置

4.创建DefaultLogbackConfiguration对象,设置到configurator中,设置转换规则,例如颜色转换,空格转换

5.设置日志文件,按天切割

Log4J2LoggingSystem

org.springframework.boot.logging.log4j2.Log4J2LoggingSystem,继承Slf4JLoggingSystem抽象类,基于log4j2的LoggingSystem实现类

和LogbackLoggingSystem基本类似,感兴趣的小伙伴可以自己去瞧一瞧

JavaLoggingSystem

org.springframework.boot.logging.java.JavaLoggingSystem,继承AbstractLoggingSystem抽象类,基于jul的LoggingSystem实现类

逻辑比较简单,感兴趣的小伙伴可以自己去瞧一瞧

总结

本文分析了Sping Boot初始化不同LoggingSystem日志系统的一个过程,同样是借助于Spring的ApplicationListener事件机制,在启动Spring应用的过程中,例如会广播应用正在启动的事件和应用环境已准备好,然后LoggingApplicationListener监听到不同的事件会进行不同的初始化操作。

LoggingSystem日志系统主要分为logback、log4j2和JUL三种,本文主要对logback的初始化过程进行了分析,因为它是Spring Boot的默认日志框架嘛。整个的初始化过程稍微有点绕,嵌套的方法有点多,主要的小节都标注了序号。

大致流程就是先配置JUL到slf4j的桥接器,然后尝试找到指定的配置文件对日志系统进行配置,可通过 loggingnfig设置;没有指定则获取约定好的配置文件,例如logback.xml、log4j2.xml;还没有获取到则 Spring约定好的配置文件,例如logback-spring.xml、log4j2-spring.xml;要是还没有找到配置文件,那只能尝试加载默认的配置了。

最后

另外还整理成了40多套PDF文档:全套的Java面试宝典手册,“性能调优+微服务架构+并发编程+开源框架+分布式”等七大面试专栏,包含Tomcat、JVM、MySQL、SpringCloud、SpringBoot、Dubbo、并发、Spring、SpringMVC、MyBatis、Zookeeper、Ngnix、Kafka、MQ、Redis、MongoDB、memcached等等。如果你对这个感兴趣,小编可以免费分享。

资料获取方式:关注小编+转发文章+私信【面试题】获取上述资料~

重要的事情说三遍,转发+转发+转发,一定要记得转发哦!!

更多资讯
游戏推荐
更多+