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

求基于Eclipse JDT和LTK实现提取方法重构的入门示例教程

基于Eclipse JDT+LTK实现提取方法重构入门指南

一、先搞懂核心组件的作用

  • JDT:负责Java代码的解析、AST(抽象语法树)生成与操作,是处理Java代码逻辑的基础
  • LTK:提供重构的通用框架,搞定变更预览、撤销、UI交互这些重复工作,不用自己从头写

二、最小可行示例分步走

1. 项目依赖配置

在Eclipse插件项目的MANIFEST.MF里添加必需的依赖,少一个都跑不起来:

Require-Bundle: org.eclipse.jdt.core,
 org.eclipse.ltk.core.refactoring,
 org.eclipse.jdt.ui,
 org.eclipse.ltk.ui.refactoring

2. 核心代码实现

第一步:获取选中代码的AST

先从编辑器选中区域拿到对应的Java元素和AST,这是所有操作的起点:

// 从当前激活的Java编辑器获取选中内容
JavaEditor javaEditor = (JavaEditor) PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().getActiveEditor();
ICompilationUnit cu = javaEditor.getCompilationUnit();
TextSelection selection = (TextSelection) javaEditor.getSelectionProvider().getSelection();

// 生成AST
ASTParser parser = ASTParser.newParser(AST.JLS17); // 对应你的Java版本
parser.setSource(cu);
parser.setResolveBindings(true); // 启用绑定,方便后续获取变量/方法信息
CompilationUnit astCu = (CompilationUnit) parser.createAST(null);

第二步:实现重构逻辑

继承LTK的Refactoring类,核心是三个方法:检查合法性、生成变更。以下是简化版,只处理最基础的代码块提取:

public class SimpleExtractMethodRefactoring extends Refactoring {
    private final CompilationUnit astCu;
    private final TextSelection selection;
    private final IMethod parentMethod;
    private String newMethodName = "extractedMethod"; // 默认方法名

    public SimpleExtractMethodRefactoring(CompilationUnit astCu, TextSelection selection, IMethod parentMethod) {
        this.astCu = astCu;
        this.selection = selection;
        this.parentMethod = parentMethod;
    }

    // 设置用户输入的新方法名
    public void setNewMethodName(String name) {
        this.newMethodName = name;
    }

    @Override
    public RefactoringStatus checkInitialConditions(IProgressMonitor pm) {
        RefactoringStatus status = new RefactoringStatus();
        // 检查选中区域是否在方法内部
        ASTNode selectedNode = astCu.findDeclaringNode(selection.getOffset(), selection.getLength());
        if (!(selectedNode instanceof Block) || !isNodeInMethod(selectedNode, parentMethod)) {
            status.addError("请选中方法内部的一段代码块");
        }
        return status;
    }

    @Override
    public RefactoringStatus checkFinalConditions(IProgressMonitor pm) {
        RefactoringStatus status = new RefactoringStatus();
        // 检查方法名是否合法
        if (!IdentifierValidator.isValidIdentifier(newMethodName)) {
            status.addError("方法名不合法");
        }
        return status;
    }

    @Override
    public Change createChange(IProgressMonitor pm) throws CoreException {
        ASTRewrite rewrite = ASTRewrite.create(astCu.getAST());
        ASTNode selectedNode = astCu.findDeclaringNode(selection.getOffset(), selection.getLength());
        Block selectedBlock = (Block) selectedNode;

        // 1. 创建新方法
        MethodDeclaration newMethod = astCu.getAST().newMethodDeclaration();
        newMethod.setName(astCu.getAST().newSimpleName(newMethodName));
        newMethod.setBody((Block) ASTNode.copySubtree(astCu.getAST(), selectedBlock));

        // 2. 把新方法插入到原方法前面
        rewrite.insertBefore(newMethod, astCu.findDeclaringNode(parentMethod), null);

        // 3. 替换原选中代码为新方法调用
        MethodInvocation invocation = astCu.getAST().newMethodInvocation();
        invocation.setName(astCu.getAST().newSimpleName(newMethodName));
        rewrite.replace(selectedBlock, invocation, null);

        // 返回变更对象,LTK会处理预览和应用
        return rewrite.rewriteAST();
    }

    // 辅助方法:判断节点是否在目标方法内
    private boolean isNodeInMethod(ASTNode node, IMethod method) {
        while (node != null) {
            if (node instanceof MethodDeclaration) {
                MethodDeclaration md = (MethodDeclaration) node;
                return md.resolveBinding().getJavaElement().equals(method);
            }
            node = node.getParent();
        }
        return false;
    }
}

第三步:添加UI触发逻辑

给编辑器右键加个菜单选项,打开LTK的重构向导让用户输入方法名:

// 注册右键菜单(可以在plugin.xml里配置,或者代码动态添加)
// 这里简化为直接在编辑器右键点击时触发
public void triggerExtractMethod() {
    JavaEditor javaEditor = (JavaEditor) PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().getActiveEditor();
    ICompilationUnit cu = javaEditor.getCompilationUnit();
    TextSelection selection = (TextSelection) javaEditor.getSelectionProvider().getSelection();
    IMethod parentMethod = (IMethod) cu.getElementAt(selection.getOffset());

    SimpleExtractMethodRefactoring refactoring = new SimpleExtractMethodRefactoring(
            (CompilationUnit) cu.getAST(), selection, parentMethod);

    // 创建重构向导,添加输入页面
    RefactoringWizard wizard = new RefactoringWizard(refactoring, RefactoringWizard.DIALOG_BASED_USER_INTERFACE) {
        @Override
        protected void addUserInputPages() {
            addPage(new ExtractMethodInputPage(refactoring));
        }
    };

    // 打开向导对话框
    RefactoringWizardOpenOperation op = new RefactoringWizardOpenOperation(wizard);
    try {
        op.run(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), "提取方法");
    } catch (InterruptedException e) {
        // 用户取消操作,不用处理
    }
}

// 自定义输入页面,让用户输入新方法名
class ExtractMethodInputPage extends RefactoringWizardPage {
    private final SimpleExtractMethodRefactoring refactoring;
    private Text methodNameText;

    public ExtractMethodInputPage(SimpleExtractMethodRefactoring refactoring) {
        super("ExtractMethodInputPage");
        this.refactoring = refactoring;
        setTitle("提取方法");
        setDescription("输入新方法的名称");
    }

    @Override
    public void createControl(Composite parent) {
        Composite composite = new Composite(parent, SWT.NONE);
        composite.setLayout(new GridLayout(2, false));

        new Label(composite, SWT.NONE).setText("方法名:");
        methodNameText = new Text(composite, SWT.BORDER);
        methodNameText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
        methodNameText.setText("extractedMethod");

        setControl(composite);
    }

    @Override
    public boolean performFinish() {
        refactoring.setNewMethodName(methodNameText.getText().trim());
        return true;
    }
}

三、调试和踩坑技巧

  • 用Eclipse的AST View插件查看AST结构,搞清楚每个代码块对应的节点类型,避免操作错节点
  • 在createChange里用rewrite.getPreview()打印变更预览,不用实际修改代码就能验证逻辑
  • 利用RefactoringStatus返回详细错误,用户一眼就知道哪里错了

四、进阶学习方向

  • 扒JDT内置的ExtractMethodRefactoring源码(包路径org.eclipse.jdt.internal.corext.refactoring.code),学习处理局部变量传递、返回值、异常这些复杂场景
  • 研究LTK的Change和ChangeManager,实现重构的撤销/重做功能
  • 在plugin.xml里注册重构扩展点,让你的功能和Eclipse自带重构集成在一起,更符合IDE交互习惯

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 16:22:53