Groovy闭包委托未指向子对象问题排查与修复咨询
问题描述
我编写了一套处理数据源电子表格两个工作表的代码,核心结构如下:
- 负责处理表格本身的
BaseRecordHandler,包含initDataSourceFile方法:
protected void initDataSourceFile() throws IOException { File file = new File(this.dataSourceFilename); this.excelFile = this.getWorkbook(file); if (file.exists()) return; file.getParentFile().mkdirs(); FileOutputStream outputStream = new FileOutputStream(file); this.setupSpreadsheet({ Sheet sheet -> setupFirstRow(sheet.createRow(0)); sheet.createFreezePane(0, 1); // returning the sheet allows for the functional programming technique known as composition return sheet; }); this.excelFile.write(outputStream); outputStream.close(); this.excelFile.close(); }
- 实际表格处理器
PracticeProfileHandler继承自BaseRecordHandler,拥有两个BaseSheetHandler子类实例,重写了setupSpreadsheet方法:
@Override protected void setupSpreadsheet(Closure<Sheet> onSetupSheet) { Closure onGetAndSetupSheet = SMDSpreadsheetUtils.OnCreateIfNotExistSheet(this.excelFile) >> onSetupSheet; this.childPracticeURLHandler = new PracticeURLHandler(onGetAndSetupSheet); this.childOrgNameHandler = new OrganizationNameHandler(onGetAndSetupSheet); } @Override protected void setupFirstRow(Row firstRow) { throw new Exception("PracticeProfileHandler should not be trying to set up the first row of a Sheet"); }
BaseSheetHandler的定义:
public abstract class BaseSheetHandler<T> { protected Sheet sheet; protected List<T> usedRecords; protected BaseSheetHandler(Closure<Sheet> onGetSheet) { onGetSheet.setDelegate(this); this.sheet = onGetSheet(this.getSheetName()); this.init(); } // ...rest of code... }
子类处理器(PracticeURLHandler和OrganizationNameHandler)各自实现了fillInRow方法,但调用practiceProfileHandler.initDataSourceFile()时,程序未调用子类的fillInRow,反而调用了PracticeProfileHandler中的对应方法——尽管我已在BaseSheetHandler构造器里把包含该调用的回调委托给了子实例。
问题成因
问题出在Groovy闭包的委托策略与方法调用解析逻辑:
- 你在
BaseSheetHandler构造器中给onGetSheet设置了delegate = this(子实例),但Groovy闭包的默认方法解析顺序是优先调用闭包的owner,再调用delegate。 - 这里的
onSetupSheet闭包定义在BaseRecordHandler.initDataSourceFile中,它的owner是PracticeProfileHandler实例。当闭包里调用setupFirstRow时,会优先在owner(即PracticeProfileHandler)中查找该方法,而非delegate(子BaseSheetHandler实例)。 - 这直接导致
setupFirstRow调用了错误的实现,后续依赖该流程触发的fillInRow自然也走了错误分支。
修复方案
有两种直接有效的修复方式:
方式一:修改闭包的委托优先级
在BaseSheetHandler构造器中,给闭包设置优先使用delegate的解析策略:
protected BaseSheetHandler(Closure<Sheet> onGetSheet) { onGetSheet.setDelegate(this); // 设置委托优先级高于owner onGetSheet.setResolveStrategy(Closure.DELEGATE_FIRST); this.sheet = onGetSheet(this.getSheetName()); this.init(); }
这样闭包内的方法调用会先去delegate(子BaseSheetHandler实例)中查找,找不到才会转向owner,确保setupFirstRow调用子类处理器的实现。
方式二:显式指定调用delegate的方法
修改initDataSourceFile中传入setupSpreadsheet的闭包,直接调用delegate的setupFirstRow:
this.setupSpreadsheet({ Sheet sheet -> delegate.setupFirstRow(sheet.createRow(0)); sheet.createFreezePane(0, 1); return sheet; });
这种方式强制指定调用delegate的方法,彻底避免Groovy默认解析顺序的干扰。
另外建议检查PracticeProfileHandler是否意外实现了fillInRow方法,如果有,会进一步加剧优先级问题,建议移除该不必要的实现,确保只有子类处理器拥有fillInRow的实现。
内容的提问来源于stack exchange,提问作者Mike Warren
相关产品推荐
相关产品推荐

