侧边栏壁纸
  • 累计撰写 104 篇文章
  • 累计创建 42 个标签
  • 累计收到 1 条评论

目 录CONTENT

文章目录

jar-saner

天明
2026-08-17 / 0 评论 / 0 点赞 / 1 阅读 / 0 字 / 正在检测是否收录...
# jar-scaner-kit
一个jar包扫描工具
## 功能说明
1. 扫描jar包中的类文件,并输出包名、类名、方法名
2. 对比两个jar包中的类文件,输出差异信息
3. 扫描指定目录下的所有jar包,检索指定类名、包名、方法名
4. 扫描指定目录下的所有jar包,输出类冲突信息


## 使用方法
```
# 查看帮助
java -jar jar-scaner-kit-1.0.0.jar -h
# 扫描jar包中的类文件,并输出包名、类名、方法名
java -jar jar-scaner-kit-1.0.0.jar -f /data/test/jar-1.0.0.jar
# 对比两个jar包中的类文件,输出差异信息
java -jar jar-scaner-kit-1.0.0.jar -f /data/test/jar-1.0.0.jar -f2 /data/test/jar-1.0.2.jar
# 扫描指定目录下的所有jar包输出类冲突信息
java -jar jar-scaner-kit-1.0.0.jar -d /data/test/ 
# 检索指定类名、包名、方法名
java -jar jar-scaner-kit-1.0.0.jar -d /data/test/ -c com.test.TestClass
```

1

package com.jnliok.jarscaner;

/**
 * 方法信息模型(与 classgraph 生命周期解耦)
 */
public class MethodModel {

    /** 方法名 */
    public final String name;

    /** 格式化后的方法签名,例如:public void hello(java.lang.String): java.lang.String */
    public final String signature;

    public MethodModel(String name, String signature) {
        this.name = name;
        this.signature = signature;
    }

    public String getName() {
        return name;
    }

    public String getSignature() {
        return signature;
    }

    @Override
    public String toString() {
        return signature;
    }
}
package com.jnliok.jarscaner;

import java.io.File;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;

/**
 * jar 相关工具类
 */
public final class JarUtil {

    private JarUtil() {
    }

    /**
     * 判断文件是否为 jar 文件(不区分大小写)
     */
    public static boolean isJarFile(File file) {
        return file.isFile() && file.getName().toLowerCase().endsWith(".jar");
    }

    /**
     * 递归扫描目录,收集所有 jar 文件,按路径排序
     */
    public static List<File> listJars(File dir) {
        List<File> result = new ArrayList<>();
        if (dir == null || !dir.isDirectory()) {
            return result;
        }
        collectJars(dir, result);
        result.sort((a, b) -> a.getPath().compareTo(b.getPath()));
        return result;
    }

    private static void collectJars(File file, List<File> result) {
        File[] children = file.listFiles();
        if (children == null) {
            return;
        }
        for (File child : children) {
            if (child.isDirectory()) {
                collectJars(child, result);
            } else if (isJarFile(child)) {
                result.add(child);
            }
        }
    }

    /**
     * 将 class 文件路径转换为类全限定名,例如 com/foo/Bar.class -> com.foo.Bar
     */
    public static String classPathToName(String path) {
        return path.substring(0, path.length() - ".class".length()).replace('/', '.');
    }

    /**
     * 计算字节数组的 MD5(十六进制小写)
     */
    public static String md5(byte[] data) {
        try {
            MessageDigest digest = MessageDigest.getInstance("MD5");
            byte[] bytes = digest.digest(data);
            StringBuilder sb = new StringBuilder(bytes.length * 2);
            for (byte b : bytes) {
                String hex = Integer.toHexString(b & 0xff);
                if (hex.length() == 1) {
                    sb.append('0');
                }
                sb.append(hex);
            }
            return sb.toString();
        } catch (NoSuchAlgorithmException e) {
            throw new IllegalStateException("MD5 算法不可用", e);
        }
    }
}
package com.jnliok.jarscaner;

import java.util.List;

/**
 * 类信息模型(与 classgraph 生命周期解耦)
 */
public class ClassModel {

    /** 包名 */
    public final String packageName;

    /** 类全限定名 */
    public final String name;

    /** 类简单名 */
    public final String simpleName;

    /** 用户可见的方法列表(已过滤合成/桥方法,已排序) */
    public final List<MethodModel> methods;

    public ClassModel(String packageName, String name, String simpleName, List<MethodModel> methods) {
        this.packageName = packageName;
        this.name = name;
        this.simpleName = simpleName;
        this.methods = methods;
    }
}

2

package com.jnliok.jarscaner;

/**
 * jar-scaner-kit 入口类,负责命令行参数解析与分发
 */
public class Main {

    public static void main(String[] args) {
        Options opts = new Options();
        String error = opts.parse(args);
        if (error != null) {
            System.out.println("[错误] " + error);
            System.out.println();
            printHelp();
            return;
        }
        if (opts.help) {
            printHelp();
            return;
        }
        if (opts.jar1 != null && opts.jar2 != null) {
            JarComparator.compare(opts.jar1, opts.jar2);
        } else if (opts.jar1 != null) {
            JarScanner.scanAndPrint(opts.jar1);
        } else if (opts.dir != null) {
            if (opts.pattern != null) {
                ClassSearcher.search(opts.dir, opts.pattern);
            } else {
                ConflictDetector.detect(opts.dir);
            }
        } else {
            printHelp();
        }
    }

    private static void printHelp() {
        System.out.println("========================================");
        System.out.println(" jar-scaner-kit - Jar 包扫描工具 v1.0.0");
        System.out.println("========================================");
        System.out.println("用法:");
        System.out.println("  java -jar jar-scaner-kit-1.0.0.jar -h");
        System.out.println("      查看帮助");
        System.out.println();
        System.out.println("  java -jar jar-scaner-kit-1.0.0.jar -f <jar路径>");
        System.out.println("      扫描 jar 包中的类文件,输出包名、类名、方法名");
        System.out.println();
        System.out.println("  java -jar jar-scaner-kit-1.0.0.jar -f <jar路径1> -f2 <jar路径2>");
        System.out.println("      对比两个 jar 包,输出类差异信息");
        System.out.println();
        System.out.println("  java -jar jar-scaner-kit-1.0.0.jar -d <目录>");
        System.out.println("      扫描目录下所有 jar 包,输出类冲突信息");
        System.out.println();
        System.out.println("  java -jar jar-scaner-kit-1.0.0.jar -d <目录> -c <类名/包名/方法名>");
        System.out.println("      扫描目录下所有 jar 包,检索指定的类名、包名或方法名");
        System.out.println();
        System.out.println("参数说明:");
        System.out.println("  -h            显示帮助信息");
        System.out.println("  -f <path>     指定 jar 包文件路径");
        System.out.println("  -f2 <path>    指定第二个 jar 包文件路径(与 -f 一起使用进行对比)");
        System.out.println("  -d <path>     指定扫描目录");
        System.out.println("  -c <pattern>  检索模式:类名(全限定名/简单名)、包名或方法名");
        System.out.println();
        System.out.println("示例:");
        System.out.println("  java -jar jar-scaner-kit-1.0.0.jar -f /data/test/jar-1.0.0.jar");
        System.out.println("  java -jar jar-scaner-kit-1.0.0.jar -f /data/test/jar-1.0.0.jar -f2 /data/test/jar-1.0.2.jar");
        System.out.println("  java -jar jar-scaner-kit-1.0.0.jar -d /data/test/");
        System.out.println("  java -jar jar-scaner-kit-1.0.0.jar -d /data/test/ -c com.test.TestClass");
    }

    /**
     * 命令行参数
     */
    private static class Options {
        boolean help;
        String jar1;
        String jar2;
        String dir;
        String pattern;

        /**
         * 解析参数,返回错误信息(null 表示解析成功)
         */
        String parse(String[] args) {
            for (int i = 0; i < args.length; i++) {
                String arg = args[i];
                switch (arg) {
                    case "-h":
                    case "--help":
                        help = true;
                        break;
                    case "-f":
                        if (i + 1 >= args.length) {
                            return "参数 -f 缺少值";
                        }
                        jar1 = args[++i];
                        break;
                    case "-f2":
                        if (i + 1 >= args.length) {
                            return "参数 -f2 缺少值";
                        }
                        jar2 = args[++i];
                        break;
                    case "-d":
                        if (i + 1 >= args.length) {
                            return "参数 -d 缺少值";
                        }
                        dir = args[++i];
                        break;
                    case "-c":
                        if (i + 1 >= args.length) {
                            return "参数 -c 缺少值";
                        }
                        pattern = args[++i];
                        break;
                    default:
                        return "未知参数: " + arg;
                }
            }
            return null;
        }
    }
}

3

package com.jnliok.jarscaner;

import io.github.classgraph.ClassGraph;
import io.github.classgraph.ClassInfo;
import io.github.classgraph.MethodInfo;
import io.github.classgraph.MethodParameterInfo;
import io.github.classgraph.MethodTypeSignature;
import io.github.classgraph.ScanResult;
import io.github.classgraph.TypeSignature;

import java.io.File;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

/**
 * 功能1:扫描 jar 包中的类文件,输出包名、类名、方法名
 */
public final class JarScanner {

    private JarScanner() {
    }

    /**
     * 扫描单个 jar 并输出类信息(包名、类名、方法名)
     */
    public static void scanAndPrint(String jarPath) {
        File jarFile = new File(jarPath);
        if (!jarFile.isFile()) {
            System.out.println("[错误] 文件不存在或不是有效文件: " + jarPath);
            return;
        }
        List<ClassModel> classes = scanJar(jarPath);
        System.out.println("Jar 文件: " + jarFile.getAbsolutePath());
        System.out.println("扫描到 " + classes.size() + " 个类");
        System.out.println("========================================");
        Set<String> packageNames = new TreeSet<>();
        Set<String> classNames = new TreeSet<>();
        for (ClassModel cm : classes) {
            packageNames.add(cm.packageName);
            classNames.add(cm.name);
            printClass(cm);
        }
        System.out.println("----------------------------------------");
        System.out.println("## 共 " + packageNames.size() + " 个包名");
        packageNames.forEach(System.out::println);
        System.out.println("----------------------------------------");
        System.out.println("## 共 " + classNames.size() + " 个类名");
        classNames.forEach(System.out::println);
    }

    /**
     * 扫描单个 jar 文件,返回按类名排序的类模型列表。
     * 所有 classgraph 对象的访问都必须在 ScanResult 打开期间完成。
     */
    public static List<ClassModel> scanJar(String jarPath) {
        List<ClassModel> models = new ArrayList<>();
        try (ScanResult scanResult = new ClassGraph()
                .enableAllInfo()
                .overrideClasspath(jarPath)
                .scan()) {
            List<ClassInfo> classes = new ArrayList<>(scanResult.getAllClasses());
            classes.sort(Comparator.comparing(ClassInfo::getName));
            for (ClassInfo ci : classes) {
                models.add(toModel(ci));
            }
        }
        return models;
    }

    /**
     * 将 ClassInfo 转换为与 ScanResult 生命周期解耦的 ClassModel
     */
    private static ClassModel toModel(ClassInfo ci) {
        List<MethodModel> methods = new ArrayList<>();
        for (MethodInfo m : ci.getMethodInfo()) {
            if (m.isSynthetic() || m.isBridge()) {
                continue;
            }
            methods.add(new MethodModel(m.getName(), formatMethod(m)));
        }
        // 先按方法名、再按签名排序;使用显式方法引用让泛型推断更明确
        methods.sort(Comparator
                .comparing(MethodModel::getName)
                .thenComparing(mm -> mm.signature));
        return new ClassModel(ci.getPackageName(), ci.getName(), ci.getSimpleName(), methods);
    }

    /**
     * 打印单个类:包名、类名、方法名
     */
    private static void printClass(ClassModel cm) {
        System.out.println("包名: " + cm.packageName);
        System.out.println("类名: " + cm.name);
        if (cm.methods.isEmpty()) {
            System.out.println("方法: (无)");
        } else {
            for (MethodModel mm : cm.methods) {
                System.out.println("方法: " + mm.signature);
            }
        }
        System.out.println("----------------------------------------");
    }

