求基于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
相关产品推荐
相关产品推荐

