封面来源:本文封面来源于网络,如有侵权,请联系删除。

0.前言

拥有臭虫(BUG)聚集体质的我,总会遇到各种各样的臭虫,有些臭虫还很顽固,驱散半天都不走,为了以后的美好生活,我将这些臭虫的驱散(DEBUG)方式总结下来,方便其他小伙伴遇到这些臭虫时可以“对症下药”、“药到病除”。

1. 问题与解决方法

1.1 Assert.assertEquals()

在使用 Junit 的 Assert.assertEquals() 对预期值和实际值进行比较时:

1
Assert.assertEquals(6L, borrowInfo.getBorrowId());

编译器出现了一下的错误信息:

1
2
Ambiguous method call. Both assertEquals(Object, Object) in Assert and assertEquals(long, 
long)in Assert match

产生这样的原因是:borrowInfo.getBorrowId() 返回的是 Long,而不是 longAssert 类中有很多 assertEquals() 方法的重载,因此编译器就蒙了,是该将参数类型都转换成 Object 呢?还是只将 Long 转换成 long 呢?

解决方式:明确指定参数是基本数据类型,还是 Object 类型。比如:

1
Assert.assertEquals(6L, borrowInfo.getBorrowId().longValue());

或者

1
Assert.assertEquals(6L, (long)borrowInfo.getBorrowId());

参考链接:Ambiguous method call Both assertEquals(Object, Object) in Assert and assertEquals(double, double) in Assert match:

1.2 日期校验注解 @Past

在 DTO 中对某个日期类型的属性使用了 @Past 校验注解,这个注解可以校验日期属性值是否是过去的日期。

在编写单元测试时,采用 new Date() 的方式设置属性值:

1
dto.setBirth(new Date());

这样的方式在对单个测试方法进行测试时不会出现问题,如果 All in 单元测试时,有一定概率校验不通过,提示该属性值必须是过去的日期。

注意: 是一定概率校验不通过,就是有时候是可以通过的,但是有时候又不能通过(薛定谔的测试?)。

解决方式很简单,设置属性值时传入固定的日期,且这个日期是过去的日期,比如:

1
2
// 2020-12-01 00:00:00 ==> 1606752000000
dto.setBirth(new Date(1606752000000L));

1606752000000L 是一个时间戳,表示:2020-12-01 00:00:00

更多与日期时间校验相关的注解

注解 含义
@Past 目标日期时间必须小于当前时间
@PastOrPresent 目标日期时间必须小于或等于当前时间
@Future 目标日期时间必须大于当前时间
@FutureOrPresent 目标日期时间必须大于或等于当前日期

1.3 无法创建 SpringBoot 项目

使用 IDEA 创建 SpringBoot 项目时,可能由于 https://start.spring.io/ 网址的原因(比如,无法访问)导致无法常见项目,或者创建项目失败,项目结构无法创建,我们可以更改默认的创建网址来拉取 SpringBoot 项目:

更改拉取SpringBoot项目地址

网址为:https://start.springboot.io/

1.4 Redis BGSAVE方法失败

参考链接:MISCONF Redis is configured to save RDB snapshots, but it is currently not a

快速应急方案

修改 Redis 的配置文件 redis.conf,将 stop-writes-on-bgsave-error yes 修改为 stop-writes-on-bgsave-error no,或者在 Redis 客户端中执行以下命令:

1
config set stop-writes-on-bgsave-error no

解决方案

在 Linux 系统中,切换至 / 目录下,执行以下命令:

1
vi /etc/sysctl.conf

修改 /etc/sysctl.conf 文件,添加配置:

vm.overcommit_memory=1

如:

修改sysctl.conf文件

添加完毕后,在 管理员模式 下,执行以下命令,使其生效:

1
sysctl -p /etc/sysctl.conf

1.5 Command line is too long

使用 IDEA 启动测试类进行测试时,出现以下错误:

Error running 'Test1.test': Command line is too long. Shorten command line for Test1.test or also for JUnit default configuration.

解决方法一

最简单粗暴的方法:

创建一个新的测试类,然后在这个测试类中将报错的测试类中的代码复制过来,就完事了。

解决方式二

1、打开当前项目的 .idea 文件夹,找到文件夹中的 workspace.xml文件。

2、搜索 PropertiesComponent

PropertiesComponent

3、然后在这个父级结构中添加:

1
<property name="dynamic.classpath" value="true" />

添加dynamic.classpath的property

