I created a ExecutionTimeAdvice.java to log the runtime of all my controller methods using @Aspect.
When i call the public (http://localhost:8080/) method works perfectly, but when I call the private method (http://localhost:8080/2) my @Autowired service is not instantiated:
java.lang.NullPointerException: Cannot invoke "com.example.demo.service.MyService.sayHello()" because "this.myService" is null
at com.example.demo.controller.MyController.get2(MyController.java:24) ~[classes/:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205) ~[spring-web-6.0.11.jar:6.0.11]
but if I remove the filter everything works.
You can download the project here: demo.zip
Tests were performed on the following versions:: 2.7.14 (java8) and 3.1.2 (java17)
Comment From: snicoll
Thanks for the sample. Your aspect does not work on the private method and it looks like the call to the private method is done on the raw instance, and said instance has not been initialized properly. Perhaps the creation of the aspect lead to an early initialization of the bean. It's a bit odd so we'd need to investigate a bit more.
Comment From: snicoll
So the Aspect has nothing to do with the issue, except the fact it triggers the creation of a proxy. The same. could happen with any stereotype on the controller that trigger the same thing (e.g. @Transactional
). The problem is that invoking a private method on a cglib proxy is not supported. Our dispatching algorithm does not take that into account and so we end up in the weird state that you've discovered.
We'll revisit this to trigger a proper exception upfront, rather than calling the private method on the proxy via reflection. Either way, this scenario won't be supported so you'll have to revisit your controller so that the method isn't private.
Comment From: samuelfac
Yes, I already changed all my methods to public, but it took me a few hours to find out what the problem was because I didn't find anything about it, so I opened the issue.
Thanks
Comment From: Ventura2
I have created a simple test that reproduces the error:
public class CGLibProxyPrivateMethodTest {
ClassWithAutowireAndPrivateMethod proxy;
@BeforeEach
public void setup() {
ApplicationContext context = new AnnotationConfigApplicationContext(ClassWithAutowireAndPrivateMethod.class, MySecondService.class);
ClassWithAutowireAndPrivateMethod bean1 = context.getBean(ClassWithAutowireAndPrivateMethod.class);
ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
proxyFactoryBean.setTarget(bean1);
proxy = (ClassWithAutowireAndPrivateMethod) proxyFactoryBean.getObject();
}
@Test
public void publicMethodCallAutowired_Pass() throws InterruptedException {
proxy.get1();
}
@Test
public void privateMethodCallAutowired_Fail() throws InterruptedException {
proxy.get2();
}
public static class ClassWithAutowireAndPrivateMethod {
@Autowired
MySecondService mySecondService;
public String get1() throws InterruptedException {
return mySecondService.sayHello();
}
private String get2() throws InterruptedException {
return mySecondService.sayHello();
}
}
static class MySecondService{
public String sayHello() throws InterruptedException {
return "hello";
}
}
}
Comment From: rlaope
I also had a similar situation. While developing a Handler, when I applied an AOP-annotated method to a simple operation (for example, returning the string "success"), it executed successfully. However, when debugging, I could see that it was being executed from the Enhancer class. But when it comes to executing operations that depend on classes like Service through dependency injection, the injected class ends up being null.
I believe that the statement about a proxy object being created for a private method itself is incorrect. If there is any related information, please provide a posting,
+ My guess is that the generated proxy object does not have dependency injection, but the original object has dependency injection, so the enhancer calls the method's behavior, which seems to be working as null. This seems to need modification.
Comment From: rlaope
I have registered the above comment as a cglib issue. https://github.com/cglib/cglib/issues/223
Comment From: YongGoose
I’m interested in contributing to the framework, so I looked into this issue to see if I could help.
First of all, since CGLIB
is no longer maintained, I believe this should be addressed within the Spring Framework
.
In the case of web requests
(e.g. Spring MVC controller invocations), methods are invoked via reflection
, but for transactions
, they are not invoked through reflection. This means that handling the issue in a generic way at the invocation stage doesn’t seem very straightforward.
So I tried to see if it’s possible to throw an exception at the time of proxy creation, but there’s one concern we need to think about: We must distinguish whether a given private method is genuinely being called at runtime, or if it’s simply private to prevent external access.
Otherwise, we might end up throwing exceptions even for private methods that exist only to restrict external access, which would cause unnecessary problems.
@snicoll Do you have any ideas on how this could be solved? I’d be happy to explore possible solutions together, since I enjoy these kinds of technical conversations.
Comment From: snicoll
@snicoll Do you have any ideas on how this could be solved?
No. Asking @jhoeller for some insights, here's what he shared:
The only thing we can do there is to prevent a reflective invocation on a private method in the web dispatcher. At the proxy level, we don't know whether anyone will ever call a particular private method in a non-supported way, so we can't reject that at the proxy class level. Only in an external dispatching scenario like with handler methods, we could have an explicit CGLIB-and-private check where we artificially reject the dispatch then.
Comment From: YongGoose
@snicoll @jhoeller
Please take a look :) - https://github.com/spring-projects/spring-framework/pull/35352