写点什么

Java 各类日志组件分析汇总

2020 年 7 月 30 日

Java各类日志组件分析汇总

作为一名开发人员,相信大家对日志工具不会陌生,Java 也拥有功能和性能都非常强大的日志库;不过这么多日志工具 &第三方的包,怎样保证每个组件里都能使用约定好的日志工具?


本文将和大家介绍一下 Java 主流的日志工具,以及相对应的使用场景。


基本介绍

在 java 的世界里有许多实现日志功能的工具,最早得到广泛使用的是 log4j,现在比较流行的是 slf4j+logback。作为开发人员,我们有时候需要封装一些组件(二方包)提供给其他人员使用,但是那么多的日志工具,根本没法保证每个组件里都能使用约定好的日志工具,况且还有很多第三方的包,鬼知道他会用什么日志工具。假如一个应用程序用到了两个组件,恰好两个组件使用不同的日志工具,那么应用程序就会有两份日志输出了,蛋疼吧。。


下面简单介绍下常见的日志工具:


JUL

JUL 全称 java.util.logging.Logger,JDK 自带的日志系统,从 JDK1.4 就有了。因为 log4j 的存在,这个 logger 一直沉默着,其实在一些测试性的代码中,jdk 自带的 logger 比 log4j 更方便。JUL 是自带具体实现的,与 log4j、logback 等类似,而不是像 JCL、slf4j 那样的日志接口封装。


import java.util.logging.Level;import java.util.logging.Logger;
private static final Logger LOGGER = Logger.getLogger(MyClass.class.getName());
复制代码


  • 相同名字的 Logger 对象全局只有一个;

  • 一般使用圆点分隔的层次命名空间来命名 Logger;Logger 名称可以是任意的字符串,但是它们一般应该基于被记录组件的包名或类名,如 java.net 或 javax.swing;

  • 配置文件默认使用 jre/lib/logging.properties,日志级别默认为 INFO;

  • 可以通过系统属性 java.util.logging.config.file 指定路径覆盖系统默认文件;

  • 日志级别由高到低依次为:SEVERE(严重)、WARNING(警告)、INFO(信息)、CONFIG(配置)、FINE(详细)、FINER(较详细)、FINEST(非常详细)。另外还有两个全局开关:OFF「关闭日志记录」和 ALL「启用所有消息日志记录」。

  • 《logging.properties》文件中,默认日志级别可以通过.level= ALL 来控制,也可以基于层次命名空间来控制,按照 Logger 名字进行前缀匹配,匹配度最高的优先采用;日志级别只认大写;

  • JUL 通过 handler 来完成实际的日志输出,可以通过配置文件指定一个或者多个 hanlder,多个 handler 之间使用逗号分隔;handler 上也有一个日志级别,作为该 handler 可以接收的日志最低级别,低于该级别的日志,将不进行实际的输出;handler 上可以绑定日志格式化器,比如 java.util.logging.ConsoleHandler 就是使用的 String.format 来支持的;


配置文件示例:


handlers= java.util.logging.ConsoleHandler
.level= ALLcom.suian.logger.jul.xxx.level = CONFIGcom.suian.logger.jul.xxx.demo2.level = FINEcom.suian.logger.jul.xxx.demo3.level = FINER
java.util.logging.ConsoleHandler.level = ALLjava.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatterjava.util.logging.SimpleFormatter.format=%1$tF %1$tT [%4$s] %3$s - %5$s %n
复制代码


Apache Commons Logging

之前叫 Jakarta Commons Logging,简称 JCL,是 Apache 提供的一个通用日志 API,可以让应用程序不再依赖于具体的日志实现工具。


commons-logging 包中对其它一些日志工具,包括 Log4J、Avalon LogKit、JUL 等,进行了简单的包装,可以让应用程序在运行时,直接将 JCL API 打点的日志适配到对应的日志实现工具中。


common-logging 通过动态查找的机制,在程序运行时自动找出真正使用的日志库。这一点与 slf4j 不同,slf4j 是在编译时静态绑定真正的 Log 实现库。


commons-logging 包里的包装类和简单实现列举如下:


  • org.apache.commons.logging.impl.Jdk14Logger,适配 JDK1.4 里的 JUL;

  • org.apache.commons.logging.impl.Log4JLogger,适配 Log4J;

  • org.apache.commons.logging.impl.LogKitLogger,适配 avalon-Logkit;

  • org.apache.commons.logging.impl.SimpleLog,common-logging 自带日志实现类,它实现了 Log 接口,把日志消息都输出到系统错误流 System.err 中;

  • org.apache.commons.logging.impl.NoOpLog,common-logging 自带日志实现类,它实现了 Log 接口,其输出日志的方法中不进行任何操作;


如果只引入 Apache Commons Logging,也没有通过配置文件《commons-logging.properties》进行适配器绑定,也没有通过系统属性或者 SPI 重新定义 LogFactory 实现,默认使用的就是 jdk 自带的 java.util.logging.Logger 来进行日志输出。


JCL 使用配置文件 commons-logging.properties,可以在该文件中指定具体使用哪个日志工具。不配置的话,默认会使用 JUL 来输出日志。配置示例:


Avalon LogKit

Avalon LogKit 是一个高速日志记录工具集,Avalon 里的各个组件 Framework、Excalibur、Cornerstone 和 Phoenix 都用到它。它的模型与 JDK 1.4 Logging package 采用相同的原理,但与 JDK 1.2+兼容。使用 LogKit 的原因是:Context 和 LogTargets。


使用 Log4j 的时候,日志的内容只能是一句话,而使用 LogKit,你可以记录很多项内容,甚至可以各项内容记录到对应的数据库字段中。如果使用 Log4j 存储日志到不同的存储介质,如数据库,需要使用 Appender,而 LogKit 已经可以支持多种存储目标。


log4j

Log4j 是 Apache 的一个开放源代码项目,通过使用 Log4j,我们可以控制日志信息输送的目的地是控制台、文件、数据库等;我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。


Log4j 有 7 种不同的 log 级别,按照等级从低到高依次为:TRACE、DEBUG、INFO、WARN、ERROR、FATAL、OFF。如果配置为 OFF 级别,表示关闭 log。Log4j 支持两种格式的配置文件:properties 和 xml。包含三个主要的组件:Logger、appender、Layout。


SLF4J

SLF4J 全称 The Simple Logging Facade for Java,简单日志门面,这个不是具体的日志解决方案,而是通过门面模式提供一些 Java Logging API,类似于 JCL。题外话,作者当时创建 SLF4J 的目的就是为了替代 Jakarta Commons Logging(JCL)。


SLF4J 提供的核心 API 是一些接口以及一个 LoggerFactory 的工厂类。在使用 SLF4J 的时候,不需要在代码中或配置文件中指定你打算使用哪个具体的日志系统,可以在部署的时候不修改任何配置即可接入一种日志实现方案,在编译时静态绑定真正的 Log 库。


使用 SLF4J 时,如果你需要使用某一种日志实现,那么你必须选择正确的 SLF4J 的 jar 包的集合(各种桥接包)。SLF4J 提供了统一的记录日志的接口,只要按照其提供的方法记录即可,最终日志的格式、记录级别、输出方式等通过具体日志系统的配置来实现,因此可以在应用中灵活切换日志系统。


logback 是 slf4j-api 的天然实现,不需要桥接包就可以使用。另外 slf4j 还封装了很多其他的桥接包,可以使用到其他的日志实现中,比如 slf4j-log4j12,就可以使用 log4j 进行底层日志输出,再比如 slf4j-jdk14,可以使用 JUL 进行日志输出。


Logback

Logback,一个“可靠、通用、快速而又灵活的 Java 日志框架”。Logback 当前分成三个模块:logback-core,logback- classic 和 logback-access。logback-core 是其它两个模块的基础模块。logback-classic 是 log4j 的一个改良版本,完整实现了 SLF4J API。


logback-access 模块与 Servlet 容器集成提供通过 Http 来访问日志的功能。Logback 依赖配置文件 logback.xml,当然也支持 groovy 方式。Logback 相比 log4j,有很多很多的优点,网上一搜一大片,此处就不再赘述了。


Log4j2

Log4j 2 是 log4j 1.x 和 logback 的改进版,据说采用了一些新技术(无锁异步等等),使得日志的吞吐量、性能比 log4j 1.x 提高 10 倍,并解决了一些死锁的 bug,而且配置更加简单灵活。


Log4j2 支持插件式结构,可以根据需要自行扩展 Log4j2,实现自己的 appender、logger、filter 等。在配置文件中可以引用属性,还可以直接替代或传递到组件,而且支持 json 格式的配置文件。不像其他的日志框架,它在重新配置的时候不会丢失之前的日志文件。


Log4j2 利用 Java5 中的并发特性支持,尽可能地执行最低层次的加锁。解决了在 log4j 1.x 中存留的死锁的问题。Log4j 2 是基于 LMAX Disruptor 库的。在多线程的场景下,和已有的日志框架相比,异步 logger 拥有 10 倍左右的效率提升。


Log4j2 体系结构:



使用场景

只使用 java.util.logging.Logger

最简单的场景,正式系统一般不会这么用,自己写点小 demo、测试用例啥的是可以这么用。不要任何第三方依赖,jdk 原生支持。


只使用 Apache Commons Logging

需要引入 commons-logging 包,示例如下:


      <dependency>          <groupId>commons-logging</groupId>          <artifactId>commons-logging</artifactId>          <version>1.2</version>      </dependency>
复制代码


Apache Commons Logging 和 log4j 结合使用

需要引入 commons-logging 包和 log4j 包,示例如下:


      <dependency>          <groupId>commons-logging</groupId>          <artifactId>commons-logging</artifactId>          <version>1.2</version>      </dependency>      <dependency>          <groupId>log4j</groupId>          <artifactId>log4j</artifactId>          <version>1.2.17</version>      </dependency>
复制代码


该模式下可以使用的打点 api:


  • org.apache.commons.logging.Log,commons-logging 里的 api;

  • org.apache.log4j.Logger,log4j 里的 api;


无论使用哪种 api 打点,最终日志都会通过 log4j 进行实际的日志记录。推荐用 commons-logging 里的 api,如果直接用 log4j 里的 api,就跟单用 log4j 没区别,就没有再引入 commons-logging 包的必要了。


既然最终是通过 log4j 实现日志记录,那么日志输出的 level、target 等也就是通过 log4j 的配置文件进行控制了。下面是一个 log4j 配置文件《log4j.properties》的简单示例:


log4j.logger.com.suian.logtest = trace,console
#输出源console输出到控制台log4j.appender.console = org.apache.log4j.ConsoleAppenderlog4j.appender.console.Target = System.outlog4j.appender.console.layout = org.apache.log4j.PatternLayoutlog4j.appender.console.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss,SSS} [%t] %-5p %c - [log4j]%m%n
复制代码


既然是推荐使用 commons-logging 里的 api 打点,为了能找到 log4j 的日志实现,必须通过《commons-logging.properties》配置文件显式的确定关联,示例如下:


org.apache.commons.logging.Log=org.apache.commons.logging.impl.Log4JLogger
复制代码


代码中使用 JCL api 进行日志打点,底层使用 log4j 进行日志输出。日志输出控制依托于 log4j 的配置文件,另外需要在 commons-logging.properties 配置文件中显式指定与 log4j 的绑定关系。


单独使用 log4j

这个是早几年最最流行的用法了,现在因为 log4j 本身的问题以及新的日志框架的涌现,已经逐步退出历史舞台了。具体怎么用自己去百度吧。


SLF4J 结合 Logback

当下最流行的用法,SLF4J 为使用场景最广泛的日志门面,加上 Logback 的天然实现,简单、统一、快速。


需要引入第三方依赖:


      <dependency>          <groupId>org.slf4j</groupId>          <artifactId>slf4j-api</artifactId>          <version>${slf4j.version}</version>      </dependency>      <dependency>          <groupId>ch.qos.logback</groupId>          <artifactId>logback-core</artifactId>          <version>${logback.version}</version>      </dependency>      <dependency>          <groupId>ch.qos.logback</groupId>          <artifactId>logback-classic</artifactId>          <version>${logback.version}</version>      </dependency>
复制代码


单独使用 Log4j2

Log4j2 感觉就是 SLF4J+Logback。log4j-api 等价于 SLF4J,log4j-core 等价于 Logback。


需要引入第三方依赖:


      <dependency>          <groupId>org.apache.logging.log4j</groupId>          <artifactId>log4j-api</artifactId>          <version>2.6.2</version>      </dependency>      <dependency>          <groupId>org.apache.logging.log4j</groupId>          <artifactId>log4j-core</artifactId>          <version>2.6.2</version>      </dependency>
复制代码


冲突处理

理论上各种日志输出方式是可以共存的,比如 log4j 和 log4j2 以及 logback 等,但是麻烦的是我们得维护多个配置文件,必须充分了解每个组件使用的是那种日志组件,然后进行对应的配置文件配置。


如何解决呢?每一个想做通用日志解决方案的,都对兼容性问题进行了特殊处理。目前只有 slf4j 和 log4j2 提供了这样的整合机制,其他的基本都很弱。


代码中可能使用的日志打点 Api 列举:


  • java.util.logging.Logger,jdk 自带的;

  • org.apache.commons.logging.Log,commons-logging 包里的 api;

  • org.apache.log4j.Logger,log4j 包里的 api;

  • org.apache.logging.log4j.Logger,log4j2 提供的 api,在 log4j-api 包里;

  • org.slf4j.Logger,slf4j 提供的 api,在 slf4j-api 包里;


上述打点方式,在一个应用中是有可能共存的,即使自己写的代码可以确保都使用同一类 api,但是引入的第三方依赖里就可能各式各样了。该怎么处理呢?


前面已经提过了,现在能够对各类冲突支持比较到位的就是 slf4j 和 log4j2,他们都提供了很多的绑定器和桥接器。


  • 所谓的绑定器,也可以称之为适配器或者包装类,就是将特定 api 打点的日志绑定到具体日志实现组件来输出。比如 JCL 可以绑定到 log4j 输出,也可以绑定到 JUL 输出;再比如 slf4j,可以通过 logback 输出,也可以绑定到 log4j、log4j2、JUL 等;

  • 所谓的桥接器就是一个假的日志实现工具,比如当你把 jcl-over-slf4j.jar 放到 CLASS_PATH 时,即使某个组件原本是通过 JCL 输出日志的,现在却会被 jcl-over-slf4j “骗到”SLF4J 里,然后 SLF4J 又会根据绑定器把日志交给具体的日志实现工具。


slf4j 整合日志输出

java.util.logging.Logger

将 JUL 日志整合到 slf4j 统一输出,需要引入 slf4j 提供的依赖包:


      <dependency>          <groupId>org.slf4j</groupId>          <artifactId>jul-to-slf4j</artifactId>          <version>1.7.22</version>      </dependency>
复制代码


只引入依赖并不能整合 JUL 日志,该包里只是提供了一个 JUL 的 handler,仍旧需要通过 JUL 的配置文件进行配置,slf4j 绑定器(如 logback)上设置的日志级别等价于 JUL handler 上的日志级别,因此控制 JUL 的日志输出,日志级别仍旧分两个地方控制:JUL 配置文件《logging.properties》和 slf4j 绑定器的配置文件,比如《logback.xml》、《log4j2.xml》等。


  • 建立 jdk14-logger 的配置文件《logger.properties》,加入 handler 配置以及日志级别配置;

  • 在启动程序或容器的时候加入 JVM 参数配置-Djava.util.logging.config.file = /path/logger.properties;当然也可以使用编程方式进行处理,可以在 main 方法或者扩展容器的 listener 来作为系统初始化完成;此种方式有些场景下不如配置 JVM 参数来的彻底,比如想代理 tomcat 的系统输出日志,编程方式就搞不定了。


