基于JavaParser实现Spring Boot Maven项目类依赖分析与代码生成
Great question! Building a standalone command-line tool for dependency tracing and index building using JavaParser (instead of Eclipse JDT which is tied to plugin development) is totally doable. Here's a step-by-step guide to implement this for your Spring Boot Maven project:
1. Project Setup & Dependencies
First, create a new Maven project and add these dependencies to your pom.xml—they cover JavaParser's core/symbol-solving capabilities plus Maven dependency resolution tools:
<dependencies> <!-- JavaParser core for parsing source code --> <dependency> <groupId>com.github.javaparser</groupId> <artifactId>javaparser-core</artifactId> <version>3.25.8</version> </dependency> <!-- JavaParser Symbol Solver for resolving type references --> <dependency> <groupId>com.github.javaparser</groupId> <artifactId>javaparser-symbol-solver-core</artifactId> <version>3.25.8</version> </dependency> <!-- Maven tools to parse pom.xml and resolve dependencies --> <dependency> <groupId>org.apache.maven</groupId> <artifactId>maven-model</artifactId> <version>3.9.4</version> </dependency> <dependency> <groupId>org.apache.maven.resolver</groupId> <artifactId>maven-resolver-api</artifactId> <version>1.9.13</version> </dependency> <dependency> <groupId>org.apache.maven.resolver</groupId> <artifactId>maven-resolver-impl</artifactId> <version>1.9.13</version> </dependency> </dependencies>
2. Resolve Maven Dependencies & Build Classpath
Before analyzing code, you need to fetch all jar paths from the target Spring Boot project's dependencies (including local Maven repo jars):
// Parse the target project's pom.xml MavenXpp3Reader pomReader = new MavenXpp3Reader(); Model mavenModel = pomReader.read(new FileInputStream("/path/to/target/project/pom.xml")); // Initialize Maven Resolver to fetch dependency paths RepositorySystem repoSystem = RepositorySystemUtils.newRepositorySystem(); RepositorySystemSession repoSession = MavenRepositorySystemUtils.newSession(); LocalRepository localRepo = new LocalRepository(System.getProperty("user.home") + "/.m2/repository"); repoSession.setLocalRepositoryManager(repoSystem.newLocalRepositoryManager(repoSession, localRepo)); // Add Maven Central (or your private repos) List<RemoteRepository> remoteRepos = List.of( new RemoteRepository.Builder("central", "default", "https://repo1.maven.org/maven2/").build() ); // Resolve full dependency tree DependencyRequest depRequest = new DependencyRequest(); depRequest.setRoot(new org.apache.maven.model.Dependency( mavenModel.getGroupId(), mavenModel.getArtifactId(), mavenModel.getVersion() )); depRequest.setRepositories(remoteRepos); DependencyResult depResult = repoSystem.resolveDependencies(repoSession, depRequest); List<String> jarPaths = depResult.getArtifactResults().stream() .map(ArtifactResult::getArtifact) .map(Artifact::getFile) .map(File::getAbsolutePath) .filter(path -> path.endsWith(".jar")) .collect(Collectors.toList());
3. Configure JavaParser with Symbol Solver
The Symbol Solver is key to linking method calls (like StringUtils.rpad()) to their actual class definitions in jars:
// Combine type solvers for project source + dependency jars CombinedTypeSolver typeSolver = new CombinedTypeSolver(); // Add target project's source code typeSolver.add(new JavaParserTypeSolver(new File("/path/to/target/project/src/main/java"))); // Add all dependency jars for (String jarPath : jarPaths) { typeSolver.add(new JarTypeSolver(new File(jarPath))); } // Initialize JavaParser with symbol resolution enabled ParserConfiguration parserConfig = new ParserConfiguration(); parserConfig.setSymbolResolver(new SymbolSolver(typeSolver)); JavaParser javaParser = new JavaParser(parserConfig);
4. Trace Method Calls to Their Jar Origins
Now you can scan the target project's source code, find method calls, and map them to their containing jars:
// Walk through all Java files in the target project Files.walk(Paths.get("/path/to/target/project/src/main/java")) .filter(path -> path.toString().endsWith(".java")) .forEach(path -> { try { CompilationUnit compilationUnit = javaParser.parse(path.toFile()).getResult().orElseThrow(); // Find all method call expressions compilationUnit.findAll(MethodCallExpr.class).forEach(methodCall -> { methodCall.resolve().ifPresent(resolvedMethod -> { ResolvedTypeDeclaration declaringClass = resolvedMethod.declaringType(); String className = declaringClass.getQualifiedName(); // Find which jar contains this class Optional<JarTypeSolver> jarSource = typeSolver.solvers().stream() .filter(s -> s instanceof JarTypeSolver) .map(s -> (JarTypeSolver)s) .filter(s -> s.tryToSolveType(className).isPresent()) .findFirst(); jarSource.ifPresent(solver -> { System.out.printf("Call to %s.%s() found in jar: %s%n", className, resolvedMethod.getName(), solver.getJarFile().getAbsolutePath()); }); }); }); } catch (IOException e) { e.printStackTrace(); } });
For example, when this code encounters StringUtils.rpad(...), it will output the full path to the Spring jar containing that StringUtils class.
5. Build a Class Index for Refactoring Support
To enable refactoring operations, store parsed class/method data in an index for quick lookup:
// Simple class to hold class metadata class ClassMetadata { private String qualifiedName; private String jarPath; private List<String> methodNames; // Constructor, getters, setters omitted for brevity } // Build the index Map<String, ClassMetadata> classIndex = new HashMap<>(); for (String jarPath : jarPaths) { JarTypeSolver jarSolver = new JarTypeSolver(new File(jarPath)); jarSolver.getAllTypeNames().forEach(className -> { jarSolver.tryToSolveType(className).ifPresent(type -> { List<String> methods = type.getDeclaredMethods().stream() .map(ResolvedMethodDeclaration::getName) .collect(Collectors.toList()); classIndex.put(className, new ClassMetadata(className, jarPath, methods)); }); }); }
You can use this index to quickly find all references to a class/method, or batch-update method calls during refactoring.
6. Package as a Standalone Command-Line Tool
Use Maven's Assembly Plugin to package your tool into an executable jar with all dependencies:
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <version>3.6.0</version> <configuration> <archive> <manifest> <mainClass>com.yourpackage.Main</mainClass> </manifest> </archive> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> </configuration> <executions> <execution> <id>build-executable-jar</id> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> </plugin> </plugins> </build>
Run it from the command line like this:
java -jar your-dependency-trace-tool.jar --project /path/to/your/spring-boot-project
Key Notes
- Spring Boot Fat Jars: If your target project uses a fat jar, you'll need to either extract it first or use a library like
spring-boot-loaderto access internal jars. - Performance: For large projects, parallelize file scanning and cache the class index to speed up repeated runs.
- Complex Types: JavaParser's Symbol Solver handles most cases, but you may need to add custom logic for edge cases like nested classes or generic method references.
内容的提问来源于stack exchange,提问作者user1539343




