一、概况

  在项目开发当中,日志对于我们开发或运维人员来说,是一个必不可少的工具。在线下我们可以通过 debug 来查找排除问题,但对于线上系统来说,我们只能通过日志分析来查找问题,我们可以通过日志打印来获取我们需要的信息来判断、分析系统运行结果是否正常或哪里出现了问题,可以定位到具体问题和位置。

  当前流行的日志框架有:

  • jul(java util logging)
  • log4j
  • log4j2
  • jcl(Jakarta Commons Logging)
  • logback
  • slf4j

二、应用和探讨

  1.jul(java util logging)

  java自带的日志记录技术(java.util.logging.Logger),可以直接记录日志;功能比较太过于简单,不支持占位符显示,拓展性比较差;

import java.util.logging.Logger;
public class JUL {
public static void main(String[] args) {
Logger logger = Logger.getLogger("JUL");
logger.info("java util logging");
}
}

  2.log4j

  不支持使用占位符,在高并发日志量大的情况存在bug,容易导致内存、CPU冲高;

  使用 log4j 需要 pom 文件中引入 log4j 所需要的jar包和引入 log4j 的配置文件 log4j.properties

pom.xml
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
log4j.properties

log4j.rootLogger = info,stdout
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target = System.out
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern = [%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n
import org.apache.log4j.Logger;
public class Log4j {
public static void main(String[] args) {
Logger logger = Logger.getLogger(Log4j.class);
logger.info("log4j");
}
}

    • 记住 jul 和 log4j 日志输出格式的差别,颜色不一致:jul 输出日志为红色,log4j 输出日志是白色;

  3.log4j2 

  log4j2 和 log4j 是同一个作者开发,只不过log4j2是重新架构的一款日志组件,改进了log4j的bug,以及吸取了优秀的logback的设计重新推出的一款新组件,在效率和性能上有了很大的提升;

  必须同时依赖 log4j-core 和 log4j-api,否则报错:“ERROR StatusLogger Log4j2 could not find a logging implementation”;

pom.xml
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.13.2</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.9.1</version>
</dependency>

 同时需要配置文件和测试代码:log4j2.xml、Log4j2.java

<?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>
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Log4j2 {
public static void main(String[] args) {
Logger logger = LogManager.getLogger("Log4j2");
logger.info("log4j2");
}
}

  4.jcl(Jakarta Commons Logging)

  Spring Framework 4.x 版本依赖了commons-logging.jar;Spring Framework 5.x 版本 依赖了spring-jcl.jar;

    Spring Framework 4.x :

    

    Spring Framework 5.x :

    

    1.commons-logging.jar:Spring Framework 4.x

pom.xml
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.20.RELEASE</version>
</dependency>

<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class JCL {
public static void main(String[] args) {
Log log = LogFactory.getLog("JCL");
log.info("jakatra common logging");
}
}

    跟上面的jul输出格式一致;

    探讨:为什么 jcl 的输出格式是 jul 格式呢?

    接下来从源码来进行分析:从 LogFactory.getLog("JCL") 开始,从下面可以看出是从工厂

LogFactory
public static Log getLog(String name) throws LogConfigurationException {
return getFactory().getInstance(name);
}
   getInstance(name)是一个抽象方法,看它的实现方法:
LogFactory
public abstract Log getInstance(String name)
throws LogConfigurationException;
LogFactoryImpl
public Log getInstance(String name) throws LogConfigurationException {
Log instance = (Log) instances.get(name);//开始是null
if (instance == null) {
instance = newInstance(name);//创建实例
instances.put(name, instance);//instances是一个Hashtable,实例放入缓存中
}
return instance;
}

    下面的代码就一行重要:

LogFactoryImpl
protected Log newInstance(String name) throws LogConfigurationException {
Log instance;
try {
if (logConstructor == null) {
instance = discoverLogImplementation(name);//创建实例
}
else {
Object params[] = { name };
instance = (Log) logConstructor.newInstance(params);
} if (logMethod != null) {
Object params[] = { this };
logMethod.invoke(instance, params);
} return instance; } catch (LogConfigurationException lce) { // this type of exception means there was a problem in discovery
// and we've already output diagnostics about the issue, etc.;
// just pass it on
throw lce; } catch (InvocationTargetException e) {
// A problem occurred invoking the Constructor or Method
// previously discovered
Throwable c = e.getTargetException();
throw new LogConfigurationException(c == null ? e : c);
} catch (Throwable t) {
handleThrowable(t); // may re-throw t
// A problem occurred invoking the Constructor or Method
// previously discovered
throw new LogConfigurationException(t);
}
}

    在下面的代码中可以看到实例的创建是从classesToDiscover遍历获取来的:

LogFactoryImpl
private Log discoverLogImplementation(String logCategory)
throws LogConfigurationException {
if (isDiagnosticsEnabled()) {
logDiagnostic("Discovering a Log implementation...");
} initConfiguration(); Log result = null; // See if the user specified the Log implementation to use
String specifiedLogClassName = findUserSpecifiedLogClassName(); if (specifiedLogClassName != null) {
if (isDiagnosticsEnabled()) {
logDiagnostic("Attempting to load user-specified log class '" +
specifiedLogClassName + "'...");
} 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(
"No user-specified Log implementation; performing discovery" +
" using the standard supported logging implementations...");
}
for(int i=0; i<classesToDiscover.length && result == null; ++i) {
result = createLogFromClass(classesToDiscover[i], logCategory, true);//创建实例
} if (result == null) {
throw new LogConfigurationException
("No suitable Log implementation");
} return result;
}

    从 classesToDiscover 数组中可以看到,数组存放具体的日志框架的类名,通过循环数组依次去匹配这些类名是否在项目中被依赖了,如果找到依赖则直接使用,有先后顺序,log4j>jul。

LogFactoryImpl
private static final String LOGGING_IMPL_LOG4J_LOGGER = "org.apache.commons.logging.impl.Log4JLogger";
private static final String[] classesToDiscover = {
LOGGING_IMPL_LOG4J_LOGGER,
"org.apache.commons.logging.impl.Jdk14Logger",
"org.apache.commons.logging.impl.Jdk13LumberjackLogger",
"org.apache.commons.logging.impl.SimpleLog"
};

    下面使用commons-logging.jar和 log4j :下面的运行结果验证了上面的源码

pom.xml
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>

    2.spring-jcl.jar:Spring Framework 5.x

pom.xml
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jcl</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class JCL {
public static void main(String[] args) {
Log log = LogFactory.getLog("JCL");
log.info("jakatra common logging");
}
}

  从下图可以看出,spring-jcl 默认还是使用 jul:

  接下来还是从源码来进行分析查看它的运行机制:从 LogFactory.getLog("JCL")开始,从下面可以看到它是从一个适配器获取日志对象的

public static Log getLog(String name) {
return LogAdapter.createLog(name);

  从下面代码可以看出,LogAdapter 在初始化时,会执行一个 static 的代码块,设置 logApi 的值,依次用 log4j2,slf4j-LAL(可以将 log4j 桥接到 slf4j),slf4j 去反射判断是否存在对应依赖,如果有则设置 logApi 为对应值,否则默认为 jul,然后在 createLog 时,switch-case 根据 logApi 去获取 log 对象,默认是 jul 来实现。

LogAdapter:

    private static final String LOG4J_SPI = "org.apache.logging.log4j.spi.ExtendedLogger";

    private static final String LOG4J_SLF4J_PROVIDER = "org.apache.logging.slf4j.SLF4JProvider";

    private static final String SLF4J_SPI = "org.slf4j.spi.LocationAwareLogger";

    private static final String SLF4J_API = "org.slf4j.Logger";

    private static final LogApi logApi;

    static {
if (isPresent(LOG4J_SPI)) {
if (isPresent(LOG4J_SLF4J_PROVIDER) && isPresent(SLF4J_SPI)) {
// log4j-to-slf4j bridge -> we'll rather go with the SLF4J SPI;
// however, we still prefer Log4j over the plain SLF4J API since
// the latter does not have location awareness support.
logApi = LogApi.SLF4J_LAL;
}
else {
// Use Log4j 2.x directly, including location awareness support
logApi = LogApi.LOG4J;//log4j2
}
}
else if (isPresent(SLF4J_SPI)) {
// Full SLF4J SPI including location awareness support
logApi = LogApi.SLF4J_LAL;
}
else if (isPresent(SLF4J_API)) {
// Minimal SLF4J API without location awareness support
logApi = LogApi.SLF4J;
}
else {
// java.util.logging as default
logApi = LogApi.JUL;
}
}
//加载类:加载到指定的类返回true,否则返回false(加载不到指定类会抛出异常)
private static boolean isPresent(String className) {
try {
Class.forName(className, false, LogAdapter.class.getClassLoader());
return true;
}
catch (ClassNotFoundException ex) {
return false;
}
}
public static Log createLog(String name) {
switch (logApi) {
case LOG4J://log4j2
return Log4jAdapter.createLog(name);
case SLF4J_LAL:
return Slf4jAdapter.createLocationAwareLog(name);
case SLF4J:
return Slf4jAdapter.createLog(name);
default:
// Defensively use lazy-initializing adapter class here as well since the
// java.logging module is not present by default on JDK 9. We are requiring
// its presence if neither Log4j nor SLF4J is available; however, in the
// case of Log4j or SLF4J, we are trying to prevent early initialization
// of the JavaUtilLog adapter - e.g. by a JVM in debug mode - when eagerly
// trying to parse the bytecode for all the cases of this switch clause.
return JavaUtilAdapter.createLog(name);
}
}

  总结:

    commons-logging.jar(Spring Framework 4.x )封装了一个静态数组(支持 jul 和log4j),然后依次循环遍历反射对应的依赖,如果对应依赖存在则返回对应实现,默认为 jul,其中 log4j>jul;

    spring-jcl.jar(Spring Framework 5.x)修改了commons-logging.jar,通过适配器获取日志对象,支持 jul 和 log4j2(不支持 log4j,如果项目需要 log4j,需要 slf4j 配合使用),还拓展支持 slf4j,有着更好的扩展性和兼容性;

    如下案例: 

        <dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
log4j.properties:

log4j.rootLogger = Console,stdout
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target = System.out
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern = [%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout 
@Configuration
@ComponentScan("com.hrh.log")
public class Config {
} public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
context.start();
}
}

    当上面的依赖变为 Spring Framework 4.x 时是可以打印日志:

        <dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.20.RELEASE</version>
</dependency>

    Spring Framework 5.x 下log4j 配合 slf4j 使用输出日志,上面的pom依赖修改为:

        <dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.21</version>
</dependency>

  5.logback

  log4j 的进化版,logback 除了具备 log4j 的所有优点之外,还解决了 log4j 不能使用占位符的问题。

  logback-classic依赖和logback.xml配置文件

pom.xml
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.7</version>
</dependency>
logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<Pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</Pattern>
</layout>
</appender>
<logger name="com.hrh.log" level="TRACE"/>
<root level="debug">
<appender-ref ref="STDOUT" />
</root>
</configuration>
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LogBack { public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger("logBack");
logger.trace("Trace Level.");
logger.debug("Debug Level.");
logger.info("Info Level.");
logger.warn("Warn Level.");
logger.error("Error Level.");
logger.info("{},it's OK.","Hi");//使用{}做占位符
}
}

  6.slf4j

  为了方便原先直接使用日志库(日志的核心功能实现: jul、log4j、log4j2、logback等)输出日志的形式转换为日志门面(slf4j、jcl)输出的形式提供的适配器。

  前面的 jul、log4j、log4j2、logback 是实现日志的日志库,简单日志可以使用 jul,复杂日志实现可以使用 logback 或 log4j2。很多时候我们的项目是从简单到复杂一代代迭代过来的,日志实现也是从简单到复杂,开始我们使用jul,后面系统复杂了我们需要使用 log4j2 日志框架,这时候我们如何将原来的日志输出在新的日志架构下实现呢?

  一个死板的方法是对代码一行行进行修改,把之前用 jul 的日志代码全部修改成 log4j2 的日志接口。但是这种方式不仅效率低下,而且做的工作都是重复性的工作,实际工作中不采用该方式。

  这时候我们就需要一个叫做 slf4j(Simple Logging Facade for Java,即Java简单日志记录接口集,也可以叫日志门面)的东西了,它是一个日志的接口规范,它对用户提供了统一的日志接口(使用Facade门面设计模式),屏蔽了底层不同日志组件的差异和实现细节,使用者无需关注底层实现的具体日志库。

  slf4j本身不记录日志,通过绑定器绑定一个具体的日志框架来完成日志记录。即slf4j 允许最终用户在部署时插入所需的日志框架,如果在项目中使用 slf4j 必须加一个依赖jar,即 slf4j-api-xxx.jar。而当我们需要更换日志组件的时候,我们只需要更换一个具体的日志组件 jar 包就可以了。

  比如说我们现在有个 app,通过 slf4j 打印日志,slf4j 需要通过一个绑定器(slf4j-jdk14-1.8.0-beta2)来绑定到我们的 jul 来输出日志。如果这时候需要在 app 中集成 spring4(spring4是利用jcl来打印日志的),这时候呢,考虑到系统的日志统一,可以使用桥接器(jcl-over-slf4j)将 jcl 桥接到 slf4j 来,然后通过 binding 到 jul 来输出日志,保证系统日志风格统一。

  下面是绑定类型(日志门面适配器)介绍:

绑定类型 说明
slf4j-log4j12-xxx.jar 绑定log4j,依赖log4j-xxx.jar,输出log4j日志
slf4j-jdk14-xxx.jar 绑定jul,输出jul日志
slf4j-jcl-xxx.jar 绑定jcl,默认输出jul日志,有log4j则输出log4j
logback-classic-xxx.jar 绑定logback,依赖logback-core-xxx.jar,输出logback日志

  下面是桥接类型(日志库适配器)介绍:

  比如左上角的第一个是将 jcl(或log4j、jul) 桥接到 slf4j,最后统一输出为 logback 日志;

  所以综上所述:

    绑定器绑定最后输出的日志类型;

    桥接器就是将指定日志类型桥接到 slf4j,最后统一输出绑定器绑定的日志类型,可以解释为指定日志类型输出的日志转换为绑定器绑定的日志类型;

    应用宜采用日志桥接、适配(绑定)机制,将 jul、log4j、jcl 等日志框架桥接到 slf4j,适配性能更高的 log4j2 日志输出框架打印;

  小贴士:对于日志量大的勿输出到控制台(Console);

  下面是几个简单案例实现:

1)slf4j+jul

pom.xml
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>1.7.21</version>
</dependency>
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; public class Slf4jJULLog {
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger(Slf4jJULLog.class);
logger.trace("Trace Level.");
logger.info("Info Level.");
logger.warn("Warn Level.");
logger.error("Error Level.");
}
}

2)slf4j+log4j

pom.xml

        <dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
log4j.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>
public class Slf4jLog4JLog {
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger(Slf4jLog4JLog.class);
logger.trace("Trace Level.");
logger.info("Info Level.");
logger.warn("Warn Level.");
logger.error("Error Level.");
}
}

  注意:当我们使用slf4j跟其他日志实现来搭建日志系统时,可能会存在一些循环引用导致栈溢出的问题。

  如下案例:

pom.xml
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
<version>1.7.25</version>
</dependency>
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; public class Slf4jLog4JLog {
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger(Slf4jLog4JLog.class);
logger.trace("Trace Level.");
logger.info("Info Level.");
logger.warn("Warn Level.");
logger.error("Error Level.");
}
}

  上面就是一个由于循环引用导致栈溢出的错误,这时因为绑定器绑定 log4j 时在 log4j 又经过桥接器接到 slf4j,然后又经过绑定器,依次循环,最后栈溢出。

  这个问题在实际场景中较常见,比如我们一个 app 使用 slf4j 绑定器绑定到 log4j,最后通过 log4j 输出日志,这时有个 jar 包 X1,使用 slf4j 绑定输出到 jul,然后 X2需要集成 X2,X2 使用 log4j 输出日志,X1 在集成 X2时,使用桥接器(log4j-over-slf4j),保证 X1 的日志最后都是通过 jul 输出的。这时候app 集成 X1 便会出现 log4j 的桥接器和绑定器共存的情况,便会出现上面栈溢出的错误,这时候我们就需要修改我们的 app 了。

  最后来下总结:

三.扩展

  log4j2更多详情参考:log4j2 的使用【超详细图文】

  logback更多详情参考:logback的使用和logback.xml详解

Spring笔记(10) - 日志体系的更多相关文章

  1. Spring 源码学习笔记10——Spring AOP

    Spring 源码学习笔记10--Spring AOP 参考书籍<Spring技术内幕>Spring AOP的实现章节 书有点老,但是里面一些概念还是总结比较到位 源码基于Spring-a ...

  2. Spring MVC 学习笔记10 —— 实现简单的用户管理(4.3)用户登录显示全局异常信息

    </pre>Spring MVC 学习笔记10 -- 实现简单的用户管理(4.3)用户登录--显示全局异常信息<p></p><p></p>& ...

  3. spring日志体系浅析(spring 5.x)

    日志是进行软件开发必不可少的一项功能,目前流行着很多开源日志库,比如log4j.log4j2.logback.JDK Logging.commons-logging.slf4j等. 几种日志产品的介绍 ...

  4. 【spring boot 学习笔记】日志相关

    1. 如何启用日志? maven依赖中添加:spring-boot-starter-logging <dependency> <groupId>org.springframew ...

  5. Spring笔记01_下载_概述_监听器

    目录 Spring笔记01 1.Spring介绍 1.1 Spring概述 1.2 Spring好处 1.3 Spring结构体系 1.4 在项目中的架构 1.5 程序的耦合和解耦 2. Spring ...

  6. java日志体系的思考(转)

    Java 日志缓存机制的实现 Java 日志管理最佳实践 混乱的 Java 日志体系 log日志远程统一记录 浅谈后端日志系统 Java异常处理和接口约定 用SLF4j/Logback打印日志-1 用 ...

  7. Spring笔记(6) - Spring的BeanFactoryPostProcessor探究

    一.背景 在说BeanFactoryPostProcessor之前,先来说下BeanPostProcessor,在前文Spring笔记(2) - 生命周期/属性赋值/自动装配及部分源码解析中讲解了Be ...

  8. Oracle RAC环境的日志体系

    转摘:http://blog.itpub.net/22664653/viewspace-722463/ 在Oracle RAC环境中比单个系统的日志体系要复杂:见下图: 简单介绍一下有关Oracle集 ...

  9. 【原创】架构师必备,带你弄清混乱的JAVA日志体系!

    引言 还在为弄不清commons-logging-xx.jar.log4j-xx.jar.sl4j-api-xx.jar等日志框架之间复杂的关系而感到烦恼吗? 还在为如何统一系统的日志输出而感到不知所 ...

