简体   繁体   English

动态创建Groovy类

[英]Create a Groovy class dynamically

Given a class name I would like to dynamically create a Groovy class add properties and methods to it. 给定一个类名,我想动态创建一个Groovy类,为它添加属性和方法。 I create the new class using 我使用创建新类

instance = this.class.classLoader.parseClass(
                "public class $name {}")

For methods I use 对于我使用的方法

instance.metaClass."$it.key" = it.value

where it.key is a string (method name) and it.value is a closure. 其中it.key是一个字符串(方法名称),it.value是一个闭包。 This is convenient because I can specify method parameter types and get type checking. 这很方便,因为我可以指定方法参数类型并获取类型检查。 However, I am not able to specify a dynamically created property type without assigning it a value. 但是,如果没有为其赋值,我无法指定动态创建的属性类型。 I can work around this by explicitly defining getter and setter for the property. 我可以通过明确定义属性的getter和setter来解决这个问题。 This works, but it seems that neither metaClass.name = value nor metaClass.getName = {} actually create a field in the class because the Java field operator doesn't work for the created properties. 这样可行,但似乎metaClass.name = value和metaClass.getName = {}实际上都没有在类中创建一个字段,因为Java字段运算符不适用于创建的属性。 Can I ad a property to a Groovy class and specify its type without assigning an initial value to it or explicitly defining getter and setter methods? 我可以将属性添加到Groovy类并指定其类型,而无需为其分配初始值或显式定义getter和setter方法吗? Is there a way to add a new field to a Groovy class? 有没有办法向Groovy类添加新字段? Here is the script: 这是脚本:

class SomeClass {
    Integer p1
    String p2
}

class ClassBuilder {
    def name
    def instance
    def properties
    def methods

    def ClassBuilder() {
        properties = [:]
        methods = [:]
    }

    def set_name(name) {
        this.name = name
    }

    def add_property(name, type) {
        properties[name] = type
    }

    def add_method(name, closure) {
        methods[name] = closure
    }

    def get_instance() {
        instance = this.class.classLoader.parseClass(
                "public class $name {}")

        properties.each {
            instance.metaClass."$it.key" = null
            //doesn't work
            instance.metaClass."$it.key".type = it.value
        }

        methods.each {
            instance.metaClass."$it.key" = it.value
        }

        return instance
    }
}

builder = new ClassBuilder()

builder.set_name('MyClass')

builder.add_property('property1', String)
builder.add_property('property2', SomeClass)

builder.add_method('method1', {SomeClass obj -> println obj})
builder.add_method('setProperty2', {SomeClass obj -> this.property2 = obj})
builder.add_method('getProperty2', {return this.property2})

builder.add_method('method2', {return property1 + property2})

c = builder.get_instance()

i = c.newInstance()
i.property1 = new SomeClass()
i.property2 = 5

//i.method2() //throws GroovyCastException

//i.property2 = 'throws GroovyCastException'
//i.@property1 = 'throws MissingFieldException'

//No such field: property2 for class: MyClass
//i.@property2 = new SomeClass()

i.method1(new SomeClass())
//i.method1('throws MissingMethodException')

[Edit] [编辑]

The use case is like this: I define an interface or a base class in Java. 用例是这样的:我在Java中定义了一个接口或基类。 A user implements the interface or extends the base class in Groovy and the class is passed back to Java to be used by the main application. 用户在Groovy中实现接口或扩展基类,并将类传递回Java以供主应用程序使用。 Users are not programmers so they define the class using a simple DSL and I construct the actual class using a builder. 用户不是程序员,因此他们使用简单的DSL定义类,并使用构建器构造实际的类。 I am still experimenting with Groovy/JRuby and Java interop (new to both languages). 我还在尝试使用Groovy / JRuby和Java interop(两种语言都是新的)。

I have more or less been able to get it working by using GroovyClassLoader and SimpleTemplateEngine. 通过使用GroovyClassLoader和SimpleTemplateEngine,我或多或少能够使它工作。 here is the code: 这是代码:

class ClassBuilder {

    GroovyClassLoader loader
    String name
    Class cls
    def imports
    def fields
    def methods

    def ClassBuilder(GroovyClassLoader loader) {
        this.loader = loader
        imports = []
        fields = [:]
        methods = [:]
    }

    def setName(String name) {
        this.name = name
    }

    def addImport(Class importClass) {
        imports << "${importClass.getPackage().getName()}" +
                ".${importClass.getSimpleName()}"
    }

    def addField(String name, Class type) {
        fields[name] = type.simpleName
    }

    def addMethod(String name, Closure closure) {
        methods[name] = closure
    }

    def getCreatedClass() {

        def templateText = '''
<%imports.each {%>import $it\n <% } %> 
class $name
{
<%fields.each {%>    $it.value $it.key \n<% } %>
}
'''
        def data = [name: name, imports: imports, fields: fields]

        def engine = new groovy.text.SimpleTemplateEngine()
        def template = engine.createTemplate(templateText)
        def result = template.make(data)
        cls = loader.parseClass(result.toString())
        methods.each {
            cls.metaClass."$it.key" = it.value
        }
        return cls
    }
}

and here is an example of how I use it to create a class dynamically: 以下是我如何使用它动态创建类的示例:

import java.util.Calendar
def builder = new ClassBuilder(this.class.classLoader)
builder.setName("MyClass");

builder.addImport(Calendar)

builder.addField('field1', Integer)
builder.addField('field2', Integer)

builder.addMethod('sum') { field1 + field2 }

builder.addMethod('product') { field1 * field2 }

builder.addMethod('testCalendar') {
    println Calendar.getInstance().getTime()
}

Class myClass = builder.getCreatedClass()
def myInstance = myClass.newInstance()

myInstance.field1 = 1
myInstance.field2 = 2

println myInstance.sum()
println myInstance.product()

myInstance.setField2(1500)
println myInstance.getField2()

myInstance.testCalendar()

a nice variant would be to use the GroovyShell. 一个不错的变体是使用GroovyShell。

def c = """ 
package de.myCorp.test

class Test {
    def a
    def b
    def c    

    Test(String c){ this.c = c}

    public String greet(){
        return "hello "+a
    }

    public void count(){
        (1..4).each{
            println it
        }
    }

}

def class UsingTest {
    Test mytest = null

    UsingTest (Test test){  this.mytest = test }

    def using(){
        mytest.greet();
    }

}
"""


GroovyShell gs = new GroovyShell()
//I hope this is not a too bad hack ^^ 
def erg = gs.evaluate(c+";['test':Test, 'using':UsingTest];")

def testclass = erg["test"].newInstance("Charlotte on Ice")
    testclass.a = "hugo"
    testclass.b = "sepp"


testclass.count() 
assert testclass.greet() == "hello hugo"

assert testclass.c == "Charlotte on Ice"
assert testclass.class.name == "de.myCorp.test.Test"


def usingclass = erg['using'].newInstance(testclass)
usingclass.mytest.a = "Fritzl"
assert usingclass.using() == "hello Fritzl" 

...and especially notice the GroovyShell.evaluate(URI uri) types... GroovyShellDoc ...特别注意GroovyShell.evaluate(URI uri)类型...... GroovyShellDoc

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM