The InvocationRecordingMethodInterceptor overflows the stack when using non-statically compiled Groovy. The problem exists because the interceptor is returning a new proxy with each invocation, and Groovy's method dispatching workflow inspects the metaClass of each object to determine the appropriate call site to use.
Simple example demonstrating the issue:
@Configuration
@ComponentScan
@EnableAutoConfiguration
@EnableJpaRepositories
@EnableHypermediaSupport(type = EnableHypermediaSupport.HypermediaType.HAL)
class Main {
static void main(_) {
SpringApplication.run this, [] as String[]
}
}
@javax.persistence.Entity
class Foo {
@javax.persistence.Id
Long id
String name
}
@Repository
interface Foos extends CrudRepository<Foo, Long> {
}
@RestController
@RequestMapping("/foo")
class FooController {
@Autowired
Foos foos
@RequestMapping(method = GET)
Resources<Foo> list() {
def foos = foos.findAll()
def resources = new Resources<>(foos)
if (resources) {
resources.add(linkTo(methodOn(this.class).list()).withSelfRel())
}
resources
}
}
A "fix" would be to statically compile the FooController, but this means that we can't use the metaClass:
@CompileStatic
@RestController
@RequestMapping("/foo")
class FooController {
...
}
I'm not sure what the best solution to this is... It seems like there should be a way to ascertain if the current invocation is identical to the last invocation, though I'm not sure how that could be determined given a new proxy object is returned each time. Probably need help from @melix on this one.
/cc @glaforge
I tried using the @CompileStatic work around and the stack overvlow still occurrs. @danveloper were you able to verify that statically compiling the controller worked for you? For our project, using statically compiled controllers would probably work for us.
Yeah -- @CompileStatic works
Any news? @CompileStatic does not work for me. :-(
Using @CompileStatic gave me a org.gradle.api.internal.tasks.compile.CompilationFailedException. It couldn't find the method referenced by methodOn - error message: Please check if the declared type is right and if the method exists
Still the bug with @CompileStatic, groovy 2.4.5 and spring boot 1.3.3.RELEASE
Any others solutions ?
java.lang.StackOverflowError: null
at java.lang.reflect.Proxy$KeyX.
at java.lang.reflect.Proxy$KeyFactory.apply(Proxy.java:548) ~[na:1.8.0_72]
at java.lang.reflect.Proxy$KeyFactory.apply(Proxy.java:539) ~[na:1.8.0_72]
at java.lang.reflect.WeakCache.get(WeakCache.java:120) ~[na:1.8.0_72]
at java.lang.reflect.Proxy.getProxyClass0(Proxy.java:419) ~[na:1.8.0_72]
at java.lang.reflect.Proxy.newProxyInstance(Proxy.java:719) ~[na:1.8.0_72]
at org.springframework.aop.framework.JdkDynamicAopProxy.getProxy(JdkDynamicAopProxy.java:121) ~[spring-aop-4.2.5.RELEASE.jar:4.2.5.RELEASE]
at org.springframework.aop.framework.JdkDynamicAopProxy.getProxy(JdkDynamicAopProxy.java:111) ~[spring-aop-4.2.5.RELEASE.jar:4.2.5.RELEASE]
at org.springframework.aop.framework.ProxyFactory.getProxy(ProxyFactory.java:96) ~[spring-aop-4.2.5.RELEASE.jar:4.2.5.RELEASE]
at org.springframework.hateoas.core.DummyInvocationUtils.getProxyWithInterceptor(DummyInvocationUtils.java:162) ~[spring-hateoas-0.19.0.RELEASE.jar:na]
at org.springframework.hateoas.core.DummyInvocationUtils.access$100(DummyInvocationUtils.java:38) ~[spring-hateoas-0.19.0.RELEASE.jar:na]
at org.springframework.hateoas.core.DummyInvocationUtils$InvocationRecordingMethodInterceptor.intercept(DummyInvocationUtils.java:100) ~[spring-hateoas-0.19.0.RELEASE.jar:na]
at hello.GreetingController$$EnhancerByCGLIB$$33120faf.getMetaClass(
at org.codehaus.groovy.runtime.callsite.CallSiteArray.createPogoSite(CallSiteArray.java:147) ~[groovy-all-2.4.5.jar:2.4.5]
at org.codehaus.groovy.runtime.callsite.CallSiteArray.createCallSite(CallSiteArray.java:164) ~[groovy-all-2.4.5.jar:2.4.5]
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:48) ~[groovy-all-2.4.5.jar:2.4.5]
at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.call(PogoMetaClassSite.java:57) ~[groovy-all-2.4.5.jar:2.4.5]
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:48) ~[groovy-all-2.4.5.jar:2.4.5]
at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.call(PogoMetaClassSite.java:57) ~[groovy-all-2.4.5.jar:2.4.5]
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:48) ~[groovy-all-2.4.5.jar:2.4.5]
at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.call(PogoMetaClassSite.java:57) ~[groovy-all-2.4.5.jar:2.4.5]
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:48) ~[groovy-all-2.4.5.jar:2.4.5]
at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.call(PogoMetaClassSite.java:57) ~[groovy-all-2.4.5.jar:2.4.5]
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:48) ~[groovy-all-2.4.5.jar:2.4.5]
at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.call(PogoMetaClassSite.java:57) ~[groovy-all-2.4.5.jar:2.4.5]
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:48) ~[groovy-all-2.4.5.jar:2.4.5]
at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.call(PogoMetaClassSite.java:57) ~[groovy-all-2.4.5.jar:2.4.5]
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:48) ~[groovy-all-2.4.5.jar:2.4.5]
at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.call(PogoMetaClassSite.java:57) ~[groovy-all-2.4.5.jar:2.4.5]
Same issue exists when using Hateoas with Kotlin.
Same issue with groovy 2.4.6 and spring boot 1.3.5.RELEASE (running on OSX, java 1.8)
I am basing my groovy code on https://github.com/spring-guides/gs-rest-hateoas
the issue is StackOverflow from greeting.add(linkTo(methodOn(GreetingController.class).greeting(name)).withSelfRel())
With the @CompileStatic workaround I get
Error:(26, 29) Groovyc: [Static type checking] - Cannot find matching method java.lang.Object#greeting(java.lang.String). Please check if the declared type is right and if the method exists.
@compwron Looks like your GreetingController is casted to an Object. Try
greeting.add(linkTo(((GreetingController)methodOn(GreetingController.class)).greeting(name)).withSelfRel())
That looks cool! Unfortunately I no longer have access to the project code that might be fixed by this and I am too lazy to set up a new project from scratch right now to test it. I hope that people in the future come here and find this solution helpful.
cc/ @dudadornelles
I'm seeing this issue when using Kotlin. Does anyone know of a workaround? @JAnderton possibly?
A reasonable workaround for this issue in Kotlin is to use a different linkTo method, such as linkTo(Method, Object...).
Still occurs StackOverflowError.
__GreetingController.groovy__
````groovy
@GetMapping("/greeting2")
HttpEntity
Greeting greeting = new Greeting(String.format(TEMPLATE, name))
GroovyGreetingController greetingController = methodOn(GroovyGreetingController)
HttpEntity<Greeting> httpEntity = greetingController.greeting(name) // exception here
ControllerLinkBuilder controllerLinkBuilder = linkTo(httpEntity)
Link link = controllerLinkBuilder.withSelfRel()
greeting.add(link)
return new ResponseEntity<>(greeting, HttpStatus.OK)
}
````
__Error log__
error
java.lang.StackOverflowError: null
at java.lang.reflect.Proxy$KeyX.<init>(Proxy.java:500) ~[na:1.8.0_121]
at java.lang.reflect.Proxy$KeyFactory.apply(Proxy.java:548) ~[na:1.8.0_121]
at java.lang.reflect.Proxy$KeyFactory.apply(Proxy.java:539) ~[na:1.8.0_121]
at java.lang.reflect.WeakCache.get(WeakCache.java:120) ~[na:1.8.0_121]
at java.lang.reflect.Proxy.getProxyClass0(Proxy.java:419) ~[na:1.8.0_121]
at java.lang.reflect.Proxy.newProxyInstance(Proxy.java:719) ~[na:1.8.0_121]
at org.springframework.aop.framework.JdkDynamicAopProxy.getProxy(JdkDynamicAopProxy.java:123) ~[spring-aop-5.0.0.RC3.jar:5.0.0.RC3]
at org.springframework.aop.framework.JdkDynamicAopProxy.getProxy(JdkDynamicAopProxy.java:113) ~[spring-aop-5.0.0.RC3.jar:5.0.0.RC3]
at org.springframework.aop.framework.ProxyFactory.getProxy(ProxyFactory.java:97) ~[spring-aop-5.0.0.RC3.jar:5.0.0.RC3]
at org.springframework.hateoas.core.DummyInvocationUtils.getProxyWithInterceptor(DummyInvocationUtils.java:174) ~[spring-hateoas-0.23.0.RELEASE.jar:na]
at org.springframework.hateoas.core.DummyInvocationUtils.access$000(DummyInvocationUtils.java:44) ~[spring-hateoas-0.23.0.RELEASE.jar:na]
at org.springframework.hateoas.core.DummyInvocationUtils$InvocationRecordingMethodInterceptor.intercept(DummyInvocationUtils.java:112) ~[spring-hateoas-0.23.0.RELEASE.jar:na]
at hello.GroovyGreetingController$$EnhancerByCGLIB$$e844180e.getMetaClass(<generated>) ~[classes/:na]
at org.codehaus.groovy.runtime.callsite.CallSiteArray.createPogoSite(CallSiteArray.java:147) ~[groovy-all-2.5.0-beta-1.jar:2.5.0.beta-1]
at org.codehaus.groovy.runtime.callsite.CallSiteArray.createCallSite(CallSiteArray.java:164) ~[groovy-all-2.5.0-beta-1.jar:2.5.0.beta-1]
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:48) ~[groovy-all-2.5.0-beta-1.jar:2.5.0.beta-1]
at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.call(PogoMetaClassSite.java:57) ~[groovy-all-2.5.0-beta-1.jar:2.5.0.beta-1]
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:48) ~[groovy-all-2.5.0-beta-1.jar:2.5.0.beta-1]
at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.call(PogoMetaClassSite.java:57) ~[groovy-all-2.5.0-beta-1.jar:2.5.0.beta-1]
// below omitted
__Breakpoint.png__
I was getting a similar issue in my implementation.
My class def (kotlin format)
@JsonInclude(JsonInclude.Include.NON_EMPTY)
@JsonRootName("content")
class WebSocketInfo (
@ApiModelProperty(example = "1")
val id : String,
@ApiModelProperty(example = "localhost/127.0.0.1:8080")
val local_address : String,
@ApiModelProperty(example = "/0:0:0:0:0:0:0:1:56978")
val remote_address : String,
@ApiModelProperty(example = "/sddf")
val uri: String) : ResourceSupport() {
constructor(webSocketSession: WebSocketSession) : this(webSocketSession.id,
webSocketSession.localAddress.toString(),
webSocketSession.remoteAddress.toString(),
webSocketSession.uri!!.toASCIIString())
init {
add(ControllerLinkBuilder.linkTo(ControllerLinkBuilder.methodOn(SocketController::class.java).getSocketById("0")).withRel("peer_id"))
}
}
My initial function
@GetMapping("/socket/{id}")
@ApiOperation(value = "Retrieve information on a specific websocket", produces = "application/json")
fun getSocketById(
@ApiParam(name = "id", value = "The unique ID of the websocket in question", required = true)
@PathVariable(value = "id") socketID: String): WebSocketInfo {
val filteredSession = globals.connectedSockets.filter { it.id == socketID }
if (filteredSession.isNotEmpty()) {
return WebSocketInfo(filteredSession[0])
}
throw Exception("Socket with ID $socketID not found")
}
What i did was change the return type from: WebSocketInfo to Resource< WebSocketInfo>
return Resource(WebSocketInfo(filteredSession[0]))
And the error went away - perhaps wrapping whatever object you re returning in a Resource will fix your errors.
Most helpful comment
Same issue with groovy 2.4.6 and spring boot 1.3.5.RELEASE (running on OSX, java 1.8)
I am basing my groovy code on https://github.com/spring-guides/gs-rest-hateoas
the issue is StackOverflow from
greeting.add(linkTo(methodOn(GreetingController.class).greeting(name)).withSelfRel())With the @CompileStatic workaround I get
Error:(26, 29) Groovyc: [Static type checking] - Cannot find matching method java.lang.Object#greeting(java.lang.String). Please check if the declared type is right and if the method exists.