From 590e07a595b0699a8bfa0e61f8d4fece553339fc Mon Sep 17 00:00:00 2001 From: Looly Date: Wed, 2 Apr 2025 21:41:51 +0800 Subject: [PATCH 01/25] prepare 5.8.38 --- CHANGELOG.md | 6 ++++++ README-EN.md | 6 +++--- README.md | 6 +++--- bin/version.txt | 2 +- docs/js/version.js | 2 +- hutool-all/pom.xml | 2 +- hutool-aop/pom.xml | 2 +- hutool-bloomFilter/pom.xml | 2 +- hutool-bom/pom.xml | 2 +- hutool-cache/pom.xml | 2 +- hutool-captcha/pom.xml | 2 +- hutool-core/pom.xml | 2 +- hutool-cron/pom.xml | 2 +- hutool-crypto/pom.xml | 2 +- hutool-db/pom.xml | 2 +- hutool-dfa/pom.xml | 2 +- hutool-extra/pom.xml | 2 +- hutool-http/pom.xml | 2 +- hutool-json/pom.xml | 2 +- hutool-jwt/pom.xml | 2 +- hutool-log/pom.xml | 2 +- hutool-poi/pom.xml | 2 +- hutool-script/pom.xml | 2 +- hutool-setting/pom.xml | 2 +- hutool-socket/pom.xml | 2 +- hutool-system/pom.xml | 2 +- pom.xml | 2 +- 27 files changed, 36 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 48a4e718b..15750f7a4 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # 🚀Changelog +------------------------------------------------------------------------------------------------------------- +# 5.8.38(2025-04-02) + +### 🐣新特性 +### 🐞Bug修复 + ------------------------------------------------------------------------------------------------------------- # 5.8.37(2025-03-31) diff --git a/README-EN.md b/README-EN.md index 6a029fbd5..aa70c0820 100755 --- a/README-EN.md +++ b/README-EN.md @@ -133,18 +133,18 @@ Each module can be introduced individually, or all modules can be introduced by cn.hutool hutool-all - 5.8.37 + 5.8.38 ``` ### 🍐Gradle ``` -implementation 'cn.hutool:hutool-all:5.8.37' +implementation 'cn.hutool:hutool-all:5.8.38' ``` ## 📥Download -- [Maven Repo](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.8.37/) +- [Maven Repo](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.8.38/) > 🔔️note: > Hutool 5.x supports JDK8+ and is not tested on Android platforms, and cannot guarantee that all tool classes or tool methods are available. diff --git a/README.md b/README.md index 7810cd702..cd1ef3981 100755 --- a/README.md +++ b/README.md @@ -123,20 +123,20 @@ Hutool = Hu + tool,是原公司项目底层代码剥离后的开源库,“Hu cn.hutool hutool-all - 5.8.37 + 5.8.38 ``` ### 🍐Gradle ``` -implementation 'cn.hutool:hutool-all:5.8.37' +implementation 'cn.hutool:hutool-all:5.8.38' ``` ### 📥下载jar 点击以下链接,下载`hutool-all-X.X.X.jar`即可: -- [Maven中央库](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.8.37/) +- [Maven中央库](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.8.38/) > 🔔️注意 > Hutool 5.x支持JDK8+,对Android平台没有测试,不能保证所有工具类或工具方法可用。 diff --git a/bin/version.txt b/bin/version.txt index 70b65abec..b2fdc273c 100755 --- a/bin/version.txt +++ b/bin/version.txt @@ -1 +1 @@ -5.8.37 +5.8.38 diff --git a/docs/js/version.js b/docs/js/version.js index bae6d1877..de6ce4b69 100755 --- a/docs/js/version.js +++ b/docs/js/version.js @@ -1 +1 @@ -var version = '5.8.37' \ No newline at end of file +var version = '5.8.38' \ No newline at end of file diff --git a/hutool-all/pom.xml b/hutool-all/pom.xml index f3357c975..5102ccf63 100755 --- a/hutool-all/pom.xml +++ b/hutool-all/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.37 + 5.8.38-SNAPSHOT hutool-all diff --git a/hutool-aop/pom.xml b/hutool-aop/pom.xml index 0362f71b2..9444a860a 100755 --- a/hutool-aop/pom.xml +++ b/hutool-aop/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.37 + 5.8.38-SNAPSHOT hutool-aop diff --git a/hutool-bloomFilter/pom.xml b/hutool-bloomFilter/pom.xml index b75e65f68..9336082df 100755 --- a/hutool-bloomFilter/pom.xml +++ b/hutool-bloomFilter/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.37 + 5.8.38-SNAPSHOT hutool-bloomFilter diff --git a/hutool-bom/pom.xml b/hutool-bom/pom.xml index 1b13ba539..bbb5ed25b 100755 --- a/hutool-bom/pom.xml +++ b/hutool-bom/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.37 + 5.8.38-SNAPSHOT hutool-bom diff --git a/hutool-cache/pom.xml b/hutool-cache/pom.xml index d56a29a52..a6cc2cb3c 100755 --- a/hutool-cache/pom.xml +++ b/hutool-cache/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.37 + 5.8.38-SNAPSHOT hutool-cache diff --git a/hutool-captcha/pom.xml b/hutool-captcha/pom.xml index 9b7cf5fc7..6a82b22c9 100755 --- a/hutool-captcha/pom.xml +++ b/hutool-captcha/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.37 + 5.8.38-SNAPSHOT hutool-captcha diff --git a/hutool-core/pom.xml b/hutool-core/pom.xml index dccad8fee..9a84d623a 100755 --- a/hutool-core/pom.xml +++ b/hutool-core/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.37 + 5.8.38-SNAPSHOT hutool-core diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml index bc8617ed0..62638e402 100755 --- a/hutool-cron/pom.xml +++ b/hutool-cron/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.37 + 5.8.38-SNAPSHOT hutool-cron diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml index 0fa6e0032..375b8a275 100755 --- a/hutool-crypto/pom.xml +++ b/hutool-crypto/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.37 + 5.8.38-SNAPSHOT hutool-crypto diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index b91cf5452..1173ceaa7 100755 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.37 + 5.8.38-SNAPSHOT hutool-db diff --git a/hutool-dfa/pom.xml b/hutool-dfa/pom.xml index 2e8dd5ceb..932de097f 100755 --- a/hutool-dfa/pom.xml +++ b/hutool-dfa/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.37 + 5.8.38-SNAPSHOT hutool-dfa diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index d79a46ceb..decf0a028 100755 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.37 + 5.8.38-SNAPSHOT hutool-extra diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml index bb60b401b..087187213 100755 --- a/hutool-http/pom.xml +++ b/hutool-http/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.37 + 5.8.38-SNAPSHOT hutool-http diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml index 67db37417..61514155e 100755 --- a/hutool-json/pom.xml +++ b/hutool-json/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.37 + 5.8.38-SNAPSHOT hutool-json diff --git a/hutool-jwt/pom.xml b/hutool-jwt/pom.xml index d08d6815f..4bf6221c5 100755 --- a/hutool-jwt/pom.xml +++ b/hutool-jwt/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.37 + 5.8.38-SNAPSHOT hutool-jwt diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml index cc7252634..cd856013a 100755 --- a/hutool-log/pom.xml +++ b/hutool-log/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.37 + 5.8.38-SNAPSHOT hutool-log diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml index b28cbf1e1..3128e019f 100755 --- a/hutool-poi/pom.xml +++ b/hutool-poi/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.37 + 5.8.38-SNAPSHOT hutool-poi diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml index 780c3c941..0ec5f3415 100755 --- a/hutool-script/pom.xml +++ b/hutool-script/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.37 + 5.8.38-SNAPSHOT hutool-script diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml index 0010b1bde..e61ec4e7d 100755 --- a/hutool-setting/pom.xml +++ b/hutool-setting/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.37 + 5.8.38-SNAPSHOT hutool-setting diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml index 8cc9ee71c..98924535e 100755 --- a/hutool-socket/pom.xml +++ b/hutool-socket/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.37 + 5.8.38-SNAPSHOT hutool-socket diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml index eb6f73b5f..60c57dd93 100755 --- a/hutool-system/pom.xml +++ b/hutool-system/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.37 + 5.8.38-SNAPSHOT hutool-system diff --git a/pom.xml b/pom.xml index 2312570e0..2bb3e69d2 100755 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.8.37 + 5.8.38-SNAPSHOT hutool Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。 https://github.com/chinabugotech/hutool From 9939b69cb64042f19fd810a8ac64acb53e5144b6 Mon Sep 17 00:00:00 2001 From: ysy <550244300@qq.com> Date: Thu, 3 Apr 2025 13:45:17 +0800 Subject: [PATCH 02/25] feat: sap hana db --- hutool-db/pom.xml | 6 ++ .../cn/hutool/db/dialect/DialectFactory.java | 5 + .../cn/hutool/db/dialect/DialectName.java | 2 +- .../cn/hutool/db/dialect/DriverNamePool.java | 4 + .../hutool/db/dialect/impl/HanaDialect.java | 93 +++++++++++++++++++ .../src/test/java/cn/hutool/db/HanaTest.java | 89 ++++++++++++++++++ .../src/test/resources/config/db.setting | 6 ++ 7 files changed, 204 insertions(+), 1 deletion(-) create mode 100644 hutool-db/src/main/java/cn/hutool/db/dialect/impl/HanaDialect.java create mode 100644 hutool-db/src/test/java/cn/hutool/db/HanaTest.java diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index b91cf5452..272897c17 100755 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -185,5 +185,11 @@ 23.5.0.24.07 test + + com.sap.cloud.db.jdbc + ngdbc + 2.24.6 + test + diff --git a/hutool-db/src/main/java/cn/hutool/db/dialect/DialectFactory.java b/hutool-db/src/main/java/cn/hutool/db/dialect/DialectFactory.java index 7fcc12763..e2636f766 100755 --- a/hutool-db/src/main/java/cn/hutool/db/dialect/DialectFactory.java +++ b/hutool-db/src/main/java/cn/hutool/db/dialect/DialectFactory.java @@ -62,6 +62,8 @@ public class DialectFactory implements DriverNamePool { return new PhoenixDialect(); } else if (DRIVER_DM7.equalsIgnoreCase(driverName)) { return new DmDialect(); + } else if (DRIVER_HANA.equalsIgnoreCase(driverName)) { + return new HanaDialect(); } } // 无法识别可支持的数据库类型默认使用ANSI方言,可兼容大部分SQL语句 @@ -168,6 +170,9 @@ public class DialectFactory implements DriverNamePool { } else if (nameContainsProductInfo.contains("goldendb")) { // GoldenDB driver = DRIVER_GOLDENDB; + } else if (nameContainsProductInfo.contains("sap")) { + // sap hana + driver = DRIVER_HANA; } return driver; diff --git a/hutool-db/src/main/java/cn/hutool/db/dialect/DialectName.java b/hutool-db/src/main/java/cn/hutool/db/dialect/DialectName.java index 275723b75..fab193b8d 100644 --- a/hutool-db/src/main/java/cn/hutool/db/dialect/DialectName.java +++ b/hutool-db/src/main/java/cn/hutool/db/dialect/DialectName.java @@ -9,7 +9,7 @@ import cn.hutool.core.util.StrUtil; * @author Looly */ public enum DialectName { - ANSI, MYSQL, ORACLE, POSTGRESQL, SQLITE3, H2, SQLSERVER, SQLSERVER2012, PHOENIX, DM; + ANSI, MYSQL, ORACLE, POSTGRESQL, SQLITE3, H2, SQLSERVER, SQLSERVER2012, PHOENIX, DM, HANA; /** * 是否为指定数据库方言,检查时不分区大小写 diff --git a/hutool-db/src/main/java/cn/hutool/db/dialect/DriverNamePool.java b/hutool-db/src/main/java/cn/hutool/db/dialect/DriverNamePool.java index 998bc0a0b..95a6a1a1f 100644 --- a/hutool-db/src/main/java/cn/hutool/db/dialect/DriverNamePool.java +++ b/hutool-db/src/main/java/cn/hutool/db/dialect/DriverNamePool.java @@ -120,4 +120,8 @@ public interface DriverNamePool { * JDBC 驱动 GoldenDB */ String DRIVER_GOLDENDB = "com.goldendb.jdbc.Driver"; + /** + * JDBC 驱动 Sap Hana + */ + String DRIVER_HANA = "com.sap.db.jdbc.Driver"; } diff --git a/hutool-db/src/main/java/cn/hutool/db/dialect/impl/HanaDialect.java b/hutool-db/src/main/java/cn/hutool/db/dialect/impl/HanaDialect.java new file mode 100644 index 000000000..d0bdbe7ec --- /dev/null +++ b/hutool-db/src/main/java/cn/hutool/db/dialect/impl/HanaDialect.java @@ -0,0 +1,93 @@ +package cn.hutool.db.dialect.impl; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.db.Entity; +import cn.hutool.db.Page; +import cn.hutool.db.StatementUtil; +import cn.hutool.db.dialect.DialectName; +import cn.hutool.db.sql.SqlBuilder; +import cn.hutool.db.sql.Wrapper; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Hana数据库方言 + * + * @author daoyou.dev + */ +public class HanaDialect extends AnsiSqlDialect { + + public HanaDialect() { + wrapper = new Wrapper('"'); + } + + @Override + public String dialectName() { + return DialectName.HANA.name(); + } + + @Override + protected SqlBuilder wrapPageSql(SqlBuilder find, Page page) { + // SAP HANA 使用 OFFSET LIMIT 分页 + return find.append(" LIMIT ").append(page.getPageSize()) + .append(" OFFSET ").append(page.getStartPosition()); + } + + /** + * 构建用于upsert的{@link PreparedStatement}。 + * SAP HANA 使用 MERGE INTO 语法来实现 UPSERT 操作。 + *

+ * 生成 SQL 语法为: + *

+	 *     MERGE INTO demo AS target
+	 *     USING (SELECT ? AS a, ? AS b, ? AS c FROM DUMMY) AS source
+	 *     ON target.id = source.id
+	 *     WHEN MATCHED THEN
+	 *         UPDATE SET target.a = source.a, target.b = source.b, target.c = source.c
+	 *     WHEN NOT MATCHED THEN
+	 *         INSERT (a, b, c) VALUES (source.a, source.b, source.c);
+	 * 
+ * + * @param conn 数据库连接对象 + * @param entity 数据实体类(包含表名) + * @param keys 主键字段数组,通常用于确定匹配条件(联合主键) + * @return PreparedStatement + * @throws SQLException SQL 执行异常 + */ + @Override + public PreparedStatement psForUpsert(Connection conn, Entity entity, String... keys) throws SQLException { + SqlBuilder.validateEntity(entity); + final SqlBuilder builder = SqlBuilder.create(wrapper); + + final List columns = new ArrayList<>(); + final List values = new ArrayList<>(); + + // 构建字段部分和参数占位符部分 + entity.forEach((field, value) -> { + if (StrUtil.isNotBlank(field)) { + columns.add(wrapper != null ? wrapper.wrap(field) : field); + values.add(value); + } + }); + + String tableName = entity.getTableName(); + if (wrapper != null) { + tableName = wrapper.wrap(tableName); + } + + // 构建 UPSERT 语句 + builder.append("UPSERT ").append(tableName).append(" ("); + builder.append(String.join(", ", columns)); + builder.append(") VALUES ("); + builder.append(String.join(", ", Collections.nCopies(columns.size(), "?"))); + builder.append(") WITH PRIMARY KEY"); + + return StatementUtil.prepareStatement(conn, builder.toString(), values); + } +} + diff --git a/hutool-db/src/test/java/cn/hutool/db/HanaTest.java b/hutool-db/src/test/java/cn/hutool/db/HanaTest.java new file mode 100644 index 000000000..6fe0a7955 --- /dev/null +++ b/hutool-db/src/test/java/cn/hutool/db/HanaTest.java @@ -0,0 +1,89 @@ +package cn.hutool.db; + +import cn.hutool.core.lang.Console; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import java.sql.SQLException; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * hana操作单元测试 + * + * @author daoyou.dev + */ +public class HanaTest { + @BeforeAll + public static void createTable() throws SQLException { + Db db = Db.use("hana"); + long count = db.count("SELECT * FROM SYS.TABLES WHERE TABLE_NAME = ? AND SCHEMA_NAME = CURRENT_SCHEMA", "user"); + if (count > 0) { + db.execute("drop table \"user\""); + } + db.execute("CREATE COLUMN TABLE \"user\" (\"id\" INT NOT NULL, \"account\" VARCHAR(255), \"name\" VARCHAR(255), \"text\" VARCHAR(255), \"test1\" VARCHAR(255), \"pass\" VARCHAR(255), PRIMARY KEY (\"id\"))"); + } + + @Test + @Disabled + public void insertTest() throws SQLException { + DbUtil.setReturnGeneratedKeyGlobal(false); + for (int id = 100; id < 200; id++) { + Db.use("hana").insert(Entity.create("user")// + .set("id", id)// + .set("name", "测试用户" + id)// + .set("text", "描述" + id)// + .set("test1", "t" + id)// + ); + } + } + + /** + * 事务测试
+ * 更新三条信息,低2条后抛出异常,正常情况下三条都应该不变 + * + * @throws SQLException SQL异常 + */ + @Test + @Disabled + public void txTest() throws SQLException { + Db.use("hana").tx(db -> { + int update = db.update(Entity.create("user").set("text", "描述100"), Entity.create().set("id", 100)); + db.update(Entity.create("user").set("text", "描述101"), Entity.create().set("id", 101)); + if (1 == update) { + // 手动指定异常,然后测试回滚触发 + throw new RuntimeException("Error"); + } + db.update(Entity.create("user").set("text", "描述102"), Entity.create().set("id", 102)); + }); + } + + @Test + @Disabled + public void pageTest() throws SQLException { + PageResult result = Db.use("hana").page(Entity.create("\"user\""), new Page(2, 10)); + for (Entity entity : result) { + Console.log(entity.get("id")); + } + } + + @Test + @Disabled + public void getTimeStampTest() throws SQLException { + final List all = Db.use("hana").findAll("user"); + Console.log(all); + } + + @Test + public void upsertTest() throws SQLException { + DbUtil.setReturnGeneratedKeyGlobal(false); + Db db = Db.use("hana"); + db.insert(Entity.create("user").set("id", 1).set("account", "ice").set("pass", "123456")); + db.upsert(Entity.create("user").set("id", 1).set("account", "daoyou").set("pass", "a123456").set("name", "道友")); + Entity user = db.get(Entity.create("user").set("id", 1)); + System.out.println("user=======" + user.getStr("account") + "___" + user.getStr("pass")); + assertEquals("daoyou", user.getStr("account")); + } +} diff --git a/hutool-db/src/test/resources/config/db.setting b/hutool-db/src/test/resources/config/db.setting index bb890d4db..1441d2be5 100644 --- a/hutool-db/src/test/resources/config/db.setting +++ b/hutool-db/src/test/resources/config/db.setting @@ -77,3 +77,9 @@ user = SYSDBA pass = 123456789 remarks = true +[hana] +url = jdbc:sap://127.0.0.1:30015/HAP_CONN?autoReconnect=true +user = DB +pass = 123456 +remarks = true + From 584ece852fd2fbc06652382be49145a1340e20d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=A7=E9=98=94?= <1520622465@qq.com> Date: Sun, 6 Apr 2025 18:00:18 +0800 Subject: [PATCH 03/25] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=88=86=E6=AE=B5?= =?UTF-8?q?=E9=94=81=E5=AE=9E=E7=8E=B0=20(issue#IBYLR1@Gitee)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cn/hutool/core/thread/lock/LockUtil.java | 100 ++++ .../hutool/core/thread/lock/SegmentLock.java | 505 ++++++++++++++++++ .../hutool/core/thread/SegmentLockTest.java | 207 +++++++ 3 files changed, 812 insertions(+) create mode 100644 hutool-core/src/main/java/cn/hutool/core/thread/lock/SegmentLock.java create mode 100644 hutool-core/src/test/java/cn/hutool/core/thread/SegmentLockTest.java diff --git a/hutool-core/src/main/java/cn/hutool/core/thread/lock/LockUtil.java b/hutool-core/src/main/java/cn/hutool/core/thread/lock/LockUtil.java index 8a0d12b8c..61a0fdcfc 100644 --- a/hutool-core/src/main/java/cn/hutool/core/thread/lock/LockUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/thread/lock/LockUtil.java @@ -1,5 +1,8 @@ package cn.hutool.core.thread.lock; +import java.util.concurrent.Semaphore; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.StampedLock; @@ -40,4 +43,101 @@ public class LockUtil { public static NoLock getNoLock(){ return NO_LOCK; } + + /** + * 创建分段锁(强引用),使用 ReentrantLock + * + * @param segments 分段数量,必须大于 0 + * @return 分段锁实例 + */ + public static SegmentLock createSegmentLock(int segments) { + return SegmentLock.lock(segments); + } + + /** + * 创建分段读写锁(强引用),使用 ReentrantReadWriteLock + * + * @param segments 分段数量,必须大于 0 + * @return 分段读写锁实例 + */ + public static SegmentLock createSegmentReadWriteLock(int segments) { + return SegmentLock.readWriteLock(segments); + } + + /** + * 创建分段信号量(强引用) + * + * @param segments 分段数量,必须大于 0 + * @param permits 每个信号量的许可数 + * @return 分段信号量实例 + */ + public static SegmentLock createSegmentSemaphore(int segments, int permits) { + return SegmentLock.semaphore(segments, permits); + } + + /** + * 创建弱引用分段锁,使用 ReentrantLock,懒加载 + * + * @param segments 分段数量,必须大于 0 + * @return 弱引用分段锁实例 + */ + public static SegmentLock createLazySegmentLock(int segments) { + return SegmentLock.lazyWeakLock(segments); + } + + /** + * 根据 key 获取分段锁(强引用) + * + * @param segments 分段数量,必须大于 0 + * @param key 用于映射分段的 key + * @return 对应的 Lock 实例 + */ + public static Lock getSegmentLock(int segments, Object key) { + return SegmentLock.lock(segments).get(key); + } + + /** + * 根据 key 获取分段读锁(强引用) + * + * @param segments 分段数量,必须大于 0 + * @param key 用于映射分段的 key + * @return 对应的读锁实例 + */ + public static Lock getSegmentReadLock(int segments, Object key) { + return SegmentLock.readWriteLock(segments).get(key).readLock(); + } + + /** + * 根据 key 获取分段写锁(强引用) + * + * @param segments 分段数量,必须大于 0 + * @param key 用于映射分段的 key + * @return 对应的写锁实例 + */ + public static Lock getSegmentWriteLock(int segments, Object key) { + return SegmentLock.readWriteLock(segments).get(key).writeLock(); + } + + /** + * 根据 key 获取分段信号量(强引用) + * + * @param segments 分段数量,必须大于 0 + * @param permits 每个信号量的许可数 + * @param key 用于映射分段的 key + * @return 对应的 Semaphore 实例 + */ + public static Semaphore getSegmentSemaphore(int segments, int permits, Object key) { + return SegmentLock.semaphore(segments, permits).get(key); + } + + /** + * 根据 key 获取弱引用分段锁,懒加载 + * + * @param segments 分段数量,必须大于 0 + * @param key 用于映射分段的 key + * @return 对应的 Lock 实例 + */ + public static Lock getLazySegmentLock(int segments, Object key) { + return SegmentLock.lazyWeakLock(segments).get(key); + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/thread/lock/SegmentLock.java b/hutool-core/src/main/java/cn/hutool/core/thread/lock/SegmentLock.java new file mode 100644 index 000000000..632af97aa --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/thread/lock/SegmentLock.java @@ -0,0 +1,505 @@ +package cn.hutool.core.thread.lock; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Assert; + +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReferenceArray; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.function.Supplier; + +/** + * 分段锁工具类,支持 Lock、Semaphore 和 ReadWriteLock 的分段实现。 + *

+ * 通过将锁分成多个段(segments),不同的操作可以并发使用不同的段,避免所有线程竞争同一把锁。 + * 相等的 key 保证映射到同一段锁(如 key1.equals(key2) 时,get(key1) 和 get(key2) 返回相同对象)。 + * 但不同 key 可能因哈希冲突映射到同一段,段数越少冲突概率越高。 + *

+ * 支持两种实现: + *

    + *
  • 强引用:创建时初始化所有段,内存占用稳定。
  • + *
  • 弱引用:懒加载,首次使用时创建段,未使用时可被垃圾回收,适合大量段但使用较少的场景。
  • + *
+ * + * @author dakuo + * @since 5.8.37 + */ +public abstract class SegmentLock { + + /** 当段数大于此阈值时,使用 ConcurrentMap 替代大数组以节省内存(适用于懒加载场景) */ + private static final int LARGE_LAZY_CUTOFF = 1024; + + private SegmentLock() {} + + /** + * 根据 key 获取对应的锁段,保证相同 key 返回相同对象。 + * + * @param key 非空 key + * @return 对应的锁段 + */ + public abstract L get(Object key); + + /** + * 根据索引获取锁段,索引范围为 [0, size())。 + * + * @param index 索引 + * @return 指定索引的锁段 + */ + public abstract L getAt(int index); + + /** + * 计算 key 对应的段索引。 + * + * @param key 非空 key + * @return 段索引 + */ + abstract int indexFor(Object key); + + /** + * 获取总段数。 + * + * @return 段数 + */ + public abstract int size(); + + /** + * 批量获取多个 key 对应的锁段列表,按索引升序排列,避免死锁。 + * + * @param keys 非空 key 集合 + * @return 锁段列表(可能有重复) + */ + public Iterable bulkGet(Iterable keys) { + @SuppressWarnings("unchecked") + List result = (List) CollUtil.newArrayList(keys); + if (CollUtil.isEmpty(result)) { + return Collections.emptyList(); + } + int[] stripes = new int[result.size()]; + for (int i = 0; i < result.size(); i++) { + stripes[i] = indexFor(result.get(i)); + } + Arrays.sort(stripes); + int previousStripe = stripes[0]; + result.set(0, getAt(previousStripe)); + for (int i = 1; i < result.size(); i++) { + int currentStripe = stripes[i]; + if (currentStripe == previousStripe) { + result.set(i, result.get(i - 1)); + } else { + result.set(i, getAt(currentStripe)); + previousStripe = currentStripe; + } + } + @SuppressWarnings("unchecked") + List asStripes = (List) result; + return Collections.unmodifiableList(asStripes); + } + + // 静态工厂方法 + + /** + * 创建强引用的分段锁,所有段在创建时初始化。 + * + * @param stripes 段数 + * @param supplier 锁提供者 + * @param 锁类型 + * @return 分段锁实例 + */ + public static SegmentLock custom(int stripes, Supplier supplier) { + return new CompactSegmentLock<>(stripes, supplier); + } + + /** + * 创建强引用的可重入锁分段实例。 + * + * @param stripes 段数 + * @return 分段锁实例 + */ + public static SegmentLock lock(int stripes) { + return custom(stripes, PaddedLock::new); + } + + /** + * 创建弱引用的可重入锁分段实例,懒加载。 + * + * @param stripes 段数 + * @return 分段锁实例 + */ + public static SegmentLock lazyWeakLock(int stripes) { + return lazyWeakCustom(stripes, () -> new ReentrantLock(false)); + } + + /** + * 创建弱引用的分段锁,懒加载。 + * + * @param stripes 段数 + * @param supplier 锁提供者 + * @param 锁类型 + * @return 分段锁实例 + */ + private static SegmentLock lazyWeakCustom(int stripes, Supplier supplier) { + return stripes < LARGE_LAZY_CUTOFF + ? new SmallLazySegmentLock<>(stripes, supplier) + : new LargeLazySegmentLock<>(stripes, supplier); + } + + /** + * 创建强引用的信号量分段实例。 + * + * @param stripes 段数 + * @param permits 每个信号量的许可数 + * @return 分段信号量实例 + */ + public static SegmentLock semaphore(int stripes, int permits) { + return custom(stripes, () -> new PaddedSemaphore(permits)); + } + + /** + * 创建弱引用的信号量分段实例,懒加载。 + * + * @param stripes 段数 + * @param permits 每个信号量的许可数 + * @return 分段信号量实例 + */ + public static SegmentLock lazyWeakSemaphore(int stripes, int permits) { + return lazyWeakCustom(stripes, () -> new Semaphore(permits, false)); + } + + /** + * 创建强引用的读写锁分段实例。 + * + * @param stripes 段数 + * @return 分段读写锁实例 + */ + public static SegmentLock readWriteLock(int stripes) { + return custom(stripes, ReentrantReadWriteLock::new); + } + + /** + * 创建弱引用的读写锁分段实例,懒加载。 + * + * @param stripes 段数 + * @return 分段读写锁实例 + */ + public static SegmentLock lazyWeakReadWriteLock(int stripes) { + return lazyWeakCustom(stripes, WeakSafeReadWriteLock::new); + } + + // 内部实现类 + + /** + * 弱引用安全的读写锁实现,确保读锁和写锁持有对自身的强引用。 + */ + private static final class WeakSafeReadWriteLock implements ReadWriteLock { + private final ReadWriteLock delegate; + + WeakSafeReadWriteLock() { + this.delegate = new ReentrantReadWriteLock(); + } + + @Override + public Lock readLock() { + return new WeakSafeLock(delegate.readLock(), this); + } + + @Override + public Lock writeLock() { + return new WeakSafeLock(delegate.writeLock(), this); + } + } + + /** + * 弱引用安全的锁包装类,确保持有强引用。 + */ + private static final class WeakSafeLock implements Lock { + private final Lock delegate; + private final WeakSafeReadWriteLock strongReference; + + WeakSafeLock(Lock delegate, WeakSafeReadWriteLock strongReference) { + this.delegate = delegate; + this.strongReference = strongReference; + } + + @Override + public void lock() { + delegate.lock(); + } + + @Override + public void lockInterruptibly() throws InterruptedException { + delegate.lockInterruptibly(); + } + + @Override + public boolean tryLock() { + return delegate.tryLock(); + } + + @Override + public boolean tryLock(long time, java.util.concurrent.TimeUnit unit) throws InterruptedException { + return delegate.tryLock(time, unit); + } + + @Override + public void unlock() { + delegate.unlock(); + } + + @Override + public Condition newCondition() { + return new WeakSafeCondition(delegate.newCondition(), strongReference); + } + } + + /** + * 弱引用安全的条件包装类。 + */ + private static final class WeakSafeCondition implements Condition { + private final Condition delegate; + + /** 防止垃圾回收 */ + private final WeakSafeReadWriteLock strongReference; + + WeakSafeCondition(Condition delegate, WeakSafeReadWriteLock strongReference) { + this.delegate = delegate; + this.strongReference = strongReference; + } + + @Override + public void await() throws InterruptedException { + delegate.await(); + } + + @Override + public void awaitUninterruptibly() { + delegate.awaitUninterruptibly(); + } + + @Override + public long awaitNanos(long nanosTimeout) throws InterruptedException { + return delegate.awaitNanos(nanosTimeout); + } + + @Override + public boolean await(long time, TimeUnit unit) throws InterruptedException { + return delegate.await(time, unit); + } + + @Override + public boolean awaitUntil(Date deadline) throws InterruptedException { + return delegate.awaitUntil(deadline); + } + + @Override + public void signal() { + delegate.signal(); + } + + @Override + public void signalAll() { + delegate.signalAll(); + } + } + + /** + * 抽象基类,确保段数为 2 的幂。 + */ + private abstract static class PowerOfTwoSegmentLock extends SegmentLock { + final int mask; + + PowerOfTwoSegmentLock(int stripes) { + Assert.isTrue(stripes > 0, "Segment count must be positive"); + this.mask = stripes > Integer.MAX_VALUE / 2 ? ALL_SET : ceilToPowerOfTwo(stripes) - 1; + } + + @Override + final int indexFor(Object key) { + int hash = smear(key.hashCode()); + return hash & mask; + } + + @Override + public final L get(Object key) { + return getAt(indexFor(key)); + } + } + + /** + * 强引用实现,使用固定数组存储段。 + */ + private static class CompactSegmentLock extends PowerOfTwoSegmentLock { + private final Object[] array; + + CompactSegmentLock(int stripes, Supplier supplier) { + super(stripes); + Assert.isTrue(stripes <= Integer.MAX_VALUE / 2, "Segment count must be <= 2^30"); + this.array = new Object[mask + 1]; + for (int i = 0; i < array.length; i++) { + array[i] = supplier.get(); + } + } + + @SuppressWarnings("unchecked") + @Override + public L getAt(int index) { + if (index < 0 || index >= array.length) { + throw new IllegalArgumentException("Index " + index + " out of bounds for size " + array.length); + } + return (L) array[index]; + } + + @Override + public int size() { + return array.length; + } + } + + /** + * 小规模弱引用实现,使用 AtomicReferenceArray 存储段。 + */ + private static class SmallLazySegmentLock extends PowerOfTwoSegmentLock { + final AtomicReferenceArray> locks; + final Supplier supplier; + final int size; + final ReferenceQueue queue = new ReferenceQueue<>(); + + SmallLazySegmentLock(int stripes, Supplier supplier) { + super(stripes); + this.size = (mask == ALL_SET) ? Integer.MAX_VALUE : mask + 1; + this.locks = new AtomicReferenceArray<>(size); + this.supplier = supplier; + } + + @Override + public L getAt(int index) { + if (size != Integer.MAX_VALUE) { + Assert.isTrue(index >= 0 && index < size, "Index out of bounds"); + } + ArrayReference existingRef = locks.get(index); + L existing = existingRef == null ? null : existingRef.get(); + if (existing != null) { + return existing; + } + L created = supplier.get(); + ArrayReference newRef = new ArrayReference<>(created, index, queue); + while (!locks.compareAndSet(index, existingRef, newRef)) { + existingRef = locks.get(index); + existing = existingRef == null ? null : existingRef.get(); + if (existing != null) { + return existing; + } + } + drainQueue(); + return created; + } + + private void drainQueue() { + Reference ref; + while ((ref = queue.poll()) != null) { + ArrayReference arrayRef = (ArrayReference) ref; + locks.compareAndSet(arrayRef.index, arrayRef, null); + } + } + + @Override + public int size() { + return size; + } + + private static final class ArrayReference extends WeakReference { + final int index; + + ArrayReference(L referent, int index, ReferenceQueue queue) { + super(referent, queue); + this.index = index; + } + } + } + + /** + * 大规模弱引用实现,使用 ConcurrentMap 存储段。 + */ + private static class LargeLazySegmentLock extends PowerOfTwoSegmentLock { + final ConcurrentMap locks; + final Supplier supplier; + final int size; + + LargeLazySegmentLock(int stripes, Supplier supplier) { + super(stripes); + this.size = (mask == ALL_SET) ? Integer.MAX_VALUE : mask + 1; + this.locks = new ConcurrentHashMap<>(); + this.supplier = supplier; + } + + @Override + public L getAt(int index) { + if (size != Integer.MAX_VALUE) { + Assert.isTrue(index >= 0 && index < size, "Index out of bounds"); + } + L existing = locks.get(index); + if (existing != null) { + return existing; + } + L created = supplier.get(); + existing = locks.putIfAbsent(index, created); + return existing != null ? existing : created; + } + + @Override + public int size() { + return size; + } + } + + private static final int ALL_SET = ~0; + + private static int ceilToPowerOfTwo(int x) { + return 1 << (Integer.SIZE - Integer.numberOfLeadingZeros(x - 1)); + } + + private static int smear(int hashCode) { + hashCode ^= (hashCode >>> 20) ^ (hashCode >>> 12); + return hashCode ^ (hashCode >>> 7) ^ (hashCode >>> 4); + } + + /** + * 填充锁,避免缓存行干扰。 + */ + private static class PaddedLock extends ReentrantLock { + long unused1; + long unused2; + long unused3; + + PaddedLock() { + super(false); + } + } + + /** + * 填充信号量,避免缓存行干扰。 + */ + private static class PaddedSemaphore extends Semaphore { + long unused1; + long unused2; + long unused3; + + PaddedSemaphore(int permits) { + super(permits, false); + } + } + +} diff --git a/hutool-core/src/test/java/cn/hutool/core/thread/SegmentLockTest.java b/hutool-core/src/test/java/cn/hutool/core/thread/SegmentLockTest.java new file mode 100644 index 000000000..d41ab928c --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/thread/SegmentLockTest.java @@ -0,0 +1,207 @@ +package cn.hutool.core.thread; + + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.thread.lock.SegmentLock; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * SegmentLock 单元测试类 + */ +public class SegmentLockTest { + + private static final int SEGMENT_COUNT = 4; + private SegmentLock strongLock; + private SegmentLock weakLock; + private SegmentLock semaphore; + private SegmentLock readWriteLock; + + @BeforeEach + public void setUp() { + strongLock = SegmentLock.lock(SEGMENT_COUNT); + weakLock = SegmentLock.lazyWeakLock(SEGMENT_COUNT); + semaphore = SegmentLock.semaphore(SEGMENT_COUNT, 2); + readWriteLock = SegmentLock.readWriteLock(SEGMENT_COUNT); + } + + @Test + public void testSize() { + assertEquals(SEGMENT_COUNT, strongLock.size()); + assertEquals(SEGMENT_COUNT, weakLock.size()); + assertEquals(SEGMENT_COUNT, semaphore.size()); + assertEquals(SEGMENT_COUNT, readWriteLock.size()); + } + + @Test + public void testGetWithSameKey() { + // 相同 key 应返回相同锁 + String key1 = "testKey"; + String key2 = new String("testKey"); // equals 但不同对象 + Lock lock1 = strongLock.get(key1); + Lock lock2 = strongLock.get(key2); + assertSame(lock1, lock2, "相同 key 应返回同一锁对象"); + + Lock weakLock1 = weakLock.get(key1); + Lock weakLock2 = weakLock.get(key2); + assertSame(weakLock1, weakLock2, "弱引用锁相同 key 应返回同一锁对象"); + } + + @Test + public void testGetAt() { + for (int i = 0; i < SEGMENT_COUNT; i++) { + Lock lock = strongLock.getAt(i); + assertNotNull(lock, "getAt 返回的锁不应为 null"); + } + assertThrows(IllegalArgumentException.class, () -> strongLock.getAt(SEGMENT_COUNT), + "超出段数的索引应抛出异常"); + } + + @Test + public void testBulkGet() { + List keys = CollUtil.newArrayList("key1", "key2", "key3"); + Iterable locks = strongLock.bulkGet(keys); + List lockList = CollUtil.newArrayList(locks); + + assertEquals(3, lockList.size(), "bulkGet 返回的锁数量应与 key 数量一致"); + + // 检查顺序性 + int prevIndex = -1; + for (Lock lock : lockList) { + int index = findIndex(strongLock, lock); + assertTrue(index >= prevIndex, "bulkGet 返回的锁应按索引升序"); + prevIndex = index; + } + } + + @Test + public void testLockConcurrency() throws InterruptedException { + int threadCount = SEGMENT_COUNT * 2; + CountDownLatch startLatch = new CountDownLatch(1); + CountDownLatch endLatch = new CountDownLatch(threadCount); + ExecutorService executor = Executors.newFixedThreadPool(threadCount); + List keys = new ArrayList<>(); + for (int i = 0; i < threadCount; i++) { + keys.add("key" + i); + } + + for (int i = 0; i < threadCount; i++) { + final String key = keys.get(i); + executor.submit(() -> { + try { + startLatch.await(); + Lock lock = strongLock.get(key); + lock.lock(); + try { + Thread.sleep(100); // 模拟工作 + } finally { + lock.unlock(); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } finally { + endLatch.countDown(); + } + }); + } + + startLatch.countDown(); + assertTrue(endLatch.await(2000, java.util.concurrent.TimeUnit.MILLISECONDS), + "并发锁测试应在 2 秒内完成"); + executor.shutdown(); + } + + @Test + public void testSemaphore() { + Semaphore sem = semaphore.get("testKey"); + assertEquals(2, sem.availablePermits(), "信号量初始许可应为 2"); + + sem.acquireUninterruptibly(2); + assertEquals(0, sem.availablePermits(), "获取所有许可后应为 0"); + + sem.release(1); + assertEquals(1, sem.availablePermits(), "释放一个许可后应为 1"); + } + + @Test + public void testReadWriteLock() throws InterruptedException { + ReadWriteLock rwLock = readWriteLock.get("testKey"); + Lock readLock = rwLock.readLock(); + Lock writeLock = rwLock.writeLock(); + + // 测试读锁可重入 + readLock.lock(); + assertTrue(readLock.tryLock(), "读锁应允许多个线程同时持有"); + readLock.unlock(); + readLock.unlock(); + + CountDownLatch latch = new CountDownLatch(1); + ExecutorService executor = Executors.newSingleThreadExecutor(); + AtomicBoolean readLockAcquired = new AtomicBoolean(false); + + writeLock.lock(); + executor.submit(() -> { + readLockAcquired.set(readLock.tryLock()); + latch.countDown(); + }); + + latch.await(500, TimeUnit.MILLISECONDS); + assertFalse(readLockAcquired.get(), "写锁持有时读锁应失败"); + writeLock.unlock(); + + executor.shutdown(); + executor.awaitTermination(1, TimeUnit.SECONDS); + } + + @Test + public void testWeakReferenceCleanup() throws InterruptedException { + SegmentLock weakLockLarge = SegmentLock.lazyWeakLock(1024); // 超过 LARGE_LAZY_CUTOFF + Lock lock = weakLockLarge.get("testKey"); + + System.gc(); + Thread.sleep(100); + + // 弱引用锁未被其他引用,应仍可获取 + Lock lockAgain = weakLockLarge.get("testKey"); + assertSame(lock, lockAgain, "弱引用锁未被回收时应返回同一对象"); + } + + @Test + public void testInvalidSegmentCount() { + assertThrows(IllegalArgumentException.class, () -> SegmentLock.lock(0), + "段数为 0 应抛出异常"); + assertThrows(IllegalArgumentException.class, () -> SegmentLock.lock(-1), + "负段数应抛出异常"); + } + + @Test + public void testHashDistribution() { + SegmentLock lock = SegmentLock.lock(4); + int[] counts = new int[4]; + for (int i = 0; i < 100; i++) { + int index = findIndex(lock, lock.get("key" + i)); + counts[index]++; + } + for (int count : counts) { + assertTrue(count > 0, "每个段都应至少被分配到一个 key"); + } + } + + private int findIndex(SegmentLock lock, Lock target) { + for (int i = 0; i < lock.size(); i++) { + if (lock.getAt(i) == target) { + return i; + } + } + return -1; + } +} From 4ddadfd52e904613f90f702c47999490ae338188 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=A7=E9=98=94?= <1520622465@qq.com> Date: Sun, 6 Apr 2025 10:26:37 +0000 Subject: [PATCH 04/25] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=20Guava=20author=20?= =?UTF-8?q?=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 大阔 <1520622465@qq.com> --- .../src/main/java/cn/hutool/core/thread/lock/SegmentLock.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/thread/lock/SegmentLock.java b/hutool-core/src/main/java/cn/hutool/core/thread/lock/SegmentLock.java index 632af97aa..a3b3c7f92 100644 --- a/hutool-core/src/main/java/cn/hutool/core/thread/lock/SegmentLock.java +++ b/hutool-core/src/main/java/cn/hutool/core/thread/lock/SegmentLock.java @@ -35,7 +35,7 @@ import java.util.function.Supplier; *
  • 弱引用:懒加载,首次使用时创建段,未使用时可被垃圾回收,适合大量段但使用较少的场景。
  • * * - * @author dakuo + * @author Guava,dakuo * @since 5.8.37 */ public abstract class SegmentLock { From fbbeddaa540611928384fdfc1a4eebe67b0718fd Mon Sep 17 00:00:00 2001 From: aaapig <50512159+aaapig@users.noreply.github.com> Date: Mon, 7 Apr 2025 10:23:13 +0800 Subject: [PATCH 05/25] =?UTF-8?q?=E9=94=99=E5=88=AB=E5=AD=97=E4=BF=AE?= =?UTF-8?q?=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/cn/hutool/core/text/CharSequenceUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java b/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java index 0820ca236..10c27984a 100755 --- a/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java @@ -435,7 +435,7 @@ public class CharSequenceUtil { } /** - * 是否存都不为{@code null}或空对象或空白符的对象,通过{@link #hasBlank(CharSequence...)} 判断元素 + * 是否全都不为{@code null}或空对象或空白符的对象,通过{@link #hasBlank(CharSequence...)} 判断元素 * * @param args 被检查的对象,一个或者多个 * @return 是否都不为空 From b591f153b335a03794deeebc6c6d7d56c234dc0d Mon Sep 17 00:00:00 2001 From: Yurin Date: Mon, 7 Apr 2025 17:03:29 +0800 Subject: [PATCH 06/25] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=88=A0=E9=99=A4Path?= =?UTF-8?q?=E6=97=B6=E5=A6=82=E6=9E=9C=E4=B8=BAnull=E6=97=B6=E7=A9=BA?= =?UTF-8?q?=E6=8C=87=E9=92=88bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/cn/hutool/core/io/file/PathUtil.java | 2 +- .../test/java/cn/hutool/core/io/file/PathUtilTest.java | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/io/file/PathUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/file/PathUtil.java index c14cfa206..ff270d304 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/file/PathUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/file/PathUtil.java @@ -165,7 +165,7 @@ public class PathUtil { * @since 4.4.2 */ public static boolean del(Path path) throws IORuntimeException { - if (Files.notExists(path)) { + if (null == path || Files.notExists(path)) { return true; } diff --git a/hutool-core/src/test/java/cn/hutool/core/io/file/PathUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/io/file/PathUtilTest.java index f032816de..88e68cd14 100644 --- a/hutool-core/src/test/java/cn/hutool/core/io/file/PathUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/io/file/PathUtilTest.java @@ -5,6 +5,7 @@ import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; @@ -93,4 +94,11 @@ public class PathUtilTest { public void moveTest2(){ PathUtil.move(Paths.get("D:\\project\\test1.txt"), Paths.get("D:\\project\\test2.txt"), false); } + + @Test + @Disabled + public void delNullDirTest() { + Path path = null; + assertTrue(PathUtil.del(path)); + } } From 03fd8665a2f8ec49a957b5d11e4917d47650e2b9 Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 8 Apr 2025 09:03:20 +0800 Subject: [PATCH 07/25] =?UTF-8?q?`PathUtil#del`=E5=A2=9E=E5=8A=A0null?= =?UTF-8?q?=E6=A3=80=E6=9F=A5=EF=BC=88pr#1331@Gitee=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 15750f7a4..b2ca3ef40 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,11 @@ # 🚀Changelog ------------------------------------------------------------------------------------------------------------- -# 5.8.38(2025-04-02) +# 5.8.38(2025-04-08) ### 🐣新特性 +* 【core 】 `PathUtil#del`增加null检查(pr#1331@Gitee) + ### 🐞Bug修复 ------------------------------------------------------------------------------------------------------------- From 791fa4e45d5d3be025536702548419a3740aa224 Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 8 Apr 2025 16:23:32 +0800 Subject: [PATCH 08/25] fix comment --- hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java index fec7c759d..11294c108 100755 --- a/hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java @@ -1592,7 +1592,7 @@ public class ArrayUtil extends PrimitiveArrayUtil { } /** - * 是否存都为{@code null}或空对象,通过{@link ObjectUtil#isEmpty(Object)} 判断元素 + * 是否全都为{@code null}或空对象,通过{@link ObjectUtil#isEmpty(Object)} 判断元素 * * @param args 被检查的对象,一个或者多个 * @return 是否都为空 @@ -1608,7 +1608,7 @@ public class ArrayUtil extends PrimitiveArrayUtil { } /** - * 是否存都不为{@code null}或空对象,通过{@link ObjectUtil#isEmpty(Object)} 判断元素 + * 是否全都不为{@code null}或空对象,通过{@link ObjectUtil#isEmpty(Object)} 判断元素 * * @param args 被检查的对象,一个或者多个 * @return 是否都不为空 From a287abf33b9d1f330862ad9efb2688ea9f4a0f31 Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 10 Apr 2025 16:48:25 +0800 Subject: [PATCH 09/25] add test --- .../java/cn/hutool/core/date/IssueIC00HGTest.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 hutool-core/src/test/java/cn/hutool/core/date/IssueIC00HGTest.java diff --git a/hutool-core/src/test/java/cn/hutool/core/date/IssueIC00HGTest.java b/hutool-core/src/test/java/cn/hutool/core/date/IssueIC00HGTest.java new file mode 100644 index 000000000..7b03fa575 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/date/IssueIC00HGTest.java @@ -0,0 +1,13 @@ +package cn.hutool.core.date; + +import cn.hutool.core.lang.Console; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +public class IssueIC00HGTest { + @Test + @Disabled + void dateToStringTest(){ + Console.log(DateUtil.date().toSqlDate()); + } +} From 5e394cdf08dc95ff893a210d57d1535cccf7cc9a Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 10 Apr 2025 16:51:30 +0800 Subject: [PATCH 10/25] add test --- .../src/test/java/cn/hutool/core/date/IssueIC00HGTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hutool-core/src/test/java/cn/hutool/core/date/IssueIC00HGTest.java b/hutool-core/src/test/java/cn/hutool/core/date/IssueIC00HGTest.java index 7b03fa575..f5a01f500 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/IssueIC00HGTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/IssueIC00HGTest.java @@ -8,6 +8,6 @@ public class IssueIC00HGTest { @Test @Disabled void dateToStringTest(){ - Console.log(DateUtil.date().toSqlDate()); + Console.log(DateUtil.date().toString()); } } From 5c66079f15ec1a40d627fc76150fb0429cde56e3 Mon Sep 17 00:00:00 2001 From: Looly Date: Fri, 11 Apr 2025 09:55:52 +0800 Subject: [PATCH 11/25] gts --- CHANGELOG.md | 3 ++- hutool-db/pom.xml | 2 +- .../src/main/java/cn/hutool/db/dialect/impl/HanaDialect.java | 4 ++++ hutool-db/src/test/java/cn/hutool/db/HanaTest.java | 4 ++-- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b2ca3ef40..1f800c3ba 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,11 @@ # 🚀Changelog ------------------------------------------------------------------------------------------------------------- -# 5.8.38(2025-04-08) +# 5.8.38(2025-04-11) ### 🐣新特性 * 【core 】 `PathUtil#del`增加null检查(pr#1331@Gitee) +* 【db 】 增加SAP HANA识别及方言(pr#3914@Github) ### 🐞Bug修复 diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index f8fd0ccb3..2e1aa3124 100755 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -188,7 +188,7 @@ com.sap.cloud.db.jdbc ngdbc - 2.24.6 + 2.24.7 test diff --git a/hutool-db/src/main/java/cn/hutool/db/dialect/impl/HanaDialect.java b/hutool-db/src/main/java/cn/hutool/db/dialect/impl/HanaDialect.java index d0bdbe7ec..531ad9923 100644 --- a/hutool-db/src/main/java/cn/hutool/db/dialect/impl/HanaDialect.java +++ b/hutool-db/src/main/java/cn/hutool/db/dialect/impl/HanaDialect.java @@ -21,7 +21,11 @@ import java.util.List; * @author daoyou.dev */ public class HanaDialect extends AnsiSqlDialect { + private static final long serialVersionUID = 1L; + /** + * 构造 + */ public HanaDialect() { wrapper = new Wrapper('"'); } diff --git a/hutool-db/src/test/java/cn/hutool/db/HanaTest.java b/hutool-db/src/test/java/cn/hutool/db/HanaTest.java index 6fe0a7955..78eab57cc 100644 --- a/hutool-db/src/test/java/cn/hutool/db/HanaTest.java +++ b/hutool-db/src/test/java/cn/hutool/db/HanaTest.java @@ -1,7 +1,6 @@ package cn.hutool.db; import cn.hutool.core.lang.Console; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -16,7 +15,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; * @author daoyou.dev */ public class HanaTest { - @BeforeAll + //@BeforeAll public static void createTable() throws SQLException { Db db = Db.use("hana"); long count = db.count("SELECT * FROM SYS.TABLES WHERE TABLE_NAME = ? AND SCHEMA_NAME = CURRENT_SCHEMA", "user"); @@ -77,6 +76,7 @@ public class HanaTest { } @Test + @Disabled public void upsertTest() throws SQLException { DbUtil.setReturnGeneratedKeyGlobal(false); Db db = Db.use("hana"); From a618001723893a3e2f7e78dd72b1c1b9228b047a Mon Sep 17 00:00:00 2001 From: Looly Date: Fri, 11 Apr 2025 10:45:04 +0800 Subject: [PATCH 12/25] =?UTF-8?q?=E5=A2=9E=E5=8A=A0`Argon2`=E7=B1=BB?= =?UTF-8?q?=EF=BC=8C=E5=AE=9E=E7=8E=B0Argon2=E7=AE=97=E6=B3=95=EF=BC=88iss?= =?UTF-8?q?ue#3890@Github=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 1 + .../java/cn/hutool/crypto/digest/Argon2.java | 152 ++++++++++++++++++ .../cn/hutool/crypto/digest/Argon2Test.java | 22 +++ 3 files changed, 175 insertions(+) create mode 100644 hutool-crypto/src/main/java/cn/hutool/crypto/digest/Argon2.java create mode 100644 hutool-crypto/src/test/java/cn/hutool/crypto/digest/Argon2Test.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f800c3ba..03a682c77 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ ### 🐣新特性 * 【core 】 `PathUtil#del`增加null检查(pr#1331@Gitee) * 【db 】 增加SAP HANA识别及方言(pr#3914@Github) +* 【crypto 】 增加`Argon2`类,实现Argon2算法(issue#3890@Github) ### 🐞Bug修复 diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/Argon2.java b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/Argon2.java new file mode 100644 index 000000000..f5535664d --- /dev/null +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/Argon2.java @@ -0,0 +1,152 @@ +package cn.hutool.crypto.digest; + +import org.bouncycastle.crypto.generators.Argon2BytesGenerator; +import org.bouncycastle.crypto.params.Argon2Parameters; + +/** + * Argon2加密实现 + * + * @author changhr2013 + * @author Looly + * @since 5.8.38 + */ +public class Argon2 { + + /** + * 默认hash长度 + */ + public static final int DEFAULT_HASH_LENGTH = 32; + + private int hashLength = DEFAULT_HASH_LENGTH; + private final Argon2Parameters.Builder paramsBuilder; + + /** + * 构造,默认使用{@link Argon2Parameters#ARGON2_id}类型 + */ + public Argon2(){ + this(Argon2Parameters.ARGON2_id); + } + + /** + * 构造 + * + * @param type {@link Argon2Parameters#ARGON2_d}、{@link Argon2Parameters#ARGON2_i}、{@link Argon2Parameters#ARGON2_id} + */ + public Argon2(int type){ + this(new Argon2Parameters.Builder(type)); + } + + /** + * 构造 + * + * @param paramsBuilder 参数构造器 + */ + public Argon2(Argon2Parameters.Builder paramsBuilder){ + this.paramsBuilder = paramsBuilder; + } + + /** + * 设置hash长度 + * + * @param hashLength hash长度 + * @return this + */ + public Argon2 setHashLength(int hashLength){ + this.hashLength = hashLength; + return this; + } + + /** + * 设置版本 + * + * @param version 版本 + * @return this + * @see Argon2Parameters#ARGON2_VERSION_10 + * @see Argon2Parameters#ARGON2_VERSION_13 + */ + public Argon2 setVersion(int version){ + this.paramsBuilder.withVersion(version); + return this; + } + + /** + * 设置盐 + * + * @param salt 盐 + * @return this + */ + public Argon2 setSalt(byte[] salt){ + this.paramsBuilder.withSalt(salt); + return this; + } + + /** + * 设置可选的密钥数据,用于增加哈希的复杂性 + * + * @param secret 密钥 + * @return this + */ + public Argon2 setSecret(byte[] secret){ + this.paramsBuilder.withSecret(secret); + return this; + } + + /** + * @param additional 附加数据 + * @return this + */ + public Argon2 setAdditional(byte[] additional){ + this.paramsBuilder.withAdditional(additional); + return this; + } + + /** + * 设置迭代次数
    + * 迭代次数越多,生成哈希的时间就越长,破解哈希就越困难 + * + * @param iterations 迭代次数 + * @return this + */ + public Argon2 setIterations(int iterations){ + this.paramsBuilder.withIterations(iterations); + return this; + } + + /** + * 设置内存,单位KB
    + * 内存越大,生成哈希的时间就越长,破解哈希就越困难 + * + * @param memoryAsKB 内存,单位KB + * @return this + */ + public Argon2 setMemoryAsKB(int memoryAsKB){ + this.paramsBuilder.withMemoryAsKB(memoryAsKB); + return this; + } + + /** + * 设置并行度,即同时使用的核心数
    + * 值越高,生成哈希的时间就越长,破解哈希就越困难 + * + * @param parallelism 并行度 + * @return this + */ + public Argon2 setParallelism(int parallelism){ + this.paramsBuilder.withParallelism(parallelism); + return this; + } + + /** + * 生成hash值 + * + * @param password 密码 + * @return hash值 + */ + public byte[] digest(char[] password){ + final Argon2BytesGenerator generator = new Argon2BytesGenerator(); + generator.init(paramsBuilder.build()); + byte[] result = new byte[hashLength]; + generator.generateBytes(password, result); + return result; + } +} diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/digest/Argon2Test.java b/hutool-crypto/src/test/java/cn/hutool/crypto/digest/Argon2Test.java new file mode 100644 index 000000000..5fbe3126f --- /dev/null +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/digest/Argon2Test.java @@ -0,0 +1,22 @@ +package cn.hutool.crypto.digest; + +import cn.hutool.core.codec.Base64; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class Argon2Test { + @Test + public void argon2Test() { + Argon2 argon2 = new Argon2(); + final byte[] digest = argon2.digest("123456".toCharArray()); + Assertions.assertEquals("wVGMOdzf5EdKGANPeHjaUnaFEJA0BnAq6HcF2psFmFo=", Base64.encode(digest)); + } + + @Test + public void argon2WithSaltTest() { + final Argon2 argon2 = new Argon2(); + argon2.setSalt("123456".getBytes()); + final byte[] digest = argon2.digest("123456".toCharArray()); + Assertions.assertEquals("sEpbXTdMWra36JXPVxrZMm3xyoR5GkMlLhtW0Kwp9Ag=", Base64.encode(digest)); + } +} From e8e340d9ebeac6cf50434998ba00d70be377cdba Mon Sep 17 00:00:00 2001 From: Looly Date: Fri, 11 Apr 2025 19:05:42 +0800 Subject: [PATCH 13/25] add plugin --- pom.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pom.xml b/pom.xml index 2bb3e69d2..1375ea854 100755 --- a/pom.xml +++ b/pom.xml @@ -112,6 +112,12 @@ -Xlint:unchecked + + + org.apache.maven.plugins + maven-surefire-plugin + 3.5.3 + org.apache.maven.plugins maven-jar-plugin From d5d81bdd15e14c309905420b78b9ddbc31ced364 Mon Sep 17 00:00:00 2001 From: Looly Date: Fri, 11 Apr 2025 19:13:40 +0800 Subject: [PATCH 14/25] =?UTF-8?q?`CharSequenceUtil`=E5=A2=9E=E5=8A=A0toLow?= =?UTF-8?q?eCase=E5=92=8CtoUpperCase=E6=96=B9=E6=B3=95=EF=BC=88issue#IC0H2?= =?UTF-8?q?B@Gitee=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 1 + .../cn/hutool/core/text/CharSequenceUtil.java | 36 +++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 03a682c77..b0168b2a3 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ * 【core 】 `PathUtil#del`增加null检查(pr#1331@Gitee) * 【db 】 增加SAP HANA识别及方言(pr#3914@Github) * 【crypto 】 增加`Argon2`类,实现Argon2算法(issue#3890@Github) +* 【core 】 `CharSequenceUtil`增加toLoweCase和toUpperCase方法(issue#IC0H2B@Gitee) ### 🐞Bug修复 diff --git a/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java b/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java index 10c27984a..358822592 100755 --- a/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java @@ -4237,6 +4237,42 @@ public class CharSequenceUtil { // ------------------------------------------------------------------------ lower and upper + /** + * 将字符串转为小写 + * + * @param str 被转的字符串 + * @return 转换后的字符串 + * @see String#toLowerCase() + * @since 5.8.38 + */ + public static String toLoweCase(final CharSequence str) { + if (null == str) { + return null; + } + if(0 == str.length()){ + return EMPTY; + } + return str.toString().toLowerCase(); + } + + /** + * 将字符串转为大写 + * + * @param str 被转的字符串 + * @return 转换后的字符串 + * @see String#toUpperCase() + * @since 5.8.38 + */ + public static String toUpperCase(final CharSequence str) { + if (null == str) { + return null; + } + if(0 == str.length()){ + return EMPTY; + } + return str.toString().toUpperCase(); + } + /** * 原字符串首字母大写并在其首部添加指定字符串 例如:str=name, preString=get =》 return getName * From 56c853661fc409dd9213b6afb6af73c6ac9b0481 Mon Sep 17 00:00:00 2001 From: Looly Date: Fri, 11 Apr 2025 19:25:39 +0800 Subject: [PATCH 15/25] fix test --- .../src/test/java/cn/hutool/core/lang/SimpleCacheTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/SimpleCacheTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/SimpleCacheTest.java index 99a665f7a..5ee4fabeb 100755 --- a/hutool-core/src/test/java/cn/hutool/core/lang/SimpleCacheTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/lang/SimpleCacheTest.java @@ -51,7 +51,7 @@ public class SimpleCacheTest { @Test public void getConcurrencyTest(){ final SimpleCache cache = new SimpleCache<>(); - final ConcurrencyTester tester = new ConcurrencyTester(9000); + final ConcurrencyTester tester = new ConcurrencyTester(500); tester.test(()-> cache.get("aaa", ()-> { ThreadUtil.sleep(200); return "aaaValue"; From ef12890c67263822d291d0d09e8ecdf9325e3163 Mon Sep 17 00:00:00 2001 From: Looly Date: Fri, 11 Apr 2025 19:28:36 +0800 Subject: [PATCH 16/25] fix test --- .../test/java/cn/hutool/core/swing/ClipboardUtilTest.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/hutool-core/src/test/java/cn/hutool/core/swing/ClipboardUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/swing/ClipboardUtilTest.java index 750c2279f..57fc5992a 100755 --- a/hutool-core/src/test/java/cn/hutool/core/swing/ClipboardUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/swing/ClipboardUtilTest.java @@ -1,9 +1,10 @@ package cn.hutool.core.swing; -import static org.junit.jupiter.api.Assertions.*; +import cn.hutool.core.swing.clipboard.ClipboardUtil; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import cn.hutool.core.swing.clipboard.ClipboardUtil; +import static org.junit.jupiter.api.Assertions.assertEquals; /** * 剪贴板工具类单元测试 @@ -14,6 +15,7 @@ import cn.hutool.core.swing.clipboard.ClipboardUtil; public class ClipboardUtilTest { @Test + @Disabled public void setAndGetStrTest() { try { ClipboardUtil.setStr("test"); From 6a0777c9458a69fbd5c3ae2ef474d3fe69f23a77 Mon Sep 17 00:00:00 2001 From: Looly Date: Fri, 11 Apr 2025 20:17:53 +0800 Subject: [PATCH 17/25] fix test --- hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java | 2 ++ .../src/test/java/cn/hutool/core/text/csv/Issue3705Test.java | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java index 0b1de97d1..3cc8c7c42 100644 --- a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java @@ -63,6 +63,8 @@ public class FileUtilTest { final String parseSmbPath = FileUtil.getAbsolutePath(smbPath); assertEquals(smbPath, parseSmbPath); assertTrue(FileUtil.isAbsolutePath(smbPath)); + Console.log(Paths.get(smbPath)); + Console.log(Paths.get(smbPath).isAbsolute()); assertTrue(Paths.get(smbPath).isAbsolute()); } diff --git a/hutool-core/src/test/java/cn/hutool/core/text/csv/Issue3705Test.java b/hutool-core/src/test/java/cn/hutool/core/text/csv/Issue3705Test.java index efe05ac34..b2049b2c3 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/csv/Issue3705Test.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/csv/Issue3705Test.java @@ -1,6 +1,5 @@ package cn.hutool.core.text.csv; -import cn.hutool.core.io.FileUtil; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -19,8 +18,9 @@ public class Issue3705Test { csvWriter.flush(); } + // CsvWriteConfig中默认为`\r\n` Assertions.assertEquals( - "\"2024-08-20 14:24:35,\"" + FileUtil.getLineSeparator() + "最后一行", + "\"2024-08-20 14:24:35,\"\r\n最后一行", stringWriter.toString()); } } From 2f88648cc9057cf60298d7d21ffe6399141a7780 Mon Sep 17 00:00:00 2001 From: Looly Date: Fri, 11 Apr 2025 20:25:10 +0800 Subject: [PATCH 18/25] fix test --- .../src/test/java/cn/hutool/core/io/FileUtilTest.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java index 3cc8c7c42..22433c33a 100644 --- a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java @@ -63,9 +63,11 @@ public class FileUtilTest { final String parseSmbPath = FileUtil.getAbsolutePath(smbPath); assertEquals(smbPath, parseSmbPath); assertTrue(FileUtil.isAbsolutePath(smbPath)); - Console.log(Paths.get(smbPath)); - Console.log(Paths.get(smbPath).isAbsolute()); - assertTrue(Paths.get(smbPath).isAbsolute()); + if(FileUtil.isWindows()){ + // 在Windows下`\`路径是绝对路径,也表示SMB路径 + // 但是在Linux下,`\`表示转义字符,并不被识别为路径 + assertTrue(Paths.get(smbPath).isAbsolute()); + } } @Test From 122c68052e49fe416a1eb8bc6755506b801cdb06 Mon Sep 17 00:00:00 2001 From: Looly Date: Fri, 11 Apr 2025 20:33:29 +0800 Subject: [PATCH 19/25] fix test --- .../src/test/java/cn/hutool/core/lang/SimpleCacheTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/SimpleCacheTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/SimpleCacheTest.java index 5ee4fabeb..5a2680828 100755 --- a/hutool-core/src/test/java/cn/hutool/core/lang/SimpleCacheTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/lang/SimpleCacheTest.java @@ -4,6 +4,7 @@ import cn.hutool.core.io.IoUtil; import cn.hutool.core.thread.ConcurrencyTester; import cn.hutool.core.thread.ThreadUtil; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -49,6 +50,7 @@ public class SimpleCacheTest { } @Test + @Disabled public void getConcurrencyTest(){ final SimpleCache cache = new SimpleCache<>(); final ConcurrencyTester tester = new ConcurrencyTester(500); From ed54fc2c01fa78818d83028d900ffd33c0aa1e5f Mon Sep 17 00:00:00 2001 From: Looly Date: Fri, 11 Apr 2025 20:35:28 +0800 Subject: [PATCH 20/25] fix test --- .../src/test/java/cn/hutool/core/lang/SimpleCacheTest.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/SimpleCacheTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/SimpleCacheTest.java index 5a2680828..5ee4fabeb 100755 --- a/hutool-core/src/test/java/cn/hutool/core/lang/SimpleCacheTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/lang/SimpleCacheTest.java @@ -4,7 +4,6 @@ import cn.hutool.core.io.IoUtil; import cn.hutool.core.thread.ConcurrencyTester; import cn.hutool.core.thread.ThreadUtil; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -50,7 +49,6 @@ public class SimpleCacheTest { } @Test - @Disabled public void getConcurrencyTest(){ final SimpleCache cache = new SimpleCache<>(); final ConcurrencyTester tester = new ConcurrencyTester(500); From 8a17fae3b7d6047bebd9fd2155deb8767014d655 Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 15 Apr 2025 11:27:46 +0800 Subject: [PATCH 21/25] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=88=86=E6=AE=B5?= =?UTF-8?q?=E9=94=81=E5=AE=9E=E7=8E=B0`SegmentLock`=EF=BC=88pr#1330@Gitee?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 3 ++- .../java/cn/hutool/core/thread/lock/SegmentLock.java | 10 ++++++++-- .../java/cn/hutool/core/thread/SegmentLockTest.java | 2 ++ 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0168b2a3..7d1f0da25 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,13 +2,14 @@ # 🚀Changelog ------------------------------------------------------------------------------------------------------------- -# 5.8.38(2025-04-11) +# 5.8.38(2025-04-15) ### 🐣新特性 * 【core 】 `PathUtil#del`增加null检查(pr#1331@Gitee) * 【db 】 增加SAP HANA识别及方言(pr#3914@Github) * 【crypto 】 增加`Argon2`类,实现Argon2算法(issue#3890@Github) * 【core 】 `CharSequenceUtil`增加toLoweCase和toUpperCase方法(issue#IC0H2B@Gitee) +* 【core 】 增加分段锁实现`SegmentLock`(pr#1330@Gitee) ### 🐞Bug修复 diff --git a/hutool-core/src/main/java/cn/hutool/core/thread/lock/SegmentLock.java b/hutool-core/src/main/java/cn/hutool/core/thread/lock/SegmentLock.java index a3b3c7f92..df38621ee 100644 --- a/hutool-core/src/main/java/cn/hutool/core/thread/lock/SegmentLock.java +++ b/hutool-core/src/main/java/cn/hutool/core/thread/lock/SegmentLock.java @@ -35,8 +35,9 @@ import java.util.function.Supplier; *
  • 弱引用:懒加载,首次使用时创建段,未使用时可被垃圾回收,适合大量段但使用较少的场景。
  • * * + * @param 锁类型 * @author Guava,dakuo - * @since 5.8.37 + * @since 5.8.38 */ public abstract class SegmentLock { @@ -82,7 +83,7 @@ public abstract class SegmentLock { * @param keys 非空 key 集合 * @return 锁段列表(可能有重复) */ - public Iterable bulkGet(Iterable keys) { + public Iterable bulkGet(Iterable keys) { @SuppressWarnings("unchecked") List result = (List) CollUtil.newArrayList(keys); if (CollUtil.isEmpty(result)) { @@ -268,6 +269,7 @@ public abstract class SegmentLock { /** * 弱引用安全的条件包装类。 */ + @SuppressWarnings("FieldCanBeLocal") private static final class WeakSafeCondition implements Condition { private final Condition delegate; @@ -480,6 +482,8 @@ public abstract class SegmentLock { * 填充锁,避免缓存行干扰。 */ private static class PaddedLock extends ReentrantLock { + private static final long serialVersionUID = 1L; + long unused1; long unused2; long unused3; @@ -493,6 +497,8 @@ public abstract class SegmentLock { * 填充信号量,避免缓存行干扰。 */ private static class PaddedSemaphore extends Semaphore { + private static final long serialVersionUID = 1L; + long unused1; long unused2; long unused3; diff --git a/hutool-core/src/test/java/cn/hutool/core/thread/SegmentLockTest.java b/hutool-core/src/test/java/cn/hutool/core/thread/SegmentLockTest.java index d41ab928c..287e0595c 100644 --- a/hutool-core/src/test/java/cn/hutool/core/thread/SegmentLockTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/thread/SegmentLockTest.java @@ -42,6 +42,7 @@ public class SegmentLockTest { assertEquals(SEGMENT_COUNT, readWriteLock.size()); } + @SuppressWarnings("StringOperationCanBeSimplified") @Test public void testGetWithSameKey() { // 相同 key 应返回相同锁 @@ -132,6 +133,7 @@ public class SegmentLockTest { assertEquals(1, sem.availablePermits(), "释放一个许可后应为 1"); } + @SuppressWarnings("ResultOfMethodCallIgnored") @Test public void testReadWriteLock() throws InterruptedException { ReadWriteLock rwLock = readWriteLock.get("testKey"); From 9689ab119a836214e72b9f329932397031cf1f22 Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 15 Apr 2025 18:42:08 +0800 Subject: [PATCH 22/25] fix test --- .../test/java/cn/hutool/core/io/FileUtilTest.java | 15 +++++++++------ .../java/cn/hutool/core/io/file/PathUtilTest.java | 9 +++++++-- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java index 22433c33a..79a75551f 100644 --- a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java @@ -482,12 +482,15 @@ public class FileUtilTest { assertTrue(list.contains(mimeType)); // office03 - mimeType = FileUtil.getMimeType("test.doc"); - assertEquals("application/msword", mimeType); - mimeType = FileUtil.getMimeType("test.xls"); - assertEquals("application/vnd.ms-excel", mimeType); - mimeType = FileUtil.getMimeType("test.ppt"); - assertEquals("application/vnd.ms-powerpoint", mimeType); + if(FileUtil.isWindows()){ + // Linux下的OpenJDK无法正确识别 + mimeType = FileUtil.getMimeType("test.doc"); + assertEquals("application/msword", mimeType); + mimeType = FileUtil.getMimeType("test.xls"); + assertEquals("application/vnd.ms-excel", mimeType); + mimeType = FileUtil.getMimeType("test.ppt"); + assertEquals("application/vnd.ms-powerpoint", mimeType); + } // office07+ mimeType = FileUtil.getMimeType("test.docx"); diff --git a/hutool-core/src/test/java/cn/hutool/core/io/file/PathUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/io/file/PathUtilTest.java index 88e68cd14..be9c2a07d 100644 --- a/hutool-core/src/test/java/cn/hutool/core/io/file/PathUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/io/file/PathUtilTest.java @@ -1,7 +1,6 @@ package cn.hutool.core.io.file; import cn.hutool.core.io.FileUtil; -import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -9,6 +8,9 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + public class PathUtilTest { @Test @@ -83,7 +85,10 @@ public class PathUtilTest { @Test public void issue3179Test() { final String mimeType = PathUtil.getMimeType(Paths.get("xxxx.jpg")); - assertEquals("image/jpeg", mimeType); + if(FileUtil.isWindows()){ + // Linux下,OpenJDK可能报路径不存在 + assertEquals("image/jpeg", mimeType); + } } /** From b1301e8dea7ab8f075ecb6c582ccb42052a4bf8c Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 15 Apr 2025 18:43:47 +0800 Subject: [PATCH 23/25] fix test --- .../java/cn/hutool/core/io/FileUtilTest.java | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java index 79a75551f..f53a13d83 100644 --- a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java @@ -481,24 +481,26 @@ public class FileUtilTest { final List list = ListUtil.of("text/javascript", "application/x-javascript"); assertTrue(list.contains(mimeType)); - // office03 if(FileUtil.isWindows()){ // Linux下的OpenJDK无法正确识别 + + // office03 mimeType = FileUtil.getMimeType("test.doc"); assertEquals("application/msword", mimeType); mimeType = FileUtil.getMimeType("test.xls"); assertEquals("application/vnd.ms-excel", mimeType); mimeType = FileUtil.getMimeType("test.ppt"); assertEquals("application/vnd.ms-powerpoint", mimeType); + + // office07+ + mimeType = FileUtil.getMimeType("test.docx"); + assertEquals("application/vnd.openxmlformats-officedocument.wordprocessingml.document", mimeType); + mimeType = FileUtil.getMimeType("test.xlsx"); + assertEquals("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", mimeType); + mimeType = FileUtil.getMimeType("test.pptx"); + assertEquals("application/vnd.openxmlformats-officedocument.presentationml.presentation", mimeType); } - // office07+ - mimeType = FileUtil.getMimeType("test.docx"); - assertEquals("application/vnd.openxmlformats-officedocument.wordprocessingml.document", mimeType); - mimeType = FileUtil.getMimeType("test.xlsx"); - assertEquals("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", mimeType); - mimeType = FileUtil.getMimeType("test.pptx"); - assertEquals("application/vnd.openxmlformats-officedocument.presentationml.presentation", mimeType); // pr#2617@Github mimeType = FileUtil.getMimeType("test.wgt"); From 52674f69fac0581cb03539b0a8db9ce6fdfe7c3c Mon Sep 17 00:00:00 2001 From: Looly Date: Wed, 16 Apr 2025 17:50:03 +0800 Subject: [PATCH 24/25] add test --- .../cn/hutool/core/util/NumberUtilTest.java | 38 +++++++++++-------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/hutool-core/src/test/java/cn/hutool/core/util/NumberUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/NumberUtilTest.java index 08c27f841..145ad5bb6 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/NumberUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/NumberUtilTest.java @@ -144,43 +144,43 @@ public class NumberUtilTest { @Test public void roundStrTest() { final String roundStr = NumberUtil.roundStr(2.647, 2); - assertEquals(roundStr, "2.65"); + assertEquals("2.65", roundStr); final String roundStr1 = NumberUtil.roundStr(0, 10); - assertEquals(roundStr1, "0.0000000000"); + assertEquals("0.0000000000", roundStr1); } @Test public void roundHalfEvenTest() { String roundStr = NumberUtil.roundHalfEven(4.245, 2).toString(); - assertEquals(roundStr, "4.24"); + assertEquals("4.24", roundStr); roundStr = NumberUtil.roundHalfEven(4.2450, 2).toString(); - assertEquals(roundStr, "4.24"); + assertEquals("4.24", roundStr); roundStr = NumberUtil.roundHalfEven(4.2451, 2).toString(); - assertEquals(roundStr, "4.25"); + assertEquals("4.25", roundStr); roundStr = NumberUtil.roundHalfEven(4.2250, 2).toString(); - assertEquals(roundStr, "4.22"); + assertEquals("4.22", roundStr); roundStr = NumberUtil.roundHalfEven(1.2050, 2).toString(); - assertEquals(roundStr, "1.20"); + assertEquals("1.20", roundStr); roundStr = NumberUtil.roundHalfEven(1.2150, 2).toString(); - assertEquals(roundStr, "1.22"); + assertEquals("1.22", roundStr); roundStr = NumberUtil.roundHalfEven(1.2250, 2).toString(); - assertEquals(roundStr, "1.22"); + assertEquals("1.22", roundStr); roundStr = NumberUtil.roundHalfEven(1.2350, 2).toString(); - assertEquals(roundStr, "1.24"); + assertEquals("1.24", roundStr); roundStr = NumberUtil.roundHalfEven(1.2450, 2).toString(); - assertEquals(roundStr, "1.24"); + assertEquals("1.24", roundStr); roundStr = NumberUtil.roundHalfEven(1.2550, 2).toString(); - assertEquals(roundStr, "1.26"); + assertEquals("1.26", roundStr); roundStr = NumberUtil.roundHalfEven(1.2650, 2).toString(); - assertEquals(roundStr, "1.26"); + assertEquals("1.26", roundStr); roundStr = NumberUtil.roundHalfEven(1.2750, 2).toString(); - assertEquals(roundStr, "1.28"); + assertEquals("1.28", roundStr); roundStr = NumberUtil.roundHalfEven(1.2850, 2).toString(); - assertEquals(roundStr, "1.28"); + assertEquals("1.28", roundStr); roundStr = NumberUtil.roundHalfEven(1.2950, 2).toString(); - assertEquals(roundStr, "1.30"); + assertEquals("1.30", roundStr); } @Test @@ -673,4 +673,10 @@ public class NumberUtilTest { final double result = NumberUtil.add(v1, v2); assertEquals(91007279.3545, result, 0); } + + @Test + void issueIC1MXETest(){ + final boolean equals = NumberUtil.equals(104557543L, 104557544); + assertFalse(equals); + } } From a2cc1e158776409583b99fe5238cd58aeddece30 Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 17 Apr 2025 17:02:41 +0800 Subject: [PATCH 25/25] =?UTF-8?q?=E4=BF=AE=E5=A4=8D`Setting`autoLoad?= =?UTF-8?q?=E5=8F=AF=E8=83=BD=E7=9A=84=E5=8A=A0=E8=BD=BD=E4=B8=BA=E7=A9=BA?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98=EF=BC=88issue#3919@Github=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 3 ++- .../main/java/cn/hutool/setting/Setting.java | 19 ++++++------------- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d1f0da25..7fc6e51ce 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ # 🚀Changelog ------------------------------------------------------------------------------------------------------------- -# 5.8.38(2025-04-15) +# 5.8.38(2025-04-17) ### 🐣新特性 * 【core 】 `PathUtil#del`增加null检查(pr#1331@Gitee) @@ -12,6 +12,7 @@ * 【core 】 增加分段锁实现`SegmentLock`(pr#1330@Gitee) ### 🐞Bug修复 +* 【setting】 修复`Setting`autoLoad可能的加载为空的问题(issue#3919@Github) ------------------------------------------------------------------------------------------------------------- # 5.8.37(2025-03-31) diff --git a/hutool-setting/src/main/java/cn/hutool/setting/Setting.java b/hutool-setting/src/main/java/cn/hutool/setting/Setting.java index 8cdd797d5..2a2022015 100644 --- a/hutool-setting/src/main/java/cn/hutool/setting/Setting.java +++ b/hutool-setting/src/main/java/cn/hutool/setting/Setting.java @@ -4,14 +4,11 @@ import cn.hutool.core.collection.CollUtil; import cn.hutool.core.convert.Convert; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; -import cn.hutool.core.io.resource.ClassPathResource; -import cn.hutool.core.io.resource.FileResource; -import cn.hutool.core.io.resource.Resource; -import cn.hutool.core.io.resource.ResourceUtil; -import cn.hutool.core.io.resource.UrlResource; +import cn.hutool.core.io.resource.*; import cn.hutool.core.io.watch.SimpleWatcher; import cn.hutool.core.io.watch.WatchMonitor; import cn.hutool.core.io.watch.WatchUtil; +import cn.hutool.core.io.watch.watchers.DelayWatcher; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.CharUtil; import cn.hutool.core.util.CharsetUtil; @@ -24,12 +21,7 @@ import java.net.URL; import java.nio.charset.Charset; import java.nio.file.Path; import java.nio.file.WatchEvent; -import java.util.Collection; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Properties; -import java.util.Set; +import java.util.*; import java.util.function.Consumer; /** @@ -229,7 +221,7 @@ public class Setting extends AbsSetting implements Map { // 先关闭之前的监听 this.watchMonitor.close(); } - this.watchMonitor = WatchUtil.createModify(resource.getUrl(), new SimpleWatcher() { + this.watchMonitor = WatchUtil.createModify(resource.getUrl(), new DelayWatcher(new SimpleWatcher() { @Override public void onModify(WatchEvent event, Path currentPath) { boolean success = load(); @@ -238,7 +230,8 @@ public class Setting extends AbsSetting implements Map { callback.accept(success); } } - }); + }, 600)); + this.watchMonitor.start(); StaticLog.debug("Auto load for [{}] listenning...", this.resource.getUrl()); } else {