I have a problem on a SpringBoot (3.2.0) application that uses GraphQL with spring-boot-starter-graphql.
I have several annotated controllers that contain some @SchemaMapping, @QueryMapping and @MutationMapping. For some reasons that I can’t figure out, some @SchemaMapping methods will be called but the injected dependencies of the Controller are null, leading to a NPE.
@Controller
@AllArgsConstructor
public class MyController{
private final MyService service;
// This one is working
@SchemaMapping(typeName = "Foo", field = "bar")
public String getBar(Foo foo) {
return service.someMethod();
}
// This one is NOT working : service is null
@SchemaMapping(typeName = "Foo", field = "bar2")
public String getBar2(Foo foo) {
return service.someOtherMethod()
}
}
When I put breakpoint inside a @SchemaMapping method that I know it is not working, I can see that the type of the controller is MyController$$SpringCGLIB$$0@17823
and just MyController
when it works. And when I look at the Threads stack for a "wrong" call and a "good" call I see a difference : the org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:713)
method is called when its working.
Example of "good" stacktrace :
java.lang.Thread.State: RUNNABLE
at com.company.project.controller.MyController.getData(MyController.java:39)
at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:-1)
at jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:352)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:713)
at com.company.project.controller.MyController$$SpringCGLIB$$0.getData(<generated>:-1)
at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:-1)
at jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.graphql.data.method.InvocableHandlerMethodSupport.doInvoke(InvocableHandlerMethodSupport.java:87)
Example of "wrong" stacktrace :
java.lang.Thread.State: RUNNABLE
at com.company.project.controller.MyController.getData(MyController.java:44)
at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:-1)
at jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.graphql.data.method.InvocableHandlerMethodSupport.doInvoke(InvocableHandlerMethodSupport.java:87)
It seems that for some @SchemaMapping methods the AOP Proxy of the controller is frozen and cannot be intercepted.
If anyone has any idea what’s going on, any help is welcome :). Thanks
2 Answers
It seems like the issue is with Spring’s AOP proxy not being applied consistently to all @SchemaMapping methods, resulting in null dependencies. You should ensure that all methods in the controller that require dependency injection are properly intercepted by the Spring AOP proxy.
Try this instead:
@Controller
@AllArgsConstructor
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyController {
private final MyService service;
@SchemaMapping(typeName = "Foo", field = "bar")
public String getBar(Foo foo) {
return service.someMethod();
}
@SchemaMapping(typeName = "Foo", field = "bar2")
public String getBar2(Foo foo) {
return service.someOtherMethod();
}
}
Let me know if this does the trick.
Can you make sure to try annotating the MyService to @Service and autowire myService?
@Autowire
private MyService myService;
Spring’s AOP infrastructure automatically uses CGLIB or JDK dynamic proxies unless any condition is stopping it from doing that.
For AOP proxy, a final class is generally not considered a suitable proxy target, as Spring AOP cannot create subclasses or extend a final classes. It needs this ability to create CGLIB-based proxies for myService, by using which you will be calling proxy of someMethod().
Note that this is happening because @SchemaMapping
Let me know if this worked for you.