Skip to content

Calls to FactoryBean @Bean methods cause ClassCastException [SPR-6602] #11268

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
spring-projects-issues opened this issue Dec 22, 2009 · 6 comments
Labels
in: core Issues in core modules (aop, beans, core, context, expression) type: bug A general bug
Milestone

Comments

@spring-projects-issues
Copy link
Collaborator

spring-projects-issues commented Dec 22, 2009

Liu, Yinwei David opened SPR-6602 and commented

Hi,
It looks like that the javaconfig in Spring 3 does not support configure a FactoryBean in javaconfig, and then easily inject it to another Bean.

What I want in Spring 3 java config is:
class MyFactoryBean implements FactoryBean<B>{}

@Bean
public MyFactoryBean bFactoryBean() {
    return new MyFactoryBean();
}

@Bean
public A a(B b) {
    A a = new A();
    a.setB( bFactoryBean().getObject() );
    return a;
}

Can Spring 3 support FactoryBean in its javaconfig?


Affects: 3.0 GA

Attachments:

Issue Links:

Referenced from: commits 4c05eae

1 votes, 1 watchers

@spring-projects-issues
Copy link
Collaborator Author

Liu, Yinwei David commented

Please see test case:
@Configuration
public class FactoryBeanConfig {
@Bean
public C3 c3() throws Exception {
C3 c = new C3();
c.setMyBean(this.getFactoryBean().getObject());
return c;
}

@Bean
public MyFactoryBean getFactoryBean() {
    return new MyFactoryBean();
}

public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(FactoryBeanConfig.class);
}
}

public class C3 {
MyBean myBean;
...

public class MyFactoryBean implements FactoryBean<MyBean>, InitializingBean, SmartLifecycle {

public MyFactoryBean() {
    System.out.println("***** Create MyFactoryBean");
}
@Override
public MyBean getObject() throws Exception {
    return new MyBean();
}

...

public class MyBean {
}

@spring-projects-issues
Copy link
Collaborator Author

clay atkins commented

I get a class cast exception when the applicationEntityManagerFactoryBean is called in the applicationEntityManagerFactory method:

    @Bean
    public LocalContainerEntityManagerFactoryBean applicationEntityManagerFactoryBean() {
        LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean();
        emf.setPersistenceUnitName(bsmartProperties.getPersistenceUnitName());
        emf.setPersistenceUnitManager((PersistenceUnitManager) pum());
        emf.setPersistenceProvider((PersistenceProvider) persistenceProvider());
        return emf;
    }

    @Bean
    public EntityManagerFactory applicationEntityManagerFactory() {
        EntityManagerFactory b = applicationEntityManagerFactoryBean().getObject();
        return b;
    }
Caused by: java.lang.ClassCastException: $Proxy91 cannot be cast to org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean
	at bsmart.config.ApplicationConfigDatabase$$EnhancerByCGLIB$$1b5b6d61.applicationEntityManagerFactoryBean(<generated>) [cglib-2.1_3.jar:na]
	at bsmart.config.ApplicationConfigDatabase.applicationEntityManagerFactory(ApplicationConfigDatabase.java:154) [ApplicationConfigDatabase.class:na]
	at bsmart.config.ApplicationConfigDatabase$$EnhancerByCGLIB$$1b5b6d61.CGLIB$applicationEntityManagerFactory$1(<generated>) [cglib-2.1_3.jar:na]
	at bsmart.config.ApplicationConfigDatabase$$EnhancerByCGLIB$$1b5b6d61$$FastClassByCGLIB$$3002b5e5.invoke(<generated>) [cglib-2.1_3.jar:na]
	at net.sf.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:167) [cglib-2.1_3.jar:na]
	at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:170) [spring-context-3.0.0.RELEASE.jar:3.0.0.RELEASE]
	at bsmart.config.ApplicationConfigDatabase$$EnhancerByCGLIB$$1b5b6d61.applicationEntityManagerFactory(<generated>) [cglib-2.1_3.jar:na]
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) [na:1.6.0_17]
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) [na:1.6.0_17]
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) [na:1.6.0_17]
	at java.lang.reflect.Method.invoke(Method.java:597) [na:1.6.0_17]
	at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:146) [spring-beans-3.0.0.RELEASE.jar:3.0.0.RELEASE]
	... 54 common frames omitted

