1

所以我想向 JDK 类添加方法,如 InputStream、File 等。我试图找出最好的方法是什么,但似乎有几种选择。一种方法是通过将方法添加到 Class 的 metaClass 属性中,如下所示:

   InputStream.metaClass.copy = { OutputStream out ->
        long total = 0
        byte[] buffer = new byte[8096]
        int len
        while ((len = read(buffer)) >= 0) {
            out.write(buffer, 0, len)
            total += len
        }
        out.flush()
        return delegate
    }

另一种方法是使用这样的动态混合:

class EnhancedInputStream {
    static {
         InputStream.metaClass.mixin( EnhancedInputStream )
    }

    public InputStream copy( OutputStream out ) {
        long total = 0
        byte[] buffer = new byte[8096]
        int len
        while ((len = mixinOwner.read(buffer)) >= 0) {
            out.write(buffer, 0, len)
            total += len
        }
        out.flush()
        return mixinOwner
    }
}

那么第一个问题是动态 Mixins 是否替代了使用 metaClass + Closure 创建 mixins?动态混合的示例并没有真正详细讨论我能找到的范围规则。这将我引向下一点。

您可以在使用元类的第一个代码示例中看到,我使用委托来访问我正在向其中添加方法的类的 this 指针。使用动态 Mixins 的等效方法是什么?我发现的所有例子都是无状态的(真的毫无意义)。我发现一个例子提到了一个可以用来代替委托的特殊成员 mixinOwner。真的吗?

其次,你会看到我在 EnhancedInputStream 中使用了一个静态块来动态地将 mixin 添加到 InputStream。使用 metaClass 时,添加这些的最佳方法是什么?另一个带有 import 语句的静态块?

我想我真的想要一个编译时 Mixin,我可以在 mixin 的源而不是目标上定义 @Mixin,因为我没有写目标。像

@MixinInto(File)
public class EnhancedFileMixin {
    public void zip( File output ) {
        // .....
    }
}

但这在 Groovy 领域似乎并不存在。那么使用 metaClass 或动态 mixins 实现这一目标的最佳方法是什么?

4

3 回答 3

2

我想最接近的@MixinInto魔术包约定。我无法将它混入一个界面,但我设法将它混入一个FileInputStream,如果这适合你的情况。我想您可以使用MetaClass构造函数中的 which 添加状态。

写一个要混入的类InputStream。它需要是:

  • 包装内groovy.runtime.metaclass.java.io
  • 命名FileInputStreamMetaClass准确
  • 编译并放入类路径
  • 延长DelegatingMetaClass

它只能拦截GroovyObject方法,所以不是那么简单。如果您喜欢纯粹的动态 groovy,那就太好了:

package groovy.runtime.metaclass.java.io

class FileInputStreamMetaClass extends DelegatingMetaClass {
  FileInputStreamMetaClass(MetaClass meta) {
    super(meta)
    println "built FileInputStreamMetaClass"
  }

  Object invokeMethod(Object delegate, String method, Object[] args) {
    switch (method) {
      case "copy": 
        return "i'm copying stuff"
      default:
        return super.invokeMethod(delegate, method, args)
    }
  }
}

编译:

$ groovyc FileInputStreamMetaClass.groovy
$ groovy -cp . InputTest.groovy

一个测试:

InputStream input = new FileInputStream("/tmp/test.tmp")
assert input.copy() == "i'm copying stuff"

有点麻烦。


我会在一天中的任何时间进行扩展。三个文件:

// file META-INF/services/org.codehaus.groovy.runtime.ExtensionModule
moduleName=InputExtensionModule
moduleVersion=0.1
extensionClasses=InputStreamExtension

扩展:

class InputStreamExtension {
  static String copy(InputStream input) {
    "copying stuff, doc"
  }
} 

考试:

def input = new FileInputStream("/tmp/test.tmp")
assert input.copy() == "copying stuff, doc"

编译并运行:

$ groovyc InputStreamExtension.groovy
$ groovy ISExtensionTest.groovy

而且我认为扩展是使用该static { mixin }块的理想场所。有一些变化:

class InputStreamExtension {
  static {
    InputStream.mixin InputStreamMixin
  }

  static String copy(InputStream input) { "copying stuff, doc" }
} 

@Category(InputStream)
class InputStreamMixin {
  Object pop() {
    "input mixin pop"
  }
}

一个新的测试:

def input = new FileInputStream("/tmp/test.tmp")

assert input.copy() == "copying stuff, doc"
assert input.pop() == "input mixin pop"
于 2013-06-08T15:16:34.140 回答
1

好吧,我终于自己弄清楚了。本质上,this 引用指的是对我们没有多大好处的 Mixin 实例。但是,您可以使用“as”关键字将其转换为目标类的实例。例如:

class MyMixin {
   static {
      File mixin MyMixin
   }

   File foo() {
       return this as File
   }
}

File f = new File()
println( f.foo().equals( f ) )

至于jira bug所指的mixinOwner和owner引用。它们不存在。这是获取对添加了 mixin 的实例的引用的唯一方法。

我写了一篇关于它的更长的博客文章,因为我认为这对于未来的 Groovy 程序员来说是重要的信息,因为讨论这个的官方文档为零。

http://wrongnotes.blogspot.com/2013/06/groovy-mixins-and-this-pointer.html

于 2013-06-06T22:51:35.407 回答
1

我很高兴你问了这个问题。回答一个非常重要的问题:

我想我真的想要一个编译时 Mixin,我可以在 mixin 的源而不是目标上定义 @Mixin,因为我没有写目标。

你无法做到这一点,@Mixin但我们在 Groovy 中确实有一些东西可以帮助你。它被称为@Category。让我再次通过您的示例向您展示如何this在类别中实际有效地使用。看看下面的脚本:

@Category(InputStream)
class InputStreamCategory{
    def copy(OutputStream out){
        long total = 0
        byte[] buffer = new byte[8096]
        int len
        while ((len = this.read(buffer)) >= 0) {
            out.write(buffer, 0, len)
            total += len
        }
        out.flush()
        return this
    }
}

class MyUtil{
    def str = 'This is a dummy String!!!!'

    InputStream inputS = new ByteArrayInputStream(str.bytes)
    OutputStream outputS = new ByteArrayOutputStream()

    def copy(){
        use(InputStreamCategory){
            inputS.copy(outputS)
            println "Printing Output Stream: " + outputS
        }
    }
}

def util = new MyUtil()
util.copy()

//Prints:
Printing Output Stream: This is a dummy String!!!!

解释:-

@Category(InputStream)thisInputStreamCategory你的 util 类中设置你只是use新添加的方法copyInputStream. 使用 category 的好处是在这种情况下您可以获得调用者对象inputS。传递给类别的第一个参数总是指调用者。您可以为不同的实现设置不同的类别,例如FileCategoryetc,然后为use这些创建一个实用程序类Categories。因此,您最终会使用 , 等实用zip程序copy

您可以从 api 获取有关相同内容的详细信息。我还强烈推荐通过Category 和 Mixin Transformations

要回答第一个问题:-
动态 Mixins 是否取代了使用 metaClass + Closure 创建 mixins? 不,他们没有。metaClass实现不会创建 Mixin。它只是在运行时在有关该类的元数据注册表中添加另一个方法。你得到一个句柄delegate。另一方面,@Mixin 使您能够继承预定义的属性。

于 2013-06-06T23:56:05.343 回答