    /**
     * 格式化方法签名,例如:public void method(int, java.lang.String): int
     */
    private static String formatMethod(MethodInfo m) {
        StringBuilder sb = new StringBuilder();
        String mods = m.getModifiersStr();
        if (mods != null && !mods.isEmpty()) {
            sb.append(mods).append(' ');
        }
        String name = m.isConstructor() ? m.getClassInfo().getSimpleName() : m.getName();
        sb.append(name).append('(');
        MethodParameterInfo[] params = m.getParameterInfo();
        for (int i = 0; i < params.length; i++) {
            if (i > 0) {
                sb.append(", ");
            }
            TypeSignature ts = params[i].getTypeDescriptor();
            sb.append(ts != null ? ts.toString() : "?");
        }
        sb.append(')');
        MethodTypeSignature sig = m.getTypeDescriptor();
        if (sig != null && sig.getResultType() != null) {
            sb.append(": ").append(sig.getResultType().toString());
        }
        return sb.toString();
    }
}

4

package com.jnliok.jarscaner;

import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;

/**
 * 功能2:对比两个 jar 包中的类文件,输出差异信息
 */
public final class JarComparator {

    private JarComparator() {
    }

    public static void compare(String jarPath1, String jarPath2) {
        File f1 = new File(jarPath1);
        File f2 = new File(jarPath2);
        if (!f1.isFile()) {
            System.out.println("[错误] 文件不存在或不是有效文件: " + jarPath1);
            return;
        }
        if (!f2.isFile()) {
            System.out.println("[错误] 文件不存在或不是有效文件: " + jarPath2);
            return;
        }

        Map<String, ClassModel> map1 = toMap(JarScanner.scanJar(jarPath1));
        Map<String, ClassModel> map2 = toMap(JarScanner.scanJar(jarPath2));

        TreeSet<String> onlyIn1 = new TreeSet<>(map1.keySet());
        onlyIn1.removeAll(map2.keySet());

        TreeSet<String> onlyIn2 = new TreeSet<>(map2.keySet());
        onlyIn2.removeAll(map1.keySet());

        TreeSet<String> common = new TreeSet<>(map1.keySet());
        common.retainAll(map2.keySet());

        System.out.println("===== Jar 对比结果 =====");
        System.out.println("Jar1: " + f1.getAbsolutePath() + " (" + map1.size() + " 个类)");
        System.out.println("Jar2: " + f2.getAbsolutePath() + " (" + map2.size() + " 个类)");
        System.out.println();

        System.out.println("[仅存在于 Jar1 的类] (" + onlyIn1.size() + "):");
        if (onlyIn1.isEmpty()) {
            System.out.println("  (无)");
        }
        for (String name : onlyIn1) {
            System.out.println("  " + name);
        }
        System.out.println();

        System.out.println("[仅存在于 Jar2 的类] (" + onlyIn2.size() + "):");
        if (onlyIn2.isEmpty()) {
            System.out.println("  (无)");
        }
        for (String name : onlyIn2) {
            System.out.println("  " + name);
        }
        System.out.println();

        System.out.println("[两包共有类的方法差异]:");
        int methodDiffClassCount = 0;
        for (String name : common) {
            TreeSet<String> m1 = methodSet(map1.get(name));
            TreeSet<String> m2 = methodSet(map2.get(name));
            TreeSet<String> diff1 = new TreeSet<>(m1);
            diff1.removeAll(m2);
            TreeSet<String> diff2 = new TreeSet<>(m2);
            diff2.removeAll(m1);
            if (diff1.isEmpty() && diff2.isEmpty()) {
                continue;
            }
            methodDiffClassCount++;
            System.out.println("  类: " + name);
            for (String s : diff1) {
                System.out.println("    仅 Jar1: " + s);
            }
            for (String s : diff2) {
                System.out.println("    仅 Jar2: " + s);
            }
        }
        if (methodDiffClassCount == 0) {
            System.out.println("  (无)");
        }
        System.out.println();

        System.out.println("========== 汇总 ==========");
        System.out.println("新增类: " + onlyIn2.size() + " 个");
        System.out.println("删除类: " + onlyIn1.size() + " 个");
        System.out.println("方法有变化的类: " + methodDiffClassCount + " 个");
    }

    private static Map<String, ClassModel> toMap(List<ClassModel> classes) {
        Map<String, ClassModel> map = new HashMap<>();
        for (ClassModel cm : classes) {
            map.put(cm.name, cm);
        }
        return map;
    }

