简体   繁体   中英

inject prototype into singleton(java configuration + annotation)

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)

PS

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

PS2

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

PS3

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:

  • XML
  • annotated classes (eg @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);
}
}

also read http://shekhargulati.com/tag/method-injection/

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM