多线程环境中使用 Mockito 来 Mock 静态方法

回看三年前的一篇日志 Mockito 3.4.0 开始可 Mock  静态方法,最后对 Mockito 产生的缺憾是它无法用来 Mock 非测试线程(主线程)中的静态方法调用。其实这也是可以变通的,下面慢慢道来。

首先回顾一下 Mockito  的静态方法 Mock 的使用方法,随着 Mockito 版本的升级,引入依赖的方式也发生了些许的变化,以 Maven 项目为例,如果在 JUnit 5 下用 Mockito 的 pom.xml 依赖中为

由它引入的全部相关依赖

阅读全文 >>

Java, Python 两种形式的 base64encode

在用 Python 写 Web 服务端代码时,用 base64.encodebytes() 函数对字符串进行编码,然后在 Java 端用 Base64.getDecoder().decode() 时无法解码,难道 base64 编码在两种语言间还有这等差异。Google 一下,得到的答案是在 Java 端要用 Base64.getMimeDecoder().decode() 函数解码。这一问题算是解决了, 不过后来又在 Python 写的 AWS Lambda 中输出

return {
    "statusCode": 200,
    "body": base64.encodebytes(b"short message"),
    "isBase64Encoded": True
}

以 AWS Lambda functionURL 的方式来访问,对于 body 中的小字符串是没问题,一旦 body 够大时在 Postman 或 curl 命令中无法直接展示出来,用 curl --output a.out 存成本地文件,打开后看到的是带换行的格式

H4sIAAZi7GYC/+19WXfcOLLmX+HxwxzXOS6b2AhiprvnyFtZt7yoJbdr6r74UEpKyq5UpjoXL/fX
D8AlkysIkCFmpo2H7pJJEBkAAsCHQMQXf/t6HX/YrO83a++P168+xcvVdDH/+yP6lOGnzEciCNHn
gD7yTufT9evpLD6L1rd/f3Q6n03nsXexXk7nN4+8F4v59fRms4zW8uNtHdjH9KkfPEXoM6JPsf8U
......

阅读全文 >>

ThreadLocal, InheritableThreadLocal 以及 TransmittableThreadLocal

ThreadLocal 是 Java 编程人员要掌握的一个基本类,似乎没什么太多要说。但因为本文要牵出 TransmittableThreadlLocal, 再顺带说下几乎隐形的 InheritableThreadLocal。

ThreadLocal 用于保存与线程绑定的数据,它在框架内部使用的很频繁,但凡见到 XxxContextHolder.currentContext() 之类的十之八九用到了 ThreadLocal, 如 Spring 框架中的

RequestContextHolder

阅读全文 >>

Dubbo 最基础的 RPC 应用(使用 ZooKeeper)

看国内的一些项目时 Dubbo 这个词经常闪现,一直也不以为然,未作搜索,当然也不知道它是做什么用的。直到最近阅读关于大型网站架构相关的书中反复提到 Dubbo 后,觉得不能再对它视而不见。Google 了一下,它是在阿里巴巴创建贡献给了 Apache 的开源项目,在阿里巴巴的大型应用中久经考验过的。Dubbo 是什么呢?借用官方 Dubbo 介绍

Apache Dubbo 是一款 RPC 服务开发框架,用于解决微服务架构下的服务治理与通信问题,官方提供了 Java、Golang 等多语言 SDK 实现。使用 Dubbo 开发的微服务原生具备相互之间的远程地址发现与通信能力, 利用 Dubbo 提供的丰富服务治理特性,可以实现诸如服务发现、负载均衡、流量调度等服务治理诉求。Dubbo 被设计为高度可扩展,用户可以方便的实现流量拦截、选址的各种定制逻辑。

Dubbo 是国内企业贡献的,所以官方有原生的中文文档,它某些时候与 Spring Cloud 齐名,又有些像 AWS 的 ECS Service Discovery, Service Connect 加上 ELB 的功能。 阅读全文 >>

Java 使用堆外内存(off-heap memory) 作为缓存

我们通常用的 Java 缓存基本可认为是扩展了 HashMap 或 ConcurrentHashMap 的实现,它们各自实现自己的缓存策略,如时间与空间的控制,生命周期管理,是否支持分布式,溢出时能否转储到磁盘。关于 Java 本地缓存的存储分为内存与磁盘,内存多数情况下指的是堆内内存(on-heap), 而介于堆内内存与文件存储之间的就是堆外内存(off-heap)

  1. 堆内存储(on-heap): 操作最快,无需序列化,但大量数据时会影响到 GC 的效率
  2. 堆外存储(off-heap): 存储在 Java 进程内存但非 JVM 堆内(不在 -Xmx 指定的内存范围内),使用或保存时需进行序列化/反序列化过程(在堆内与堆外转换),但不受 GC 影响,有助于提它来 GC 的效率
  3. 文件存储:不仅存在序列化与反序列化过程,还带 IO 操作,所以最慢,唯一优点就是大

我们查看一下当前 Spring 支持的缓存实现, Supported Cache Providers, 列有 Generic, JCache(JSR-107), EhCache 2.x, Hazelcast, Infinispan, Couchbase, Redis, Caffeine, Simple, 这其中无一支持堆外缓存,其中的 EhCache 要付费使用 EhCache 3(Big Memory) 才能支持 off-heap。 阅读全文 >>

Java 线程池有限大小工作队列 - 不丢弃任务的实现

我们在创建 Java 线程池,无论是用 Executors, ThreadPoolExecutor, 还是 Spring 的 ThreadPoolTaskExecutor, 如果不指定工作队列的大小的话,默认为 Integer.MAX_VALUE(2147483647), 基本不会把它爆满,但是在许多的任务要执行时大量 Runnable 对象的创建却足以把内存撑爆掉。所以才有必要使用一个有限大小的工作队列,如 5000, 再配上 RejectedExecutionHandler(DiscardOldestPolicy, DiscardPolicy, 或 CallerRunsPolicy)。前两种策略会主动放弃最旧最新的任务,一般不是我们想要的,CallerRunsPolicy 还能主动发挥任务提交者的计算能力,是一种不错的选择(只可能会发生工作队列太小且提交者执行的任务太忙时产生线程池一时的空闲。

所以总结起来我们可以有以下几种实现

直接使用 CallerRunsPolicy

在工作队列满时有效利用提交任务的线程,不让它闲着,这种实现最简单, 像下面那样声明线程池

阅读全文 >>

Java 9 前/后使用 JAXB (包括支持 javax.* 或 jakarta.*)

使用 Java 处理 Object 与 XML 之间的转换时 JAXB(Java Architecture for XML Binding) 仍然被广泛使用。但随着 Java 9 模块化后把 JAXB 从标准 JDK 中移除后,和 Java EE 8(Jakarta EE 8) 到 Jakarta EE 9 的变迁时命名空间由 javax.* 变成了 jakarta.*,我们在使用 JAXB 时需作出相应的适配。

本文分别使用 Java 1.8, 17,通过 Maven 插件 jaxb2-maven-plugin 的 xjc (从 xsd 文件生成 Java 类), 和如何切换 jakarta.* 命名空间,由此可给我们对使用了 JAXB 的项目升级 JDK 时指明方向。从而不致于因不了解每部分组件的具体功效而在 pom.xml 中胡乱配置,比如之前对 jaxb2-maven-plugin 插件本身配置了多余的 org.glassfish.jaxb:jaxb-xjc 和 org.glassfish.jaxb:jaxb-runtime 依赖,也未能理解 jaxb2-maven-plugin 与 org.glassfish.jaxb:jaxb-runtime 之间的版本对应关系。

 实验准备,创建简单的 Maven 项目,并在路径 src/main/xsd/ 下新建 Schema 文件 sample.xsd,内容为 阅读全文 >>

SpringBoot 应用出错 Comparison method violates its general contract!

出现此错误的大致环境如下

  1. SpringBoot 2.7.17, SpringWeb 项目,所引用入的 spring-webmvc-5.3.30, spring 6 已解决
  2. JDK 1.8 或 JDK 17
  3. 依赖了 jackson-dataformat-xml:2.12.6 和 jackson-dataformat-cbor:2.12.6, 它会在 RestTemplate 加上 application/xml, application/cbor 等 Accept 类型
  4. 代码中用 RestTemplate 调用此应用的 Endpoint, 未设置任何头

后面会详细列出能重现此问题的 pom.xml 配置及 Java 代码

在执行

restTemplate.getForEntity("http://localhost:8080/test2", String.class)

时出现如下错误 阅读全文 >>

Java StackOverflowError 与递归及尾递归优化

本篇同样是阅读《100 Java Mistakes and How to Avoid Them》的一则笔记,只是火力全集中在 StackOverflowError 这个单一主题之上, 且主要与递归及尾递归优化相关。一提到 StackOverflow, 恐怕首先是想到那个代码搬运源网站 stackoverflow.com,其次才是代码执行过程中的 StackOverflowError 错误。

什么是 StackOverflow, 准确来说是指线程的栈内存不足,无法在栈中分配新的内存(或创建新的栈帧)。我们通常会把它与方法调用关联起来,因为一次方法调用会创建一个新的栈帧,分配在栈上的局部变量(包括基本类型与对象引用),和栈帧都要占用线程栈内存。而我们平时所说的方法调用栈,或出现异常时打印出的异常栈都是一个概念。

StackOverflowError 一般出现在方法调用太深(方法调方法),手动编写的方法调用一般不容易达到这个限制,所以它与递归调用关系最为密切,递归调用次数太多或甚至没有出口条件无限递归; 也可能是经过一番递归调用后,再正常调用后续方法时出现 StackOverflowError(因为前面的递归调用资源消耗的差不多)

何谓递归调用,简单的说就是方法自己调用自己,从而实现循环的效果,或使代码更精练,例如经典排序中的快速和归并排序就要用到递归。但循环与递归又有本质上的区别,循环不增加调用深度;递归却不同,递归分递进与回归两个过程,递进调用时每次调用前都需通过压栈来保留现场,逆向回归时再逐级恢复现场,保留现场的过程就要从线程栈中分配内存空间; 死循环可能不会造成程序异常,但死(无限)递归必定出现 StackOverflowError。 阅读全文 >>

《100 Java Mistakes and How to Avoid Them》笔记 3

本书的阅读又搁置了许久,虽然感觉 Manning 出版社的这一 100 Mistakes 系列从书的质量不是那么的高,但开了头还是继续从本书 40% 的位置往下。

开始要讲述到异常了,异常还是有必要认真对待的,比如

  1. Java 中很容易被 CheckedException 弄得代码不整洁
  2. 缺少必要的参数检查,不舍得抛出异常,视异常为 Bug
  3. 不明确出现异常时后续如何处理,
  4. 或者是捕获而隐藏了异常致使定位错误变得更难。

Java 的主要异常大分类是

Throwable
├── Error
└── Exception
          └── RuntimeException

NullPointerException, 这恐怕是一个最常见的异常,Java 对一个对象是否能为 null 值没什么约束,甚至用 null 来表示业务上的空。比如说方法的参数与返回值,Java 都可以是 null 值,而在 Kotlin 中非明确可为 null 的时不能为 null 阅读全文 >>