`
shinesuo
  • 浏览: 156139 次
  • 性别: Icon_minigender_1
  • 来自: 宇宙
社区版块
存档分类
最新评论

Spring Boot 源码分析 —— 日志系统

 
阅读更多

1. 概述

在使用 Spring Boot 时,默认就已经提供了日志功能,使用 Logback 作为默认的日志框架。本文,我们就来一起研究下,Spring Boot 是如何自动初始化好日志系统的。

不了解 Spring Boot 日志功能的胖友,可以先看看 《一起来学 SpringBoot 2.x | 第三篇:SpringBoot 日志配置》 文章。

2. LoggingApplicationListener

Spring Boot 提供日志功能,关键在于 LoggingApplicationListener 类。在 《精尽 Spring Boot 源码分析 —— ApplicationListener》 中,我们已经简单介绍过它:

org.springframework.boot.context.logging.LoggingApplicationListener ,实现 GenericApplicationListener 接口,实现根据配置初始化日志系统 Logger 。

2.1 supportsEventType

实现 #supportsEventType(ResolvableType resolvableType) 方法,判断是否是支持的事件类型。代码如下:

// LoggingApplicationListener.java

private static final Class<?>[] EVENT_TYPES = { ApplicationStartingEvent.class,
ApplicationEnvironmentPreparedEvent.class, ApplicationPreparedEvent.class,
ContextClosedEvent.class, ApplicationFailedEvent.class };

@Override
public boolean supportsEventType(ResolvableType resolvableType) {
return isAssignableFrom(resolvableType.getRawClass(), EVENT_TYPES);
}

private boolean isAssignableFrom(Class<?> type, Class<?>... supportedTypes) {
if (type != null) {
for (Class<?> supportedType : supportedTypes) {
if (supportedType.isAssignableFrom(type)) {
return true;
}
}
}
return false;
}

2.2 supportsSourceType

实现 #supportsSourceType(Class<?> sourceType) 方法,判断是否是支持的事件来源。代码如下:

// LoggingApplicationListener.java

private static final Class<?>[] SOURCE_TYPES = { SpringApplication.class,
ApplicationContext.class };

@Override
public boolean supportsSourceType(Class<?> sourceType) {
return isAssignableFrom(sourceType, SOURCE_TYPES);
}

2.3 onApplicationEvent

实现 #onApplicationEvent(ApplicationEvent event) 方法,处理事件。代码如下:

// LoggingApplicationListener.java

@Override
public void onApplicationEvent(ApplicationEvent event) {
// 在 Spring Boot 应用启动的时候
if (event instanceof ApplicationStartingEvent) {
onApplicationStartingEvent((ApplicationStartingEvent) event);
// 在 Spring Boot 的 Environment 环境准备完成的时候
} else if (event instanceof ApplicationEnvironmentPreparedEvent) {
onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
// 在 Spring Boot 容器的准备工作已经完成(并未启动)的时候
} else if (event instanceof ApplicationPreparedEvent) {
onApplicationPreparedEvent((ApplicationPreparedEvent) event);
// 在 Spring Boot 容器关闭的时候
} else if (event instanceof ContextClosedEvent
&& ((ContextClosedEvent) event).getApplicationContext().getParent() == null) {
onContextClosedEvent();
// 在 Spring Boot 容器启动失败的时候
} else if (event instanceof ApplicationFailedEvent) {
onApplicationFailedEvent();
}
}
  • 不同的事件,对应不同的处理方法。下文,我们一一来看。

2.4 onApplicationStartingEvent

#onApplicationStartingEvent(ApplicationStartingEvent event) 方法,代码如下:

// LoggingApplicationListener.java

private LoggingSystem loggingSystem;

private void onApplicationStartingEvent(ApplicationStartingEvent event) {
// <1> 创建 LoggingSystem 对象
this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader());
// <2> LoggingSystem 的初始化的前置处理
this.loggingSystem.beforeInitialize();
}
  • <1> 处,调用 LoggingSystem#get(ClassLoader classLoader) 方法,创建(获得) LoggingSystem 对象。关于这个,可以先看看 「3.1 get」 小节。
    • 通过 LoggingSystem 的抽象,对应不同日志框架对应的 LoggingSystem 实现,达到方便透明的接入不同的日志框架~
  • <2> 处,调用 LoggingSystem#beforeInitialize() 方法,执行 LoggingSystem 的初始化的前置处理。关于这个,可以先看看 「3.2 beforeInitialize」 小节。

2.5 onApplicationEnvironmentPreparedEvent

#onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) 方法,代码如下:

// LoggingApplicationListener.java

private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
if (this.loggingSystem == null) {
this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader());
}
// 初始化
initialize(event.getEnvironment(), event.getSpringApplication().getClassLoader());
}

protected void initialize(ConfigurableEnvironment environment, ClassLoader classLoader) {
// <1> 初始化 LoggingSystemProperties 配置
new LoggingSystemProperties(environment).apply();
// <2> 初始化 LogFile
LogFile logFile = LogFile.get(environment);
if (logFile != null) {
logFile.applyToSystemProperties(); // <2.1>
}
// <3> 初始化早期的 Spring Boot Logging 级别
initializeEarlyLoggingLevel(environment);
// <4> 初始化 LoggingSystem 日志系统
initializeSystem(environment, this.loggingSystem, logFile);
// <5> 初始化最终的 Spring Boot Logging 级别
initializeFinalLoggingLevels(environment, this.loggingSystem);
// <6>
registerShutdownHookIfNecessary(environment, this.loggingSystem);
}
  • <1> 处,调用 LoggingSystemProperties#apply() 方法,初始化 LoggingSystemProperties 配置。关于这个,可以先看看 「4. LoggingSystemProperties」 小节。
  • <2> 处,调用 LogFile#get(environment) 方法,创建(获得)LogFile 。关于这个,可以先看看 「5. LogFile」 小节。
    • <2.1> 处,调用 LogFile#applyToSystemProperties() 方法,应用 LogFile.path  LogFile.file 到系统属性中。
  • <3> 处,调用 #initializeEarlyLoggingLevel(ConfigurableEnvironment environment) 方法,初始化早期的 Spring Boot Logging 级别。详细解析,见 「2.5.1 initializeEarlyLoggingLevel」 中。
  • <4> 处,调用 #initializeSystem(ConfigurableEnvironment environment, LoggingSystem system, LogFile logFile) 方法,初始化 LoggingSystem 日志系统。详细解析,见 「2.5.2 initializeSystem」 中。
  • <5> 处,调用 #initializeFinalLoggingLevels(ConfigurableEnvironment environment, LoggingSystem system) 方法,初始化最终的 Spring Boot Logging 级别。详细解析,见 「2.5.3 initializeFinalLoggingLevels」 中。
  • <6> 处,调用 #registerShutdownHookIfNecessary(Environment environment, LoggingSystem loggingSystem) 方法,注册 ShutdownHook 。详细解析,见 「2.5.4」 中。

2.5.1 initializeEarlyLoggingLevel

#initializeEarlyLoggingLevel(ConfigurableEnvironment environment) 方法,初始化早期的 Spring Boot Logging 级别。代码如下:

// LoggingApplicationListener.java

private boolean parseArgs = true;

private LogLevel springBootLogging = null;

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;
}
}
}

private boolean isSet(ConfigurableEnvironment environment, String property) {
String value = environment.getProperty(property);
return (value != null && !value.equals("false"));
}
  • 可以通过在启动 jar 的时候,跟上 --debug  --trace 
  • 也可以在配置文件中,添加 debug=true  trace=true 
  • 关于日志级别,可以先看看 「6. LogLevel」 

2.5.2 initializeSystem

#initializeSystem(ConfigurableEnvironment environment, LoggingSystem system, LogFile logFile) 方法,初始化 LoggingSystem 日志系统。代码如下:

// LoggingApplicationListener.java

public static final String CONFIG_PROPERTY = "logging.config";

private void initializeSystem(ConfigurableEnvironment environment, LoggingSystem system, LogFile logFile) {
// <1> 创建 LoggingInitializationContext 对象
LoggingInitializationContext initializationContext = new LoggingInitializationContext(environment);
// <2> 获得日志组件的配置文件
String logConfig = environment.getProperty(CONFIG_PROPERTY);
// <3> 如果没配置,则直接初始化 LoggingSystem
if (ignoreLogConfig(logConfig)) {
system.initialize(initializationContext, null, logFile);
// <3> 如果有配置,先尝试加载指定配置文件,然后在初始化 LoggingSystem
} else {
try {
ResourceUtils.getURL(logConfig).openStream().close();
system.initialize(initializationContext, logConfig, logFile); // <X>
} catch (Exception ex) {
// NOTE: We can't use the logger here to report the problem
System.err.println("Logging system failed to initialize " + "using configuration from '" + logConfig + "'");
ex.printStackTrace(System.err);
throw new IllegalStateException(ex);
}
}
}
  • <1> 处,创建 LoggingInitializationContext 对象。其中,org.springframework.boot.logging.LoggingInitializationContext ,LoggingSystem 初始化时的 Context 。代码如下:

    // LoggingInitializationContext.java

    public class LoggingInitializationContext {

    private final ConfigurableEnvironment environment;

    public LoggingInitializationContext(ConfigurableEnvironment environment) {
    this.environment = environment;
    }

    public Environment getEnvironment() {
    return this.environment;
    }

    }
    • 虽然目前只有 environment 属性。但是未来可以在后面增加新的参数,而无需改动 LoggingSystem#initialize(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile) 方法。
  • <2> 处,从 environment 中获得 "logging.config" ,即获得日志组件的配置文件。一般情况下,我们无需配置。因为根据不同的日志系统,Spring Boot 按如下“约定规则”组织配置文件名加载日志配置文件:

日志框架 配置文件
Logback logback-spring.xml, logback-spring.groovy, logback.xml, logback.groovy
Log4j log4j-spring.properties, log4j-spring.xml, log4j.properties, log4j.xml
Log4j2 log4j2-spring.xml, log4j2.xml
JDK (Java Util Logging) logging.properties
  • <3>  <4> 处,调用 LoggingSystem#initialize(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile) 方法,初始化 LoggingSystem 日志系统。详细解析,可以先看看 「3.3 initialize」 
  • <3>  <4> 处,差异点在于后者多了 ResourceUtils.getURL(logConfig).openStream().close() 代码块,看着有点奇怪哟?它的作用是,尝试去加载 logConfig 对应的配置文件,看看是否真的存在~

2.5.3 initializeFinalLoggingLevels

#initializeFinalLoggingLevels(ConfigurableEnvironment environment, LoggingSystem system) 方法,初始化最终的 Spring Boot Logging 级别。代码如下:

// LoggingApplicationListener.java

private void initializeFinalLoggingLevels(ConfigurableEnvironment environment, LoggingSystem system) {
// <1> 如果 springBootLogging 非空,则设置到日志级别
if (this.springBootLogging != null) {
initializeLogLevel(system, this.springBootLogging);
}
// <2> 设置 environment 中配置的日志级别
setLogLevels(system, environment);
}
  • <1> 处,如果 springBootLogging 非空,则调用 #initializeLogLevel(LoggingSystem system, LogLevel level) 方法,设置日志级别。代码如下:

    // LoggingApplicationListener.java

    private static final Map<LogLevel, List<String>> LOG_LEVEL_LOGGERS;

    static {
    MultiValueMap<LogLevel, String> loggers = new LinkedMultiValueMap<>();
    loggers.add(LogLevel.DEBUG, "sql");
    loggers.add(LogLevel.DEBUG, "web");
    loggers.add(LogLevel.DEBUG, "org.springframework.boot");
    loggers.add(LogLevel.TRACE, "org.springframework");
    loggers.add(LogLevel.TRACE, "org.apache.tomcat");
    loggers.add(LogLevel.TRACE, "org.apache.catalina");
    loggers.add(LogLevel.TRACE, "org.eclipse.jetty");
    loggers.add(LogLevel.TRACE, "org.hibernate.tool.hbm2ddl");
    LOG_LEVEL_LOGGERS = Collections.unmodifiableMap(loggers);
    }

    protected void initializeLogLevel(LoggingSystem system, LogLevel level) {
    List<String> loggers = LOG_LEVEL_LOGGERS.get(level);
    if (loggers != null) {
    for (String logger : loggers) {
    system.setLogLevel(logger, level);
    }
    }
    }
    • 遍历的 loggers ,是 LOG_LEVEL_LOGGERS 中对应的 level 的值。
    • 调用 LoggingSystem#setLogLevel(String loggerName, LogLevel level) 方法,设置指定 loggerName 的日志级别。详细解析,见 「3.4 setLogLevel」 
  • <2> 处,调用 #setLogLevels(LoggingSystem system, Environment environment) 方法,设置 environment 中配置的日志级别。代码如下:

    // LoggingApplicationListener.java

    private static final ConfigurationPropertyName LOGGING_LEVEL = ConfigurationPropertyName.of("logging.level");
    private static final ConfigurationPropertyName LOGGING_GROUP = ConfigurationPropertyName.of("logging.group");

    private static final Bindable<Map<String, String>> STRING_STRING_MAP = Bindable.mapOf(String.class, String.class);
    private static final Bindable<Map<String, String[]>> STRING_STRINGS_MAP = Bindable.mapOf(String.class, String[].class);

    protected void setLogLevels(LoggingSystem system, Environment environment) {
    if (!(environment instanceof ConfigurableEnvironment)) {
    return;
    }
    // 创建 Binder 对象
    Binder binder = Binder.get(environment);
    // <1> 获得日志分组的集合
    Map<String, String[]> groups = getGroups(); // <1.1>
    binder.bind(LOGGING_GROUP, STRING_STRINGS_MAP.withExistingValue(groups)); // <1.2>
    // <2> 获得日志级别的集合
    Map<String, String> levels = binder.bind(LOGGING_LEVEL, STRING_STRING_MAP).orElseGet(Collections::emptyMap);
    // <3> 遍历 levels 集合,逐个设置日志级别
    levels.forEach((name, level) -> {
    String[] groupedNames = groups.get(name);
    if (ObjectUtils.isEmpty(groupedNames)) {
    setLogLevel(system, name, level);
    } else {
    setLogLevel(system, groupedNames, level);
    }
    });
    }
    • <1> 处,获得日志分组的集合。

      • <1.1> 处,调用 #getGroups() 方法,获得默认的日志分组集合。代码如下:

        // LoggingApplicationListener.java

        private static final Map<String, List<String>> DEFAULT_GROUP_LOGGERS;
        static {
        MultiValueMap<String, String> loggers = new LinkedMultiValueMap<>();
        loggers.add("web", "org.springframework.core.codec");
        loggers.add("web", "org.springframework.http");
        loggers.add("web", "org.springframework.web");
        loggers.add("web", "org.springframework.boot.actuate.endpoint.web");
        loggers.add("web", "org.springframework.boot.web.servlet.ServletContextInitializerBeans");
        loggers.add("sql", "org.springframework.jdbc.core");
        loggers.add("sql", "org.hibernate.SQL");
        DEFAULT_GROUP_LOGGERS = Collections.unmodifiableMap(loggers);
        }

        private Map<String, String[]> getGroups() {
        Map<String, String[]> groups = new LinkedHashMap<>();
        DEFAULT_GROUP_LOGGERS.forEach(
        (name, loggers) -> groups.put(name, StringUtils.toStringArray(loggers)));
        return groups;
        }
        • 实际上,就是把我们日常配置的 loggerName 进行了分组。默认情况下,内置了 sqlweb 分组。
      • <1.2> 处,从 environment 中读取 logging.group 配置的日志分组。举个例子,在配置文件里增加 logging.group.demo=xxx.Dog,yyy.Cat 
    • <2> 处,从 environment 中读取 logging.level 配置的日志分组。举两个例子,在配置文件里添加:

      • logging.level.web=INFO
      • logging.level.xxx.Dog=INFO
    • <3> 处,遍历 levels 集合,逐个设置日志级别。涉及的方法,代码如下:

      // LoggingApplicationListener.java

      private void setLogLevel(LoggingSystem system, String[] names, String level) {
      // 遍历 names 数组
      for (String name : names) {
      setLogLevel(system, name, level);
      }
      }

      private void setLogLevel(LoggingSystem system, String name, String level) {
      try {
      // 获得 loggerName
      name = name.equalsIgnoreCase(LoggingSystem.ROOT_LOGGER_NAME) ? null : name;
      // 设置日志级别
      system.setLogLevel(name, coerceLogLevel(level));
      } catch (RuntimeException ex) {
      this.logger.error("Cannot set level '" + level + "' for '" + name + "'");
      }
      }

      /**
      * @param level 日志级别字符串
      * @return 将字符串转换成 {@link LogLevel}
      */
      private LogLevel coerceLogLevel(String level) {
      String trimmedLevel = level.trim();
      if ("false".equalsIgnoreCase(trimmedLevel)) { // false => OFF
      return LogLevel.OFF;
      }
      return LogLevel.valueOf(trimmedLevel.toUpperCase(Locale.ENGLISH));
      }
      • 比较简单,胖友瞅瞅~

2.5.4 registerShutdownHookIfNecessary

#registerShutdownHookIfNecessary(Environment environment, LoggingSystem loggingSystem) 方法,注册 ShutdownHook 。代码如下:

// LoggingApplicationListener.java

/**
* The name of the Spring property that controls the registration of a shutdown hook
* to shut down the logging system when the JVM exits.
* @see LoggingSystem#getShutdownHandler
*/
public static final String REGISTER_SHUTDOWN_HOOK_PROPERTY = "logging.register-shutdown-hook";

private void registerShutdownHookIfNecessary(Environment environment, LoggingSystem loggingSystem) {
// 获得 logging.register-shutdown-hook 对应的配置值
boolean registerShutdownHook = environment.getProperty(REGISTER_SHUTDOWN_HOOK_PROPERTY, Boolean.class, false);
// 如果开启
if (registerShutdownHook) {
// <x> 获得 shutdownHandler 钩子
Runnable shutdownHandler = loggingSystem.getShutdownHandler();
// 注册 ShutdownHook(
if (shutdownHandler != null
&& shutdownHookRegistered.compareAndSet(false, true)) {
registerShutdownHook(new Thread(shutdownHandler));
}
}
}

void registerShutdownHook(Thread shutdownHook) {
Runtime.getRuntime().addShutdownHook(shutdownHook);
}
  • <X> 处,所注册的 ShutdownHook ,通过调用 LoggingSystem#getShutdownHandler() 方法,进行获得。详细解析,见 「3.5 getShutdownHandler」

2.6 onApplicationPreparedEvent

#onApplicationPreparedEvent(ApplicationPreparedEvent event) 方法,代码如下:

// LoggingApplicationListener.java

/**
* The name of the {@link LoggingSystem} bean.
*/
public static final String LOGGING_SYSTEM_BEAN_NAME = "springBootLoggingSystem";

private void onApplicationPreparedEvent(ApplicationPreparedEvent event) {
ConfigurableListableBeanFactory beanFactory = event.getApplicationContext().getBeanFactory();
if (!beanFactory.containsBean(LOGGING_SYSTEM_BEAN_NAME)) {
beanFactory.registerSingleton(LOGGING_SYSTEM_BEAN_NAME, this.loggingSystem);
}
}
  • 将创建的 LoggingSystem 对象,注册到 Spring 容器中。

2.7 onContextClosedEvent

#onContextClosedEvent() 方法,代码如下:

// LoggingApplicationListener.java

private void onContextClosedEvent() {
if (this.loggingSystem != null) {
this.loggingSystem.cleanUp();
}
}
  • 调用 LoggingSystem#cleanUp() 方法,执行清理。详细解析,见 「3.6 cleanUp」 中。

2.8 onApplicationFailedEvent

#onApplicationFailedEvent() 方法,代码如下:

// LoggingApplicationListener.java

private void onApplicationFailedEvent() {
if (this.loggingSystem != null) {
this.loggingSystem.cleanUp();
}
}

至此,我们需要来看看 LoggingSystem 的实现类。具体的,可以跳到 「7. LoggingSystem 的实现类」 中。

3. LoggingSystem

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

3.1 get

#get(ClassLoader classLoader) 方法,创建(获得) LoggingSystem 对象。代码如下:

// LoggingApplicationListener.java

/**
* A System property that can be used to indicate the {@link LoggingSystem} to use.
*/
public static final String SYSTEM_PROPERTY = LoggingSystem.class.getName();

/**
* The value of the {@link #SYSTEM_PROPERTY} that can be used to indicate that no
* {@link LoggingSystem} should be used.
*/
public static final String NONE = "none";

private static final Map<String, String> SYSTEMS;

static {
Map<String, String> systems = new LinkedHashMap<>();
systems.put("ch.qos.logback.core.Appender", "org.springframework.boot.logging.logback.LogbackLoggingSystem");
systems.put("org.apache.logging.log4j.core.impl.Log4jContextFactory", "org.springframework.boot.logging.log4j2.Log4J2LoggingSystem");
systems.put("java.util.logging.LogManager", "org.springframework.boot.logging.java.JavaLoggingSystem");
SYSTEMS = Collections.unmodifiableMap(systems);
}

public static LoggingSystem get(ClassLoader classLoader) {
// <1> 从系统参数 org.springframework.boot.logging.LoggingSystem 获得 loggingSystem 类型
String loggingSystem = System.getProperty(SYSTEM_PROPERTY);
// <2> 如果非空,说明配置了
if (StringUtils.hasLength(loggingSystem)) {
// <2.1> 如果是 none ,则创建 NoOpLoggingSystem 对象
if (NONE.equals(loggingSystem)) {
return new NoOpLoggingSystem();
}
// <2.2> 获得 loggingSystem 对应的 LoggingSystem 类,进行创建对象
return get(classLoader, loggingSystem);
}
// <3> 如果为空,说明未配置,则顺序查找 SYSTEMS 中的类。如果存在指定类,则创建该类。
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> 处,从系统参数 org.springframework.boot.logging.LoggingSystem 获得 loggingSystem 类型。
  • <2> 处,如果非空,说明配置了。

    • <2.1> 处,如果是 none ,则创建 NoOpLoggingSystem 对象。
    • <2.2> 处,调用 #get(ClassLoader classLoader, String loggingSystemClass) 方法,获得 loggingSystem 对应的 LoggingSystem 类,进行创建对象。代码如下:

      // LoggingSystem.java

      private static LoggingSystem get(ClassLoader classLoader, String loggingSystemClass) {
      try {
      Class<?> systemClass = ClassUtils.forName(loggingSystemClass, classLoader);
      return (LoggingSystem) systemClass.getConstructor(ClassLoader.class).newInstance(classLoader);
      } catch (Exception ex) {
      throw new IllegalStateException(ex);
      }
      }
      • systemClass 中的 VALUES ,就是 loggingSystem 对应的类。
  • <3> 处,如果为空,说明未配置,则顺序查找 SYSTEMS 中的类。如果存在指定类,则创建该类。

3.2 beforeInitialize

#beforeInitialize() 抽象方法,初始化的前置方法。代码如下:

// LoggingSystem.java

/**
* 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();

3.3 initialize

#initialize() 方法,初始化。代码如下:

// LoggingSystem.java

/**
* 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) {
}
  • 目前是个空方法,需要子类来实现。
  • 我们先不着急看子类的实现,等后面继续看。

3.4 setLogLevel

#setLogLevel(String loggerName, LogLevel level) 方法,设置指定 loggerName 的日志级别。代码如下:

// LoggingSystem.java

/**
* Sets the logging level for a given logger.
* @param loggerName the name of the logger to set ({@code null} can be used for the
* root logger).
* @param level the log level ({@code null} can be used to remove any custom level for
* the logger and use the default configuration instead)
*/
public void setLogLevel(String loggerName, LogLevel level) {
throw new UnsupportedOperationException("Unable to set log level");
}
  • 目前是个空方法,需要子类来实现。
  • 我们先不着急看子类的实现,等后面继续看。

3.5 getShutdownHandler

#getShutdownHandler() 方法,获得 ShutdownHook 的 Runnable 对象。代码如下:

// LoggingSystem.java

/**
* Returns a {@link Runnable} that can handle shutdown of this logging system when the
* JVM exits. The default implementation returns {@code null}, indicating that no
* shutdown is required.
* @return the shutdown handler, or {@code null}
*/
public Runnable getShutdownHandler() {
return null;
}
  • 目前是个空方法,需要子类来实现。
  • 我们先不着急看子类的实现,等后面继续看。

3.6 cleanUp

#cleanUp() 方法,清理。代码如下:

// LoggingSystem.java

/**
* Clean up the logging system. The default implementation does nothing. Subclasses
* should override this method to perform any logging system-specific cleanup.
*/
public void cleanUp() {
}
  • 目前是个空方法,需要子类来实现。
  • 我们先不着急看子类的实现,等后面继续看。

4. LoggingSystemProperties

org.springframework.boot.logging.LoggingSystemProperties ,LoggingSystem 的配置类。

4.1 构造方法

// LoggingSystemProperties.java

private final Environment environment;

public LoggingSystemProperties(Environment environment) {
Assert.notNull(environment, "Environment must not be null");
this.environment = environment;
}

4.2 apply

#apply() 方法,解析 environment 的配置变量到系统属性中。代码如下:

// LoggingSystemProperties.java

public void apply() {
apply(null);
}

public void apply(LogFile logFile) {
// <1> 获得 PropertyResolver 对象
PropertyResolver resolver = getPropertyResolver();
// <2> 解析配置文件到系统属性中
setSystemProperty(resolver, EXCEPTION_CONVERSION_WORD, "exception-conversion-word");
setSystemProperty(PID_KEY, new ApplicationPid().toString()); // 应用进程编号
setSystemProperty(resolver, CONSOLE_LOG_PATTERN, "pattern.console");
setSystemProperty(resolver, FILE_LOG_PATTERN, "pattern.file");
setSystemProperty(resolver, FILE_MAX_HISTORY, "file.max-history");
setSystemProperty(resolver, FILE_MAX_SIZE, "file.max-size");
setSystemProperty(resolver, LOG_LEVEL_PATTERN, "pattern.level");
setSystemProperty(resolver, LOG_DATEFORMAT_PATTERN, "pattern.dateformat");
// <3> 如果 logFile 非空,则应用配置
if (logFile != null) {
logFile.applyToSystemProperties();
}
}
  • <1> 处,调用 #getPropertyResolver() 方法,获得 PropertyResolver 对象。代码如下:
    // LoggingSystemProperties.java

    private PropertyResolver getPropertyResolver() {
    if (this.environment instanceof ConfigurableEnvironment) {
    PropertySourcesPropertyResolver resolver = new PropertySourcesPropertyResolver(((ConfigurableEnvironment) this.environment).getPropertySources());
    resolver.setIgnoreUnresolvableNestedPlaceholders(true);
    return resolver;
    }
    return this.environment;
    }
  • <2> 处,调用 #setSystemProperty(PropertyResolver resolver, String systemPropertyName, String propertyName) 方法,解析配置文件到系统属性中。代码如下:

    // LoggingSystemProperties.java

    public static final String PID_KEY = "PID";
    public static final String EXCEPTION_CONVERSION_WORD = "LOG_EXCEPTION_CONVERSION_WORD";
    public static final String LOG_FILE = "LOG_FILE";
    public static final String LOG_PATH = "LOG_PATH";
    public static final String FILE_LOG_PATTERN = "FILE_LOG_PATTERN";
    public static final String FILE_MAX_HISTORY = "LOG_FILE_MAX_HISTORY";
    public static final String FILE_MAX_SIZE = "LOG_FILE_MAX_SIZE";
    public static final String LOG_LEVEL_PATTERN = "LOG_LEVEL_PATTERN";
    public static final String LOG_DATEFORMAT_PATTERN = "LOG_DATEFORMAT_PATTERN";

    private void setSystemProperty(PropertyResolver resolver, String systemPropertyName, String propertyName) {
    setSystemProperty(systemPropertyName, resolver.getProperty("logging." + propertyName)); // <X>
    }

    private void setSystemProperty(String name, String value) {
    if (System.getProperty(name) == null && value != null) {
    System.setProperty(name, value);
    }
    }
    • <X> 处,读取的是 environment 中的 logging. 开头的配置属性。

5. LogFile

org.springframework.boot.logging.LogFile ,日志文件。

5.1 构造方法

// LogFile.java

/**
* 文件名
*/
private final String file;
/**
* 文件路径
*/
private final String path;

5.2 applyToSystemProperties

#applyToSystemProperties() 方法,应用 filepath 到系统属性。代码如下:

// LogFile.java

public static final String FILE_NAME_PROPERTY = "logging.file.name";
public static final String FILE_PATH_PROPERTY = "logging.file.path";

public void applyToSystemProperties() {
applyTo(System.getProperties());
}

public void applyTo(Properties properties) {
put(properties, LoggingSystemProperties.LOG_PATH, this.path);
put(properties, LoggingSystemProperties.LOG_FILE, toString());
}
  • #toString() 方法,返回文件名。代码如下:
    // LogFile.java

    @Override
    public String toString() {
    if (StringUtils.hasLength(this.file)) {
    return this.file;
    }
    return new File(this.path, "spring.log").getPath();
    }
  • #put(Properties properties, String key, String value) 方法,添加属性值到系统属性。代码如下:
    // LogFile.java

    private void put(Properties properties, String key, String value) {
    if (StringUtils.hasLength(value)) {
    properties.put(key, value);
    }
    }

5.3 get

#get(PropertyResolver propertyResolver) 方法,获得(创建)LogFile 对象。代码如下:

// LogFile.java

public static LogFile get(PropertyResolver propertyResolver) {
// <1> 获得 file 和 path 属性
String file = getLogFileProperty(propertyResolver, FILE_NAME_PROPERTY, FILE_PROPERTY);
String path = getLogFileProperty(propertyResolver, FILE_PATH_PROPERTY, PATH_PROPERTY);
// <2> 创建 LogFile 对象
if (StringUtils.hasLength(file) || StringUtils.hasLength(path)) {
return new LogFile(file, path);
}
return null;
}
  • <1> 处,调用 #getLogFileProperty(PropertyResolver propertyResolver, String propertyName, String deprecatedPropertyName) 方法,获得 file  path 属性。代码如下:
    // LogFile.java

    private static String getLogFileProperty(PropertyResolver propertyResolver, String propertyName, String deprecatedPropertyName) {
    String property = propertyResolver.getProperty(propertyName);
    if (property != null) {
    return property;
    }
    return propertyResolver.getProperty(deprecatedPropertyName);
    }
  • <2> 处,创建 LogFile 对象。

6. LogLevel

org.springframework.boot.logging.LogLevel ,Spring Boot 日志枚举类。代码如下:

// LogLevel.java

public enum LogLevel {

TRACE, DEBUG, INFO, WARN, ERROR, FATAL, OFF

}

每个日志框架,都有其日志级别。通过 LogLevel 枚举类,和它们映射。

7. LoggingSystem 的实现类

7.1 NoOpLoggingSystem

NoOpLoggingSystem ,是 LoggingSystem 的内部静态类,继承 LoggingSystem 类,空操作的 LoggingSystem 实现类,用于禁用日志系统的时候。代码如下:

// LoggingSystem#NoOpLoggingSystem.java

static class NoOpLoggingSystem extends LoggingSystem {

@Override
public void beforeInitialize() {

}

@Override
public void setLogLevel(String loggerName, LogLevel level) {

}

@Override
public List<LoggerConfiguration> getLoggerConfigurations() {
return Collections.emptyList();
}

@Override
public LoggerConfiguration getLoggerConfiguration(String loggerName) {
return null;
}

}

7.2 AbstractLoggingSystem

org.springframework.boot.logging.AbstractLoggingSystem ,继承 LoggingSystem 抽象类,是 LoggingSystem 的抽象基类。

7.2.1 构造方法

// AbstractLoggingSystem.java

private final ClassLoader classLoader;

public AbstractLoggingSystem(ClassLoader classLoader) {
this.classLoader = classLoader;
}

7.2.2 initialize

实现 #initialize(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile) 方法,提供模板化的初始化逻辑。代码如下:

// AbstractLoggingSystem.java

@Override
public void initialize(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile) {
// <1> 有自定义的配置文件,则使用指定配置文件进行初始化
if (StringUtils.hasLength(configLocation)) {
initializeWithSpecificConfig(initializationContext, configLocation, logFile);
return;
}
// <2> 无自定义的配置文件,则使用约定配置文件进行初始化
initializeWithConventions(initializationContext, logFile);
}
  • <1> 处,有自定义的配置文件,则调用 #initializeWithSpecificConfig(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile) 方法,使用指定配置文件进行初始化。详细解析,见 「7.2.2.1 initializeWithSpecificConfig」 
  • <2> 处,无自定义的配置文件,则调用 #initializeWithConventions(LoggingInitializationContext initializationContext, LogFile logFile) 方法,使用约定配置文件进行初始化。详细解析,见 7.2.2.2 initializeWithConventions 

7.2.2.1 initializeWithSpecificConfig

#initializeWithSpecificConfig(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile) 方法,使用指定配置文件进行初始化。代码如下:

// AbstractLoggingSystem.java

private void initializeWithSpecificConfig(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile) {
// <1> 获得配置文件(可能有占位符)
configLocation = SystemPropertyUtils.resolvePlaceholders(configLocation);
// <2> 加载配置文件
loadConfiguration(initializationContext, configLocation, logFile);
}
  • <1> 处,获得配置文件(可能有占位符)。
  • <2> 处,调用 #loadConfiguration(LoggingInitializationContext initializationContext, String location, LogFile logFile)) 抽象方法,加载配置文件。代码如下:
    // AbstractLoggingSystem.java

    /**
    * 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);

7.2.2.2 initializeWithConventions

#initializeWithConventions(LoggingInitializationContext initializationContext, LogFile logFile) 方法,使用约定配置文件进行初始化。代码如下:

// AbstractLoggingSystem.java

private void initializeWithConventions(LoggingInitializationContext initializationContext, LogFile logFile) {
// <1> 获得约定配置文件
String config = getSelfInitializationConfig();
// <2> 如果获取到,结果 logFile 为空,则重新初始化
if (config != null && logFile == null) {
// self initialization has occurred, reinitialize in case of property changes
reinitialize(initializationContext);
return;
}
// <3> 如果获取不到,则尝试获得约定配置文件(带 spring 后缀)
if (config == null) {
config = getSpringInitializationConfig();
}
// <4> 如果获取到,则加载配置文件
if (config != null) {
loadConfiguration(initializationContext, config, logFile);
return;
}
// <5> 如果获取不到,则加载默认配置
loadDefaults(initializationContext, logFile);
}
  • <1> 处,调用 #getSelfInitializationConfig() 方法,获得约定配置文件。代码如下:

    // AbstractLoggingSystem.java

    protected String getSelfInitializationConfig() {
    return findConfig(getStandardConfigLocations());
    }

    protected abstract String[] getStandardConfigLocations();

    private String findConfig(String[] locations) {
    // 遍历 locations 数组,逐个判断是否存在。若存在,则返回
    for (String location : locations) {
    ClassPathResource resource = new ClassPathResource(location, this.classLoader);
    if (resource.exists()) {
    return "classpath:" + location;
    }
    }
    return null;
    }
    • #getStandardConfigLocations() 抽象方法,获得约定的配置文件。例如说:LogbackLoggingSystem 返回的是 "logback-test.groovy""logback-test.xml" "logback.groovy""logback.xml" 
  • <2> 处,如果获取到,结果 logFile 为空,则调用 #reinitialize(LoggingInitializationContext initializationContext) 方法,重新初始化。代码如下:

    // AbstractLoggingSystem.java

    /**
    * Reinitialize the logging system if required. Called when
    * {@link #getSelfInitializationConfig()} is used and the log file hasn't changed. May
    * be used to reload configuration (for example to pick up additional System
    * properties).
    * @param initializationContext the logging initialization context
    */
    protected void reinitialize(LoggingInitializationContext initializationContext) {
    }
    • 一般情况下,logFile 非空~
  • <3> 处,如果获取不到,则调用 #getSpringInitializationConfig() 方法,尝试获得约定配置文件(带 -spring 后缀)。代码如下:

    // AbstractLoggingSystem.java

    /**
    * Return any spring specific initialization config that should be applied. By default
    * this method checks {@link #getSpringConfigLocations()}.
    * @return the spring initialization config or {@code null}
    */
    protected String getSpringInitializationConfig() {
    return findConfig(getSpringConfigLocations());
    }

    /**
    * Return the spring config locations for this system. By default this method returns
    * a set of locations based on {@link #getStandardConfigLocations()}.
    * @return the spring config locations
    * @see #getSpringInitializationConfig()
    */
    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;
    }
    • 例如说:LogbackLoggingSystem 返回的是 "logback-test-spring.groovy""logback-test-spring.xml" "logback-spring.groovy""logback-spring.xml" 
  • <4> 处,如果获取到,则调用 #loadConfiguration(LoggingInitializationContext initializationContext, String location, LogFile logFile)) 抽象方法,加载配置文件。

  • <5> 处,如果获取不到,则调用 #loadDefaults(LoggingInitializationContext initializationContext, LogFile logFile) 抽象方法,加载默认配置。代码如下:
    // AbstractLoggingSystem.java

    /**
    * Load sensible defaults for the logging system.
    * @param initializationContext the logging initialization context
    * @param logFile the file to load or {@code null} if no log file is to be written
    */
    protected abstract void loadDefaults(LoggingInitializationContext initializationContext, LogFile logFile);

7.2.3 LogLevels

LogLevels ,是 AbstractLoggingSystem 的内部静态类,用于 Spring Boot LogLevel 和日志框架的 LogLevel 做映射。代码如下:

// AbstractLoggingSystem#LogLevels.java

/**
* Maintains a mapping between native levels and {@link LogLevel}.
*
* @param <T> the native level type
*/
protected static class LogLevels<T> {

private final Map<LogLevel, T> systemToNative;

private final Map<T, LogLevel> nativeToSystem;

public LogLevels() {
this.systemToNative = new EnumMap<>(LogLevel.class);
this.nativeToSystem = new HashMap<>();
}

public void map(LogLevel system, T nativeLevel) {
if (!this.systemToNative.containsKey(system)) {
this.systemToNative.put(system, nativeLevel);
}
if (!this.nativeToSystem.containsKey(nativeLevel)) {
this.nativeToSystem.put(nativeLevel, system);
}
}

public LogLevel convertNativeToSystem(T level) {
return this.nativeToSystem.get(level);
}

public T convertSystemToNative(LogLevel level) {
return this.systemToNative.get(level);
}

public Set<LogLevel> getSupported() {
return new LinkedHashSet<>(this.nativeToSystem.values());
}

}

7.3 Slf4JLoggingSystem

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

7.3.1 beforeInitialize

重写 #beforeInitialize() 方法,代码如下:

// Slf4JLoggingSystem.java

@Override
public void beforeInitialize() {
// 父方法
super.beforeInitialize();
// <1> 配置 JUL 的桥接处理器
configureJdkLoggingBridgeHandler();
}
  • 因为艿艿没有特别完整的了解过日志框架,所以下面的解释,更多凭的是“直觉”!如果有错误的地方,给艿艿星球留言哈~
  • <1> 处,调用 #configureJdkLoggingBridgeHandler() 方法,配置 JUL 的桥接处理器。详细解析,见 「7.3.1.1 configureJdkLoggingBridgeHandler」 

7.3.1.1 configureJdkLoggingBridgeHandler

#configureJdkLoggingBridgeHandler() 方法,配置 JUL 的桥接处理器。代码如下:

// Slf4JLoggingSystem.java

private void configureJdkLoggingBridgeHandler() {
try {
// <1> 判断 JUL 是否桥接到 SLF4J 了
if (isBridgeJulIntoSlf4j()) {
// <2> 移除 JUL 桥接处理器
removeJdkLoggingBridgeHandler();
// <3> 重新安装 SLF4JBridgeHandler
SLF4JBridgeHandler.install();
}
} catch (Throwable ex) {
// Ignore. No java.util.logging bridge is installed.
}
}
  • <1> 处,调用 #isBridgeJulIntoSlf4j() 方法,判断 JUL 是否桥接到 SLF4J 了。代码如下:

    // Slf4JLoggingSystem.java

    private static final String BRIDGE_HANDLER = "org.slf4j.bridge.SLF4JBridgeHandler";

    /**
    * Return whether bridging JUL into SLF4J or not.
    * @return whether bridging JUL into SLF4J or not
    * @since 2.0.4
    */
    protected final boolean isBridgeJulIntoSlf4j() {
    return isBridgeHandlerAvailable() // 判断是否存在 SLF4JBridgeHandler 类
    && isJulUsingASingleConsoleHandlerAtMost(); // 判断是否 JUL 只有 ConsoleHandler 处理器被创建了
    }

    protected final boolean isBridgeHandlerAvailable() {
    return ClassUtils.isPresent(BRIDGE_HANDLER, getClassLoader());
    }

    private boolean isJulUsingASingleConsoleHandlerAtMost() {
    Logger rootLogger = LogManager.getLogManager().getLogger("");
    Handler[] handlers = rootLogger.getHandlers();
    return handlers.length == 0
    || (handlers.length == 1 && handlers[0] instanceof ConsoleHandler);
    }
    • 第一个方法,调用后面的两个方法判断~
  • <2> 处,调用 #removeJdkLoggingBridgeHandler() 方法,移除 JUL 桥接处理器。代码如下:

    // Slf4JLoggingSystem.java

    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
    }
    }
    • 移除 JUL 的 ConsoleHandler ,卸载 SLF4JBridgeHandler 。
  • <3> 处,会重新安装 SLF4JBridgeHandler。

7.3.2 cleanUp

重写 #cleanUp() 方法,代码如下:

// Slf4JLoggingSystem.java

@Override
public void cleanUp() {
// 判断 JUL 是否桥接到 SLF4J 了
if (isBridgeHandlerAvailable()) {
// 移除 JUL 桥接处理器
removeJdkLoggingBridgeHandler();
}
}

7.3.3 loadConfiguration

重写 #loadConfiguration(LoggingInitializationContext initializationContext, String location, LogFile logFile) 方法,代码如下:

// Slf4JLoggingSystem.java

@Override
protected void loadConfiguration(LoggingInitializationContext initializationContext, String location, LogFile logFile) {
Assert.notNull(location, "Location must not be null");
if (initializationContext != null) {
applySystemProperties(initializationContext.getEnvironment(), logFile);
}
}
  • 调用 #applySystemProperties(Environment environment, LogFile logFile) 方法,应用 environment  logFile 的属性,到系统属性种。在 「4.2 apply」 中,已经详细解析。
  • 不过有一点,搞不懂,为什么这么实现。

7.4 LogbackLoggingSystem

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

7.4.1 beforeInitialize

重写 #beforeInitialize() 方法,代码如下:

// LogbackLoggingSystem.java

@Override
public void beforeInitialize() {
// <1.1> 获得 LoggerContext 对象
LoggerContext loggerContext = getLoggerContext();
// <1.2> 如果已经初始化过,则直接返回
if (isAlreadyInitialized(loggerContext)) {
return;
}
// <2> 调用父方法
super.beforeInitialize();
// <3> 添加 FILTER 到其中
loggerContext.getTurboFilterList().add(FILTER);
}
  • <1.1> 处,调用 #getLoggerContext() 方法,获得 LoggerContext 对象。代码如下:
    // LogbackLoggingSystem.java

    private LoggerContext getLoggerContext() {
    ILoggerFactory factory = StaticLoggerBinder.getSingleton().getLoggerFactory();
    Assert.isInstanceOf(LoggerContext.class, factory,
    String.format(
    "LoggerFactory is not a Logback LoggerContext but Logback is on "
    + "the classpath. Either remove Logback or the competing "
    + "implementation (%s loaded from %s). If you are using "
    + "WebLogic you will need to add 'org.slf4j' to "
    + "prefer-application-packages in WEB-INF/weblogic.xml",
    factory.getClass(), getLocation(factory)));
    return (LoggerContext) factory;
    }
  • <1.2> 处,调用 #isAlreadyInitialized(LoggerContext loggerContext) 方法,判断如果已经初始化过,则直接返回。代码如下:
    // LogbackLoggingSystem.java

    private boolean isAlreadyInitialized(LoggerContext loggerContext) {
    return loggerContext.getObject(LoggingSystem.class.getName()) != null;
    }
  • <2> 处,调用父方法。
  • <3> 处,添加 FILTER  loggerContext 其中。代码如下:

    // LogbackLoggingSystem.java

    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;
    }

    };
    • 因为此时,Logback 并未初始化好,所以全部返回 FilterReply.DENY 。即,先不打印日志。

7.4.2 getStandardConfigLocations

实现 #getStandardConfigLocations() 方法,获得约定的配置文件的数组。代码如下:

// LogbackLoggingSystem.java

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

7.4.3 initialize

重写 #initialize(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile) 方法,代码如下:

// LogbackLoggingSystem.java

private static final String CONFIGURATION_FILE_PROPERTY = "logback.configurationFile";

@Override
public void initialize(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile) {
// <1> 如果已经初始化,则返回
LoggerContext loggerContext = getLoggerContext();
if (isAlreadyInitialized(loggerContext)) {
return;
}
// <2> 调用父方法
super.initialize(initializationContext, configLocation, logFile);
// <3> 移除 FILTER
loggerContext.getTurboFilterList().remove(FILTER);
// <4> 标记已经初始化
markAsInitialized(loggerContext);
// <5> 如果配置了 logback.configurationFile ,则打印日志
if (StringUtils.hasText(System.getProperty(CONFIGURATION_FILE_PROPERTY))) {
getLogger(LogbackLoggingSystem.class.getName()).warn("Ignoring '" + CONFIGURATION_FILE_PROPERTY + "' system property. " + "Please use 'logging.config' instead.");
}
}

相关推荐

    Spring5 源码分析(第 2 版) .zip

    《Spring5 源码分析(第 2 版)》是针对Spring框架第五个主要版本的深度解析著作,由知名讲师倾心打造,旨在帮助读者深入理解Spring框架的内部工作机制,提升对Java企业级应用开发的专业技能。本书涵盖了Spring框架的...

    基于Java+Spring Boot实现的样本库实验室管理系统LIMS源码

    5. 监控与日志:Spring Boot集成了Actuator,可以对应用性能进行监控,同时Spring Boot的日志管理方便了问题排查和系统优化。 四、LIMS系统核心模块 1. 样本管理:系统的核心功能,包括样本的登记、分类、编码、...

    spring高级源码分析

    总的来说,《Spring高级源码分析》将带你深入Spring的各个模块,从IoC容器到AOP,从MVC到Data访问,再到Boot的自动化配置。通过对源码的深度探索,你将能够更好地理解和利用Spring框架,提升自己的开发技能。

    Spring学习心得(二)——日志和数据库

    结合源码分析和工具使用,如IDEA的数据库插件,可以进一步提升开发效率和对Spring数据访问机制的理解。 总的来说,Spring框架提供了强大的日志管理和数据库集成能力,使得开发者能够专注于业务逻辑,而非底层细节。...

    SpringBoot源码解析——BeanDefinitionLoader 源码分析.docx

    在源码分析中,我们首先看到`prepareContext`方法被调用,这是`SpringApplication`类中的一个重要方法,它负责初始化`ApplicationContext`(应用上下文)并为后续的bean加载做准备。 `prepareContext`方法做了以下...

    基于spring boot的疫情信息管理系统设计与实现(毕业论文)

    《基于Spring Boot的疫情信息管理系统设计与实现》 在当今社会,信息的快速传播和有效管理对于应对公共卫生事件,如疫情,至关重要。本系统利用先进的技术栈,特别是Spring Boot框架,构建了一个集用户互动和管理员...

    Spring Boot企业某信点can系统(视频+源码).zip

    《Spring Boot企业微信CAN系统构建详解》 在现代企业级应用开发中,Spring Boot以其简洁高效的特点成为首选框架之一。本教程将深入探讨如何利用Spring Boot构建一个与微信深度集成的企业级CAN(Customer Activity ...

    spring-boot-master-源码.zip

    Spring Boot是Java后端开发领域中的一个热门框架,它极大地简化了Spring应用的初始搭建以及开发过程。Spring Boot的核心设计...通过阅读和分析源码,你将能够更深入地掌握Spring Boot,从而在实际开发中更加得心应手。

    java毕业设计&课设-Spring Boot企业某信点can系统(视频+源码).doc

    根据提供的文件信息,我们可以推断出这是一份关于Java毕业设计项目的文档,主要涉及Spring Boot技术栈,并且针对的是一个类似企业微信的功能模块——“点can系统”。下面将基于这个项目的信息来提炼出相关的知识点。...

    Java毕业设计——基于spring boot的在线招标网站设计与实现(源码+数据库).7z

    本项目是一个Java毕业设计,它基于Spring Boot框架构建了一个在线招标网站。这个系统的主要目标是为用户提供一个方便、...通过学习和分析这个项目,学生不仅可以深化对Spring Boot的理解,还能提升实际项目开发的能力。

    Spring Boot 视频

    本视频教程将带你深入理解Spring Boot的核心特性,并结合源码分析,提升你的技能水平。 首先,我们来探讨Spring Boot的初始化机制。Spring Boot启动时,会自动扫描`@SpringBootApplication`注解所在的包及其子包下...

    springboot整合activity工作流审批前后台代码(有数据库)

    这个项目提供了从需求分析到实际开发的完整实例,适合学习Spring Boot集成第三方库以及如何构建企业级的审批流程管理系统。通过对源代码的深入研究,开发者可以学习到如何在实际场景中应用上述技术。

    ProSpring——Spring专业开发指南

    书中可能包含如何阅读和分析Spring源码的方法和技巧。 10. **工具的使用**: 除了理论知识,本书可能还会介绍一些辅助开发的工具,如IDE集成、构建工具Maven或Gradle,以及调试和测试工具的使用方法。 在提供的...

    SpringBoot企业级开发教程_源代码.zip

    通过`spring-boot-starter-actuator`起步依赖,可以查看应用的健康状况、指标、日志等。 8. **测试支持**: SpringBoot内置了测试支持,`@SpringBootTest`注解可以启动整个应用上下文进行集成测试,而`@WebMvcTest...

    Spring有关资料 源码

    Spring框架是Java开发中最常用的...对于初学者,通过阅读和分析这些源码,可以加深对Spring的理解,提升开发技能。同时,文档则可能包含Spring的使用指南、最佳实践以及常见问题解答,是学习和解决问题的重要参考资料。

    spring源码, 可以很好的学习spring源码, 对spring更深入理解

    这些切面可以在不修改原有代码的情况下被插入到系统中,如日志、事务管理等。AOP通过代理模式实现,Spring支持JDK动态代理和CGLIB字节码生成两种方式。 再来,Spring MVC是Spring用于构建Web应用的模块,它提供了...

    demospringboot-源码.rar

    《Spring Boot源码深度解析——以demospringboot为例》 Spring Boot作为现代Java开发的首选框架,以其简洁、高效和快速启动的特点深受开发者喜爱。本文将以“demospringboot”项目为例,深入剖析Spring Boot的源码...

    学分制选课系统(源码——文档)

    通过深入理解源码,可以掌握Web开发技术,如前端框架(如React、Vue)、后端框架(如Spring Boot)、数据库操作(如SQL)、权限控制机制(如JWT)等。此外,还可以学习到如何处理并发选课、如何优化数据库查询效率等...

    spring源码代码

    Spring 源码分析对于理解其内部工作原理、优化应用性能以及进行二次开发至关重要。让我们深入探讨 Spring 框架的核心组成部分和主要特性。 1. **依赖注入(Dependency Injection, DI)** Spring 的核心特性之一是...

    spring宝典书源码07

    源码分析可以帮助我们理解如何配置Spring Security,保护应用程序免受未经授权的访问。 10. **测试支持**:Spring提供强大的单元测试和集成测试支持,如MockMVC、TestContext框架等。通过源码,我们可以了解如何...

Global site tag (gtag.js) - Google Analytics