org.apache.commons.logging.Log

将 JCL 日志整合到 slf4j 统一输出,需要引入 slf4j 提供的依赖包:


      <dependency>          <groupId>org.slf4j</groupId>          <artifactId>jcl-over-slf4j</artifactId>          <version>1.7.22</version>      </dependency>
复制代码


jcl-over-slf4j 包里所有类的根路径为 org.apache.commons.logging,也有 Log 和 LogFactory 类,相当于以重写 commons-logging 包的代价来实现对 JCL 的桥接。Log 与 commons-logging 包里的一模一样,LogFactory 的实现,代码写死使用的是 org.apache.commons.logging.impl.SLF4JLogFactory。


commons-logging 包里默认使用的是 org.apache.commons.logging.impl.LogFactoryImpl。以这样的代价来实现桥接,可以实现无缝对接,不像 JUL 那样还得添加额外配置,但是有一个坏处就是需要处理类库冲突了。commons-logging 包和 jcl-over-slf4j 包肯定是不能共存的,需要将 commons-logging 包在 classpath 里排掉。


题外话,因为 JCL 本身就支持通过配置文件《commons-logging.properties》绑定适配器,所以个人感觉更倾向于封装一个适配器的方式来支持,就像 commons-logging 包里的 org.apache.commons.logging.impl.Log4JLogger,这样更符合程序员的思维,明明白白。


桥接包的命名也是很讲究的,覆写的这种,命名为 xxx-over-slf4j,如本例的 jcl-over-slf4j;纯桥接的,命名为 xxx-to-slf4j,如文章前面提到的 jul-to-slf4j。


org.apache.log4j.Logger

将 log4j 日志整合到 slf4j 统一输出,需要引入 slf4j 提供的依赖包:


      <dependency>          <groupId>org.slf4j</groupId>          <artifactId>log4j-over-slf4j</artifactId>          <version>1.7.22</version>      </dependency>
复制代码


看桥接包的名字就知道了,log4j-over-slf4j 肯定是覆写了 log4j:log4j 包,因此使用起来只需要引入依赖即可,不需要其他额外的配置。但是仍旧是要处理冲突的,log4j 包和 log4j-over-slf4j 是不能共存的哦。


org.apache.logging.log4j.Logger

将 log4j2 日志整合到 slf4j 统一输出,slf4j 没有提供桥接包,但是 log4j2 提供了,原理是一样的,首先引入 log4j2 的桥接包:


      <dependency>          <groupId>org.apache.logging.log4j</groupId>          <artifactId>log4j-to-slf4j</artifactId>          <version>2.6.2</version>      </dependency>
复制代码


log4j2 提供的依赖包有 org.apache.logging.log4j:log4j-api 和 org.apache.logging.log4j:log4j-core,其作用看包名就清楚了。log4j-core 是 log4j-api 的标准实现,同样 log4j-to-slf4j 也是 log4j-api 的一个实现。


log4j-to-slf4j 用于将 log4j2 输出的日志桥接到 slf4j 进行实际的输出,作用上来讲,log4j-core 和 log4j-to-slf4j 是不能共存的,因为会存在两个 log4j2 的实现。


经测试,就测试结果分析,共存也是木有问题的,何解?log4j2 加载 provider 的时候采用了优先级策略,即使找到多个也能决策出一个可用的 provider 来。在所有提供 log4j2 实现的依赖包中,都有一个 META-INF/log4j-provider.properties 配置文件,里面的 FactoryPriority 属性就是用来配置 provider 优先级的,幸运的是 log4j-to-slf4j(15)的优先级是高于 log4j-core(10)的,因此测试结果符合预期,log4j2 的日志桥接到了 slf4j 中进行输出。


同样,为确保系统的确定性,不会因为 log4j2 的 provider 决策策略变更导致问题,建议还是要在 classpath 里排掉 log4j-core,log4j2 也是推荐这么做的。


log4j2 整合日志输出

java.util.logging.Logger

