4

As far as I read from spring documentation, if I put a prototype scoped bean inside a larger scoped bean, prototype scoped bean acts like as if it is also a view scoped bean. Meaning, it will only be initialized when the container bean is initialized.

In my prototype scoped bean, I have a simple property and a getter setter for that.

@Repository
@Transactional( readonly=true )
@MyPrototypeScope // see definition of this one at [1]
class MyPrototypeScopedBean {
    protected String member ;
    public String getMember( ) { return this.member ; }
    public void setMember( String member ) { 
        this.member = member ; 
        System.err.println( "member is set to " + this.member + ", class is : " 
                             + this.getClass( ).getName( ) ) ;
    }

    protected void someMethod( ) {
        System.err.println( "The member is " + member + ", class is : " 
                             + this.getClass( ).getName( ) ) ;
    }
}

My view scoped bean autowires it and tries to use it.

class MyViewScopedBean {
    @Autowired
    MyPrototypeScopedBean proto ;
....
    public void problematicMethod( ) {
        MyPrototypeScopedBean tmpReference = this.proto ; // to be sure we access it only once

        tmpReference.setMember( "X" ) ;

        System.err.println( "Value of member is " + tmpReference.getMember( ) + ", class is : " 
                             + this.getClass( ).getName( ) ) ;
        this.proto.someMethod( ) ;
    }
}

In the above case, I expect to see an output like below:

member is set to X, class is : com.xxx.MyPrototypeScopedBean$$EnhancerBySpringCGLIB$$xxxxxx
Value of member is X, class is : com.xxx.MyPrototypeScopedBean
The member is X, class is : com.xxx.MyPrototypeScopedBean

But instead, what I see is similar to below:

member is set to X, class is : com.xxx.MyPrototypeScopedBean$$EnhancerBySpringCGLIB$$xxxxxx
Value of member is null, class is : com.xxx.MyPrototypeScopedBean
The member is null, class is : com.xxx.MyPrototypeScopedBean

As if, spring somehow gives me a fresh instance for each reference of the "proto", be it a member access or a method call.

My code is obviously more complicated than this, but this is what the design is supposed to do. So, my question is, am I expecting something wrong? Is there any configuration issues that could cause this problem? Or this is just a symptom of a bug in some place else?

thank you.

Update 1: Following the suggestion of skaffman, I have added the class names, at the end of println statements. Code and outputs are updated to reflect the changes.

Also added the scope definitions on the bean.

[1]

@Target( { ElementType.TYPE, ElementType.METHOD } )
@Retention( RetentionPolicy.RUNTIME )
@Documented
@Scope( BeanDefinition.SCOPE_PROTOTYPE )
public @interface MyPrototypeScope {
    ScopedProxyMode proxyMode( ) default ScopedProxyMode.TARGET_CLASS ;
}
xycf7
  • 913
  • 5
  • 10
  • 2
    Out of curiosity, what happens when you call `System.err.println(proto.getClass().getName())`? It's possible Spring is proxying the prototype-scoped bean. – skaffman Jan 05 '15 at 09:47
  • How have you defined the scope on the `MyPrototypeScopedBean` instance? – M. Deinum Jan 05 '15 at 10:09
  • @skaffman : I have just updated the question with the class names. They are as expected. – xycf7 Jan 05 '15 at 13:28
  • @xycf7: The `EnhancerBySpringCGLIB` part of the class name means you're getting a proxy of your class, not the class itself. See @Aaron's answer. – skaffman Jan 05 '15 at 21:22

1 Answers1

3

Your hunch is correct: Every time you access this.proto, you get a new prototype. To implement this, Spring wires a proxy into the field. Every time that you access a method, Spring creates a new instance, calls the method, ... and eventually, you realize you have a problem.

So assigning the result of this.proto to a local variable doesn't help. Spring doesn't wrap accesses to the field, so it doesn't matter how you access it.

That means for beans with scope prototype, you have to be very, very careful. The best way to use them is to just call a single method and then get rid of them. My solution was to add a test case to the code which scans the whole spring configuration, fails for any beans in the scope BeanDefinition.SCOPE_PROTOTYPE and use my own factories instead.

If you need stateful prototyped beans, then there are two options:

  1. Create a singleton factory and use that to create instances when you need one.
  2. Use Spring to autowire a bean instance after you created it.

The first step works well in Java config:

@Autowired
private BarFactory barFactory;

@Bean
public Foo foo() {
    Foo result = new Foo();
    result.setBar(barFactory.create());
    return result;
}

The second one uses Spring's BeanFactory:

@Autowired
private ApplicationContext appContext;

....
public void problematicMethod( ) {
    MyPrototypeBean tmp = new MyPrototypeBean();
    appContex.getappContext.getAutowireCapableBeanFactory().autowireBean( tmp );

    ...
}

I think you can't rely on @PostConstruct in this case; instead you have to call those methods yourself.

Related:

Community
  • 1
  • 1
Aaron Digulla
  • 321,842
  • 108
  • 597
  • 820
  • Ok, this is good to know. But if this is the case, how am I supposed to do what I need to do? What I mean is, I want a different instance of `this.proto` for each different instance of the container bean. But at the same time, I want `this.proto` to be stateful so it should use the same instance as long as the container bean instance exist. Is there another way to handle this? thank you – xycf7 Jan 05 '15 at 15:15
  • Thank you, I will try your suggestions. – xycf7 Jan 05 '15 at 16:25