随机推荐

  1. Atcoder ABC162 D - RGB Triplets

    传送门:D - RGB Triplets  题意:给你一个只含'R','G','B'的字符串,求有多少个长度为3且每个字符都不相等,并且第一第二和第二第三的区间长度不同的子序列. 题解:统计每个字符各 ...

  2. Codeforces Round #667 (Div. 3) B. Minimum Product (贪心,数学)

    题意:给你\(a\)和\(b\)两个数,每次操作可以是任意一个数\(-1\),最多操作\(n\),并且\(a\ge x\),\(b\ge y\),求操作后\(a*b\)的最小值. 题解:观察样例并且在 ...

  3. Python3.9.1中如何使用match方法?

    接触编程的朋友都听过正则表达式,在python中叫re模块,属于文字处理服务里面的一个模块.re里面有一个方法叫match,接下来的文章我来详细讲解一下match. 作为新手,我建议多使用帮助文档,也 ...

  4. SpringSecurity简单使用

    什么是SpringSecurity? Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架.它提供了一组可以在Spring应用上下文中配置 ...

  5. 国产网络测试仪MiniSMB - 如何配置VLAN数据流

    国产网络测试仪MiniSMB(www.minismb.com)是复刻smartbits的IP网络性能测试工具,是一款专门用于测试智能路由器,网络交换机的性能和稳定性的软硬件相结合的工具.可以通过此以太 ...

  6. HDU 4649 Professor Tian(概率DP)题解

    题意:一个表达式,n + 1个数,n个操作,每个操作Oi和数Ai+1对应,给出每个操作Oi和数Ai+1消失的概率,给出最后表达式值得期望.只有| , ^,&三个位操作 思路:显然位操作只对当前 ...

  7. SASS CSS3 koala

    CSS with superpowers Sass: Syntactically Awesome Style Sheets http://sass-lang.com/ Sass is the most ...

  8. ES6 & Classes & Interface

    ES6 & Classes & Interface what's the difference between javascript Classes & Interface ? ...

  9. modal 弹框遮罩层,滚动穿透bug 解决方案

    modal 弹框遮罩层,滚动穿透bug 解决方案 parent component 动态设置 lock css const computedClassName = classNames( 'activ ...

  10. nasm win x86 hello world

    hello.asm: extern MessageBoxA extern ExitProcess section .data title db "caption.",0 messa ...