将 JUL 日志整合到 log4j2 统一输出,需要引入 log4j2 提供的依赖包:


      <dependency>          <groupId>org.apache.logging.log4j</groupId>          <artifactId>log4j-jul</artifactId>          <version>2.6.2</version>      </dependency>
复制代码


log4j2 整合 JUL 日志的方式与 slf4j 不同,slf4j 只是定义了一个 handler,仍旧依赖 JUL 的配置文件;log4j2 则直接继承重写了 java.util.logging.LogManager。


使用时,只需要通过系统属性 java.util.logging.manager 绑定重写后的 LogManager(org.apache.logging.log4j.jul.LogManager)即可,感觉比 slf4j 的方式要简单不少。


org.apache.commons.logging.Log

将 JCL 日志整合到 log4j2 统一输出,需要引入 log4j2 提供的依赖包:


      <dependency>          <groupId>org.apache.logging.log4j</groupId>          <artifactId>log4j-jcl</artifactId>          <version>2.6.2</version>      </dependency>
复制代码


基于 log4j-jcl 包整合 JCL 比较简单,只要把 log4j-jcl 包扔到 classpath 就可以了。看起来 slf4j 的整合方式优雅多了,底层原理是这样的:JCL 的 LogFactory 在初始化的时候,查找 LogFactory 的具体实现,是分了几种场景的,简单描述如下:


  1. 首先根据系统属性 org.apache.commons.logging.LogFactory 查找 LogFactory 实现类;

  2. 如果找不到,则以 SPI 方式查找实现类,META-INF/services/org.apache.commons.logging.LogFactory;log4j-jcl 就是以这种方式支撑的;此种方式必须确保整个应用中,包括应用依赖的第三方 jar 包中,org.apache.commons.logging.LogFactory 文件只有一个,如果存在多个的话,哪个先被加载则以哪个为准。万一存在冲突的话,排查起来也挺麻烦的。

  3. 还找不到,则读取《commons-logging.properties》配置文件,使用 org.apache.commons.logging.LogFactory 属性指定的 LogFactory 实现类;

  4. 最后再找不到,就使用默认的实现 org.apache.commons.logging.impl.LogFactoryImpl。


org.apache.log4j.Logger

将 log4j 1.x 日志整合到 log4j2 统一输出,需要引入 log4j2 提供的依赖包:


      <dependency>          <groupId>org.apache.logging.log4j</groupId>          <artifactId>log4j-1.2-api</artifactId>          <version>2.6.2</version>      </dependency>
复制代码


log4j2 里整合 log4j 1.x 日志,也是通过覆写 log4j 1.x api 的方式来实现的,跟 slf4j 的实现原理一致。因此也就存在类库冲突的问题,使用 log4j-1.2-api 的话,必须把 classpath 下所有 log4j 1.x 的包清理掉。


org.slf4j.Logger

将 slf4j 日志整合到 log4j2 统一输出,需要引入 log4j2 提供的依赖包:


      <dependency>          <groupId>org.apache.logging.log4j</groupId>          <artifactId>log4j-slf4j-impl</artifactId>          <version>2.6.2</version>      </dependency>
复制代码


log4j-slf4j-impl 基于 log4j2 实现了 slf4j 的接口,其就是 slf4j-api 和 log4j2-core 之间的一个桥梁。这里有个问题需要注意下,务必确保 classpath 下 log4j-slf4j-impl 和 log4j-to-slf4j 不要共存,否则会导致事件无止尽地在 SLF4J 和 Log4j2 之间路由。


日志打点 API 绑定实现

slf4j-api 和 log4j-api 都是接口,不提供具体实现,理论上基于这两种 api 输出的日志可以绑定到很多的日志实现上。slf4j 和 log4j2 也确实提供了很多的绑定器。简单列举几种可能的绑定链:


  • slf4j → logback

  • slf4j → slf4j-log4j12 → log4j

  • slf4j → log4j-slf4j-impl → log4j2

  • slf4j → slf4j-jdk14 → jul

  • slf4j → slf4j-jcl → jcl

  • jcl → jul

  • jcl → log4j

  • log4j2-api → log4j2-cor

  • log4j2-api → log4j-to-slf4j → slf4j