    private static TreeSet<String> methodSet(ClassModel cm) {
        TreeSet<String> set = new TreeSet<>();
        for (MethodModel mm : cm.methods) {
            set.add(mm.signature);
        }
        return set;
    }
}

6

package com.jnliok.jarscaner;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * 功能4:扫描指定目录下的所有 jar 包,输出类冲突信息
 */
public final class ConflictDetector {

    private ConflictDetector() {
    }

    public static void detect(String dirPath) {
        File dir = new File(dirPath);
        if (!dir.isDirectory()) {
            System.out.println("[错误] 目录不存在或不是有效目录: " + dirPath);
            return;
        }
        List<File> jars = JarUtil.listJars(dir);
        if (jars.isEmpty()) {
            System.out.println("目录下未找到 jar 文件: " + dir.getAbsolutePath());
            return;
        }
        System.out.println("扫描目录: " + dir.getAbsolutePath());
        System.out.println("jar 文件数: " + jars.size());
        System.out.println("========================================");

        // className -> (jarName -> class 文件 md5)
        Map<String, Map<String, String>> classOwners = new TreeMap<>();
        for (File jar : jars) {
            scanJarClasses(jar, classOwners);
        }

        int conflictCount = 0;
        int duplicateCount = 0;
        for (Map.Entry<String, Map<String, String>> entry : classOwners.entrySet()) {
            String className = entry.getKey();
            Map<String, String> owners = entry.getValue();
            if (owners.size() < 2) {
                continue;
            }
            boolean contentSame = new TreeSet<>(owners.values()).size() == 1;
            if (contentSame) {
                duplicateCount++;
                System.out.println("[重复] " + className + "  出现于: " + owners.keySet());
            } else {
                conflictCount++;
                System.out.println("[冲突] " + className + "  出现于:");
                for (Map.Entry<String, String> o : owners.entrySet()) {
                    System.out.println("    " + o.getKey() + "  (md5: " + o.getValue() + ")");
                }
            }
        }
        System.out.println("========================================");
        System.out.println("检测完成: 冲突 " + conflictCount + " 个类, 重复(内容一致) " + duplicateCount + " 个类");
    }

    private static void scanJarClasses(File jar, Map<String, Map<String, String>> classOwners) {
        try (JarFile jarFile = new JarFile(jar)) {
            Enumeration<JarEntry> entries = jarFile.entries();
            while (entries.hasMoreElements()) {
                JarEntry entry = entries.nextElement();
                if (entry.isDirectory()) {
                    continue;
                }
                String name = entry.getName();
                if (!name.endsWith(".class") || name.startsWith("META-INF/")) {
                    continue;
                }
                String className = JarUtil.classPathToName(name);
                if (className.equals("module-info") || className.equals("package-info")
                        || className.endsWith(".module-info") || className.endsWith(".package-info")) {
                    continue;
                }
                byte[] data = readEntry(jarFile, entry);
                String md5 = JarUtil.md5(data);
                classOwners.computeIfAbsent(className, k -> new TreeMap<>())
                        .put(jar.getName(), md5);
            }
        } catch (IOException e) {
            System.out.println("[警告] 无法读取 jar 文件: " + jar.getAbsolutePath() + " (" + e.getMessage() + ")");
        }
    }

    private static byte[] readEntry(JarFile jarFile, JarEntry entry) throws IOException {
        try (InputStream in = jarFile.getInputStream(entry);
             ByteArrayOutputStream out = new ByteArrayOutputStream()) {
            byte[] buf = new byte[8192];
            int n;
            while ((n = in.read(buf)) != -1) {
                out.write(buf, 0, n);
            }
            return out.toByteArray();
        }
    }
}

7

package com.jnliok.jarscaner;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

/**
 * 功能3:扫描指定目录下的所有 jar 包,检索指定的类名、包名或方法名
 */
public final class ClassSearcher {

    private ClassSearcher() {
    }

