I was asked on interview question about injectin prototype into singleton. I difficult to diificult to answer and now I am trying to research this.
I have wrote following code(pring boot)
bean 1:
@Service
@Scope(value = "prototype")
public class MyValidator {
}
bean 2:
@Service
public class ValidatorHolder {
@Autowired
MyValidator myValidator;
public MyValidator getMyValidator() {
return myValidator;
}
}
configuration:
@SpringBootApplication
@Configuration
@ComponentScan("com.example.domain")
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
ApplicationContext context = new AnnotationConfigApplicationContext(DemoApplication.class);
ValidatorHolder validatorHolder1 = (ValidatorHolder) context.getBean("validatorHolder");
ValidatorHolder validatorHolder2 = (ValidatorHolder) context.getBean("validatorHolder");
System.out.println("=====================================");
System.out.println(validatorHolder1.getMyValidator() == validatorHolder2.getMyValidator());
System.out.println("=====================================");
}
}
This code retirns true.
As I understood while reading article link It is possible to configure to return false.
How can I do in my code? (without xml)
I tryed to rewrite code like in article:
<bean id="validatorHolder" class="com.example.domain.ValidatorHolder">
<property name="myValidator" ref="validator"/>
</bean>
<bean id="validator" scope="prototype" class="com.example.domain.MyValidator">
<!-- This instructs the container to proxy the current bean-->
<aop:scoped-proxy/>
</bean>
Inside main method I have wrote following code:
ApplicationContext xmlContext = new FileSystemXmlApplicationContext("classpath:applicationContext.xml");
ValidatorHolder validatorHolder21 = (ValidatorHolder) xmlContext.getBean("validatorHolder");
ValidatorHolder validatorHolder22 = (ValidatorHolder) xmlContext.getBean("validatorHolder");
System.out.println("=====================================");
System.out.println(validatorHolder21.getMyValidator() == validatorHolder22.getMyValidator());
System.out.println("=====================================");
anyway I see true
lets reserch Sean Patrick Floyd answer (scope proxy, b))
I use following main method class:
@SpringBootApplication
@ComponentScan("com.example.domain")
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
ApplicationContext context = new AnnotationConfigApplicationContext(DemoApplication.class);
ValidatorHolder validatorHolder1 = (ValidatorHolder) context.getBean("validatorHolder");
ValidatorHolder validatorHolder2 = (ValidatorHolder) context.getBean("validatorHolder");
System.out.println("=====================================");
System.out.println(validatorHolder1.getMyValidator() == validatorHolder2.getMyValidator());
System.out.println("=====================================");
}
when I run application - I see
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'validatorHolder' defined in file [D:\freelance\demo\target\classes\com\example\domain\ValidatorHolder.class]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.example.domain.ValidatorHolder]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.example.domain.ValidatorHolder.<init>()
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1099)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1044)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:504)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:476)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:303)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:299)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:755)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:759)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:480)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:689)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:321)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:969)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:958)
at com.example.DemoApplication.main(DemoApplication.java:20)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.example.domain.ValidatorHolder]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.example.domain.ValidatorHolder.<init>()
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:85)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1092)
... 20 common frames omitted
Caused by: java.lang.NoSuchMethodException: com.example.domain.ValidatorHolder.<init>()
at java.lang.Class.getConstructor0(Class.java:3074)
at java.lang.Class.getDeclaredConstructor(Class.java:2170)
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:80)
... 21 common frames omitted
PS2 issue was related with missed @Autowired
on constructor
after fix this issue
System.out.println(validatorHolder1.getMyValidator() == validatorHolder2.getMyValidator());
returns true
but if a bit replace MyValidator code:
@Service
@Scope(value="prototype", proxyMode=ScopedProxyMode.TARGET_CLASS)
public class MyValidator {
Object object = new Object();
public Object getObject() {
return object;
}
public void setObject(Object object) {
this.object = object;
}
}
System.out.println(validatorHolder1.getMyValidator() == validatorHolder2.getMyValidator());
true
System.out.println(validatorHolder1.getMyValidator().getObject() == validatorHolder2.getMyValidator().getObject());
false
and even
System.out.println(validatorHolder1.getMyValidator().getObject() == validatorHolder1.getMyValidator().getObject());
false
You have misunderstood the method injection technique. You need to make your bean abstract for it to work:
public class MyValidator {}
public abstract class ValidatorHolder {
public abstract MyValidator getMyValidator();
}
Now you can define the beans in XML as follows:
<bean class="com.somepackage.MyValidator" scope="prototype" />
<bean class="com.somepackage.ValidatorHolder">
<lookup-method name="getMyValidator" bean="myValidator" />
</bean>
In this case, Spring will create an anonymous subclass of ValidatorHolder
that returns the prototype bean (a new copy) every type it is called.
With annotated service classes, lookup method injection is not possible, but this is how you can do it with @Configuration
classes:
@Configuration
public class MyConfiguration{
@Bean
@Scope("prototype")
public MyValidator myValidator(){
return new MyValidator();
}
@Bean
public ValidatorHolder validatorHolder(){
return new ValidatorHolder(){
@Override public MyValidator getMyValidator(){
return myValidator();
}
};
}
}
In this case, you are creating the subclass of ValidatorHolder
yourself, and you can see clearly what happens.
But either version only works if you make bean and the provider method abstract.
On a final note, there are three different ways to define spring beans:
@Service
, @Component
) with a component scan @Configuration
classes with @Bean
methods. In your sample code, you are mixing these three styles, which is almost never a good idea. Pick one technique and stick with it.
Regarding the scoped proxy, this can be achieved in all three bean registration techniques.
a) XML
public class MyValidator {}
public class ValidatorHolder {
private MyValidator myValidator;
public void setMyValidator(MyValidator myValidator){
this.myValidator = myValidator;}
public MyValidator getMyValidator();
}
<bean class="com.somepackage.MyValidator" scope="prototype" />
<bean class="com.somepackage.ValidatorHolder">
<aop:scoped-proxy />
</bean>
b) annotated service class
@Service @Scope(value="prototype", proxyMode=ScopedProxyMode.TARGET_CLASS)
public class MyValidator {}
@Service
public class ValidatorHolder {
@Autowired
public ValidatorHolder(MyValidator myValidator){
this.myValidator=myValidator;
}
private final MyValidator myValidator;
public MyValidator getMyValidator(){ return myValidator; };
}
c) @Configuration
classes, Bean classes like in XML version
@Configuration
public class MyConfiguration{
@Bean
@Scope("prototype")
public MyValidator myValidator(){
return new MyValidator();
}
@Bean
public ValidatorHolder validatorHolder(){
return new ValidatorHolder(myValidator());
}
}
Please note that all proxy solutions will always return the same object, the proxy. But the underlying functionality will delegate to different Objects. Try it out by adding this code to MyValidator:
private int counter = 1;
public int counter(){
return counter ++;
}
Now, independent of how often you call this code:
validatorHolder.getMyValidator().counter();
it will always return 1
.
@Service
public class ValidatorHolder {
@Autowired
ApplicatioContext context;
public MyValidator getMyValidator() {
return context.getBean(MyValidator.class);
}
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.