当前位置:   article > 正文

Maven构建SpringBoot项目加密Jar包_springboot jar包加密

springboot jar包加密

目录

摘要

整体描述

过程分解

1、创建动态链接库

2、 SpringBoot 生成可执行 jar 包

3、 Maven 自定义插件

3.3.1、 自定义插件

3.3.2、 创建Jar 包加密工具

3.3.3、 插件与加密工具整合

3.3.4、插件打成依赖 Jar 包

4、插件应用

参考文献


摘要

        由于业务需要,需要将提供给客户的可执行 Jar 包源码加密处理;基本要求:不能改变原有的逻辑和代码结构;团队研发人员无感知。最终实现过程可以概括为:

        1、使用Maven工具,执行打包命令,生成加密 jar 包;

        2、java -jar 命令直接运行加密后的 jar 包。

        本文章参考了 Xjar 项目的加密方式,结合自定义 Maven 插件和JVMTI,最终实现了上述过程。

整体描述

1、SpringBoot 项目使用 Maven 工具自动构建可执行 jar 包;

2、(主要过程)实现加密 jar 包的过程本质上是将 Maven 构建的可执行 jar 包进一步封装,对 jar 包中的 class 文件进行加密处理;

Tip: 具体的加密算法根据不同需求场景自由定义

3、C/C++ 创建动态链接库 encrypt.dll(window平台,linux平台为 encrypt.so),通过以下命令参数匹配动态链接库,并实现 class 文件的解密运行过程

java -agentlib:encrypt -jar your-executeable-jar-file.jar

过程分解

1、创建动态链接库

windows

gcc -shared encrypt.c -o encrypt.dll -I "{JAVA_HOME}/include" -I "{JAVA_HOME}/include/win32"

linux

gcc -shared encrypt.c -o encrypt.so -I "{JAVA_HOME}/include" -I "{JAVA_HOME}/include/linux"

将通过以上指令生成的动态链接库文件(.dll 或 .so)放到系统路径下;

Java 可通过以下方式查看系统路径:

System.getProperty("java.library.path")

encrypt.c 文件:

由 C/C++ 语言编写的代码,用于生成动态链接库,通过 java agent 方式实现 class 文件解密过程

完整代码

  1. #include <string.h>
  2. #include <stdlib.h>
  3. #include "jni.h"
  4. #include <jvmti.h>
  5. #include <jni_md.h>
  6. // 加密方法
  7. void encode(char *str) {
  8. unsigned int len = strlen(str);
  9. for (int i = 0; i < len; i ++) {
  10. str[i] = str[i] ^ 0x07;
  11. }
  12. }
  13. // jni 接口实现
  14. JNIEXPORT jbyteArray JNICALL Java_com_wyhw_plugin_encrypt_encrypt_JarEncryptUtil_encrypt(JNIEnv * env, jclass cls, jbyteArray bytes) {
  15. char* dst = (char *) ((*env) -> GetByteArrayElements(env, bytes, 0));
  16. encode(dst);
  17. (*env) -> SetByteArrayRegion(env, bytes, 0, strlen(dst), (jbyte *) dst);
  18. return bytes;
  19. }
  20. // class 文件解密
  21. void JNICALL ClassDecryptHook(jvmtiEnv *jvmti_env, JNIEnv* jni_env, jclass class_being_redefined, jobject loader, const char* name,
  22. jobject protection_domain, jint class_data_len, const unsigned char* class_data, jint* new_class_data_len, unsigned char** new_class_data) {
  23. //printf("ClassDecryptHook Files: %s\n", (char *) name);
  24. *new_class_data_len = class_data_len;
  25. (*jvmti_env) -> Allocate(jvmti_env, class_data_len, new_class_data);
  26. unsigned char* _data = *new_class_data;
  27. if (name && strncmp(name, "com/zk/sdk", 10) == 0 && strstr(name, "$$") == NULL) {
  28. //printf("\n\nhit: %s\n", (char *) name);
  29. //printf("len=%d\n\n", class_data_len);
  30. for (int i = 0; i < class_data_len; ++i) {
  31. _data[i] = class_data[i];
  32. }
  33. encode(_data);
  34. } else {
  35. for (int i = 0; i < class_data_len; ++i) {
  36. _data[i] = class_data[i];
  37. }
  38. }
  39. }
  40. JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void *reserved) {
  41. jvmtiEnv *jvmtiEnv;
  42. jint ret = (*vm) -> GetEnv(vm, (void **)&jvmtiEnv, JVMTI_VERSION);
  43. if (JNI_OK != ret) {
  44. printf("ERROR: Fail get jvmtiEvn!\n");
  45. return ret;
  46. }
  47. jvmtiCapabilities jvmtiCapabilities;
  48. (void) memset(&jvmtiCapabilities, 0, sizeof(jvmtiCapabilities));
  49. jvmtiCapabilities.can_generate_all_class_hook_events = 1;
  50. jvmtiCapabilities.can_tag_objects = 1;
  51. jvmtiCapabilities.can_generate_object_free_events = 1;
  52. jvmtiCapabilities.can_get_source_file_name = 1;
  53. jvmtiCapabilities.can_get_line_numbers = 1;
  54. jvmtiCapabilities.can_generate_vm_object_alloc_events = 1;
  55. jvmtiError error = (*jvmtiEnv) -> AddCapabilities(jvmtiEnv, &jvmtiCapabilities);
  56. if (JVMTI_ERROR_NONE != error) {
  57. printf("ERROR: Unable to AddCapabilities JVMTI!\n");
  58. return error;
  59. }
  60. jvmtiEventCallbacks callbacks;
  61. (void) memset(&callbacks, 0, sizeof(callbacks));
  62. callbacks.ClassFileLoadHook = &ClassDecryptHook;
  63. error = (*jvmtiEnv) -> SetEventCallbacks(jvmtiEnv, &callbacks, sizeof(callbacks));
  64. if (JVMTI_ERROR_NONE != error) {
  65. printf("ERROR: Fail setEventCallBacks!\n");
  66. return error;
  67. }
  68. error = (*jvmtiEnv) -> SetEventNotificationMode(jvmtiEnv, JVMTI_ENABLE, JVMTI_EVENT_CLASS_FILE_LOAD_HOOK, NULL);
  69. if (JVMTI_ERROR_NONE != error) {
  70. printf("ERROR: Fail SetEventNotificationMode!\n");
  71. return error;
  72. }
  73. return JNI_OK;
  74. }

2、 SpringBoot 生成可执行 jar 包

在项目 pom.xml 文件中添加 spring-boot-maven-plugin 插件

  1. <build>
  2. <plugins>
  3. <plugin>
  4. <groupId>org.springframework.boot</groupId>
  5. <artifactId>spring-boot-maven-plugin</artifactId>
  6. </plugin>
  7. </plugins>
  8. </build>

 之后会在 Maven 插件处出现 spring-boot 插件

 运行 mvn clean package 命令,生成可执行 jar 包(一般放在项目 target 目录下)

Tip:Maven 命令 clean 和 package 所涉及的生命周期不同,可以参考相关文档 参考文献

3、 Maven 自定义插件

        本插件的目标是对 spring-boot-maven-plugin 插件生成的可执行 jar 包进行二次处理,实现 class 字节码文件加密处理。

3.3.1、 自定义插件

引入 pom 依赖 

  1. <dependency>
  2. <groupId>org.apache.maven.plugin-tools</groupId>
  3. <artifactId>maven-plugin-annotations</artifactId>
  4. <version>3.4</version>
  5. <scope>provided</scope>
  6. </dependency>
  7. <dependency>
  8. <groupId>org.apache.maven</groupId>
  9. <artifactId>maven-core</artifactId>
  10. <version>2.2.1</version>
  11. </dependency>

1、创建自定义 Mojo 类 EncryptMojo;

2、继承 org.apache.maven.plugin.AbstractMojo 抽象类;

3、类文件需要添加 org.apache.maven.plugins.annotations.Mojo 注解

4、实现 execute() 方法(加密过程就是在这里实现的)

EncryptMojo.java 完整代码

  1. package com.wyhw.plugins.plugin.encrypt;
  2. import org.apache.maven.plugin.AbstractMojo;
  3. import org.apache.maven.plugin.MojoExecutionException;
  4. import org.apache.maven.plugin.MojoFailureException;
  5. import org.apache.maven.plugin.logging.Log;
  6. import org.apache.maven.plugins.annotations.LifecyclePhase;
  7. import org.apache.maven.plugins.annotations.Mojo;
  8. import org.apache.maven.plugins.annotations.Parameter;
  9. import org.apache.maven.plugins.annotations.ResolutionScope;
  10. import org.apache.maven.project.MavenProject;
  11. /**
  12. * maven 加密插件(只适用于 springboot 项目)
  13. *
  14. * 此插件处于 Maven 生命周期的 package 阶段(若多个插件同处于 package 阶段时,此插件应处于最后执行的位置),
  15. * 将 spring-boot-maven-plugin 插件生成的可执行 jar 包加密处理,生成加密包
  16. *
  17. * @author wanyanhw
  18. * @since 2021/12/31 10:44
  19. */
  20. @Mojo(
  21. name = "encrypt",
  22. defaultPhase = LifecyclePhase.PACKAGE,
  23. requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME
  24. )
  25. public class EncryptMojo extends AbstractMojo {
  26. @Parameter(defaultValue = "${project}", readonly = true)
  27. private MavenProject project;
  28. /**
  29. * 是否执行加密逻辑标识,默认“不加密”
  30. * 支持命令行设置,如:mvn package -Dencrypt.on=true
  31. */
  32. @Parameter(property = "encrypt.on", defaultValue = "true")
  33. private boolean on;
  34. /**
  35. * 是否覆盖原包,默认“不覆盖”
  36. * 支持命令行设置,如:mvn package -Dencrypt.over=true
  37. */
  38. @Parameter(property = "encrypt.over", defaultValue = "false")
  39. private boolean over;
  40. /**
  41. * 生成新包的名称后缀,例如:encrypt-maven-plugin-0.0.1-SNAPSHOT-entry.jar
  42. * 支持命令行设置,如:mvn package -Dencrypt.suffix=-entry
  43. */
  44. @Parameter(property = "encrypt.suffix", defaultValue = "-entry")
  45. private String archiveSuffix;
  46. public void setOn(String on) {
  47. this.on = Boolean.valueOf(on);
  48. }
  49. public void setOver(String over) {
  50. this.over = Boolean.valueOf(over);
  51. }
  52. public void setArchiveSuffix(String archiveSuffix) {
  53. this.archiveSuffix = archiveSuffix;
  54. }
  55. public void execute() throws MojoExecutionException, MojoFailureException {
  56. Log log = getLog();
  57. if (!on) {
  58. log.info("Disable encrypt package !!!");
  59. return;
  60. }
  61. String originalArchivePath = project.getArtifact().getFile().getAbsolutePath();
  62. log.info("Original file path: " + originalArchivePath);
  63. int lastSeparatorIndex = originalArchivePath.lastIndexOf(".");
  64. String fileName = originalArchivePath.substring(0, lastSeparatorIndex);
  65. String extendName = originalArchivePath.substring(lastSeparatorIndex + 1);
  66. String newFileName = fileName + archiveSuffix + "." + extendName;
  67. log.info("Encrypt file path: " + newFileName);
  68. try {
  69. if (over) {
  70. log.info("Overwrite original file");
  71. newFileName = originalArchivePath;
  72. }
  73. JarEncryptUtil.encrypt(originalArchivePath, newFileName);
  74. } catch (Exception e) {
  75. log.error("自动加密失败", e);
  76. }
  77. }
  78. }

3.3.2、 创建Jar 包加密工具

引入 pom 依赖

  1. <dependency>
  2. <groupId>org.apache.commons</groupId>
  3. <artifactId>commons-compress</artifactId>
  4. <version>LATEST</version>
  5. </dependency>

