If same scoped beans are wired together there's no problem. For example a singleton bean A injected into another singleton bean B. But if the bean A has the shorted-liver scope say prototype scope then there's a problem.
When injecting a shorter-lived scoped bean into a longer-lived scoped bean (for example, injecting a prototype bean into a single bean or an HTTP Session-scoped bean into singleton bean), then we may want to get a new instance of shorter-lived scoped bean multiple times during the lifespan of longer-lived scoped bean.
To understand the problem let's see an example. We are going to have two beans MyPrototypeBean , scoped as prototype and MySingletonBean , scoped as singleton . We will inject the prototype bean into the singleton bean. We will also access MySingletonBean via method call context#getBean(MySingletonBean.class) multiple times. We are expecting (per prototype specifications) that a new prototype bean will be created and be injected into MySingletonBean every time.
Example
Creating a prototype bean
package com.logicbig.example;
import java.time.LocalDateTime;
public class MyPrototypeBean {
private String dateTimeString = LocalDateTime.now().toString();
public String getDateTime() {
return dateTimeString;
}
}
Creating a singleton bean and injecting the prototype bean in it
package com.logicbig.example;
import org.springframework.beans.factory.annotation.Autowired;
public class MySingletonBean {
@Autowired
private MyPrototypeBean prototypeBean;
public void showMessage(){
System.out.println("Hi, the time is "+prototypeBean.getDateTime());
}
}
Declaring beans via @Bean and initiating app
package com.logicbig.example;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
@Configuration
public class AppConfig {
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public MyPrototypeBean prototypeBean() {
return new MyPrototypeBean();
}
@Bean
public MySingletonBean singletonBean() {
return new MySingletonBean();
}
public static void main(String[] args) throws InterruptedException {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig.class);
MySingletonBean bean = context.getBean(MySingletonBean.class);
bean.showMessage();
Thread.sleep(1000);
bean = context.getBean(MySingletonBean.class);
bean.showMessage();
}
}
OutputHi, the time is 2021-05-05T00:07:09.977 Hi, the time is 2021-05-05T00:07:09.977
In above output we are expecting MySingletonBean would print two different time, with approximately one sec difference.
The problem is: spring container creates the singleton bean MySingletonBean only once, and thus only gets one opportunity to inject the dependencies into it. The container cannot provide MySingletonBean with a new instance of MyPrototypeBean every time one is needed.
Solution to the problem
Following are the solutions to the above problems
- Inject
ApplicationContext bean into MySingletonBean to get instance of MyPrototypeBean, whenever we need it (example).
- Lookup method injection. This approach involves in dynamic bytecode generation (example).
- Using Scoped Proxy. (example here and here).
- Using JSR 330
Provider<T> injected by Spring (example).
- Injecting Spring's
ObjectProvider<T> (example).
- By using Java 8 Functions (example).
Example ProjectDependencies and Technologies Used: - spring-context 6.1.2 (Spring Context)
Version Compatibility: 3.2.3.RELEASE - 6.1.2 Version compatibilities of spring-context with this example: Versions in green have been tested.
- JDK 17
- Maven 3.8.1
|