一、概况

  在项目开发当中,日志对于我们开发或运维人员来说,是一个必不可少的工具。在线下我们可以通过 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. vlc音视频开发(一)环境搭建(qt篇)

    来源:微信公众号「编程学习基地」 目录 简介 qt配置vlc环境 simple_libvlc_qt_player 项目地址 简介 VLC 是一款自由.开源的跨平台多媒体播放器及框架,可播放大多数多媒体 ...

  2. ssh配置方面小实验①

    注意:sshd_config配置文件有些特殊:注释掉的选项,并不是不生效的,而是默认生效选项.使用某选项时,要先取消注释,再修改为yes或no关于效率和安全的说明:安全:telnet < ssh ...

  3. QXDM和QCAT软件使用指南

    一.传送门 链接:https://pan.baidu.com/s/1i55YXnf 密码:v6nw 二.QXDM,QPST和QCAT的简单说明 QXDM,QPST和QCAT是Qualcomm高通公司针 ...

  4. TCP协议与UDP协议的区别以及与TCP/IP协议的联系

    先介绍下什么是TCP,什么是UDP. 1. 什么是TCP? TCP(Transmission Control Protocol,传输控制协议)是面向连接的.可靠的字节流服务,也就是说,在收发数据前,必 ...

  5. js的变量,作用域,内存

    一,基本类型和引用类型的值基本类型的值是按值访问的,引用类型的值是保存在内存中的对象1,动态的属性 只有引用类型的值可以添加属性方法 不能给基本类型添加属性和方法2,复制变量值 复制基本类型的值,两个 ...

  6. Kattis amazingadventures Amazing Adventures(费用流路径)题解

    题意: 在一个\(100*100\)的方格中,要求从\(b\)走到\(g\),途中经过\(c\)但不经过\(u\),并且不能走已经做过的路.如果可以,就求出路径. 思路: 拆点建费用流,看能不能从\( ...

  7. 多线程(一)java并发编程基础知识

    线程的应用 如何应用多线程 在 Java 中,有多种方式来实现多线程.继承 Thread 类.实现 Runnable 接口.使用 ExecutorService.Callable.Future 实现带 ...

  8. js & document.designMode

    js & document.designMode js 一键开启页面编辑模式 var mode = document.designMode; document.designMode = val ...

  9. How to get the real screen size(screen resolution) by using js

    How to get the real screen size(screen resolution) by using js 获取用户屏幕的真实像素分辨率, 屏幕实际尺寸 window.deviceP ...

  10. nodejs 显示进度条插件

    ora 显示loading.. progress 进度条 progress var ProgressBar = require("progress"); var bar = new ...