来个环图:



本文转载自公众号淘系技术(ID:AlibabaMTT)。

原文链接


https://mp.weixin.qq.com/s?__biz=MzAxNDEwNjk5OQ==&mid=2650408994&idx=1&sn=5eff3507a83861c77a3f0a5b9dfa69aa&chksm=8396c03ab4e1492c9b51e34d7a5f0fc2aa0bc868c8d69e5aa90f71e3e42eb3d2cc50ddcb78e1&scene=27#wechat_redirect


2020 年 7 月 30 日 10:001601

评论 1 条评论

发布
用户头像
介绍得很清楚
2020 年 12 月 24 日 13:17
回复
没有更多了
发现更多内容

SpringBoot-技术专题-多环境下maven打包

李浩宇/Alex

Java-技术专题-Pattern类与Matcher类详解

李浩宇/Alex

Hive UDF/UDAF 总结

windism

一个草根的日常杂碎(10月11日)

刘新吾

随笔杂谈 生活记录 社会百态

优质数据库管理工具盘点,看看这三个软件的区别

CloudQuery社区

数据库 sql 云原生 工具 编辑器

媒介狂想曲

善宝橘

媒介 想象

Guava-技术专题-Cache用法介绍

李浩宇/Alex

spring-boot-route(十六)使用logback生产日志文件

Java旅途

Java Spring Boot logback

如何优化多表查询情况下的查询性能问题

迹_Jason

数据库设计 架构设计 查询优化 数据优化

极客时间 - 架构师一期 - 第四周作业

_

第四周作业 架构师一期

一个草根的日常杂碎(10月10日)

刘新吾

随笔杂谈 生活记录 社会百态

每个数据科学家都应该知道的5个概念

计算机与AI

学习 数据科学

通过MapReduce降低服务响应时间

Kevin Wan

golang mapreduce

iOS Handle Refunds 处理退款 --- WWDC20(Session 10661)

iHTC

WWDC2020 wwdc iap 苹果退款 iOS退款

当我们在谈论跨平台的时候 ——— 我们在说什么

iHTC

跨平台

一个草根的日常杂碎(10月9日)

刘新吾

随笔杂谈 生活记录 社会百态

你不知道的java对象序列化的秘密

程序那些事

Java java序列化 序列化的秘密

华为程序员发现孩子不是自己的,怒提离婚!女方不要孩子!绿他的竟然是个酒吧混混!

程序员生活志

华为 程序员

为什么学Go(二)

soolaugust

go

容器技术之发展简史

阿里云基础软件团队

云原生

比MySQL快839倍!揭开分析型数据库JCHDB的神秘面纱

京东科技开发者

数据库 JCHDB

随想

Nydia

第四周 系统架构学习总结

钟杰

极客大学架构师训练营

重新学习面向对象设计之开放-封闭原则

IT老兵重开始

面向对象设计 OCP 开闭原则

数字货币交易所系统开发app,交易所搭建源码

WX13823153201

数字货币交易所系统开发

【高并发】面试官:讲讲高并发场景下如何优化加锁方式?

冰河

性能优化 高并发 线程安全 同步 加锁

我们可以把Adapter精简到什么地步

mengxn

RecyclerView BetterAdapter Adapter

通俗易懂和你聊聊寄存器那些事(精美图文)

cxuan

后端 计算机 汇编

JDK14性能管理工具:jmap和jhat使用介绍

程序那些事

内存泄露 JDK14 jmap jhat

第四周总结

_

极客大学架构师训练营 第四周总结

面经手册 · 第13篇《除了JDK、CGLIB,还有3种类代理方式?面试又卡住!》

小傅哥

Java 字节码编程 asm 动态代理 cglib

2021年全国大学生计算机系统能力大赛操作系统设计赛 技术报告会

2021年全国大学生计算机系统能力大赛操作系统设计赛 技术报告会

Java各类日志组件分析汇总-InfoQ