Resolved SPR-6602, relating to FactoryBean behavior in @Configuration classes. See issue and code comments for full details.
This commit is contained in:
parent
1cd0a9750d
commit
4c05eaeb16
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beansProjectDescription>
|
||||
<version>1</version>
|
||||
<pluginVersion><![CDATA[2.2.7.200910141010-RELEASE]]></pluginVersion>
|
||||
<pluginVersion><![CDATA[2.3.0.200912170948-RELEASE]]></pluginVersion>
|
||||
<configSuffixes>
|
||||
<configSuffix><![CDATA[xml]]></configSuffix>
|
||||
</configSuffixes>
|
||||
|
|
@ -10,6 +10,7 @@
|
|||
<config>src/test/java/org/springframework/context/annotation/configuration/ImportXmlConfig-context.xml</config>
|
||||
<config>src/test/java/org/springframework/context/annotation/configuration/SecondLevelSubConfig-context.xml</config>
|
||||
<config>src/test/java/org/springframework/context/annotation/configuration/ImportXmlWithAopNamespace-context.xml</config>
|
||||
<config>src/test/java/org/springframework/context/annotation/Spr6602Tests-context.xml</config>
|
||||
</configs>
|
||||
<configSets>
|
||||
</configSets>
|
||||
|
|
|
|||
|
|
@ -26,9 +26,10 @@ import net.sf.cglib.proxy.Enhancer;
|
|||
import net.sf.cglib.proxy.MethodInterceptor;
|
||||
import net.sf.cglib.proxy.MethodProxy;
|
||||
import net.sf.cglib.proxy.NoOp;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.aop.scope.ScopedProxyFactoryBean;
|
||||
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.util.Assert;
|
||||
|
|
@ -120,6 +121,24 @@ class ConfigurationClassEnhancer {
|
|||
Enhancer.registerStaticCallbacks(subclass, this.callbackInstances.toArray(new Callback[this.callbackInstances.size()]));
|
||||
return subclass;
|
||||
}
|
||||
|
||||
|
||||
private static class GetObjectMethodInterceptor implements MethodInterceptor {
|
||||
|
||||
private final ConfigurableBeanFactory beanFactory;
|
||||
private final String beanName;
|
||||
|
||||
public GetObjectMethodInterceptor(ConfigurableBeanFactory beanFactory, String beanName) {
|
||||
this.beanFactory = beanFactory;
|
||||
this.beanName = beanName;
|
||||
}
|
||||
|
||||
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
|
||||
System.out.println("intercepting request for getObject()");
|
||||
return beanFactory.getBean(beanName);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
|
|
@ -161,12 +180,29 @@ class ConfigurationClassEnhancer {
|
|||
|
||||
// to handle the case of an inter-bean method reference, we must explicitly check the
|
||||
// container for already cached instances
|
||||
|
||||
// first, check to see if the requested bean is a FactoryBean. If so, create a subclass
|
||||
// proxy that intercepts calls to getObject() and returns any cached bean instance.
|
||||
// this ensures that the semantics of calling a FactoryBean from within @Bean methods
|
||||
// is the same as that of referring to a FactoryBean within XML. See SPR-6602.
|
||||
if (factoryContainsBean('&'+beanName) && factoryContainsBean(beanName)) {
|
||||
Object factoryBean = this.beanFactory.getBean('&'+beanName);
|
||||
if (factoryBean instanceof ScopedProxyFactoryBean) {
|
||||
// pass through - scoped proxy factory beans are a special case and should not
|
||||
// be further proxied
|
||||
} else {
|
||||
// it is a candidate FactoryBean - go ahead with enhancement
|
||||
return enhanceFactoryBean(factoryBean.getClass(), beanName);
|
||||
}
|
||||
}
|
||||
|
||||
// the bean is not a FactoryBean - check to see if it has been cached
|
||||
if (factoryContainsBean(beanName)) {
|
||||
// we have an already existing cached instance of this bean -> retrieve it
|
||||
return this.beanFactory.getBean(beanName);
|
||||
}
|
||||
|
||||
// actually create and return the bean
|
||||
// no cached instance of the bean exists - actually create and return the bean
|
||||
return proxy.invokeSuper(obj, args);
|
||||
}
|
||||
|
||||
|
|
@ -187,5 +223,38 @@ class ConfigurationClassEnhancer {
|
|||
return (this.beanFactory.containsBean(beanName) && !this.beanFactory.isCurrentlyInCreation(beanName));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, String beanName) throws InstantiationException, IllegalAccessException {
|
||||
Enhancer enhancer = new Enhancer();
|
||||
enhancer.setUseCache(false);
|
||||
enhancer.setSuperclass(fbClass);
|
||||
enhancer.setUseFactory(false);
|
||||
enhancer.setCallbackFilter(new CallbackFilter() {
|
||||
public int accept(Method method) {
|
||||
return method.getName().equals("getObject") ? 0 : 1;
|
||||
}
|
||||
});
|
||||
List<Callback> callbackInstances = new ArrayList<Callback>();
|
||||
callbackInstances.add(new GetObjectMethodInterceptor(this.beanFactory, beanName));
|
||||
callbackInstances.add(NoOp.INSTANCE);
|
||||
|
||||
List<Class<? extends Callback>> callbackTypes = new ArrayList<Class<? extends Callback>>();
|
||||
|
||||
for (Callback callback : callbackInstances) {
|
||||
callbackTypes.add(callback.getClass());
|
||||
}
|
||||
|
||||
enhancer.setCallbackTypes(callbackTypes.toArray(new Class[callbackTypes.size()]));
|
||||
Class<?> fbSubclass = enhancer.createClass();
|
||||
Enhancer.registerCallbacks(fbSubclass, callbackInstances.toArray(new Callback[callbackInstances.size()]));
|
||||
return fbSubclass.newInstance();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<bean id="foo" class="org.springframework.context.annotation.Foo">
|
||||
<constructor-arg ref="barFactory"/>
|
||||
</bean>
|
||||
|
||||
<bean id="barFactory" class="org.springframework.context.annotation.BarFactory"/>
|
||||
|
||||
</beans>
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
package org.springframework.context.annotation;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.CoreMatchers.not;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
|
||||
/**
|
||||
* Tests to verify that FactoryBean semantics are the same in Configuration
|
||||
* classes as in XML.
|
||||
*
|
||||
* @author Chris Beams
|
||||
*/
|
||||
public class Spr6602Tests {
|
||||
@Test
|
||||
public void testXmlBehavior() throws Exception {
|
||||
doAssertions(new ClassPathXmlApplicationContext("Spr6602Tests-context.xml", Spr6602Tests.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConfigurationClassBehavior() throws Exception {
|
||||
doAssertions(new AnnotationConfigApplicationContext(FooConfig.class));
|
||||
}
|
||||
|
||||
private void doAssertions(ApplicationContext ctx) throws Exception {
|
||||
Foo foo = ctx.getBean(Foo.class);
|
||||
|
||||
Bar bar1 = ctx.getBean(Bar.class);
|
||||
Bar bar2 = ctx.getBean(Bar.class);
|
||||
assertThat(bar1, is(bar2));
|
||||
assertThat(bar1, is(foo.bar));
|
||||
|
||||
BarFactory barFactory1 = ctx.getBean(BarFactory.class);
|
||||
BarFactory barFactory2 = ctx.getBean(BarFactory.class);
|
||||
assertThat(barFactory1, is(barFactory2));
|
||||
|
||||
Bar bar3 = barFactory1.getObject();
|
||||
Bar bar4 = barFactory1.getObject();
|
||||
assertThat(bar3, is(not(bar4)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
class FooConfig {
|
||||
public @Bean Foo foo() throws Exception {
|
||||
return new Foo(barFactory().getObject());
|
||||
}
|
||||
|
||||
public @Bean BarFactory barFactory() {
|
||||
return new BarFactory();
|
||||
}
|
||||
}
|
||||
|
||||
class Foo { final Bar bar; public Foo(Bar bar) { this.bar = bar; } }
|
||||
class Bar { }
|
||||
|
||||
class BarFactory implements FactoryBean<Bar> {
|
||||
|
||||
public Bar getObject() throws Exception {
|
||||
return new Bar();
|
||||
}
|
||||
|
||||
public Class<? extends Bar> getObjectType() {
|
||||
return Bar.class;
|
||||
}
|
||||
|
||||
public boolean isSingleton() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue