Why Groovy’s getTest Method Hijacks Your Static Calls – A Deep Dive
An in‑depth analysis reveals how Groovy’s implicit getter feature and method‑resolution rules cause a static method call to be intercepted by a subclass’s getTest method, leading to unexpected output and thread locking during performance testing.
This article originates from a performance‑testing scenario where an asynchronous QPS display consistently reported a value of 1, while normal logging showed correct QPS. Thread dumps and jconsole indicated that the executing thread was being locked, prompting a deeper investigation.
First Layer – Reproducing the Issue
A minimal demo is provided with a Parent class containing a static test(int i) method and a Child class extending it. Child defines a static getTest() method and an instance method bugs() that calls test(12). The main method creates a Child instance and invokes bugs().
class FunTester {
public static void main(String[] args) {
new Child().bugs()
}
private static class Child extends Parent {
void bugs() {
println(test(12))
}
static Child getTest() {
println("子类方法 无参数")
return new Child()
}
}
private static class Parent {
static def test(int i) {
return "父类方法 参数$i"
}
}
}Running this code prints:
子类方法 无参数
父类方法 参数12
Process finished with exit code 0Surprisingly, the subclass’s getTest is invoked before the parent’s test method.
Second Layer – Changing Return Type to String
Modifying getTest to return a String produces the same output order, confirming that the return type does not affect the call sequence.
Third Layer – Returning void
When getTest returns void, the console output becomes:
父类方法 参数12
Process finished with exit code 0The subclass method is no longer called, and the behavior matches Java’s expectations.
Fourth Layer – Renaming the Method
Renaming getTest to getTeest (a typo) also results in the parent method being called directly, demonstrating that Groovy’s special handling is tied to the exact getX naming pattern.
Fifth Layer – Passing a String Argument
Altering bugs() to call test("FunTester") while keeping getTest unchanged triggers a MissingMethodException because the parent’s test expects an int. The console shows the subclass’s getTest being called first, then the failed parent call.
Sixth Layer – Groovy Features Behind the Bug
Any method named getX implicitly creates a property x on the class or instance.
When a method call is resolved, Groovy first looks for a property with the same name; if found, it treats the property as a closure and invokes call() on it.
Groovy does not enforce strict type checking for these dynamic properties.
Thus, the presence of getTest creates an implicit test property in the subclass, causing the call test(12) to be interpreted as invoking the closure stored in that property, i.e., the getTest method.
Seventh Layer – Final Understanding
In Groovy, test(12) is first resolved to the subclass’s implicit test property (generated by getTest), which is a closure pointing to getTest. If getTest returns void, the property does not satisfy Groovy’s GET method contract, so the lookup falls back to the parent’s static test method, matching Java’s behavior.
The analysis explains why the original QPS‑display thread was blocked: the unexpected static method interception caused by Groovy’s getter semantics introduced hidden synchronization points.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
