通过java代码,将jar或class反编译为java文件的四种方式

目标

在spring boot项目中,通过给定的文件地址,将*.jar或*.class反编译为*.java。

方式一:cfr

pom引用

        <!-- https://mvnrepository.com/artifact/org.benf/cfr -->
        <dependency>
            <groupId>org.benf</groupId>
            <artifactId>cfr</artifactId>
            <version>0.151</version>
        </dependency>

java实现

package com.demo.decompile.cfr;

import org.benf.cfr.reader.api.CfrDriver;
import org.benf.cfr.reader.util.getopt.OptionsImpl;
import org.springframework.util.ResourceUtils;

import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

/**
 * @Author: huangzh
 * @Date: 2024/3/5 19:43
 **/
public class CFRDemo {
    public static void main(String[] args) throws IOException {

        String sourceJar = "D:/xx/xx/xx.jar";
        Path sourceJarPath = Paths.get(sourceJar);
        String sourceJarFileName = sourceJarPath.getFileName().toString().replaceFirst("[.][^.]+$", "");

        File file = ResourceUtils.getFile("classpath:");
        String relativePath = file.getPath();
        String path = relativePath.substring(0, relativePath.lastIndexOf(File.separatorChar));
        String outputPath = path + File.separator + "cfr" + File.separator + sourceJarFileName;

        Long time = cfr(sourceJar, outputPath);
        System.out.println(String.format("decompiler time: %dms, outputPath: %s", time, outputPath));
    }

    public static Long cfr(String source, String targetPath) throws IOException {
        Long start = System.currentTimeMillis();
        // source jar
        List<String> files = new ArrayList<>();
        files.add(source);

        // target dir
        HashMap<String, String> outputMap = new HashMap<>();
        outputMap.put("outputdir", targetPath);

        OptionsImpl options = new OptionsImpl(outputMap);
        CfrDriver cfrDriver = new CfrDriver.Builder().withBuiltOptions(options).build();
        cfrDriver.analyse(files);
        Long end = System.currentTimeMillis();
        return (end - start);
    }
}

方式二:jd-core

pom引用


        <!-- https://mvnrepository.com/artifact/org.jd/jd-core -->
        <!-- 如果下载不下来,可使用华为镜像:https://repo.huaweicloud.com/repository/maven/org/jd/jd-core/1.1.3/jd-core-1.1.3.pom-->
        <dependency>
            <groupId>org.jd</groupId>
            <artifactId>jd-core</artifactId>
            <version>1.1.3</version>
        </dependency>

java实现

package com.demo.decompile.jdcore;

import org.jd.core.v1.ClassFileToJavaSourceDecompiler;
import org.jd.core.v1.api.loader.Loader;
import org.jd.core.v1.api.printer.Printer;
import org.springframework.util.ResourceUtils;
import org.springframework.util.StringUtils;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

/**
 * @Author: huangzh
 * @Date: 2024/3/5 10:36
 **/
public class JDCoreDemo {

    public static void main(String[] args) throws Exception {

        File file = ResourceUtils.getFile("classpath:");
        String relativePath = file.getPath();
        String path = JDCoreDecompiler.substringBeforeLast(relativePath, File.separator);
        System.out.println(path);

        JDCoreDecompiler jdCoreDecompiler = new JDCoreDecompiler();
        Long time = jdCoreDecompiler.decompiler("D:\\xx\\xx\\xx.jar", path + File.separator + "JDCore");
        System.out.println(String.format("decompiler time: %dms", time));
    }
}

class JDCoreDecompiler{

    private ClassFileToJavaSourceDecompiler decompiler = new ClassFileToJavaSourceDecompiler();
    // 存放字节码
    private HashMap<String,byte[]> classByteMap = new HashMap<>();

    /**
     * 注意:没有考虑一个 Java 类编译出多个 Class 文件的情况。
     *
     * @param source
     * @param target
     * @return
     * @throws Exception
     */
    public Long decompiler(String source,String target) throws Exception {
        long start = System.currentTimeMillis();
        String sourceFileName = substringAfterLast(source, File.separator);
        String sourcePathName = substringBeforeLast(sourceFileName, ".");
        target = target + File.separator + sourcePathName;

        // 解压
        archive(source);
        for (String className : classByteMap.keySet()) {
            String path = substringBeforeLast(className, "/");
            String name = substringAfterLast(className, "/");
            if (contains(name, "$")) {
                name = substringAfterLast(name, "$");
            }
            name = StringUtils.replace(name, ".class", ".java");
            decompiler.decompile(loader, printer, className);
            String context = printer.toString();
            Path targetPath = Paths.get(target + "/" + path + "/" + name);
            if (!Files.exists(Paths.get(target + "/" + path))) {
                Files.createDirectories(Paths.get(target + "/" + path));
            }
            Files.deleteIfExists(targetPath);
            Files.createFile(targetPath);
            Files.write(targetPath, context.getBytes());
        }
        return System.currentTimeMillis() - start;
    }

