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() returns null for scoped bean in case of ScopeNotActiveException
  • 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.