开发者

Groovy adding code to a constructor

开发者 https://www.devze.com 2023-03-03 01:19 出处:网络
Is there a way in Groovy that I can add code to a constructor when a class is instantiated? I have a Groovy class (but I can\'t modify the source of this particular one), but I was hoping there was a

Is there a way in Groovy that I can add code to a constructor when a class is instantiated? I have a Groovy class (but I can't modify the source of this particular one), but I was hoping there was a way to inject code (maybe via the metaclass) so my code gets run as part of the constructor (in this case there is only one, default constructor).

Th开发者_如何学Pythonanks, Jeff


You can override the constructor, but it's a little tricky, particularly if you're overriding the default constructor. You need to assign a closure to the class's metaClass.constructor, and the closure should return a new instance. The tricky part is that if you call the constructor you've overriden, you'll get into a recursive loop and generate a stack overflow. You need another way to get an instance of the class, such as a different constructor.

For testing, it's sometimes possible to get around this limitation. Usually, it's enough to first instantiate an object, then override the constructor to return the existing instance. Example:

class MyObject {
    String something
    MyObject() { something = "initialized" }
}

testInstance = new MyObject()
testInstance.something = "overriden"
MyObject.metaClass.constructor = { -> testInstance }

aNewObject = new MyObject()
assert aNewObject.is(testInstance)
assert aNewObject.something == "overriden"


It is possible to add new constructors or replace the old one. If you need the original constructor, you can use reflection for that:

MyObject.metaClass.constructor = { -> // for the no-arg ctor
  // use reflection to get the original constructor
  def constructor = MyObject.class.getConstructor()
  // create the new instance
  def instance = constructor.newInstance()
  // ... do some further stuff with the instance ...
  println "Created ${instance}"
  instance
}

Note that you have to change this if you have parameters to your constructors, e.g:

// Note that the closure contains the signature of the constructor
MyObject.metaClass.constructor = { int year, String reason ->
  def constructor = MyObject.class.getConstructor(Integer.TYPE, String.class)
  def instance = constructor.newInstance(
    2014, "Boy, am I really answering a question three years old?")
  // ... do some further stuff with the instance ...
  println "Created ${instance}"
  instance
}

PS: Note that when you want to add constructors which are not yet existent, use the << operator instead: MyObject.metaClass.constructor << { /* as above */ }.


You can bypass the limitations in the solution proposed by storing the original constructor using standard Java reflection. For example, this is what I do initialize a class (basic injection) in a spock test:

def setupSpec() {
    MockPlexusContainer mockPlexusContainer = new MockPlexusContainer()
    def oldConstructor = MY_CLASS.constructors[0]

    MY_CLASS.metaClass.constructor = { ->
        def mojo = oldConstructor.newInstance()
        mockPlexusContainer.initializeContext(mojo)
        return mojo
    }
}

This gets invoked only once, but eveytime someone calls a constructor I get a different instance avoiding cleaning values and ensuring thread safety.

0

精彩评论

暂无评论...
验证码 换一张
取 消