    /**
     * 解压
     * @param path
     * @throws IOException
     */
    private void archive(String path) throws IOException {
        try (ZipFile archive = new JarFile(new File(path))) {
            Enumeration<? extends ZipEntry> entries = archive.entries();
            while (entries.hasMoreElements()) {
                ZipEntry entry = entries.nextElement();
                if (!entry.isDirectory()) {
                    String name = entry.getName();
                    if (name.endsWith(".class")) {
                        byte[] bytes = null;
                        try (InputStream stream = archive.getInputStream(entry)) {
                            bytes = toByteArray(stream);
                        }
                        classByteMap.put(name, bytes);
                    }
                }
            }
        }
    }

    private Loader loader = new Loader() {
        @Override
        public byte[] load(String internalName) {
            return classByteMap.get(internalName);
        }
        @Override
        public boolean canLoad(String internalName) {
            return classByteMap.containsKey(internalName);
        }
    };

    private Printer printer = new Printer() {
        protected static final String TAB = "  ";
        protected static final String NEWLINE = "\n";
        protected int indentationCount = 0;
        protected StringBuilder sb = new StringBuilder();
        @Override public String toString() {
            String toString = sb.toString();
            sb = new StringBuilder();
            return toString;
        }
        @Override public void start(int maxLineNumber, int majorVersion, int minorVersion) {}
        @Override public void end() {}
        @Override public void printText(String text) { sb.append(text); }
        @Override public void printNumericConstant(String constant) { sb.append(constant); }
        @Override public void printStringConstant(String constant, String ownerInternalName) { sb.append(constant); }
        @Override public void printKeyword(String keyword) { sb.append(keyword); }
        @Override public void printDeclaration(int type, String internalTypeName, String name, String descriptor) { sb.append(name); }
        @Override public void printReference(int type, String internalTypeName, String name, String descriptor, String ownerInternalName) { sb.append(name); }
        @Override public void indent() { this.indentationCount++; }
        @Override public void unindent() { this.indentationCount--; }
        @Override public void startLine(int lineNumber) { for (int i=0; i<indentationCount; i++) sb.append(TAB); }
        @Override public void endLine() { sb.append(NEWLINE); }
        @Override public void extraLine(int count) { while (count-- > 0) sb.append(NEWLINE); }
        @Override public void startMarker(int type) {}
        @Override public void endMarker(int type) {}
    };


    /**
     * Represents a failed index search.
     */
    public static final int INDEX_NOT_FOUND = -1;

    /**
     * The empty String {@code ""}.
     */
    public static final String EMPTY = "";

    public static String substringBeforeLast(final String str, final String separator) {
        if (isEmpty(str) || isEmpty(separator)) {
            return str;
        }
        final int pos = str.lastIndexOf(separator);
        if (pos == INDEX_NOT_FOUND) {
            return str;
        }
        return str.substring(0, pos);
    }

    /**
     * Checks if a CharSequence is empty ("") or null.
     * @param cs
     * @return
     */
    public static boolean isEmpty(final CharSequence cs) {
        return cs == null || cs.length() == 0;
    }


    /**
     * Checks if CharSequence contains a search CharSequence, handling {@code null}.
     * This method uses {@link String#indexOf(String)} if possible.
     * @param seq
     * @param searchSeq
     * @return
     */
    public static boolean contains(final CharSequence seq, final CharSequence searchSeq) {
        if (seq == null || searchSeq == null) {
            return false;
        }
        return indexOf(seq, searchSeq, 0) >= 0;
    }

    /**
     * Used by the indexOf(CharSequence methods) as a green implementation of indexOf.
     * @param cs
     * @param searchChar
     * @param start
     * @return
     */
    static int indexOf(final CharSequence cs, final CharSequence searchChar, final int start) {
        if (cs instanceof String) {
            return ((String) cs).indexOf(searchChar.toString(), start);
        }
        if (cs instanceof StringBuilder) {
            return ((StringBuilder) cs).indexOf(searchChar.toString(), start);
        }
        if (cs instanceof StringBuffer) {
            return ((StringBuffer) cs).indexOf(searchChar.toString(), start);
        }
        return cs.toString().indexOf(searchChar.toString(), start);
    }


    /**
     * Gets the substring after the last occurrence of a separator.
     * The separator is not returned.
     * @param str
     * @param separator
     * @return
     */
    public static String substringAfterLast(final String str, final String separator) {
        if (isEmpty(str)) {
            return str;
        }
        if (isEmpty(separator)) {
            return EMPTY;
        }
        final int pos = str.lastIndexOf(separator);
        if (pos == INDEX_NOT_FOUND || pos == str.length() - separator.length()) {
            return EMPTY;
        }
        return str.substring(pos + separator.length());
    }


    public static byte[] toByteArray(InputStream inputStream) throws IOException {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        byte[] buffer = new byte[4096];
        int bytesRead;

        while ((bytesRead = inputStream.read(buffer)) != -1) {
            outputStream.write(buffer, 0, bytesRead);
        }
        return outputStream.toByteArray();
    }
}

方式三:procyon.jar

jar下载

