Skip to content

Commit bb573af

Browse files
authored
🎨 #3732 Add Quarkus/GraalVM Native Image support - Fix Random instance initialization issues
1 parent e9bc5d0 commit bb573af

File tree

7 files changed

+182
-13
lines changed

7 files changed

+182
-13
lines changed

QUARKUS_SUPPORT.md

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# WxJava Quarkus/GraalVM Native Image Support
2+
3+
## 概述
4+
5+
从 4.7.8.B 版本开始,WxJava 提供了对 Quarkus 和 GraalVM Native Image 的支持。这允许您将使用 WxJava 的应用程序编译为原生可执行文件,从而获得更快的启动速度和更低的内存占用。
6+
7+
## 问题背景
8+
9+
在之前的版本中,使用 Quarkus 构建 Native Image 时会遇到以下错误:
10+
11+
```
12+
Error: Unsupported features in 3 methods
13+
Detailed message:
14+
Error: Detected an instance of Random/SplittableRandom class in the image heap.
15+
Instances created during image generation have cached seed values and don't behave as expected.
16+
The culprit object has been instantiated by the 'org.apache.http.impl.auth.NTLMEngineImpl' class initializer
17+
```
18+
19+
## 解决方案
20+
21+
为了解决这个问题,WxJava 进行了以下改进:
22+
23+
### 1. Random 实例的延迟初始化
24+
25+
所有 `java.util.Random` 实例都已改为延迟初始化,避免在类加载时创建:
26+
27+
- `RandomUtils` - 使用双重检查锁定模式延迟初始化
28+
- `SignUtils` - 使用双重检查锁定模式延迟初始化
29+
- `WxCryptUtil` - 使用双重检查锁定模式延迟初始化
30+
31+
### 2. Native Image 配置
32+
33+
`weixin-java-common` 模块中添加了 GraalVM Native Image 配置文件:
34+
35+
- `META-INF/native-image/com.github.binarywang/weixin-java-common/native-image.properties`
36+
- 配置 Apache HttpClient 相关类在运行时初始化,避免在构建时创建 SecureRandom 实例
37+
38+
- `META-INF/native-image/com.github.binarywang/weixin-java-common/reflect-config.json`
39+
- 配置反射访问的类和方法
40+
41+
## 使用方式
42+
43+
### Quarkus 项目配置
44+
45+
在您的 Quarkus 项目中使用 WxJava,只需正常引入依赖即可:
46+
47+
```xml
48+
<dependency>
49+
<groupId>com.github.binarywang</groupId>
50+
<artifactId>weixin-java-miniapp</artifactId> <!-- 或其他模块 -->
51+
<version>4.7.8.B</version>
52+
</dependency>
53+
```
54+
55+
### 构建 Native Image
56+
57+
使用 Quarkus 构建原生可执行文件:
58+
59+
```bash
60+
./mvnw package -Pnative
61+
```
62+
63+
或使用容器构建:
64+
65+
```bash
66+
./mvnw package -Pnative -Dquarkus.native.container-build=true
67+
```
68+
69+
### GraalVM Native Image
70+
71+
如果直接使用 GraalVM Native Image 工具:
72+
73+
```bash
74+
native-image --no-fallback \
75+
-H:+ReportExceptionStackTraces \
76+
-jar your-application.jar
77+
```
78+
79+
WxJava 的配置文件会自动被 Native Image 工具识别和应用。
80+
81+
## 测试验证
82+
83+
建议在构建 Native Image 后进行以下测试:
84+
85+
1. 验证应用程序可以正常启动
86+
2. 验证微信 API 调用功能正常
87+
3. 验证随机字符串生成功能正常
88+
4. 验证加密/解密功能正常
89+
90+
## 已知限制
91+
92+
- 本配置主要针对 Quarkus 3.x 和 GraalVM 22.x+ 版本进行测试
93+
- 如果使用其他 Native Image 构建工具(如 Spring Native),可能需要额外配置
94+
- 部分反射使用可能需要根据实际使用的 WxJava 功能进行调整
95+
96+
## 问题反馈
97+
98+
如果在使用 Quarkus/GraalVM Native Image 时遇到问题,请通过以下方式反馈:
99+
100+
1.[GitHub Issues](https://github.com/binarywang/WxJava/issues) 提交问题
101+
2. 提供详细的错误信息和 Native Image 构建日志
102+
3. 说明使用的 Quarkus 版本和 GraalVM 版本
103+
104+
## 参考资料
105+
106+
- [Quarkus 官方文档](https://quarkus.io/)
107+
- [GraalVM Native Image 文档](https://www.graalvm.org/latest/reference-manual/native-image/)
108+
- [Quarkus Tips for Writing Native Applications](https://quarkus.io/guides/writing-native-applications-tips)
109+
110+
## 贡献
111+
112+
欢迎提交 PR 完善 Quarkus/GraalVM 支持!如果您发现了新的兼容性问题或有改进建议,请参考 [代码贡献指南](CONTRIBUTING.md)

README.md

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,14 @@
6565
1. [`WxJava` 荣获 `GitCode` 2024年度十大开源社区奖项](https://mp.weixin.qq.com/s/wM_UlMsDm3IZ1CPPDvcvQw)
6666
2. 项目合作洽谈请联系微信`binary0000`(在微信里自行搜索并添加好友,请注明来意,如有关于SDK问题需讨论请参考下文入群讨论,不要加此微信)。
6767
3. **2024-12-30 发布 [【4.7.0正式版】](https://mp.weixin.qq.com/s/_7k-XLYBqeJJhvHWCsdT0A)**
68-
4. 贡献源码可以参考视频:[【贡献源码全过程(上集)】](https://mp.weixin.qq.com/s/3xUZSATWwHR_gZZm207h7Q)[【贡献源码全过程(下集)】](https://mp.weixin.qq.com/s/nyzJwVVoYSJ4hSbwyvTx9A) ,友情提供:[程序员小山与Bug](https://space.bilibili.com/473631007)
69-
5. 新手重要提示:本项目仅是一个SDK开发工具包,未提供Web实现,建议使用 `maven``gradle` 引用本项目即可使用本SDK提供的各种功能,详情可参考 **[【Demo项目】](demo.md)** 或本项目中的部分单元测试代码;
70-
6. 微信开发新手请务必阅读【开发文档】([Gitee Wiki](https://gitee.com/binary/weixin-java-tools/wikis/Home) 或者 [Github Wiki](https://github.com/binarywang/WxJava/wiki))的常见问题部分,可以少走很多弯路,节省不少时间。
71-
7. 技术交流群:想获得QQ群/微信群/钉钉企业群等信息的同学,请使用微信扫描上面的微信公众号二维码关注 `WxJava` 后点击相关菜单即可获取加入方式,同时也可以在微信中搜索 `weixin-java-tools``WxJava` 后选择正确的公众号进行关注,该公众号会及时通知SDK相关更新信息,并不定期分享微信Java开发相关技术知识;
72-
8. 钉钉技术交流群:`32206329`(技术交流2群), `30294972`(技术交流1群,目前已满),`35724728`(通知群,实时通知Github项目变更记录)。
73-
9. 微信开发新手或者Java开发新手在群内提问或新开Issue提问前,请先阅读[【提问的智慧】](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/blob/master/README-zh_CN.md),并确保已查阅过 [【开发文档Wiki】](https://github.com/binarywang/WxJava/wiki) ,避免浪费大家的宝贵时间;
74-
10. 寻求帮助时需贴代码或大长串异常信息的,请利用 http://paste.ubuntu.com
68+
4. **从 4.7.8.B 版本开始支持 Quarkus/GraalVM Native Image,详见 [【Quarkus 支持文档】](QUARKUS_SUPPORT.md)**
69+
5. 贡献源码可以参考视频:[【贡献源码全过程(上集)】](https://mp.weixin.qq.com/s/3xUZSATWwHR_gZZm207h7Q)[【贡献源码全过程(下集)】](https://mp.weixin.qq.com/s/nyzJwVVoYSJ4hSbwyvTx9A) ,友情提供:[程序员小山与Bug](https://space.bilibili.com/473631007)
70+
6. 新手重要提示:本项目仅是一个SDK开发工具包,未提供Web实现,建议使用 `maven``gradle` 引用本项目即可使用本SDK提供的各种功能,详情可参考 **[【Demo项目】](demo.md)** 或本项目中的部分单元测试代码;
71+
7. 微信开发新手请务必阅读【开发文档】([Gitee Wiki](https://gitee.com/binary/weixin-java-tools/wikis/Home) 或者 [Github Wiki](https://github.com/binarywang/WxJava/wiki))的常见问题部分,可以少走很多弯路,节省不少时间。
72+
8. 技术交流群:想获得QQ群/微信群/钉钉企业群等信息的同学,请使用微信扫描上面的微信公众号二维码关注 `WxJava` 后点击相关菜单即可获取加入方式,同时也可以在微信中搜索 `weixin-java-tools``WxJava` 后选择正确的公众号进行关注,该公众号会及时通知SDK相关更新信息,并不定期分享微信Java开发相关技术知识;
73+
9. 钉钉技术交流群:`32206329`(技术交流2群), `30294972`(技术交流1群,目前已满),`35724728`(通知群,实时通知Github项目变更记录)。
74+
10. 微信开发新手或者Java开发新手在群内提问或新开Issue提问前,请先阅读[【提问的智慧】](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/blob/master/README-zh_CN.md),并确保已查阅过 [【开发文档Wiki】](https://github.com/binarywang/WxJava/wiki) ,避免浪费大家的宝贵时间;
75+
11. 寻求帮助时需贴代码或大长串异常信息的,请利用 http://paste.ubuntu.com
7576

7677
--------------------------------
7778
### 其他说明

weixin-java-common/src/main/java/me/chanjar/weixin/common/util/RandomUtils.java

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,24 @@ public class RandomUtils {
44

55
private static final String RANDOM_STR = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
66

7-
private static final java.util.Random RANDOM = new java.util.Random();
7+
private static volatile java.util.Random random;
8+
9+
private static java.util.Random getRandom() {
10+
if (random == null) {
11+
synchronized (RandomUtils.class) {
12+
if (random == null) {
13+
random = new java.util.Random();
14+
}
15+
}
16+
}
17+
return random;
18+
}
819

920
public static String getRandomStr() {
1021
StringBuilder sb = new StringBuilder();
22+
java.util.Random r = getRandom();
1123
for (int i = 0; i < 16; i++) {
12-
sb.append(RANDOM_STR.charAt(RANDOM.nextInt(RANDOM_STR.length())));
24+
sb.append(RANDOM_STR.charAt(r.nextInt(RANDOM_STR.length())));
1325
}
1426
return sb.toString();
1527
}

weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/WxCryptUtil.java

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,19 @@ public class WxCryptUtil {
3737
private static final Base64 BASE64 = new Base64();
3838
private static final Charset CHARSET = StandardCharsets.UTF_8;
3939

40+
private static volatile Random random;
41+
42+
private static Random getRandom() {
43+
if (random == null) {
44+
synchronized (WxCryptUtil.class) {
45+
if (random == null) {
46+
random = new Random();
47+
}
48+
}
49+
}
50+
return random;
51+
}
52+
4053
private static final ThreadLocal<DocumentBuilder> BUILDER_LOCAL = ThreadLocal.withInitial(() -> {
4154
try {
4255
final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
@@ -109,10 +122,10 @@ private static int bytesNetworkOrder2Number(byte[] bytesInNetworkOrder) {
109122
*/
110123
private static String genRandomStr() {
111124
String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
112-
Random random = new Random();
125+
Random r = getRandom();
113126
StringBuilder sb = new StringBuilder();
114127
for (int i = 0; i < 16; i++) {
115-
int number = random.nextInt(base.length());
128+
int number = r.nextInt(base.length());
116129
sb.append(base.charAt(number));
117130
}
118131
return sb.toString();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Args = --initialize-at-run-time=org.apache.http.impl.auth.NTLMEngineImpl \
2+
--initialize-at-run-time=org.apache.http.impl.auth.NTLMEngine \
3+
--initialize-at-run-time=org.apache.http.impl.auth.KerberosScheme \
4+
--initialize-at-run-time=org.apache.http.impl.auth.SPNegoScheme
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[
2+
{
3+
"name": "me.chanjar.weixin.common.util.RandomUtils",
4+
"methods": [
5+
{"name": "getRandomStr", "parameterTypes": []}
6+
]
7+
},
8+
{
9+
"name": "me.chanjar.weixin.common.util.crypto.WxCryptUtil",
10+
"allDeclaredConstructors": true,
11+
"allDeclaredMethods": true,
12+
"allDeclaredFields": true
13+
}
14+
]

weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/util/SignUtils.java

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,19 @@ public static String genRandomStr() {
3333
return genRandomStr(32);
3434
}
3535

36+
private static volatile Random random;
37+
38+
private static Random getRandom() {
39+
if (random == null) {
40+
synchronized (SignUtils.class) {
41+
if (random == null) {
42+
random = new Random();
43+
}
44+
}
45+
}
46+
return random;
47+
}
48+
3649
/**
3750
* 生成随机字符串
3851
*
@@ -41,10 +54,10 @@ public static String genRandomStr() {
4154
*/
4255
public static String genRandomStr(int length) {
4356
String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
44-
Random random = new Random();
57+
Random r = getRandom();
4558
StringBuilder sb = new StringBuilder();
4659
for (int i = 0; i < length; i++) {
47-
int number = random.nextInt(base.length());
60+
int number = r.nextInt(base.length());
4861
sb.append(base.charAt(number));
4962
}
5063
return sb.toString();

0 commit comments

Comments
 (0)