<dependency>
<groupId>org.logback-extensions</groupId>
<artifactId>logback-ext-spring</artifactId>
<version>0.1.2</version>
<scope>compile</scope>
</dependency>
此外,号外下 LogbackConfigListener 和spring-web-${version}.jar 里面的org.springframework.web.util.Log4jConfigListener 的作者是同一个人 : Juergen Hoeller
大家对于spring 可能只知道spring 之父 Rod Johnson,而Juergen Hoeller 也是牛人,Juergen Hoeller是Spring框架的联合创始人,自2003年2月Rod的Interface21将Spring框架开源起,他就是最活跃的Spring开发者。Juergen是一名经验丰富的咨询师,擅长于Web应用、事务管理、O/R映射技术以及轻量级远程调用。
大家有兴趣的话,可以在这里看到他的专访
spring4是他主导的
https://spring.io/team/jhoeller
好吧,我们回到正题, "为毛官方还会提供spring ch.qos.logback.ext.spring.web.LogbackConfigListener 呢?"
在这里我们可以找到说明
https://github.com/qos-ch/logback-extensions/wiki/Spring
官方给出了两点理由:
- 通过
web.xml
ServletContextListener or Servlet for Spring-based web applications初始化 Intialize Logback . - 使用 Spring configuration (e.g.
applicationContext.xml
and Spring Java config) 配置Logback components (Appenders, Layouts, 等) .
注意: ch.qos.logback.ext.spring.web.LogbackConfigListener 需要 spring 3.1.1+版本
看了下usage 感觉没有什么用
比如 ,可以在spring applicationContext.xml bean 里面 配置 相关的 logback appender
<beans ...>
...
<bean id="consoleAppender" class="ch.qos.logback.core.ConsoleAppender" init-method="start" destroy-method="stop">
<property name="context" value="#{ T(org.slf4j.LoggerFactory).getILoggerFactory() }"/>
<property name="encoder">
<bean class="ch.qos.logback.classic.encoder.PatternLayoutEncoder" init-method="start" destroy-method="stop">
<property name="context" value="#{ T(org.slf4j.LoggerFactory).getILoggerFactory() }"/>
<property name="pattern" value="%date %-5level [%thread] %logger{36} %m%n"/>
</bean>
</property>
</bean>
...
</beans>
不过 usage 实用的(适合我用的)没什么, 但是我们还是追追源码,瞅瞅有啥好玩的
public class LogbackConfigListener implements ServletContextListener {
@Override
public void contextDestroyed(ServletContextEvent event) {
WebLogbackConfigurer.shutdownLogging(event.getServletContext());
}
@Override
public void contextInitialized(ServletContextEvent event) {
WebLogbackConfigurer.initLogging(event.getServletContext());
}
}
ch.qos.logback.ext.spring.web.LogbackConfigListener 就是个普通的 ServletContextListener
核心代码 ,调用的 ch.qos.logback.ext.spring.web.WebLogbackConfigurer.initLogging(ServletContext)
public static void initLogging(ServletContext servletContext) {
// Expose the web app root system property.
if (exposeWebAppRoot(servletContext)) {
WebUtils.setWebAppRootSystemProperty(servletContext);
}
// Only perform custom Logback initialization in case of a config file.
String location = servletContext.getInitParameter(CONFIG_LOCATION_PARAM);
if (location != null) {
// Perform actual Logback initialization; else rely on Logback's default initialization.
try {
// Resolve system property placeholders before potentially resolving real path.
location = SystemPropertyUtils.resolvePlaceholders(location);
// Return a URL (e.g. "classpath:" or "file:") as-is;
// consider a plain file path as relative to the web application root directory.
if (!ResourceUtils.isUrl(location)) {
location = WebUtils.getRealPath(servletContext, location);
}
// Write log message to server log.
servletContext.log("Initializing Logback from [" + location + "]");
// Initialize
LogbackConfigurer.initLogging(location);
} catch (FileNotFoundException ex) {
throw new IllegalArgumentException("Invalid 'logbackConfigLocation' parameter: " + ex.getMessage());
} catch (JoranException e) {
throw new RuntimeException("Unexpected error while configuring logback", e);
}
}
//If SLF4J's java.util.logging bridge is available in the classpath, install it. This will direct any messages
//from the Java Logging framework into SLF4J. When logging is terminated, the bridge will need to be uninstalled
try {
Class<?> julBridge = ClassUtils.forName("org.slf4j.bridge.SLF4JBridgeHandler", ClassUtils.getDefaultClassLoader());
Method removeHandlers = ReflectionUtils.findMethod(julBridge, "removeHandlersForRootLogger");
if (removeHandlers != null) {
servletContext.log("Removing all previous handlers for JUL to SLF4J bridge");
ReflectionUtils.invokeMethod(removeHandlers, null);
}
Method install = ReflectionUtils.findMethod(julBridge, "install");
if (install != null) {
servletContext.log("Installing JUL to SLF4J bridge");
ReflectionUtils.invokeMethod(install, null);
}
} catch (ClassNotFoundException ignored) {
//Indicates the java.util.logging bridge is not in the classpath. This is not an indication of a problem.
servletContext.log("JUL to SLF4J bridge is not available on the classpath");
}
}
关于WebUtils.setWebAppRootSystemProperty(ServletContext)
我们先来看看
// Expose the web app root system property.
if (exposeWebAppRoot(servletContext)) {
WebUtils.setWebAppRootSystemProperty(servletContext);
}
意思是 如果web.xml配置了
<context-param>
<param-name>logbackExposeWebAppRoot</param-name>
<param-value>true</param-value>
</context-param>
logbackExposeWebAppRoot
会调用 org.springframework.web.util.WebUtils.setWebAppRootSystemProperty(ServletContext)
public static void setWebAppRootSystemProperty(ServletContext servletContext) throws IllegalStateException {
Assert.notNull(servletContext, "ServletContext must not be null");
String root = servletContext.getRealPath("/");
if (root == null) {
throw new IllegalStateException(
"Cannot set web app root system property when WAR file is not expanded");
}
String param = servletContext.getInitParameter(WEB_APP_ROOT_KEY_PARAM);
String key = (param != null ? param : DEFAULT_WEB_APP_ROOT_KEY);
String oldValue = System.getProperty(key);
if (oldValue != null && !StringUtils.pathEquals(oldValue, root)) {
throw new IllegalStateException(
"Web app root system property already set to different value: '" +
key + "' = [" + oldValue + "] instead of [" + root + "] - " +
"Choose unique values for the 'webAppRootKey' context-param in your web.xml files!");
}
System.setProperty(key, root);
servletContext.log("Set web app root system property: '" + key + "' = [" + root + "]");
}
对于 这个方法 ,如果使用过 org.springframework.web.util.WebAppRootListener 的同学,应该会熟悉
可以看到
org.springframework.web.util.WebAppRootListener
ch.qos.logback.ext.spring.web.WebLogbackConfigurer
org.springframework.web.util.Log4jWebConfigurer
会调用这个方法
那么这个方法到底是做什么的呢?
step1.从 servletContext init param中查找 webAppRootKey参数值
step2:如果没有配置 webAppRootKey 那么使用 "webapp.root",从 java.lang.System.getProperty(String) 查到值
step3:如果从 System Property 得到的值 不是null,并且 值不等于 servletContext.getRealPath("/")
那么就抛
throw new IllegalStateException(
"Web app root system property already set to different value: '" +
key + "' = [" + oldValue + "] instead of [" + root + "] - " +
"Choose unique values for the 'webAppRootKey' context-param in your web.xml files!")
step4:设置属性到JVM Property
System.setProperty(key, root);
好,分析完了 WebUtils.setWebAppRootSystemProperty(ServletContext)
继续看 ch.qos.logback.ext.spring.web.WebLogbackConfigurer.initLogging(ServletContext)
从 servletContext 获得 logbackConfigLocation参数
如果不是null, 那么 调用 org.springframework.util.SystemPropertyUtils.resolvePlaceholders(String)
来解析这个配置参数值
也就是说, logbackConfigLocation配置的路径支持 ${}这样的写法
注意 : org.springframework.util.SystemPropertyUtils.resolvePlaceholders(String) 调用的是 org.springframework.util.SystemPropertyUtils.SystemPropertyPlaceholderResolver
而这个 Resolver只会使用 JVM properties 以及操作系统的env 环境变量
我了个去咧, 要是使用的是 ServletContextPropertyUtils org.springframework.web.util.ServletContextPropertyUtils.ServletContextPlaceholderResolver就好了
大家来看看
org.springframework.web.util.Log4jWebConfigurer.initLogging(ServletContext) 就是使用的 ServletContextPropertyUtils
话又说回来了 ,org.springframework.web.util.ServletContextPropertyUtils @since 3.2.2
期待 LogbackConfigListener 升级吧