procyon-decompiler-0.6.0.jar下载地址:https://github.com/mstrobel/procyon/releases/tag/v0.6.0
下载后可存放于合适的位置,如:src/main/resources/procyon/procyon-decompiler-0.6.0.jar
在这里插入图片描述

java实现

package com.demo.decompile.procyon;

import org.springframework.util.ResourceUtils;

import java.io.*;
import java.nio.file.Path;
import java.nio.file.Paths;

/**
 * @Author: huangzh
 * @Date: 2024/3/7 11:46
 **/
public class ProcyonJarDemo {
    public static void main(String[] args) throws FileNotFoundException {
        String sourceJar = "D:/xx/xx/xx.jar";
        Path sourceJarPath = Paths.get(sourceJar);
        String sourceJarFileName = sourceJarPath.getFileName().toString().replaceFirst("[.][^.]+$", "");

        File file = ResourceUtils.getFile("classpath:");
        String relativePath = file.getPath();
        String path = relativePath.substring(0, relativePath.lastIndexOf(File.separatorChar));
        String outputPath = path + File.separator + "procyon" + File.separator + sourceJarFileName;

        String[] command = {
                "java",
                "-jar",
                "src/main/resources/procyon/procyon-decompiler-0.6.0.jar",
                "-jar",
                sourceJar,
                "-o",
                outputPath
        };

        try {
            Process process = new ProcessBuilder(command).start();

            InputStream inputStream = process.getInputStream();
            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println("info: " + line);
            }

            InputStream inputStreamErr = process.getErrorStream();
            BufferedReader readerErr = new BufferedReader(new InputStreamReader(inputStreamErr));
            String lineErr;
            while ((lineErr = readerErr.readLine()) != null) {
                System.out.println("error: " + lineErr);
            }

            // waiting for command execution to complete
            int exitCode = process.waitFor();
            System.out.println("decompile completed,exit code: " + exitCode);
            System.out.println("output dir: " + outputPath);
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
    }
}

方式四:procyon

pom引用

        <!-- https://mvnrepository.com/artifact/org.jboss.windup.decompiler/decompiler-procyon -->
        <dependency>
            <groupId>org.jboss.windup.decompiler</groupId>
            <artifactId>decompiler-procyon</artifactId>
            <version>5.1.4.Final</version>
        </dependency>

java实现

package com.demo.decompile.procyon;

import org.jboss.windup.decompiler.api.DecompilationFailure;
import org.jboss.windup.decompiler.api.DecompilationListener;
import org.jboss.windup.decompiler.api.DecompilationResult;
import org.jboss.windup.decompiler.api.Decompiler;
import org.jboss.windup.decompiler.procyon.ProcyonDecompiler;
import org.springframework.util.ResourceUtils;

import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Iterator;
import java.util.List;

/**
 * @Author: huangzh
 * @Date: 2024/3/5 18:42
 **/
public class ProcyonDemo {
    public static void main(String[] args) throws IOException {

        String sourceJar = "D:/xx/xx/xx.jar";
        Path sourceJarPath = Paths.get(sourceJar);
        String sourceJarFileName = sourceJarPath.getFileName().toString().replaceFirst("[.][^.]+$", "");

        File file = ResourceUtils.getFile("classpath:");
        String relativePath = file.getPath();
        String path = relativePath.substring(0, relativePath.lastIndexOf(File.separatorChar));
        String outputPath = path + File.separator + "procyon" + File.separator + sourceJarFileName;

        Long time = procyon(sourceJar, outputPath);
        System.out.println(String.format("decompiler time: %dms", time));
    }

    /**
     * 解析存在问题,不推荐
     *
     * @param source
     * @param targetPath
     * @return
     * @throws IOException
     */
    public static Long procyon(String source, String targetPath) throws IOException {
        long start = System.currentTimeMillis();
        Path archive = Paths.get(source);
        Path outDir = Paths.get(targetPath);
        Decompiler dec = new ProcyonDecompiler();
        DecompilationResult res = dec.decompileArchive(archive, outDir, new DecompilationListener() {
            public void decompilationProcessComplete() {
                System.out.println("decompilationProcessComplete");
            }

            public void decompilationFailed(List<String> inputPath, String message) {
                System.out.println("decompilationFailed");
            }

            public void fileDecompiled(List<String> inputPath, String outputPath) {
            }

            public boolean isCancelled() {
                return false;
            }
        });

        if (!res.getFailures().isEmpty()) {
            StringBuilder sb = new StringBuilder();
            sb.append("Failed decompilation of " + res.getFailures().size() + " classes: ");
            Iterator failureIterator = res.getFailures().iterator();
            while (failureIterator.hasNext()) {
                DecompilationFailure dex = (DecompilationFailure) failureIterator.next();
                sb.append(System.lineSeparator() + "    ").append(dex.getMessage());
            }
            System.out.println(sb);
        }
        System.out.println("Compilation results: " + res.getDecompiledFiles().size() + " succeeded, " + res.getFailures().size() + " failed.");
        dec.close();
        Long end = System.currentTimeMillis();
        return end - start;
    }
}
  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值