直接上代码

 JarEncryptUtil .java

  1. package com.wyhw.plugins.plugin.encrypt;
  2. import org.apache.commons.compress.archivers.jar.JarArchiveEntry;
  3. import org.apache.commons.compress.archivers.jar.JarArchiveInputStream;
  4. import org.apache.commons.compress.archivers.jar.JarArchiveOutputStream;
  5. import java.io.*;
  6. import java.util.jar.Manifest;
  7. import java.util.logging.Logger;
  8. /**
  9. * @author wanyanhw
  10. * @since 2021/12/20 13:52
  11. */
  12. public class JarEncryptUtil {
  13. static {
  14. System.loadLibrary("encrypt");
  15. }
  16. private static Logger logger = Logger.getGlobal();
  17. public native static byte[] encrypt(byte[] bytes);
  18. public static void encrypt(String src, String desc) throws Exception {
  19. File srcFile = new File(src);
  20. File descFile = new File(desc);
  21. try (FileInputStream fis = new FileInputStream(srcFile);
  22. FileOutputStream fos = new FileOutputStream(descFile);
  23. JarArchiveInputStream jis = new JarArchiveInputStream(fis);
  24. JarArchiveOutputStream jos = new JarArchiveOutputStream(fos);) {
  25. UnclosedInputStream nis = new UnclosedInputStream(jis);
  26. UnclosedOutputStream nos = new UnclosedOutputStream(jos);
  27. JarArchiveEntry jarArchiveEntry;
  28. while ((jarArchiveEntry = jis.getNextJarEntry()) != null) {
  29. long time = jarArchiveEntry.getTime();
  30. String name = jarArchiveEntry.getName();
  31. JarArchiveEntry descJarEntry = new JarArchiveEntry(name);
  32. descJarEntry.setTime(time);
  33. if (jarArchiveEntry.isDirectory()) {
  34. jos.putArchiveEntry(descJarEntry);
  35. } else if (name.equals("META-INF/MANIFEST.MF")) {
  36. Manifest manifest = new Manifest(nis);
  37. jos.putArchiveEntry(descJarEntry);
  38. manifest.write(nos);
  39. } else if (name.startsWith("BOOT-INF/classes/")) {
  40. jos.putArchiveEntry(descJarEntry);
  41. if (name.contains("com/zk/") && name.endsWith(".class")) {
  42. transfer(encryptCus(nis), nos);
  43. logger.info(name + " 文件已加密");
  44. } else {
  45. transfer(nis, nos);
  46. }
  47. } else if (name.startsWith("BOOT-INF/lib/")) {
  48. descJarEntry.setMethod(JarArchiveEntry.STORED);
  49. descJarEntry.setSize(jarArchiveEntry.getSize());
  50. descJarEntry.setCrc(jarArchiveEntry.getCrc());
  51. jos.putArchiveEntry(descJarEntry);
  52. transfer(nis, nos);
  53. } else {
  54. jos.putArchiveEntry(descJarEntry);
  55. transfer(nis, nos);
  56. }
  57. jos.closeArchiveEntry();
  58. }
  59. jos.finish();
  60. }
  61. }
  62. private static InputStream encryptCus(InputStream nis) throws IOException {
  63. try (BufferedInputStream bis = new BufferedInputStream(nis);
  64. ByteArrayOutputStream bos = new ByteArrayOutputStream();) {
  65. byte[] buffer = new byte[4096];
  66. int len;
  67. while ((len = bis.read(buffer)) != -1) {
  68. bos.write(buffer, 0, len);
  69. }
  70. bos.flush();
  71. byte[] bytes = bos.toByteArray();
  72. encrypt(bytes);
  73. return new ByteArrayInputStream(bytes);
  74. }
  75. }
  76. /**
  77. * 输入流传输到输出流
  78. *
  79. * @param in 输入流
  80. * @param out 输出流
  81. * @throws IOException I/O 异常
  82. */
  83. private static void transfer(InputStream in, OutputStream out) throws IOException {
  84. byte[] buffer = new byte[4096];
  85. int length;
  86. while ((length = in.read(buffer)) != -1) {
  87. out.write(buffer, 0, length);
  88. }
  89. out.flush();
  90. }
  91. }

UnclosedOutputStream.java

  1. package com.wyhw.plugins.plugin.encrypt;
  2. import java.io.IOException;
  3. import java.io.OutputStream;
  4. /**
  5. * 不关闭的输出流
  6. *
  7. * @author wanyanhw
  8. * @since 2021/12/31 10:44
  9. */
  10. public class UnclosedOutputStream extends OutputStream {
  11. private final OutputStream out;
  12. public UnclosedOutputStream(OutputStream out) {
  13. this.out = out;
  14. }
  15. @Override
  16. public void write(int b) throws IOException {
  17. out.write(b);
  18. }
  19. @Override
  20. public void write(byte[] b) throws IOException {
  21. out.write(b);
  22. }
  23. @Override
  24. public void write(byte[] b, int off, int len) throws IOException {
  25. out.write(b, off, len);
  26. }
  27. @Override
  28. public void flush() throws IOException {
  29. out.flush();
  30. }
  31. @Override
  32. public void close() {
  33. }
  34. }

UnclosedInputStream .java

  1. package com.wyhw.plugins.plugin.encrypt;
  2. import java.io.IOException;
  3. import java.io.InputStream;
  4. /**
  5. * 不关闭的输入流
  6. *
  7. * @author wanyanhw
  8. * @since 2021/12/31 10:44
  9. */
  10. public class UnclosedInputStream extends InputStream {
  11. private final InputStream in;
  12. public UnclosedInputStream(InputStream in) {
  13. this.in = in;
  14. }
  15. @Override
  16. public int read() throws IOException {
  17. return in.read();
  18. }
  19. @Override
  20. public int read(byte[] b) throws IOException {
  21. return in.read(b);
  22. }
  23. @Override
  24. public int read(byte[] b, int off, int len) throws IOException {
  25. return in.read(b, off, len);
  26. }
  27. @Override
  28. public long skip(long n) throws IOException {
  29. return in.skip(n);
  30. }
  31. @Override
  32. public int available() throws IOException {
  33. return in.available();
  34. }
  35. @Override
  36. public void mark(int readLimit) {
  37. in.mark(readLimit);
  38. }
  39. @Override
  40. public void reset() throws IOException {
  41. in.reset();
  42. }
  43. @Override
  44. public boolean markSupported() {
  45. return in.markSupported();
  46. }
  47. @Override
  48. public void close() {
  49. }
  50. }

3.3.3、 插件与加密工具整合

在 execute() 方法中直接调用加密工具方法: 

JarEncryptUtil.encrypt(originalArchivePath, newFileName);

3.3.4、插件打成依赖 Jar 包

需要加密的项目在使用自定义 Maven 插件时,需要引入插件 Jar 包;所以需要将此插件打包成供第三方项目依赖的 Jar 包

引入打包插件:

  1. <build>
  2. <plugins>
  3. <plugin>
  4. <artifactId>maven-plugin-plugin</artifactId>
  5. <version>3.6.0</version>
  6. <executions>
  7. <execution>
  8. <id>default-addPluginArtifactMetadata</id>
  9. <phase>package</phase>
  10. <goals>
  11. <goal>addPluginArtifactMetadata</goal>
  12. </goals>
  13. </execution>
  14. <execution>
  15. <id>default-descriptor</id>
  16. <phase>process-classes</phase>
  17. <goals>
  18. <goal>descriptor</goal>
  19. </goals>
  20. </execution>
  21. </executions>
  22. </plugin>
  23. </plugins>
  24. </build>

执行 maven 命令:

mvn clean package

至此,已经完成了加密插件的定义;

4、插件应用

在需要加密的第三方项目 pom.xml 文件中引入依赖:

  1. <dependency>
  2. <groupId>com.wyhw.plugin</groupId>
  3. <artifactId>encrypt-maven-plugin</artifactId>
  4. <version>0.0.1-SNAPSHOT</version>
  5. </dependency>

配置打包插件

在 spring-boot-maven-plugin 插件之后配置自定义加密插件(spring-boot-maven-plugin 插件和自定义的加密插件都是处于 Maven 生命周期中 package 阶段的插件,插件执行顺序跟配置的前后顺序一致;只有先执行 spring-boot-maven-plugin 插件,再执行自定义加密插件,加密过程才能正常执行)

  1. <plugins>
  2. <plugin>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-maven-plugin</artifactId>
  5. </plugin>
  6. <plugin>
  7. <groupId>com.wyhw.plugin</groupId>
  8. <artifactId>encrypt-maven-plugin</artifactId>
  9. <version>0.0.1-SNAPSHOT</version>
  10. <executions>
  11. <execution>
  12. <goals>
  13. <goal>encrypt</goal>
  14. </goals>
  15. </execution>
  16. </executions>
  17. </plugin>
  18. </plugins>

执行 package 命令,生成加密 jar 包

