You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何独立启动VSCode Java Language Server提取不可编译Java项目调用图

独立启动VS Code Java Language Server并提取调用图指南

一、获取Java Language Server(JLS)程序

VS Code的Red Hat官方Java扩展内置了独立的JLS,可直接从已安装的扩展目录提取:

  • 找到扩展安装路径:Windows为%USERPROFILE%\.vscode\extensions\redhat.java-<版本号>,Linux/macOS为~/.vscode/extensions/redhat.java-<版本号>
  • 进入server目录,提取jdt-language-server-<版本号>.jar,这是JLS核心运行程序
  • 同时复制对应系统的config_<系统>目录(如Linux对应config_linux),用于JLS的配置加载

二、启动Java Language Server

使用Java命令直接启动JLS,默认通过标准输入输出(stdio)与客户端通信:

# 替换为你的实际路径
java -jar /path/to/jdt-language-server-<版本号>.jar \
  -configuration /path/to/config_<系统> \
  -data /path/to/temp-workspace-dir
  • -data指定的临时工作目录用于存储JLS的索引缓存,建议每次运行新建空目录,避免遗留数据干扰

三、编写LSP客户端与JLS交互

LSP基于JSON-RPC协议,推荐使用eclipse-lsp4j库编写Java客户端,核心步骤与示例如下:

1. 引入依赖(Maven)

<dependencies>
    <dependency>
        <groupId>org.eclipse.lsp4j</groupId>
        <artifactId>org.eclipse.lsp4j</artifactId>
        <version>0.21.1</version>
    </dependency>
    <dependency>
        <groupId>org.eclipse.lsp4j</groupId>
        <artifactId>org.eclipse.lsp4j.jsonrpc</artifactId>
        <version>0.21.1</version>
    </dependency>
</dependencies>

2. 客户端核心代码示例

import org.eclipse.lsp4j.*;
import org.eclipse.lsp4j.jsonrpc.Launcher;
import org.eclipse.lsp4j.services.LanguageClient;
import org.eclipse.lsp4j.services.LanguageServer;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Path;
import java.util.Collections;
import java.util.concurrent.CompletableFuture;

public class JLSCallGraphClient {
    public static void main(String[] args) throws IOException {
        // 1. 启动JLS进程
        Process jlsProcess = new ProcessBuilder(
            "java", "-jar", "/path/to/jdt-language-server.jar",
            "-configuration", "/path/to/config_linux",
            "-data", "/path/to/temp-workspace"
        ).start();

        InputStream jlsIn = jlsProcess.getInputStream();
        OutputStream jlsOut = jlsProcess.getOutputStream();

        // 2. 创建LSP客户端与服务端连接
        Launcher<LanguageServer> launcher = Launcher.createLauncher(
            new LanguageClient() {
                // 监听JLS日志,用于判断索引进度
                @Override
                public void logMessage(LogMessageParams params) {
                    System.out.println("[JLS Log] " + params.getMessage());
                }

                @Override
                public void showMessage(ShowMessageParams params) {
                    System.out.println("[JLS Message] " + params.getMessage());
                }

                // 其他默认方法留空即可
            },
            LanguageServer.class,
            jlsIn,
            jlsOut
        );

        LanguageServer languageServer = launcher.getRemoteProxy();
        launcher.startListening();

        // 3. 发送初始化请求,指定目标项目路径
        InitializeParams initParams = new InitializeParams();
        String projectUri = Path.of("/path/to/your-java-project").toUri().toString();
        initParams.setRootUri(projectUri);
        initParams.setWorkspaceFolders(Collections.singletonList(
            new WorkspaceFolder(projectUri, "Target Project")
        ));

        CompletableFuture<InitializeResult> initResult = languageServer.initialize(initParams);
        initResult.join();

        // 4. 发送初始化完成通知
        languageServer.initialized(new InitializedParams());

        // 5. 等待JLS完成项目索引(可根据日志判断,比如出现"Finished indexing workspace")
        try {
            Thread.sleep(10000); // 按需调整等待时间,或监听进度事件替代固定休眠
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 6. 提取调用图:以目标方法为例,获取其向外调用关系
        // 示例:目标方法在Test.java的第10行第5列(LSP位置从0开始计数,所以填9,4)
        TextDocumentPositionParams positionParams = new TextDocumentPositionParams();
        positionParams.setTextDocument(new TextDocumentIdentifier(
            Path.of("/path/to/your-java-project/src/main/java/com/example/Test.java").toUri().toString()
        ));
        positionParams.setPosition(new Position(9, 4));

        // 调用JLS扩展方法获取向外调用
        CompletableFuture<CallHierarchyOutgoingCall[]> outgoingCalls = languageServer
            .sendRequest("java/callHierarchy/outgoingCalls", positionParams)
            .thenApply(response -> (CallHierarchyOutgoingCall[]) response);

        // 处理返回结果,构建调用图
        outgoingCalls.thenAccept(calls -> {
            for (CallHierarchyOutgoingCall call : calls) {
                System.out.println("被调用方法: " + call.getTo().getName());
                for (Range range : call.getFromRanges()) {
                    System.out.println("调用位置: 行" + (range.getStart().getLine() + 1) + ", 列" + (range.getStart().getCharacter() + 1));
                }
            }
        }).join();

        // 7. 关闭连接与进程
        languageServer.shutdown().join();
        languageServer.exit();
        jlsProcess.destroy();
    }
}

四、关键注意事项

  • 不可编译项目适配:JLS支持增量解析存在编译错误的代码,只要语法结构基本完整,就能提取大部分调用关系,无需项目完全可编译
  • 扩展方法说明:JLS提供了Java特有的LSP扩展方法,除java/callHierarchy/outgoingCalls外,java/callHierarchy/incomingCalls可获取方法被调用关系,java/project/getTypes可遍历项目类结构
  • 索引等待优化:建议通过监听window/logMessage事件中的索引完成日志,替代固定休眠时间,确保数据准确性
  • 版本兼容性:确保lsp4j版本与JLS版本匹配,避免协议不兼容问题

内容的提问来源于stack exchange,提问作者tlx0001

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 21:43:13