【问题标题】:methodMissing closure - not encapsulated?methodMissing 闭包 - 未封装?
【发布时间】:2012-12-28 06:02:21
【问题描述】:

我了解 groovy 基础知识 - 和闭包...

我正在尝试从 java 调用 groovy:

// Patient.java

public class Patient {  
    //... other data  
    private Map<String, String> attribStore = new HashMap<String,String>();  
    // getters/setters for attribStore omitted  

    public void addAttribute(String key, String val) {
        if (!attribStore.containsKey(key)) {
            attribStore.put(key, val);
        }
    }

// GroovyHelper.java

public class GroovyHelper {
    private String codeSnippet; // groovy script code

    public String evaluateSnippetToString(Binding binding) {
        addMethodMissingHandler();
        GroovyShell shell = createGroovyShell(binding);
        Object result = shell.evaluate(codeSnippet);
        return result.toString();
    }

    // installs a patient in the binding - accesses the patient  
    // attribStore from groovy
    // The missing method is used to create an "attribute" i.e.
    // a (key,val) pair in the patient map 
    private void addMethodMissingHandler() {
        codeSnippet = "def attribStore = p.getAttribStore();\n"
        + "Patient.metaClass.methodMissing = \n{"
        + " String methodName, args -> \n"
        + "methodName = methodName.replaceFirst(/^get/, '');\n"  
        + "def attrib = methodName[0].toLowerCase() + methodName.substring(1);\n"
        + "if (!attribStore.containsKey(attrib)) { attribStore[attrib] = '0'; }\n"
        + "return attribStore[attrib]; \n" + "}\n" + codeSnippet;
    }  
}

//junit测试代码

private Patient p;
private Binding binding;
private GroovyHelper gh;

    @Before 
    public void init() {
        p = new PatientBuilder().build();
        binding = new Binding();
        binding.setVariable("p", p);
        gh = new GroovyHelper();
    }

    @Test //passes
    public void testPopulatePatientAttribStore() throws Exception {
        p.addAttribute("xyz", "4");
        gh.setCodeSnippet("p.getXyz()");
        gh.evaluateSnippetToString(binding);
    }

    @Test
    public void testGroovy() throws Exception {
        Binding binding = new Binding();
        binding.setVariable("p", new Patient()); // new patient
        p.addAttribute("xyz", "9");

        GroovyShell gs1 = new GroovyShell(binding);

        assertEquals("9", gs1.evaluate("p.getXyz()")); // fails??? - expected: <[9]> but was: <[4]>
    }

我的问题是 - 闭包是否保留了早期绑定的属性存储?
这里到底发生了什么?
为冗长的代码道歉 - 我正在研究它
- 减少不相关的代码
将其缩小到最低限度 - 任何指针,“更多阅读要做”提示?

【问题讨论】:

  • 实际上第二种测试方法 testGroovy 应该导致 MissingMethodException...奇怪的是,单独运行,它工作正常(即导致异常) - 作为一组测试一起运行- 它失败了......

标签: groovy


【解决方案1】:

我认为部分问题在于您在Patient 元类中创建了一个methodMissing,该元类将委托给一位特定患者的attribStore。我会以不同的方式解决这个问题 - 是否可以选择直接在 Patient 类本身中实现 ​​methodMissing

public class Patient {
  // other members as before

  public Object methodMissing(String name, Object[] args) {
    if(name != null && name.startsWith("get") && name.length() > 3) {
      String attrName = name.substring(3,1).toLowerCase() + name.substring(4);
      addAttribute(attrName, "0");
      return attribStore.get(attrName);
    } else {
      throw new MissingMethodException(name, this.getClass(), args);
    }
  }
}

或者如果这不是一个选项,您能否在 Groovy 中实现 GroovyHelper 类(使用 groovyc 编译它,您可以像任何 Java 类一样从 Java 中调用它)?

public class GroovyHelper {
    static {
      // add a methodMissing to the Patient metaclass
      Patient.metaClass.methodMissing = { String name, args ->
        if(name?.startsWith("get") && name.length() > 3) {
          String attrName = name.substring(3,1).toLowerCase() + name.substring(4)
          // delegate here is the particular Patient instance on which the
          // missing method call was made
          delegate.addAttribute(attrName, "0")
          return delegate.attribStore[attrName];
        } else {
          throw new MissingMethodException(name, this.getClass(), args);
        }
      }
    }

    private String codeSnippet // groovy script code

    public String evaluateSnippetToString(Binding binding) {
        GroovyShell shell = createGroovyShell(binding)
        Object result = shell.evaluate(codeSnippet)
        return result.toString()
    }
}

【讨论】:

  • 尊敬的 Ian 和 Brian, 非常感谢您抽出宝贵时间来研究这个问题。我正在更新我的问题...
  • 是的 - 将 methodMissing 移至 Patient 肯定是可能的...会尝试一下...
【解决方案2】:

显然Patient 的元类在 b/w GroovyShell 运行时仍然存在 - MetaClassRegistry 可能是一个单例,因此在类加载器或 JVM 的生命周期中存在(不能完全思考如何这目前在JVM中有效)。而且,正如您所建议的,我相信您的 methodMissing 闭包围绕它最初获得的 attribStore 变量关闭。 (快速测试似乎表明您应该可以从该闭包内访问绑定中的p。我猜您尝试过但没有用?)。一种选择可能是在每次测试后以拆卸方法强制 remove the MetaClassPatient

【讨论】:

    猜你喜欢
    • 2012-02-02
    • 2013-03-12
    • 1970-01-01
    • 2017-11-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-02-27
    • 2013-02-18
    相关资源
    最近更新 更多