使用 javassist 可以操作字节码文件,我分享一下一个简单的编译和类方法解析代码。
什么是 Javassist?
Javassist 是一个强大的字节码操作工具,它提供了在运行时编辑 Java 字节码的能力。通过Javassist,开发人员可以动态地创建和修改 Java 类
。这使得在不重新编译整个程序的情况下,能够对类进行动态修改和增强。
代码(请勿转载)
package com.hao.insertmu;import javassist.ClassPool;
import javassist.CtClass;
import javassist.bytecode.MethodInfo;
import org.jetbrains.annotations.NotNull;import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.*;// 8.8 最新版
public class CompileCodeAndAnalyze {public static void main(String[] args) {String javaFilePath = "!!!Java 文件路径!!!!";String classOutputDir = "out";List<ClassAnalysisResult> results = getClassAnalysisResult(javaFilePath, classOutputDir, "major");for (ClassAnalysisResult result : results) {System.out.println(result.getClassName() + "@" + result.getMethodName() + "(" + getParametersDescription(result) + ")");System.out.println("\t Arrays.asList(" + getParametersSimpleName(result) + ")");}boolean isCompiled = compileJavaFile(javaFilePath, classOutputDir);if (isCompiled) {String className = getFullClassName(javaFilePath);List<ClassAnalysisResult> results2 = analyzeClass(className, javaFilePath, classOutputDir, "major");// 打印结果for (ClassAnalysisResult result : results2) {System.out.println(result.getClassName() + "@" + result.getMethodName() + "(" + getParametersDescription(result) + ")");System.out.println("\tReturns: " + result.getReturnType()); // 打印返回类型System.out.println("\tArrays.asList(" + getParametersSimpleName(result) + ")");}} else {System.out.println("Compilation failed.");}}private static String getFullClassName(String javaFilePath) {String packageName = "";String className = "";try (BufferedReader reader = new BufferedReader(new FileReader(javaFilePath))) {String line;while ((line = reader.readLine()) != null) {if (line.startsWith("package")) {packageName = line.substring(line.indexOf("package") + 8, line.indexOf(';')).trim();} else if (line.startsWith("public class") || line.startsWith("class") ||line.startsWith("public static class") || line.startsWith("static class")) {line = line.replace("{", "").trim();className = line.substring(line.lastIndexOf(' ') + 1).trim();break;}}} catch (IOException e) {e.printStackTrace();}return packageName.isEmpty() ? className : packageName + "." + className;}private static boolean checkJavaCompilerVersion(@NotNull JavaCompiler compiler) {String version = System.getProperty("java.version");System.out.println("Java version: " + version);if (version.startsWith("1.")) {version = version.substring(2, 3);} else {int dotIndex = version.indexOf('.');if (dotIndex != -1) {version = version.substring(0, dotIndex);}}int majorVersion = Integer.parseInt(version);if (majorVersion < 8) {System.out.println("Error: JDK version is below 8. Please update to JDK 8 or higher.");return false;} else {System.out.println("Java Compiler version is sufficient (JDK 8 or higher).");return true;}}@SuppressWarnings("ResultOfMethodCallIgnored")public static boolean compileJavaFile(String javaFilePath, String outputDir) {JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();if (compiler == null) {System.out.println("Java Compiler not found. Make sure you're using a JDK.");return false;}if (!checkJavaCompilerVersion(compiler)) {return false;}Path tempDir;try {tempDir = Files.createTempDirectory("compile_output");} catch (IOException e) {System.out.println("Failed to create temporary directory.");return false;}int compilationResult = compiler.run(null, null, null, "-encoding", "utf8", "-parameters", "-d", tempDir.toString(), javaFilePath);if (compilationResult == 0) {System.out.println("Compilation successful.");try {Files.createDirectories(Paths.get(outputDir));Files.walk(tempDir).forEach(source -> {try {Path destination = Paths.get(outputDir, source.toString().substring(tempDir.toString().length()));if (Files.isDirectory(source)) {Files.createDirectories(destination);} else {Files.copy(source, destination, StandardCopyOption.REPLACE_EXISTING);}} catch (IOException e) {e.printStackTrace();}});} catch (IOException e) {e.printStackTrace();return false;} finally {try {Files.walk(tempDir).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete);} catch (IOException e) {e.printStackTrace();}}return true;} else {System.out.println("Compilation failed.");return false;}}public static List<ClassAnalysisResult> getClassAnalysisResult(String javaFilePath, String classOutputDir, String logInfoType) {boolean isCompiled = compileJavaFile(javaFilePath, classOutputDir);List<ClassAnalysisResult> results = null;if (isCompiled) {String className = getFullClassName(javaFilePath);results = analyzeClass(className, classOutputDir, classOutputDir, logInfoType);} else {System.out.println("Compilation failed.");}return results;}public static List<ClassAnalysisResult> analyzeClass(String className, String classPath,String classOutputDir, String logInfoType) {List<ClassAnalysisResult> results = new ArrayList<>();try {File file = new File(classOutputDir);ClassLoader classLoader = new URLClassLoader(new java.net.URL[]{file.toURI().toURL()});Class<?> clazz = Class.forName(className, true, classLoader);//System.out.println(className);// 使用你想要解析的类ClassPool pool = ClassPool.getDefault();pool.insertClassPath(classPath);CtClass ctClass = pool.get(className); // 你可以替换为需要解析的类名analyzeClassRecursively(clazz, ctClass, results, logInfoType);} catch (Exception e) {e.printStackTrace();}return results;}private static void analyzeClassRecursively(Class<?> clazz, CtClass ctClass, List<ClassAnalysisResult> results, String logInfoType) {Constructor<?>[] constructors = clazz.getDeclaredConstructors();for (Constructor<?> constructor: constructors) {List<ParameterInfo> parameters = new ArrayList<>();for (Parameter parameter : constructor.getParameters()) {parameters.add(new ParameterInfo(parameter.getName(), parameter.getType().getCanonicalName()));}results.add(new ClassAnalysisResult(clazz.getName(), "<init>", parameters, "void"));}// 解析静态初始化块ClassAnalysisResult result = parseStaticInitializer(ctClass);if(result != null) {results.add(result);}Method[] methods = clazz.getDeclaredMethods();for (Method method : methods) {List<ParameterInfo> parameters = new ArrayList<>();for (Parameter parameter : method.getParameters()) {String typeName;// "mujava" 情况的处理if ("mujava".compareTo(logInfoType) == 0) {Class<?> parameterType = parameter.getType();typeName = parameterType.getSimpleName(); // 获取简单名称if (parameterType.isArray()) {// 如果是数组类型,获取其元素类型的简单名称typeName = parameterType.getComponentType().getSimpleName();}} else {// 默认处理方式,获取参数类型的全限定名typeName = parameter.getType().getCanonicalName();}// 添加参数信息parameters.add(new ParameterInfo(parameter.getName(), typeName));}// 获取返回类型String returnType = method.getReturnType().getCanonicalName();results.add(new ClassAnalysisResult(clazz.getName(), method.getName(), parameters, returnType));}Class<?>[] innerClasses = clazz.getDeclaredClasses();for (Class<?> innerClass : innerClasses) {analyzeClassRecursively(innerClass, ctClass, results, logInfoType);}}private static ClassAnalysisResult parseStaticInitializer(CtClass ctClass) {MethodInfo clinitMethodInfo = ctClass.getClassFile().getMethod("<clinit>");if (clinitMethodInfo != null) {return new ClassAnalysisResult(ctClass.getName(), "", new ArrayList<>(), "");}return null;}public static String getParametersDescription(ClassAnalysisResult analysisResult) {if (analysisResult == null) {return "";}List<ParameterInfo> parameters = analysisResult.getParameters();StringBuilder paramsDescription = new StringBuilder();for (int i = 0; i < parameters.size(); i++) {paramsDescription.append(parameters.get(i).getType()).append(" ").append(parameters.get(i).getName());if (i < parameters.size() - 1) {paramsDescription.append(",");}}return paramsDescription.toString();}public static String getParametersTypesName(ClassAnalysisResult analysisResult) {if (analysisResult == null) {return "";}List<ParameterInfo> parameters = analysisResult.getParameters();StringBuilder paramsTypesName = new StringBuilder();for (int i = 0; i < parameters.size(); i++) {paramsTypesName.append(parameters.get(i).getType());if (i < parameters.size() - 1) {paramsTypesName.append(",");}}return paramsTypesName.toString();}public static String getParametersSimpleName(ClassAnalysisResult analysisResult) {if (analysisResult == null) {return "";}List<ParameterInfo> parameters = analysisResult.getParameters();StringBuilder paramsSimpleName = new StringBuilder();for (int i = 0; i < parameters.size(); i++) {paramsSimpleName.append(parameters.get(i).getName());if (i < parameters.size() - 1) {paramsSimpleName.append(",");}}return paramsSimpleName.toString();}public static Map<String, String> getClassInfoHashMap(List<ClassAnalysisResult> analysisResults, String logInfoType) {Map<String, String> infoMap = new HashMap<>();if("major".compareTo(logInfoType) == 0)for (ClassAnalysisResult result:analysisResults) {String flatName;if(result.getMethodName().isEmpty()) {flatName = result.getClassName();} else {flatName = result.getClassName()+ "@" + result.getMethodName()+ "(" + getParametersTypesName(result) + ")";}String paramsList = getParametersSimpleName(result);infoMap.put(flatName, paramsList);}elsefor (ClassAnalysisResult result:analysisResults) {String flatName;flatName = result.getReturnType()+ "_" + result.getMethodName()+ "(" + getParametersTypesName(result) + ")";String paramsList = getParametersSimpleName(result);infoMap.put(flatName, paramsList);}return infoMap;}
}class ClassAnalysisResult {private final String className;private final String methodName;private final List<ParameterInfo> parameters;private final String returnType;public ClassAnalysisResult(String className, String methodName, List<ParameterInfo> parameters, String returnType) {this.className = className;this.methodName = methodName;this.parameters = parameters;this.returnType = returnType;}public String getClassName() {return className;}public String getMethodName() {return methodName;}public List<ParameterInfo> getParameters() {return parameters;}public String getReturnType() {return returnType;}
}class ParameterInfo {private final String name;private final String type;public ParameterInfo(String name, String type) {this.name = name;this.type = type;}public String getName() {return name;}public String getType() {return type;}
}
配合 mujava 或者 major 使用。
调用示例:
// 获取代码的方法签名信息 List<ClassAnalysisResult> results = getClassAnalysisResult(sourceCodeFilePath,"out", logType);其中,sourceCodeFilePath 为源代码路径,"out" 是输出路径,logType 是格式化输出匹配的工具日志类型(目前支持: mujava、major)。返回类中每个方法的分析结果。
执行效果展示:
文章出处链接:[https://blog.csdn.net/qq_59075481/article/details/144888424].