[SPR-12915] ConfigurationClassEnhancer.enhanceFactoryBean is not transparent for method calls other than getObject() Created: 15/Apr/15  Updated: 04/Jun/15  Resolved: 04/Jun/15

Status: Closed
Project: Spring Framework
Component/s: Core:DI
Affects Version/s: 4.1.2
Fix Version/s: 4.2 RC1

Type: Bug Priority: Major
Reporter: Adrian Moos Assignee: Juergen Hoeller
Resolution: Complete Votes: 0
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Issue Links:
Relate
relates to SPR-6602 Calls to FactoryBean @Bean methods ca... Closed
is related to SPR-13095 CGLIB code generation failure for cro... Closed
Days since last comment: 3 years, 24 weeks, 6 days ago
Last commented by a User: false
Last updater: Juergen Hoeller

 Description   

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.



 Comments   
Comment by Adrian Moos [ 15/Apr/15 ]

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 https://jira.spring.io/browse/SPR-12915
        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;
        }
Comment by Juergen Hoeller [ 15/Apr/15 ]

Good catch! Fixed for 4.2 and 4.1.7 now.

Juergen

Comment by Juergen Hoeller [ 04/Jun/15 ]

Due to the implications reported in SPR-13095, 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

Generated at Wed Nov 21 18:54:08 UTC 2018 using JIRA 7.9.2#79002-sha1:3bb15b68ecd99a30eb364c4c1a393359bcad6278.