mvn clean package

 加密成功后,控制台会打印出相应文件加密成功的日志信息

  1. "C:\Program Files\Java\jdk1.8.0_211\bin\java.exe" -Dmaven.multiModuleProjectDirectory=E:\space\idea\sdk "-Dmaven.home=D:\IntelliJ IDEA 2019.1.2\plugins\maven\lib\maven3" "-Dclassworlds.conf=D:\IntelliJ IDEA 2019.1.2\plugins\maven\lib\maven3\bin\m2.conf" "-javaagent:D:\IntelliJ IDEA 2019.1.2\lib\idea_rt.jar=57987:D:\IntelliJ IDEA 2019.1.2\bin" -Dfile.encoding=UTF-8 -classpath "D:\IntelliJ IDEA 2019.1.2\plugins\maven\lib\maven3\boot\plexus-classworlds-2.5.2.jar" org.codehaus.classworlds.Launcher -Didea.version2019.1.2 -DskipTests=true package
  2. [INFO] Scanning for projects...
  3. [INFO]
  4. [INFO] ------------------------------------------------------------------------
  5. [INFO] Building sdk 0.0.1-SNAPSHOT
  6. [INFO] ------------------------------------------------------------------------
  7. [INFO]
  8. [INFO] --- maven-resources-plugin:3.2.0:resources (default-resources) @ sdk ---
  9. [INFO] Using 'UTF-8' encoding to copy filtered resources.
  10. [INFO] Using 'UTF-8' encoding to copy filtered properties files.
  11. [INFO] Copying 1 resource
  12. [INFO] Copying 0 resource
  13. [INFO]
  14. [INFO] --- maven-compiler-plugin:3.8.1:compile (default-compile) @ sdk ---
  15. [INFO] Nothing to compile - all classes are up to date
  16. [INFO]
  17. [INFO] --- maven-resources-plugin:3.2.0:testResources (default-testResources) @ sdk ---
  18. [INFO] Using 'UTF-8' encoding to copy filtered resources.
  19. [INFO] Using 'UTF-8' encoding to copy filtered properties files.
  20. [INFO] skip non existing resourceDirectory E:\space\idea\sdk\src\test\resources
  21. [INFO]
  22. [INFO] --- maven-compiler-plugin:3.8.1:testCompile (default-testCompile) @ sdk ---
  23. [INFO] Nothing to compile - all classes are up to date
  24. [INFO]
  25. [INFO] --- maven-surefire-plugin:2.22.2:test (default-test) @ sdk ---
  26. [INFO] Tests are skipped.
  27. [INFO]
  28. [INFO] --- maven-jar-plugin:3.2.0:jar (default-jar) @ sdk ---
  29. [INFO] Building jar: E:\space\idea\sdk\target\sdk-0.0.1-SNAPSHOT.jar
  30. [INFO]
  31. [INFO] --- spring-boot-maven-plugin:2.5.3:repackage (repackage) @ sdk ---
  32. [INFO] Replacing main artifact with repackaged archive
  33. [INFO]
  34. [INFO] --- zk-common-plugins:0.0.1-SNAPSHOT:encrypt (default) @ sdk ---
  35. [INFO] Original file path: E:\space\idea\sdk\target\sdk-0.0.1-SNAPSHOT.jar
  36. [INFO] Encrypt file path: E:\space\idea\sdk\target\sdk-0.0.1-SNAPSHOT-encrypt.jar
  37. 一月 04, 2022 7:11:42 下午 com.zk.common.plugins.encrypt.JarEncryptUtil encrypt
  38. 信息: BOOT-INF/classes/com/zk/sdk/core/util/CRC32Util.class 文件已加密
  39. 一月 04, 2022 7:11:42 下午 com.zk.common.plugins.encrypt.JarEncryptUtil encrypt
  40. 信息: BOOT-INF/classes/com/zk/sdk/core/wrapper/RGBTriple.class 文件已加密
  41. 一月 04, 2022 7:11:42 下午 com.zk.common.plugins.encrypt.JarEncryptUtil encrypt
  42. 信息: BOOT-INF/classes/com/zk/sdk/SdkApplication.class 文件已加密
  43. [INFO] ------------------------------------------------------------------------
  44. [INFO] BUILD SUCCESS
  45. [INFO] ------------------------------------------------------------------------
  46. [INFO] Total time: 2.235 s
  47. [INFO] Finished at: 2022-01-04T19:11:42+08:00
  48. [INFO] Final Memory: 27M/408M
  49. [INFO] ------------------------------------------------------------------------
  50. Process finished with exit code 0

参考文献

JVMTI 官方文档:JVM(TM) Tool Interface 1.2.3

XJar 开源项目:GitHub - core-lib/xjar: Spring Boot JAR 安全加密运行工具,支持的原生JAR。

Maven: Maven 的生命周期

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/984602
推荐阅读
相关标签
  

闽ICP备14008679号