    public static void search(String dirPath, String pattern) {
        File dir = new File(dirPath);
        if (!dir.isDirectory()) {
            System.out.println("[错误] 目录不存在或不是有效目录: " + dirPath);
            return;
        }
        List<File> jars = JarUtil.listJars(dir);
        if (jars.isEmpty()) {
            System.out.println("目录下未找到 jar 文件: " + dir.getAbsolutePath());
            return;
        }
        System.out.println("检索模式: " + pattern);
        System.out.println("扫描目录: " + dir.getAbsolutePath());
        System.out.println("jar 文件数: " + jars.size());
        System.out.println("========================================");

        int hitJarCount = 0;
        int hitClassCount = 0;
        for (File jar : jars) {
            List<ClassModel> classes = JarScanner.scanJar(jar.getAbsolutePath());
            boolean jarHit = false;
            StringBuilder sb = new StringBuilder();
            for (ClassModel cm : classes) {
                List<MethodModel> matchedMethods = match(cm, pattern);
                if (matchedMethods == null) {
                    continue;
                }
                jarHit = true;
                hitClassCount++;
                sb.append("  类: ").append(cm.name).append('\n');
                for (MethodModel mm : matchedMethods) {
                    sb.append("    方法: ").append(mm.signature).append('\n');
                }
            }
            if (jarHit) {
                hitJarCount++;
                System.out.println("[命中] " + jar.getAbsolutePath());
                System.out.print(sb);
                System.out.println("----------------------------------------");
            }
        }
        System.out.println("========================================");
        System.out.println("命中 " + hitJarCount + " 个 jar 包, " + hitClassCount + " 个类");
    }

    /**
     * 匹配规则:
     * 1. 类全限定名完全匹配
     * 2. 类简单名完全匹配
     * 3. 包名匹配(等于或以 pattern. 开头)
     * 4. 方法名匹配(等于或包含)
     * 5. 类简单名包含匹配(模糊检索)
     *
     * @return 命中的方法列表(类/包名命中时为空列表);未命中返回 null
     */
    private static List<MethodModel> match(ClassModel cm, String pattern) {
        if (cm.name.equals(pattern) || cm.simpleName.equals(pattern)) {
            return new ArrayList<>();
        }
        if (cm.packageName.equals(pattern) || cm.packageName.startsWith(pattern + ".")) {
            return new ArrayList<>();
        }
        List<MethodModel> matched = new ArrayList<>();
        for (MethodModel mm : cm.methods) {
            if (mm.name.equals(pattern) || mm.name.contains(pattern)) {
                matched.add(mm);
            }
        }
        if (!matched.isEmpty()) {
            return matched;
        }
        if (cm.simpleName.contains(pattern)) {
            return new ArrayList<>();
        }
        return null;
    }
}

pom

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.jnliok</groupId>
	<artifactId>jar-scaner-kit</artifactId>
	<version>1.0.0</version>
	<name>jar-scaner-kit</name>
	<description>一个 jar 包扫描工具</description>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<maven.compiler.source>1.8</maven.compiler.source>
		<maven.compiler.target>1.8</maven.compiler.target>
	</properties>

	<dependencies>
		<dependency>
			<groupId>io.github.classgraph</groupId>
			<artifactId>classgraph</artifactId>
			<version>4.8.181</version>
		</dependency>
	</dependencies>

	<build>
		<finalName>jar-scaner-kit-1.0.0</finalName>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.10.1</version>
				<configuration>
					<source>1.8</source>
					<target>1.8</target>
					<encoding>UTF-8</encoding>
				</configuration>
			</plugin>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-shade-plugin</artifactId>
				<version>3.5.1</version>
				<executions>
					<execution>
						<phase>package</phase>
						<goals>
							<goal>shade</goal>
						</goals>
						<configuration>
							<createDependencyReducedPom>false</createDependencyReducedPom>
							<transformers>
								<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
									<mainClass>com.jnliok.jarscaner.Main</mainClass>
								</transformer>
							</transformers>
							<filters>
								<filter>
									<artifact>*:*</artifact>
									<excludes>
										<exclude>META-INF/*.SF</exclude>
										<exclude>META-INF/*.DSA</exclude>
										<exclude>META-INF/*.RSA</exclude>
									</excludes>
								</filter>
							</filters>
						</configuration>
					</execution>
				</executions>
			</plugin>
		</plugins>
	</build>
</project>

0

评论区