将Java项目打包为可执行文件

本实例的Java工程构建了一个简单的服务器,监听本地的12345端口,当接收到GET请求:/get_cpuid时,返回当前运行终端的CPUID信息 整个流程分为两部分: 工程打包为jar文件 jar文件转换为exe安装程序,安装后得到exe可执行程序 在打包为jar之前需要指定主类,Maven设置: <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.2.4</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <mainClass>org.example.LocalHardwareService</mainClass> </transformer> </transformers> </configuration> </execution> </executions> </plugin> </plugins> </build> 执行mvn package后会打包为一个fat jar,文件名GetDeviceInfo-1.0-SNAPSHOT-shaded.jar 警告 若不使用fat jar,则安装的exe会提示启动JVM失败 执行以下jpackage命令: jpackage --input target/ --name LocalHWService --main-jar GetDeviceInfo-1.0-SNAPSHOT-shaded.jar --main-class org.example.LocalHardwareService --type exe --vendor "FICN" --description "Local Hardware Info Service" input:输入目录。因为jar文件是被打包存储于项目的/target目录中的; main-jar:jar文件名; main-class:主类名; vendor:发行公司名; description:文件描述,会显示在任务管理器中 说明 一般来说,vendor和description属性是无需特别指定的,但由于执行这jpackage指令会使用WiX,而我所安装的WiX(v3.14.1)的这两个属性默认会使用非ASCII字符,这就导致了jpackage命令报错,所以需要手动指定这两个属性,保证是纯英文 命令完成后在当前目录生成LocalHWService-1.0.exe文件,这个文件并不能直接运行服务,而是一个安装程序 双击打开后自动安装,一般会安装到C:\Program Files中,进入子目录\LocalHWService后,可以看到文件: LocalHWService.exe就是服务文件了,双击运行,打开任务管理器可以看到其在后台正常运行: 在浏览器中进行测试,此时请求正常得到响应:

August 5, 2025

Keycloak安装自定义插件及代码解析

参考官方开发示例,使用Java为Keycloak系统编写一套个人隐私问题的认证模块 代码编写完成后,借助Maven将项目打包为jar文件,传输至集群主节点准备部署 注意在打包时需要添加resources/META-INF/services目录,并注册Factory类 导入插件 首先创建初始ConfigMap: kubectl create configmap secret-question-plugin --from-file=SecretQuestion.jar=./kc-plugins/SecretQuestion.jar -n keycloak 然后执行kubectl edit statefulset keycloak -n keycloak对keycloak的StatefulSet进行修改: spec: template: spec: volumes: - name: plugins-volume configMap: name: secret-question-plugin containers: - name: keycloak # 其他配置... volumeMounts: - name: plugins-volume mountPath: /opt/keycloak/providers/SecretQuestion.jar subPath: SecretQuestion.jar 更新插件 之后每次更新迭代插件时,需要先删除原ConfigMap,然后创建新ConfigMap,最后重启Pod即可 # 删除旧的ConfigMap kubectl delete configmap secret-question-plugin -n keycloak # 创建新的ConfigMap kubectl create configmap secret-question-plugin --from-file=SecretQuestion.jar=./kc-plugins/SecretQuestion.jar -n keycloak # 重启Keycloak Pod kubectl delete pod keycloak-0 -n keycloak 插件代码解析 将插件导入Keycloak系统后,需要先在【身份验证】【必需的操作】处将之开启,然后将自定义的认证执行器插入流程之中,如此才能使新认证功能发挥作用 这里将新认证器放置在“账户密码验证”之后且作为必需行动 当用户首先进入认证界面(账户密码界面)时,仅账户密码认证执行器在发挥作用 ...

July 13, 2025

基于归并排序的逆序数统计

问题定义 给定一个长度为n的整数数组,计算数组中的逆序对的数量并返回 逆序对的定义如下:对于数组的第i个和第j个元素,如果满足i<j且a[i]>a[j],则其为一个逆序对;否则不是。 数据范围 1≤n≤100000,数组中的元素的取值范围 [1,1e9]。 示例:输入[2,3,4,5,6,1],输出5 解释:逆序对共5对,[2, 1], [3, 1], [4, 1], [5, 1], [6, 1] 本题暴力硬解的复杂度为$O(n^2)$,较为繁琐,于是考虑是否存在复杂度为$O(nlogn)$的算法 要求逆序数,就不能在寻找数对时未经相关处理就更改数组元素顺序打乱原有顺序,因此要找到一种在实施过程中能够在一定程度上保留元素顺序关系的算法。 设想一种很理想的情况:元素a与b满足逆序数的定义(即a>b且i<j),同时还已知有一批比a大的数,那么很容易推知这一批数都可以与b构成逆序数,其个数就等于这批数的个数 以上情况正好符合归并排序的过程,首先在这里复习一下归并排序的原理: 归并排序 归并排序先递归地两两划分数组,每轮递归中都将整个数组分为左右两个部分,然后在每一次左右划分完成后,为左右两个数组各设置一个指针i与j,比较大小并将小者放入一个临时数组中(这个临时数组的长度是本轮划分左右数组的总长),放入一边的指针前进一步,再继续比较。当某一边全部比较完后,将可能还未放完的另一边数组元素全部放入临时数组 代码可以是: class Solution { public int[] mergeSort(int[] nums, int l, int r) { // l=r证明已经划分到单个元素,返回的排序数组就是以这个单元素组成的数组 if (l == r) return new int[]{nums[l]}; int mid = (l + r) / 2; int[] leftNums = mergeSort(nums, l, mid); int[] rightNums = mergeSort(nums, mid+1, r); // 能执行到这里,说明左右划分都已完成,此时如果左右都是单元素数组[2][3],则这个栈会返回[2,3] // 此时如果左右都是多元素数组,那至少能保证这两个数组各自的内部是有序的 int[] tempNums = new int[leftNums.length + rightNums.length]; int i = 0, j = 0, m = 0; // i左j右,m是临时数组索引 while (i < leftNums.length && j < rightNums.length) tempNums[m++] = leftNums[i] < rightNums[j] ? leftNums[i++] : rightNums[j++]; // 接下来处理一边走完一边还没走完的情况,由于单边必定已经有序,所以直接顺序放入即可 while (i < leftNums.length) tempNums[m++] = leftNums[i++]; while (j < rightNums.length) tempNums[m++] = rightNums[j++]; return tempNums; } } 题解 回到本题,核心在于:在归并排序比较leftNums[i]与rightNums[j]的大小关系时,如果正好左大于右,(即满足逆序数的定义),则可以直接确定这两个数之间的数都满足逆序数的定义 ...

December 11, 2024

顺序统计

顺序统计的问题是: 给定一组元素,找到其中第k大(小)的元素 仅从功能实现上考虑,完全可以先对数组排序,然后直接取nums[k-1]作为结果输出。但这个方法是需要先对数组排序的,所以效率为$\Theta(nlogn)$,我们的目标是找到一种$\Theta(n)$的方法 基于随机数的分治法 为了尽量压缩时间,可以考虑能不能在排序进行时就顺便找到所需元素,为此,可以使用快速排序算法中的分治思想 在快速排序算法中,每选定一个基准数pivot,就需要将所有小于pivot的元素移动至其左侧、所有大于pivot的元素移动至其右侧,当全部移动完成时,左右指针会正好相遇在pivot元素上 不难想到,当获得了pivot的索引值index,就可以直接检查index是否正好等于k-1(假设k指第k小的元素值),如果相等,则说明这个pivot就是要找的第k小的元素;如果不等,则进一步检查index与k-1之间的大小关系并确定新pivot: 如果index > k-1,说明要寻找的元素相较于pivot来说更小,那么去小于pivot的区域再找 如果index < k-1,说明要寻找的元素相较于pivot来说更大,那么去大于pivot的区域再找 以上过程完全可以用递归来实现(事实上这也是快速排序所进行的过程),而在区域中选取pivot实际上是随机取得的。另外,当传入的左右限制left == right时,说明此区域仅有一个元素,需要寻找的只能是该元素,直接返回nums[left]即可 代码实现(Java) 类的总体结构应为: import java.util.Random; class Find { private int[] nums; private int k; public Find(int[] nums) {...} public int getKElement(int k) {...} private int randomSelect(int left, int right) {...} // 递归部分,选取随机数、划分后判断 private int partition(int random, int p, int q) {...} // 划分部分 private void swap(int i, int j) {...} // 用于交换数组元素的工具方法 } 先编写划分部分的方法,输入数组、随机索引值、左右限制,输出划分后pivot的索引值: public int partition(int random, int p, int q) { while (p != q) { int pivot = nums[random]; while (pivot > nums[p]) p++; swap(nums, p, random); random = p; while (pivot < nums[q]) q--; swap(nums, q, random); random = q; } return random; } 编写递归部分: ...

December 10, 2024

Stream流遍历处理字符串中各字符

Stream在数组处理上的特点 首先明确一点: char[] chs = {'h','e','l','l','o'}; int[] ints = {1,2,3,4}; String str = "hello"; String[] strs = {"hello","world"}; //数组的Stream流泛型都是数组,而不是数组中的单个元素 Stream<char[]> chsStream = Stream.of(chs); Stream<int[]> intsStream = Stream.of(ints); //String的Stream流泛型都是String Stream<String> strStream = Stream.of(str); Stream<String> strsStream = Stream.of(strs); //在这种情况下,流中的每个元素都是String[]数组中的单个元素 如果我们的目的是处理数组中的每一个元素,那么对于Stream<T[]>这种类型的流我们一般是并不想见到的 解决方法就是使用基本类型流,即IntStream、DoubleStream和LongStream,然后再操作(或对基本类型流先进行boxed()转为Stream<T>再操作),而得到基本类型流的方法有两种: Arrays.stream() 基本类型流名.of() Stream处理字符串 - 以凯撒密码为例 若要对字符串中的每个字符进行逐个操作,最容易想到,也最快的方法就是先使用toCharArray()将字符串转为一个char[]数组,再使用循环进行操作,最后使用String的构造方法将之再转换回字符串: //正常方法 char[] chars = str.toCharArray(); for(int i=0;i< chars.length;i++) { chars[i] = (char)((chars[i]-'a'+k)%26+'a'); } System.out.println(new String(chars)); 这次我舍近求远,想用Stream流来模拟生成凯撒密码密文的操作 根据Stream处理数组的特点,String转换成char[]之后,要想对数组的每个元素都进行操作,就要使用基本类型流,但并没有char类型的基本类型流,在这就导致我只能得到Stream<char[]>,而得不到如CharStream或是Stream<Character>的流 所以,不能将String转为char[]数组 那么,借助String[]数组的流中可以遍历到数组中的每个元素的特点,考虑将String转为String[]数组,再使用map()将每个元素转换成char,这样才得到一个Stream<Character>: Stream<String> t = Stream.of(str.split("")); //str:{"h","e","l","l","o"} Stream<Character> chStream = t.map(s -> s.charAt(0)); chStream = t.map(ch -> (char)((ch-'a'+1)%26+'a'); /* * 也可直接一步完成操作: * s -> (char)((s.charAt(0)-'a'+1)%26+'a') */ 得到了Stream<Character>以后,才能正式地开始遍历每个字符并处理。处理结束后,将Stream<Character>重新拼接成字符串即可 ...

September 6, 2022