Skip to content
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

ConfigurationClassEnhancer.enhanceFactoryBean is not transparent for method calls other than getObject() [SPR-12915] #17514

Closed
spring-issuemaster opened this issue Apr 15, 2015 · 3 comments

Comments

Projects
None yet
2 participants
@spring-issuemaster
Copy link
Collaborator

commented Apr 15, 2015

Adrian Moos opened SPR-12915 and commented

When a @Bean method returns an instance of a FactoryBean, Spring proxies the factory bean, redirecting calls to getObject() to applicationContext.getBean().

The relevant code reads:

	/**
	 * Create a subclass proxy that intercepts calls to getObject(), delegating to the current BeanFactory
	 * instead of creating a new instance. These proxies are created only when calling a FactoryBean from
	 * within a Bean method, allowing for proper scoping semantics even when working against the FactoryBean
	 * instance directly. If a FactoryBean instance is fetched through the container via &-dereferencing,
	 * it will not be proxied. This too is aligned with the way XML configuration works.
	 */
	private Object enhanceFactoryBean(Class<?> fbClass, final ConfigurableBeanFactory beanFactory,
			final String beanName) throws InstantiationException, IllegalAccessException {

		Enhancer enhancer = new Enhancer();
		enhancer.setSuperclass(fbClass);
		enhancer.setUseFactory(false);
		enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
		enhancer.setCallback(new MethodInterceptor() {
			@Override
			public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
				if (method.getName().equals("getObject") && args.length == 0) {
					return beanFactory.getBean(beanName);
				}
				return proxy.invokeSuper(obj, args); // bug here?
			}
		});
		return enhancer.create();
	}

In the marked line, obj refers to the proxy object.

Therefore, calls to methods other than getObject() are forwarded to the super implementation on the proxy object, which has a different state than the FactoryBean it proxies.

This breaks the following usecase:

        @Bean
        protected DBTool dbTool() {
            return new DBTool(hibernateSessionFactory());
        }

        @Bean
        protected AnnotationSessionFactoryBean hibernateSessionFactory() {
             // hibernate setup goes here
        }

where DBTool has a method:

public void createSchema() {
    annotationSessionFactory.createDatabaseSchema();
}

which now throws

java.lang.IllegalStateException: SessionFactory not initialized yet
	at org.springframework.orm.hibernate3.AbstractSessionFactoryBean.getSessionFactory(AbstractSessionFactoryBean.java:215)
	at org.springframework.orm.hibernate3.LocalSessionFactoryBean.createDatabaseSchema(LocalSessionFactoryBean.java:989)
	at org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean$$EnhancerBySpringCGLIB$$2fba14bc.CGLIB$createDatabaseSchema$12(<generated>)
	at org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean$$EnhancerBySpringCGLIB$$2fba14bc$$FastClassBySpringCGLIB$$52787a0.invoke(<generated>)
	at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:228)
	at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor$1.intercept(ConfigurationClassEnhancer.java:383)
	at org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean$$EnhancerBySpringCGLIB$$2fba14bc.createDatabaseSchema(<generated>)
	at ch.bedag.ste.app.cf.hibernate.framework.DBTool.createSchema(DBTool.java:23)

because the proxy object is a factory that has not been configured.


Affects: 4.1.2

Issue Links:

  • #11268 Calls to FactoryBean @Bean methods cause ClassCastException
  • #17686 CGLIB code generation failure for cross-@Bean FactoryBean call

Referenced from: commits 063a720, 1da98b0, 6783ba2

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Apr 15, 2015

Adrian Moos commented

As a workaround, the @Bean method can put a reference to the FactoryBean it has configured into a field:

        // This is an ugly workaround for #17514
        AnnotationSessionFactoryBean sessionFactoryFactory;

        @Bean
        protected DBTool dbTool() {
            hibernateSessionFactory(); // force initialization
            return new DBTool(sessionFactoryFactory);
        }

        @Bean
        protected AnnotationSessionFactoryBean hibernateSessionFactory() {
            AnnotationSessionFactoryBean factory = new AnnotationSessionFactoryBean();
            // factory configuration omitted
            sessionFactoryFactory = factory;
            return factory;
        }
@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Apr 15, 2015

Juergen Hoeller commented

Good catch! Fixed for 4.2 and 4.1.7 now.

Juergen

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Jun 4, 2015

Juergen Hoeller commented

Due to the implications reported in #17686, I'll reduce this change to the 4.2 line.

Modifying the CGLIB proxy to delegate all calls is surprisingly non-trivial, in particular for cross-method calls within the same instance. We might even have to create such FactoryBean proxy instances via Objenesis, just like we do for AOP proxies. For that reason, this definitely has to go through another RC and is therefore better suited for the 4.2 line.

Juergen

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.