4、如果这样配置后还是有问题,可以在以下界面中,将 Shorten command line 修改为 JAR manifest

修改Shorten-command-line

1.6 整合 Redis 时出现 Unable to connect to

Springboot 配置 Redis 时出现 Unable to connect to 和 ERR invalid password

检查配置文件后发现是密码字段没有使用引号,只需要加上引号之后就可以了。

但一般来说,即使不使用引号应该也是可以连接上的。

1.7 Sourcetree 提示 SSH 密钥认证失败

使用 Sourcetree 在推送代码时,可能会出现 启用 SSH 代理? 的提示框,并告诉你:

通过SSH密钥认证失败, 你想要运行SSH密钥代理( Pageant )并重试吗?

一般来说,在安装 Git 后都会绑定 SSH 公钥(不知道如何操作可在本站搜索【Git理论与使用 - Gitee的使用】进行查看),既然已经绑定了,为什么还会出现这个提示框呢?

只需要在 Sourcetree 进行如下设置即可:

点击菜单栏的 工具 - 选项 - 一般,对 SSH 客户端进行配置:

对Sourcetree的SSH客户端进行配置

将 SSH 客户端配置为 OpenSSH 即可,SSH 秘钥会自动填写(如果没有,自行选择)。

1.8 npm ERR! Cannot read property ‘match’ of undefined

当我们对前端项目执行 npm install 命令时,出现了以下错误:

npm ERR! Cannot read property 'match' of undefined

解决方法:把项目中 package-lock.json 文件删除,再次运行 npm 命令即可。

1.9 VS Code 自带终端 无法执行 yarn

参考链接:vscode 自带终端无法执行yarn

当我们安装并配置好 Yarn 后,使用 VS Code 自带的终端执行 yarn start 命令启动项目时,出现了以下错误:

找不到或无法加载主类

原因: VS Code 中的集成终端使用的是 PowerShell,所以我们要设置一下 PowerShell 的执行权限或者说策略。

其默认执行策略为 Restricted(默认设置),该不允许任何脚本运行。我们可以将其设置为 RemoteSigned,该执行策略可防止 Windows PowerShell 运行没有数字签名的脚本。

有关 Windows PowerShell 执行策略的详细信息,可以查考:关于执行策略

结局方式一

这种方式最简单,在 VS Code 内部中安装名为 yarn 的插件即可。它长这样:

VSCode中yarn插件

解决方式二

1、以 管理员身份 运行 VS Code。

2、打开 VS Code 终端,执行以下命令:

1
2
get-ExecutionPolicy # 查看执行策略
set-ExecutionPolicy RemoteSigned # 设置执行策略

3、重启 VS Code,再打开终端,执行 yarn start 试试? 😉

1.10 SpringBoot 项目运行后无法打开 Swagger 界面

运行 SpringBoot 项目后,能够访问到 Swagger 的 api-docs 页面,数据能够正常显示,但是无法加载 Swagger 界面,界面出现 404 错误码。

除了网上能够找到的解决方法外,还可能是由于 IDEA 的配置造成的。

修改项目的启动配置:

SwaggerUI无法访问进行的配置

Shorten command line 修改为 classpath file 即可,当然使用默认的 user-local default 也行,但是使用 JAR manifest 是不行的,将无法访问 Swagger UI 界面。

1.11 Error:java: Compilation failed: internal java compiler error

在 IDEA 中运行项目时,在编译期控制台可能会出现这个错误:

Error:java: Compilation failed: internal java compiler error

出现这个问题主要是因为 JDK 的版本问题。一个是编译版本不匹配,一个是当前项目 JDK 版本不支持。

主要需要在 IDEA 查看以下三个地方的 JDK 版本,看它们是否一致且符合要求:

首先进行以下步骤查看项目的 JDK 版本:

1、 File -> Project Structure -> Project Settings -> Project

2、或者使用快捷键 Ctrl + Alt + Shift + S

查看项目JDK版本

然后点击上图中 Modules 查看当前 Module 对应 JDK 版本:

当前Module对应JDK版本

最后查看 Java 编译器版本:

查看Java编译器版本

1.12 MySQL 查询条件不区分大小写?

场景重现

今天在对数据库进行如下条件查询时出现了意想不到的结果:

1
SELECT * FROM tb_user WHERE username = 'mofan';

很显然,我这里是想要查询 usernamemofan 的数据,结果在运行上述 SQL 语句时,查询得到了 usernameMofan 的数据。

这是怎么个情况,数据库不区分大小写?

不对啊,我记得区分啊,那咋回事?难不成是我使用的 MyBatis-Plus 的问题?

解决思路

在使用数据库可视化工具(比如 Navicat)创建 MySQL 数据库时,需要我们指定数据库名称、字符集(CHARSET)和排序规则(COLLATE)。

指定之后,当我们在这个数据库下创建表时、在表中新添字段时,都会 默认 采用数据库的字符集和排序规则。

小贴士: 我们一般将数据库、表、表中字段的字符集设置为 utf8mb4。MySQL 中 utf8 字符集不是真正的 UTF-8utf8mb4 才是真正的 UTF-8

如果在创建数据表时,没有指定 字符集和排序规则,那么这张数据表将会默认采用所在数据库的字符集和排序规则;如果在表中新增字段时 没有指定 字符集和排序规则,那么这个字段将会默认采用所在数据表的字符集和排序规则。

回归正题,在进行条件查询时没有区分查询条件的大小写是 由字段的排序规则造成的

MySQL 的各个排序规则结尾由以下三种组成,它们也代表了不同的含义:

结尾 含义
_bin 将字符串中的每一个字符用二进制数据存储,区分大小写。
_ci 不区分大小写,ci 为 case insensitive 的缩写,即大小写不敏感。
_cs 区分大小写,cs为 case sensitive 的缩写,即大小写敏感。

因此,MySQL 查询条件不区分大小写的解决方案就是:将字段的排序规则设置为以 _cs 或 _bin 结尾

当然为了后续操作方便,建议一并将数据库和数据表的排序规则也设置为以 _cs_bin 结尾。

1.13 移除 Goovy 导致编译报错

近期公司移除了项目中的 Goovy,但由于 IDEA 会默认对其进行编译,所以 IDEA 出现了编译报错,我们只需要在设置中删除 !?*.groovy 即可:

删除goovy的编译设置

1.14 Chrome 打开开发者工具缓慢

在有次更新 Chrome 后,不知道点到了那,后续在打开开发者工具时等半天才打开。可以对 Chrome 的设置进行重置就好了:

重置Chrome设置

1.15 Java 编译时显示 code too large

在 IDEA 中不会报错,手动进行编译时则会提示 code too large,这是因为Java 里一个方法编译后的字节码不能超过 64KB 大小,因此可以考虑将一个大方法拆解为多个小方法。

参考链接:java程序过长无法编译,目前百度上只能查到java文件中的方法不能超过64k,到底是什么原因呢?

1.16 子类覆写父类的 JSR-303 校验

如果在父类字段上使用了 JSR-303 注解,子类如果想覆写父类的校验规则,不能 直接在子类定义相同的字段进行覆写,这样是无效的。比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Setter
@Getter
public abstract class Parent {
@Max(100)
private Integer integer;
}

@Setter
@Getter
public class Child extends Parent {
@Max(90)
private Integer integer;
}

以 Hibernate Validator 作为 JSR-303 的实现为例,其在执行相关校验时,会去获取被校验对象的类继承关系,因此父类上的校验也会执行。

如果针对不同的子类需要定制不同的校验规则,那么应该将父类上的校验规则转移到每个子类的 Getter 上,由各个子类单独实现。比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Setter
@Getter
public abstract class Parent {
private Integer integer;
}

@Setter
public class ChildOne extends Parent {
@Max(100)
public Long getInteger() {
return super.getInteger();
}
}

@Setter
public class ChildTwo extends Parent {
@Max(90)
public Long getInteger() {
return super.getInteger();
}
}

2. 经验之谈

2.1 Spring 的 @Scope 注解

@Scope 可以用来设置 Spring 容器中 Bean 的作用域,默认作用域是单例的,使用这个注解后可以将其设置成其他作用域。

在此不介绍 @Scope 注解的使用方式,但需要明白的是:

默认的单例作用域适用 90% 的场景。如果需要更改作用域,请在更改之前确认是不是由于自己的代码设计缺陷而导致需要更改作用域。

2.2 单元测试心得

在进行单元测试时:

对于数据库相关的查询,更新,删除等操作,不能假设数据库里的数据是存在的,或者直接操作数据库把数据插入进去,请使用程序插入或者导入数据的方式来准备数据。

保持单元测试的独立性。为了保证单元测试稳定可靠且便于维护,单元测试用例之间决不能互相调用,也不能依赖执行的先后次序。

单元测试应该是全自动执行的,并且非交互式的。测试用例通常是被定期执行的,执行过程必须完全自动化才有意义。输出结果需要人工检查的测试不是一个好的单元测试。单元测试中不准使用 System.out 来进行人肉验证,必须使用 assert 或 JUnit 的 Assert 等来验证。

注意: 注意在测试类中的 成员变量静态变量 的区别。如果需要所有测试方法共享那个变量,需要使用静态变量,反之使用成员变量,并且各测试方法每次使用成员变量时,都会初始化成员变量。

【强烈建议】

1、运行单元测试前后,数据库表中都不能存在数据。如果在运行测试代码时,出现数据无法保存导致的异常或错误,请检查数据库中数据是否符合该条件。

2、为了测试覆盖率能够达标,有时会对同一个方法进行多次调用测试以便能够覆盖到各个分支,但 在每次调用时不要使用同一份入参 进行测试,因此在被测试的方法内部很可能会对入参进行修改,这时再使用这份数据进行测试产生的结果就很有可能不符合我们的预期结果。

2.3 @Accessors 与 链式

在 Lombok 中有这样一个注解 @Accessors

1
2
3
4
5
6
7
8
9
@Target({ElementType.TYPE, ElementType.FIELD})
@Retention(RetentionPolicy.SOURCE)
public @interface Accessors {
boolean fluent() default false;

boolean chain() default false;

String[] prefix() default {};
}

其中有一个默认值为 false,类型为 boolean 名为 chain 的属性,如果在使用 Accessors 注解时将 chain 设置为 true,即:

1
@Accessors(chain = true)

表示当前类下所有属性或当前字段支持链式编程,即:Setter 方法不再返回 void,而是 this

但需要注意的是,链式编程用起来很爽,但不要随意使用,尤其是在涉及到 Bean 映射的场景,比如:BeanCopier、MapStruct 等都不支持链式,同时 EasyExcel 也不支持链式。

主要原因是:它们都存在从源中 get 出数据,然后 set 到目标中,而它们所使用的 Setter 都是返回 void

因此:不推荐随意使用链式编程。

2.4 多翻 Issues

事实证明,解决开源软件(或组件)使用过程中遇到的问题不应该在求助百度之类的中文搜索引擎,而是 应该直接去托管开源软件(或组件)的平台翻 Issues,因为总有人在你之前就已经发现了问题。

无论是 Reflections 的 ClasspathHelper#forPackage() 的解析错误 #178,还是 Jackson 的 configure-first-then-use #2426 都困扰了我很长一段时间,别无他法只能翻 Issues 寻求解决方案,幸运的是每次都能成功解决,这也算是给了我一点启示,希望下次不要再消耗过多的时间在解决软件(或组件)使用过程中遇到的问题上。

3. 生活中的运用

3.1 Windows 下的 start 命令

参考链接:

官方文档:start | Microsoft Learn

工作后一直使用的是公司配置的 Windows 电脑,那玩意隔三差五一蓝屏、七天九日一死机,总结下来就两个字:折磨。

我并没有让 Windows 系统的电脑一直保持开机的习惯,每当下班时都会 Win + XUU 一套组合技结束一天的工作,但每天早上开机后启动各种软件是又枯燥、又繁琐。

那为啥不把它们设置开机自启呢?因为设置成开机自启后开机速度、蓝屏概率都将大幅增加。

因此我想到了使用 Windows 下的批处理命令,在 Windows 下一定存在一个能运行某个位置的软件的命令,使用这个命令编写一个 bat 脚本,至少可以让多次的点击合并成一次。

使用 start 命令可以在命令行下运行一个程序,打开指定的盘符、文件、文件夹、网址等。

比如运行 WeChat 和 IDEA:

1
2
start "" "C:\IDEA\IntelliJ IDEA 2020.3\bin\idea64.exe"
start D:\WeChat\WeChat.exe

注意: 当路径中包含空格时,路径需要使用双引号括起来,否则路径会被截断导致命令执行失败。除此之外,路径前也要添加一对双引号,表示空标题,否则会在给定路径下打开 CMD,而不是运行指定的程序。

3.2 Windows 系统关闭指定端口

比如需要关闭 9090 端口,需要先查询到占用该端口的 PID:

1
netstat -ano | findstr 9090

假设查询得到的 PID 是 13400,执行以下命令关闭即可:

1
taskkill /f /pid 13400