Java 开发中遇到的 bug 记录

12 minute

前言

这里记录了我在 Java 开发中遇到的 bug,持续更新中。

Apache POI 的 autoSizeColumn 无法调整中文

1// 调整每一列宽度
2sheet.autoSizeColumn((short) i);
3// 解决自动设置列宽中文失效的问题
4sheet.setColumnWidth(i, sheet.getColumnWidth(i) * 17 / 10);

选择 17/10 是一个经验性的调整比例。

fastjson 反序列化对象修饰符问题

1Caused by: java.lang.IllegalAccessException: class com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer cannot access a member of class com.jzh.test.json.SubmitTime with modifiers "public"

fastjson 通过有参构造函数反序列化可能会出现此问题,原因是 SubmitTime 这个类没有被 public 修饰,而其构造函数使用 public 修饰,所以导致反序列化失败。而如果通过无参构造加 setter 进行反序列化则无此问题。

本地多线程测试天坑: 测试方法开的线程没跑完

如果测试方法中包含异步操作,那么直接运行测试方法,异步方法未执行完成时,主线程已经退出,导致异步方法无法正常执行完!!

解决方法是在 @Test 测试方法中打个断点,不要让 @Test 的方法直接完成退出!

IDEA maven reload : out of memory error

Maven | Importing | VM options for importer: -Xmx6144m

sqlite-jdbc No native library is found

1Caused by: java.lang.Exception: No native library is found for os.name=Mac and os.arch=aarch64. path=/org/sqlite/native/Mac/aarch64

If you are using Apple M1 chip

One of the release notes they have mentioned by jetpack (Version 2.4.0-alpha03 )

Fixed an issue with Room’s SQLite native library to support Apple’s M1 chips. Change Version to 2.4.0-alpha03 or above

For those who are facing this problem, you can simply add this line before the room-compiler as a workaround now:

kapt “org.xerial:sqlite-jdbc:3.34.0”

hibernate + sqlite 问题

 1Description:
 2
 3An attempt was made to call a method that does not exist. The attempt was made from the following location:
 4
 5    org.sqlite.hibernate.dialect.SQLiteDialect.<init>(SQLiteDialect.java:57)
 6
 7The following method did not exist:
 8
 9    'void org.sqlite.hibernate.dialect.SQLiteDialect.registerColumnType(int, java.lang.String)'
10
11The calling method's class, org.sqlite.hibernate.dialect.SQLiteDialect, was loaded from the following location:
12
13    jar:file:/Users/zh/.m2/repository/com/github/gwenn/sqlite-dialect/0.1.2/sqlite-dialect-0.1.2.jar!/org/sqlite/hibernate/dialect/SQLiteDialect.class
14
15The called method's class, org.sqlite.hibernate.dialect.SQLiteDialect, is available from the following locations:
16
17    jar:file:/Users/zh/.m2/repository/com/github/gwenn/sqlite-dialect/0.1.2/sqlite-dialect-0.1.2.jar!/org/sqlite/hibernate/dialect/SQLiteDialect.class
18
19The called method's class hierarchy was loaded from the following locations:
20
21    org.sqlite.hibernate.dialect.SQLiteDialect: file:/Users/zh/.m2/repository/com/github/gwenn/sqlite-dialect/0.1.2/sqlite-dialect-0.1.2.jar
22    org.hibernate.dialect.Dialect: file:/Users/zh/.m2/repository/org/hibernate/orm/hibernate-core/6.4.1.Final/hibernate-core-6.4.1.Final.jar
23
24
25Action:
26
27Correct the classpath of your application so that it contains a single, compatible version of org.sqlite.hibernate.dialect.SQLiteDialect

fix:

From Hibernate 6, SQLite dialect is supported. We have to import in our pom.xml:

1<dependency>
2    <groupId>org.hibernate.orm</groupId>
3    <artifactId>hibernate-community-dialects</artifactId>
4</dependency>

Copy And in our application properties:

1spring.jpa.database-platform=org.hibernate.community.dialect.SQLiteDialect
 1        <!--jpa-->
 2        <dependency>
 3            <groupId>org.springframework.boot</groupId>
 4            <artifactId>spring-boot-starter-data-jpa</artifactId>
 5        </dependency>
 6        <!-- sqlite -->
 7        <dependency>
 8            <groupId>org.xerial</groupId>
 9            <artifactId>sqlite-jdbc</artifactId>
10            <version>3.34.0</version>
11        </dependency>
12<!--        <dependency>-->
13<!--            <groupId>com.github.gwenn</groupId>-->
14<!--            <artifactId>sqlite-dialect</artifactId>-->
15<!--            <version>0.1.4</version>-->
16<!--        </dependency>-->
17        <dependency>
18            <groupId>org.hibernate.orm</groupId>
19            <artifactId>hibernate-community-dialects</artifactId>
20        </dependency>

具体就是要删去 sqlite-dialect 并引入 hibernate-community-dialects,然后添加 jpa 配置。

JAR 包项目文件上传问题

获取类路径:

如果单纯运行一个 java 项目:${project}/target/classes

如果是运行 jar 包,且是在 Linux 系统上:${jar包名}!/BOOT-INF/classes!

所以如果是 jar 包启动并上传文件,则无法正确放到类路径下,需指定新的上传路径。

可以在 jar 包同一路径下新建并编辑 application.yaml 文件如下:

1spring:
2    resources:
3        static-locations:
4            - classpath:static/
5            - file:/app/static/

然后在文件上传是指定上传路径即可。

类文件具有错误的版本 55.0, 应为 52.0

上面报错中的 55.0 是 JDK11 使用的类文件格式 (class file format) 的版本号,提示的意思是当面项目使用的类文件格式版本比某个依赖包使用的类文件格式版本低。

实际就是指当前项目使用的 JDK 版本比某个依赖包使用的 JDK 版本低。

Mockito 5.8 使用了 JDK 11,而项目使用 JDK 8,那么会报错。

The server selected protocol version TLS10 is not accepted by client preferences [TLS13, TLS12]

Remove “TLSv1, TLSv1.1” from “jdk.tls.disabledAlgorithms” property in file ${soapui_home}/jre/conf/securityjava.security.

RocketMQ: It may be caused by one of the following reasons: the broker’s disk is full

1cd conf/2m-2s-sync
2
3vim broker-a.properties
4diskMaxUsedSpaceRatio=99

SpringBoot 3.X 使用 SpringBoot 2.X 的 Starter 无法完成自动装配

SpringBoot 3.X 自动装配类通过 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 配置,而 2.X 通过 META-INF/spring.factories 完成,所以无法完成自动装配。解决方式是自己通过 @Bean@Configuration 装配 Bean。

list.remove() 正确的使用重载

调用 set.remove(i) 选择重载 remove(E) 方法,其中 E 是 set (Integer) 的元素类型,将 基本类型 i 由 int 自动装箱为 Integer 中。这是你所期望的行为,因此程序最终会从集合中删除指定大小的值。

调用 list.remove(i) 的调用选择重载 remove(int i) 方法,它将删除列表中指定位置的元素。若要删除指定大小的元素,对于 List<E> 接口对方法有的两个重载: remove(E) 和 remove(int),需要强制转换的参数为类型,迫使其选择正确的重载。

后端返回的 json 数据格式出错

首先注意,json 采用驼峰命名法。

一般对于前后端分离的项目,后端都是返回 json 格式数据,比如使用 @RestController 进行自动的转换。

对于一个采用驼峰命名法命名的变量,比如 userId,转换后返回前端的 json 属性名是 userId,没有问题。

但是当变量名为 uId时,转换后则变为 uid,这就产生了问题。我还测试了其它一些变量,如下:

1# userId
2{"code":200,"msg":"ok","obj":{"userId":"hello"}}
3# uId
4{"code":200,"msg":"ok","obj":{"uid":"hello"}}
5# Id
6{"code":200,"msg":"ok","obj":{"id":"hello"}}
7# uuId
8{"code":200,"msg":"ok","obj":{"uuId":"hello"}}

可见当为 uId 和 Id 时,都会出现问题。

一般可以考虑在后端变量命名时,不让第二个字符大写,或者采用 @JsonProperty("uId") 进行解决。

IDEA 中在 maven 配置文件中配置 JVM 启动参数

有些是 .maven/maven.config,有些是 .mvn/jvm.config,必须在设置 > maven 中查看。

BigDecimal 构造函数的结果可能不可预测

1BigDecimal amount1 = new BigDecimal(0.02);
2BigDecimal amount2 = new BigDecimal(0.03);
3System.out.println(amount2.subtract(amount1));
4// 0.0099999999999999984734433411404097569175064563751220703125

正确方法:

1BigDecimal amount1 = BigDecimal.valueOf(0.02);
2BigDecimal amount2 = BigDecimal.valueOf(0.03);

反序列化 boolean 字段失败的问题

直接以一个例子说明这个问题:

 1public class FastJsonTest {
 2    @Test
 3    public void test1() {
 4        Vote vote = new Vote();
 5        vote.setTitle("test");
 6        vote.setIsOverdue(true);
 7        vote.setDeleted(false);
 8        vote.setImportant(true);
 9        String voteJson = JSON.toJSONString(vote);
10        System.out.println(voteJson);
11        // {"deleted":false,"important":true,"isOverdue":true,"title":"test"}
12
13        // ok
14        Vote voteFromJson = JSON.parseObject(voteJson, Vote.class);
15        System.out.println(voteFromJson);
16
17        // {"deleted":false,"isImportant":true,"isOverdue":true,"title":"test"}
18        String anotherVoteJson = "{\"deleted\":false,\"isImportant\":true,\"isOverdue\":true,\"title\":\"test\"}";
19        Vote anotherVote = JSON.parseObject(anotherVoteJson, Vote.class);
20        System.out.println(anotherVote);
21        // 反序列化失败:
22        // Vote(isOverdue=true, deleted=false, isImportant=false, title=test)
23    }
24}
25
26@NoArgsConstructor
27@Data
28class Vote {
29    // ok
30    private Boolean isOverdue;
31    // ok
32    private Boolean deleted;
33    // fail
34    private boolean isImportant;
35
36    private String title;
37}

使用 fastjson 反序列化时,由于 Vote 对象存在空参构造, 所以将根据 set 方法反序列化,如果字段是 Boolean 类型,那么反序列化成功,如果是 boolean 类型,且字段命名以 is 开头,那么反序列化失败。

所以,建议布尔类型都不要加 is 作为前缀,否则部分框架解析可能会有问题。(来自阿里巴巴 Java 开发手册)

maven 的 http 问题

Maven 升级到 3.8.1 之后,执行 mvn clean package 命令后会报错如下:

1maven-default-http-blocker (http://0.0.0.0/): Blocked mirror for repositories

maven 在 3.8.1 的默认配置文件中增加了一组标签,如果仓库镜像是 http 而不是 https 就会被拦截禁止访问

新增节点:

1<mirror>
2    <id>maven-default-http-blocker</id>
3    <mirrorOf>dummy</mirrorOf>
4    <name>Dummy mirror to override default blocking mirror that blocks http</name>
5    <url>http://0.0.0.0/</url>
6</mirror>

或回退版本

Failed to determine a suitable driver class

 12023-05-31T16:52:18.233+08:00  WARN 28432 --- [           main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataSource' defined in class path resource [org/springframework/boot/autoconfigure/jdbc/DataSourceConfiguration$Hikari.class]: Failed to instantiate [com.zaxxer.hikari.HikariDataSource]: Factory method 'dataSource' threw exception with message: Failed to determine a suitable driver class
 22023-05-31T16:52:18.235+08:00  INFO 28432 --- [           main] o.apache.catalina.core.StandardService   : Stopping service [Tomcat]
 32023-05-31T16:52:18.250+08:00  INFO 28432 --- [           main] .s.b.a.l.ConditionEvaluationReportLogger : 
 4
 5......
 6
 7Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.
 8
 9Reason: Failed to determine a suitable driver class
10
11
12Action:
13
14Consider the following:
15	If you want an embedded database (H2, HSQL or Derby), please put it on the classpath.
16	If you have database settings to be loaded from a particular profile you may need to activate it (no profiles are currently active).

关键在于:

1Error creating bean with name 'dataSource' defined in class path resource [org/springframework/boot/autoconfigure/jdbc/DataSourceConfiguration$Hikari.class]: Failed to instantiate [com.zaxxer.hikari.HikariDataSource]: Factory method 'dataSource' threw exception with message: Failed to determine a suitable driver class

可以发现是 springboot 自动配置的锅,找不到一个合适的 driver 进行数据源的连接。

解决方法:

1@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})

隐藏的依赖版本冲突问题

当我从 SpringBoot 2.7 迁移到 SpringBoot 3.0 时,发现项目启动报错,原来的项目能正常启动,证明可能是依赖错误的问题了。

而 pom.xml 中有着很多依赖,由于是和 SpringBoot 有关,所以估计是一些第三方的 starter 有问题,最终发现是 MybatisPlus 的 3.4.3.4 版本不能正常在 SpringBoot 3.0 中使用,升级到 3.5.3.1 就没这个问题了。

BufferedWriter 无法正确写出问题

这是一段客户端代码,向服务器端发送消息,然后接收服务器端的回复:

 1try (
 2        BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
 3        BufferedWriter out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()))
 4) {
 5    String message;
 6    while((message = stdIn.readLine()) != null) {
 7        out.write(message);
 8        out.flush();
 9        System.out.println("Receive from server: " + in.readLine());
10    }
11} catch (Exception e) {
12    System.out.println(e.getMessage());
13}

这是服务端代码的一部分,接收客户端消息,并向客户端回复:

 1try (
 2        BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
 3        BufferedWriter out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()))
 4) {
 5    String message;
 6    while((message = in.readLine()) != null) {
 7        System.out.println(Thread.currentThread().getName() + ": receive from port " + socket.getPort() + ": " + message);
 8        out.write(message);
 9        out.flush();
10    }
11} catch (Exception e) {
12    System.out.println(e.getMessage());
13}

这里两端都使用了 BufferedReaderBufferedWriter 作为消息的传输工具。

经过测试发现,消息传输是失败的。

问题出现在 out.write(message);message = in.readLine() 这两个地方,因为 in.readLine() 读取的是一行数据,即数据必须要存在换行符,如果不存在的话将无法读完,一直处于阻塞状态。

所以,解决方法就是将写出的数据通过 newLine 操作加上换行符:

1out.write(message); // 如果使用BufferedWriter,则需要添加换行符
2out.newLine();

另一种更好的方法是使用 PrintWriter,使用它的写出操作 println 进行消息的输出。