Affects: 5.3.x
https://github.com/spring-projects/spring-framework/issues/24822 introduced a logic to return null
from ObjectProvider#getIfAvailable()
for scoped beans.
After a couple of tries, I found the scoped bean needs to have proxyMode=NO
to get this behavior.
For example, this works
@Bean
@RequestScope(proxyMode = ScopedProxyMode.NO) // need to set "proxyMode=NO"
public MyBean myBean() {
return new MyBeanImpl(...);
}
@Bean // run on non request bound thread
public CommandLineRunner runner(ObjectProvider<MyBean> myBean) {
return (args) -> {
MyBean bean = myBean.getIfAvailable(); // this returns null
...
};
}
If different proxyMode
is set on the scoped bean, getIfAvailable()
returns a proxy, not null
. (The proxy throws ScopeNotActiveException
when accessed on a non request bound thread.)
Therefore, it has to use proxyMode=NO
in order to get the null
from getIfAvailable()
on scoped beans.
This requirement brings a problem.
If I add this bean definition
@Bean
String foo(MyBean myBean) {
// do not touch myBean
return "FOO";
}
This fails to inject MyBean
to the foo
with ScopeNotActiveException
because it tries to create a MyBean
instead of a proxy, but the thread is not bound to the request at application context creation.
If the proxyMode
is not NO
, then a proxy is injected and the application context successfully created. But I cannot get null
from getIfAvailable()
because it returns a proxy.
This means, when I make proxyMode=NO
to get null
for ObjectProvider
, everywhere that uses the scoped bean is required to use the ObjectProvider
. A simple injection of the scoped bean proxy doesn't work because it doesn't create a proxy.
I am not sure it is an ideal or intended behavior of the original change.
If this is the intended behavior, I think it needs a couple of detailed documentation for this corner case behavior.
For example,
- On getIfAvailable()
and other methods, document when it returns null
(scoped beans with ScopedProxyMode.NO
)
- On @RequestScope
and maybe other scope annotations, document the scoped bean cannot directly inject to the non scoped beans(singleton) when proxyMode=NO
.
From the user's perspective, more intuitive behavior for the request scoped beans would be:
- No need to specify
proxyMode=NO
ObjectProvider#getIfAvailable()
returnsnull
for scoped bean in case ofScopeNotActiveException
- Inject a proxy bean when a scoped bean is directly autowired
@Bean
@RequestScope // does not need to specify "proxyMode=NO"
public MyBean myBean() {
return new MyBeanImpl(...);
}
@Bean
public CommandLineRunner runner(ObjectProvider<MyBean> myBean) {
return (args) -> {
MyBean bean = myBean.getIfAvailable(); // returns null since request is not bound
...
};
}
@Bean
String foo(MyBean myBean) { // proxy should be injected
myBean.hello(); // should throw exception since request is not bound
return "FOO";
}
Comment From: snicoll
That's not getIfAvailable
but ifAvailable
, the behavior is not the same and if you're requesting a bean and attempt to call something when the scope is not available, this will throw an exception.
The configuration you've added at the end of your report works if you replace with ifAvailable
that's not going to invoke the consumer (and not fail). As for returning null
for getIfAvailable
when the scope is not available, I am afraid it would be too confusing so that's not an option.