@spring-projects-issues
Copy link
Collaborator Author

Chris Beams commented

Attaching a simplified example that reproduces the issue. Import as a maven project and execute the main() method in repro.FooConfig.

@spring-projects-issues
Copy link
Collaborator Author

Chris Beams commented

Resolved. The ClassCastException was due to how caching of FactoryBean instances works. The subclass proxy implementation of the FactoryBean method was delegating to the container to return any cached instance of the value originally returned from getObject() during container initialization.

The interceptor for the proxy now specifically checks to see if a bean is a FactoryBean, and if so returns the actual FactoryBean (via &-dereferencing).

It also became clear while resolving this issue that calling a FactoryBean's getObject() method directly from within another @Bean method violates the scoping semantics that one would expect when working in Spring XML.

For example, consider a Hibernate SessionFactory configured through Spring's LocalSessionFactoryBean:

<bean id="sessionFactory" class="...LocalSessionFactoryBean">
   ...
</bean>

One would expect that references to 'sessionFactory' will all receive the same (singleton) SessionFactory instance.

<bean id="fooDao" class="FooDAO">
   <constructor-arg ref="sessionFactory"/>
</bean>
<bean id="barDao" class="BarDAO">
   <constructor-arg ref="sessionFactory"/>
</bean>

the fooDao and barDao beans should have a reference to the same single sessionFactory instance.

Now consider the same scenario in a @Configuration class:

@Configuration
class DataConfig {
    public @Bean LocalSessionFactoryBean sessionFactory() {
        LocalSessionFactoryBean fb = LocalSessionFactoryBean();
        // ...
        return fb;
    }

    public @Bean FooDAO fooDao() {
        return new FooDAO(sessionFactory().getObject());
    }

    public @Bean BarDAO barDao() {
        return new BarDAO(sessionFactory().getObject());
    }
}

In the example above, fooDao and barDao will recieve different instances of the SessionFactory object! This is because while the sessionFactory() bean method is guaranteed to return the cached, singleton LocalSessionFactoryBean instance, once that instance is obtained, calling its getObject() method simply executes the creation logic for the LSFB anew. This behavior is out of sync with XML and generally undesirable.

For this reason, functionality has been added along with this bugfix to proxy any FactoryBean instances returned directly from @Bean methods when called from within another @Bean method, such that the proxied FactoryBean's getObject() method is intercepted and delegates to the container to return any cached instance. This avoids creating additional objects and thus preserves scoping semantics.

@spring-projects-issues
Copy link
Collaborator Author

Chris Beams commented

updated issue title for searchability and changed issue type from new feature to bug

@spring-projects-issues
Copy link
Collaborator Author

Chris Beams commented

As a workaround until 3.0.1 is released, you have several options:

  • Have your @Configuration class implement BeanFactoryAware and then pull the FactoryBean out of the container using getBean("&factoryBean").
  • Simply do not mark the FactoryBean method with the @Bean annotation. This will avoid the proxying that is causing the ClassCastException. Of course, the FactoryBean will not be subject to any container lifecycle callbacks, etc.
  • Do not model the FactoryBean method as returning the FactoryBean object at all. Simply return the product of getObject() and change the signature to the corresponding type. Again, the FactoryBean will not be managed by the container in this approach.
  • Embed the creation and use of the FactoryBean within any @Bean methods that need its object as a dependency.

Again, these are all workarounds and non-ideal in different ways. The fix just committed should allow for usage of FactoryBeans in a way that parallels XML closely.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: core Issues in core modules (aop, beans, core, context, expression) type: bug A general bug
Projects
None yet
Development

No branches or pull requests

1 participant