1+ import java .io .*;
2+ import java .nio .file .*;
3+ import java .util .*;
4+ import java .util .concurrent .ExecutorService ;
5+ import java .util .concurrent .Executors ;
6+ import java .util .concurrent .TimeUnit ;
7+ import java .util .regex .*;
8+ import java .util .jar .*;
9+ import java .util .zip .*;
10+ import org .apache .bcel .classfile .ClassParser ;
11+ import org .apache .bcel .classfile .JavaClass ;
12+ import org .apache .bcel .classfile .Method ;
13+
14+ public class JarMethodSearcher {
15+
16+ public static void main (String [] args ) {
17+ // if (args.length != 2) {
18+ // System.err.println("Usage: java -jar JarMethodFinder.jar <folderPath> <keyword>");
19+ // return;
20+ // }
21+ //
22+ // String folderPath = args[0];
23+ // String keyword = args[1];
24+ String folderPath = "F:\\ yonyou-modules-2\\ modules\\ aedsm\\ lib" ;
25+ String keyword = "loadAttributes" ;
26+
27+ try {
28+ List <Path > jarFiles = findJarsRecursively (folderPath );
29+ System .out .printf ("找到 %d 个 .jar 文件,正在搜索关键字 \" %s\" ...\n " , jarFiles .size (), keyword );
30+
31+ // 获取 CPU 核心数作为线程池大小
32+ int threadCount = Runtime .getRuntime ().availableProcessors ();
33+ ExecutorService executor = Executors .newFixedThreadPool (threadCount );
34+
35+ // 并行处理每个 JAR
36+ for (Path jarFile : jarFiles ) {
37+ Path finalJarFile = jarFile ;
38+ executor .submit (() -> {
39+ try {
40+ System .out .println ("\n 🔍 正在处理 JAR:" + finalJarFile .getFileName ());
41+ searchInJar (finalJarFile .toString (), keyword );
42+ } catch (Exception e ) {
43+ System .err .println ("⚠️ 处理失败:" + finalJarFile .getFileName ());
44+ }
45+ });
46+ }
47+
48+ executor .shutdown ();
49+ executor .awaitTermination (Long .MAX_VALUE , TimeUnit .NANOSECONDS );
50+
51+ } catch (Exception e ) {
52+ e .printStackTrace ();
53+ }
54+ }
55+
56+ // 递归查找所有 .jar 文件
57+ private static List <Path > findJarsRecursively (String rootDir ) throws IOException {
58+ List <Path > jars = new ArrayList <>();
59+ Path rootPath = Paths .get (rootDir );
60+
61+ if (!Files .exists (rootPath )) {
62+ System .err .println ("路径不存在:" + rootDir );
63+ return jars ;
64+ }
65+
66+ Files .walk (rootPath )
67+ .filter (path -> path .toString ().toLowerCase ().endsWith (".jar" ))
68+ .forEach (jars ::add );
69+
70+ return jars ;
71+ }
72+
73+ // 主流程:字节码扫描 + 反编译搜索双模式
74+ private static void searchInJar (String jarPath , String keyword ) throws Exception {
75+ Pattern pattern = Pattern .compile (Pattern .quote (keyword ), Pattern .CASE_INSENSITIVE );
76+
77+ Path tempDir = Files .createTempDirectory ("decompiled_" );
78+
79+ // Step 1: 字节码扫描,获取命中的类名
80+ List <String > matchedClassEntries = scanBytecodeForMethods (jarPath , keyword );
81+
82+ if (matchedClassEntries .isEmpty ()) {
83+ System .out .println ("⚠️ 未找到匹配项" );
84+ deleteDirectory (tempDir );
85+ return ;
86+ }
87+
88+ // Step 2: 提取命中的 .class 文件
89+ List <Path > classFiles = extractOnlyMatchedClasses (jarPath , matchedClassEntries , tempDir );
90+
91+ // Step 3: 只反编译命中的类
92+ List <Path > javaFiles = decompileWithCFR (classFiles , tempDir );
93+
94+ // Step 4: 搜索源码上下文
95+ for (Path javaFile : javaFiles ) {
96+ searchInFile (javaFile , pattern );
97+ }
98+
99+ deleteDirectory (tempDir );
100+ }
101+
102+ // 新增方法:只提取命中的类文件
103+ private static List <Path > extractOnlyMatchedClasses (String jarPath , List <String > matchedEntries , Path destDir ) throws IOException {
104+ List <Path > classFiles = new ArrayList <>();
105+ try (JarFile jarFile = new JarFile (jarPath )) {
106+ for (String entryName : matchedEntries ) {
107+ JarEntry entry = jarFile .getJarEntry (entryName );
108+ if (entry != null ) {
109+ Path target = destDir .resolve (entryName );
110+ Files .createDirectories (target .getParent ());
111+ try (InputStream is = jarFile .getInputStream (entry )) {
112+ Files .copy (is , target , StandardCopyOption .REPLACE_EXISTING );
113+ }
114+ classFiles .add (target );
115+ }
116+ }
117+ }
118+ return classFiles ;
119+ }
120+
121+ // 从字节码中提取方法名和签名(支持接口方法)
122+ private static List <String > scanBytecodeForMethods (String jarPath , String keyword ) throws Exception {
123+ List <String > matchedClasses = new ArrayList <>();
124+
125+ try (JarFile jar = new JarFile (jarPath )) {
126+ Enumeration <JarEntry > entries = jar .entries ();
127+
128+ while (entries .hasMoreElements ()) {
129+ JarEntry entry = entries .nextElement ();
130+ if (entry .getName ().endsWith (".class" )) {
131+ try (InputStream is = jar .getInputStream (entry )) {
132+ JavaClass clazz = new ClassParser (is , entry .getName ()).parse ();
133+
134+ for (Method method : clazz .getMethods ()) {
135+ String methodName = method .getName ();
136+ String signature = method .getSignature ();
137+ if (methodName .toLowerCase ().contains (keyword .toLowerCase ())) {
138+ System .out .println ("【字节码匹配】" + clazz .getClassName () + "." + methodName + signature );
139+ System .out .println (" 来自:" + jarPath );
140+ matchedClasses .add (entry .getName ()); // 记录命中的类路径
141+ }
142+ }
143+ }
144+ }
145+ }
146+ }
147+
148+ return matchedClasses ;
149+ }
150+
151+ // 使用 CFR 反编译 .class 文件为 .java
152+ private static List <Path > decompileWithCFR (List <Path > classFiles , Path outputDir ) throws Exception {
153+ List <Path > javaFiles = new ArrayList <>();
154+
155+ for (Path classFile : classFiles ) {
156+ String className = classFile .toString ().replace (".class" , "" );
157+ String cfrPath = "C:\\ Users\\ 13740\\ Downloads\\ cfr-0.152.jar" ;
158+ ProcessBuilder pb = new ProcessBuilder ("java" , "-jar" , cfrPath , classFile .toString (), "--outputdir" , outputDir .toString ());
159+ pb .redirectErrorStream (true );
160+ Process process = pb .start ();
161+
162+ BufferedReader reader = new BufferedReader (new InputStreamReader (process .getInputStream ()));
163+ String line ;
164+ while ((line = reader .readLine ()) != null ) {
165+ // 可选打印日志
166+ }
167+
168+ Path javaFile = outputDir .resolve (className + ".java" );
169+ if (Files .exists (javaFile )) {
170+ javaFiles .add (javaFile );
171+ } else {
172+ System .err .println ("⚠️ 反编译失败:" + classFile .getFileName ());
173+ }
174+ }
175+
176+ return javaFiles ;
177+ }
178+
179+ // 在反编译后的 .java 文件中搜索关键字并显示上下文
180+ private static void searchInFile (Path filePath , Pattern pattern ) throws IOException {
181+ List <String > lines = Files .readAllLines (filePath );
182+ boolean found = false ;
183+
184+ for (int i = 0 ; i < lines .size (); i ++) {
185+ Matcher matcher = pattern .matcher (lines .get (i ));
186+ if (matcher .find () && !found ) {
187+ found = true ;
188+
189+ System .out .println ("\n 【代码匹配】文件:" + filePath .getFileName ());
190+ System .out .println ("匹配位置:第 " + (i + 1 ) + " 行" );
191+ System .out .println ("上下文(共6行):" );
192+
193+ int start = Math .max (0 , i - 3 );
194+ int end = Math .min (lines .size () - 1 , i + 3 );
195+ for (int j = start ; j <= end ; j ++) {
196+ String prefix = (j == i ) ? ">>> " : " " ;
197+ System .out .printf ("%4d: %s%s%n" , j + 1 , prefix , lines .get (j ));
198+ }
199+
200+ } else if (matcher .find () && found ) {
201+ System .out .println (" ... 其他匹配项已省略" );
202+ break ;
203+ }
204+ }
205+ }
206+
207+ // 清理临时目录
208+ private static void deleteDirectory (Path path ) throws IOException {
209+ if (Files .exists (path )) {
210+ Files .walk (path )
211+ .sorted (Comparator .reverseOrder ())
212+ .forEach (p -> {
213+ try {
214+ Files .deleteIfExists (p );
215+ } catch (IOException ignored ) {}
216+ });
217+ }
218+ }
219+ }
0 commit comments