目录
- 目标
- 方式一:cfr
- pom引用
- java实现
- 方式二:jd-core
- pom引用
- java实现
- 方式三:procyon.jar
- jar下载
- java实现
- 方式四:procyon
- pom引用
- 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;
}
}