ConfigurationClassEnhancer explicitly handles non-interceptable FactoryBeans
Issue: SPR-15275
This commit is contained in:
parent
6108ab1c31
commit
7fb0ad37da
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -17,7 +17,10 @@
|
|||
package org.springframework.context.annotation;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
|
@ -330,12 +333,11 @@ class ConfigurationClassEnhancer {
|
|||
factoryContainsBean(beanFactory, beanName)) {
|
||||
Object factoryBean = beanFactory.getBean(BeanFactory.FACTORY_BEAN_PREFIX + beanName);
|
||||
if (factoryBean instanceof ScopedProxyFactoryBean) {
|
||||
// Pass through - scoped proxy factory beans are a special case and should not
|
||||
// be further proxied
|
||||
// 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, beanFactory, beanName);
|
||||
return enhanceFactoryBean(factoryBean, beanMethod.getReturnType(), beanFactory, beanName);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -355,7 +357,13 @@ class ConfigurationClassEnhancer {
|
|||
}
|
||||
return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
|
||||
}
|
||||
else {
|
||||
|
||||
return obtainBeanInstanceFromFactory(beanMethod, beanMethodArgs, beanFactory, beanName);
|
||||
}
|
||||
|
||||
private Object obtainBeanInstanceFromFactory(Method beanMethod, Object[] beanMethodArgs,
|
||||
ConfigurableBeanFactory beanFactory, String beanName) {
|
||||
|
||||
// The user (i.e. not the factory) is requesting this bean through a call to
|
||||
// the bean method, direct or indirect. The bean may have already been marked
|
||||
// as 'in creation' in certain autowiring scenarios; if so, temporarily set
|
||||
|
@ -406,6 +414,20 @@ class ConfigurationClassEnhancer {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMatch(Method candidateMethod) {
|
||||
return BeanAnnotationHelper.isBeanAnnotated(candidateMethod);
|
||||
}
|
||||
|
||||
private ConfigurableBeanFactory getBeanFactory(Object enhancedConfigInstance) {
|
||||
Field field = ReflectionUtils.findField(enhancedConfigInstance.getClass(), BEAN_FACTORY_FIELD);
|
||||
Assert.state(field != null, "Unable to find generated bean factory field");
|
||||
Object beanFactory = ReflectionUtils.getField(field, enhancedConfigInstance);
|
||||
Assert.state(beanFactory != null, "BeanFactory has not been injected into @Configuration class");
|
||||
Assert.state(beanFactory instanceof ConfigurableBeanFactory,
|
||||
"Injected BeanFactory is not a ConfigurableBeanFactory");
|
||||
return (ConfigurableBeanFactory) beanFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -444,8 +466,60 @@ class ConfigurationClassEnhancer {
|
|||
* 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(final Object factoryBean, final ConfigurableBeanFactory beanFactory,
|
||||
final String beanName) {
|
||||
private Object enhanceFactoryBean(final Object factoryBean, Class<?> exposedType,
|
||||
final ConfigurableBeanFactory beanFactory, final String beanName) {
|
||||
|
||||
try {
|
||||
Class<?> clazz = factoryBean.getClass();
|
||||
boolean finalClass = Modifier.isFinal(clazz.getModifiers());
|
||||
boolean finalMethod = Modifier.isFinal(clazz.getMethod("getObject").getModifiers());
|
||||
if (finalClass || finalMethod) {
|
||||
if (exposedType.isInterface()) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Creating interface proxy for FactoryBean '" + beanName + "' of type [" +
|
||||
clazz.getName() + "] for use within another @Bean method because its " +
|
||||
(finalClass ? "implementation class" : "getObject() method") +
|
||||
" is final: Otherwise a getObject() call would not be routed to the factory.");
|
||||
}
|
||||
return createInterfaceProxyForFactoryBean(factoryBean, exposedType, beanFactory, beanName);
|
||||
}
|
||||
else {
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Unable to proxy FactoryBean '" + beanName + "' of type [" +
|
||||
clazz.getName() + "] for use within another @Bean method because its " +
|
||||
(finalClass ? "implementation class" : "getObject() method") +
|
||||
" is final: A getObject() call will NOT be routed to the factory. " +
|
||||
"Consider declaring the return type as a FactoryBean interface.");
|
||||
}
|
||||
return factoryBean;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (NoSuchMethodException ex) {
|
||||
// No getObject() method -> shouldn't happen, but as long as nobody is trying to call it...
|
||||
}
|
||||
|
||||
return createCglibProxyForFactoryBean(factoryBean, beanFactory, beanName);
|
||||
}
|
||||
|
||||
private Object createInterfaceProxyForFactoryBean(final Object factoryBean, Class<?> interfaceType,
|
||||
final ConfigurableBeanFactory beanFactory, final String beanName) {
|
||||
|
||||
return Proxy.newProxyInstance(
|
||||
factoryBean.getClass().getClassLoader(), new Class<?>[] {interfaceType},
|
||||
new InvocationHandler() {
|
||||
@Override
|
||||
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
|
||||
if (method.getName().equals("getObject") && args == null) {
|
||||
return beanFactory.getBean(beanName);
|
||||
}
|
||||
return ReflectionUtils.invokeMethod(method, factoryBean, args);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Object createCglibProxyForFactoryBean(final Object factoryBean,
|
||||
final ConfigurableBeanFactory beanFactory, final String beanName) {
|
||||
|
||||
Enhancer enhancer = new Enhancer();
|
||||
enhancer.setSuperclass(factoryBean.getClass());
|
||||
|
@ -489,21 +563,6 @@ class ConfigurationClassEnhancer {
|
|||
|
||||
return fbProxy;
|
||||
}
|
||||
|
||||
private ConfigurableBeanFactory getBeanFactory(Object enhancedConfigInstance) {
|
||||
Field field = ReflectionUtils.findField(enhancedConfigInstance.getClass(), BEAN_FACTORY_FIELD);
|
||||
Assert.state(field != null, "Unable to find generated bean factory field");
|
||||
Object beanFactory = ReflectionUtils.getField(field, enhancedConfigInstance);
|
||||
Assert.state(beanFactory != null, "BeanFactory has not been injected into @Configuration class");
|
||||
Assert.state(beanFactory instanceof ConfigurableBeanFactory,
|
||||
"Injected BeanFactory is not a ConfigurableBeanFactory");
|
||||
return (ConfigurableBeanFactory) beanFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMatch(Method candidateMethod) {
|
||||
return BeanAnnotationHelper.isBeanAnnotated(candidateMethod);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,250 @@
|
|||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.context.annotation;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.config.AbstractFactoryBean;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* @author Juergen Hoeller
|
||||
*/
|
||||
public class Spr15275Tests {
|
||||
|
||||
@Test
|
||||
public void testWithFactoryBean() {
|
||||
ApplicationContext context = new AnnotationConfigApplicationContext(ConfigWithFactoryBean.class);
|
||||
assertEquals("x", context.getBean(Bar.class).foo.toString());
|
||||
assertSame(context.getBean(FooInterface.class), context.getBean(Bar.class).foo);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithAbstractFactoryBean() {
|
||||
ApplicationContext context = new AnnotationConfigApplicationContext(ConfigWithAbstractFactoryBean.class);
|
||||
assertEquals("x", context.getBean(Bar.class).foo.toString());
|
||||
assertSame(context.getBean(FooInterface.class), context.getBean(Bar.class).foo);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithAbstractFactoryBeanForInterface() {
|
||||
ApplicationContext context = new AnnotationConfigApplicationContext(ConfigWithAbstractFactoryBeanForInterface.class);
|
||||
assertEquals("x", context.getBean(Bar.class).foo.toString());
|
||||
assertSame(context.getBean(FooInterface.class), context.getBean(Bar.class).foo);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithAbstractFactoryBeanAsReturnType() {
|
||||
ApplicationContext context = new AnnotationConfigApplicationContext(ConfigWithAbstractFactoryBeanAsReturnType.class);
|
||||
assertEquals("x", context.getBean(Bar.class).foo.toString());
|
||||
assertSame(context.getBean(FooInterface.class), context.getBean(Bar.class).foo);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithFinalFactoryBean() {
|
||||
ApplicationContext context = new AnnotationConfigApplicationContext(ConfigWithFinalFactoryBean.class);
|
||||
assertEquals("x", context.getBean(Bar.class).foo.toString());
|
||||
assertSame(context.getBean(FooInterface.class), context.getBean(Bar.class).foo);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithFinalFactoryBeanAsReturnType() {
|
||||
ApplicationContext context = new AnnotationConfigApplicationContext(ConfigWithFinalFactoryBeanAsReturnType.class);
|
||||
assertEquals("x", context.getBean(Bar.class).foo.toString());
|
||||
// not same due to fallback to raw FinalFactoryBean instance with repeated getObject() invocations
|
||||
assertNotSame(context.getBean(FooInterface.class), context.getBean(Bar.class).foo);
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
protected static class ConfigWithFactoryBean {
|
||||
|
||||
@Bean
|
||||
public FactoryBean<Foo> foo() {
|
||||
return new FactoryBean<Foo>() {
|
||||
@Override
|
||||
public Foo getObject() {
|
||||
return new Foo("x");
|
||||
}
|
||||
@Override
|
||||
public Class<?> getObjectType() {
|
||||
return Foo.class;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Bar bar() throws Exception {
|
||||
assertTrue(foo().isSingleton());
|
||||
return new Bar(foo().getObject());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
protected static class ConfigWithAbstractFactoryBean {
|
||||
|
||||
@Bean
|
||||
public FactoryBean<Foo> foo() {
|
||||
return new AbstractFactoryBean<Foo>() {
|
||||
@Override
|
||||
public Foo createInstance() {
|
||||
return new Foo("x");
|
||||
}
|
||||
@Override
|
||||
public Class<?> getObjectType() {
|
||||
return Foo.class;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Bar bar() throws Exception {
|
||||
assertTrue(foo().isSingleton());
|
||||
return new Bar(foo().getObject());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
protected static class ConfigWithAbstractFactoryBeanForInterface {
|
||||
|
||||
@Bean
|
||||
public FactoryBean<FooInterface> foo() {
|
||||
return new AbstractFactoryBean<FooInterface>() {
|
||||
@Override
|
||||
public FooInterface createInstance() {
|
||||
return new Foo("x");
|
||||
}
|
||||
@Override
|
||||
public Class<?> getObjectType() {
|
||||
return FooInterface.class;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Bar bar() throws Exception {
|
||||
assertTrue(foo().isSingleton());
|
||||
return new Bar(foo().getObject());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
protected static class ConfigWithAbstractFactoryBeanAsReturnType {
|
||||
|
||||
@Bean
|
||||
public AbstractFactoryBean<FooInterface> foo() {
|
||||
return new AbstractFactoryBean<FooInterface>() {
|
||||
@Override
|
||||
public FooInterface createInstance() {
|
||||
return new Foo("x");
|
||||
}
|
||||
@Override
|
||||
public Class<?> getObjectType() {
|
||||
return Foo.class;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Bar bar() throws Exception {
|
||||
assertTrue(foo().isSingleton());
|
||||
return new Bar(foo().getObject());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
protected static class ConfigWithFinalFactoryBean {
|
||||
|
||||
@Bean
|
||||
public FactoryBean<FooInterface> foo() {
|
||||
return new FinalFactoryBean();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Bar bar() throws Exception {
|
||||
assertTrue(foo().isSingleton());
|
||||
return new Bar(foo().getObject());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
protected static class ConfigWithFinalFactoryBeanAsReturnType {
|
||||
|
||||
@Bean
|
||||
public FinalFactoryBean foo() {
|
||||
return new FinalFactoryBean();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Bar bar() throws Exception {
|
||||
assertTrue(foo().isSingleton());
|
||||
return new Bar(foo().getObject());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static final class FinalFactoryBean implements FactoryBean<FooInterface> {
|
||||
|
||||
@Override
|
||||
public Foo getObject() {
|
||||
return new Foo("x");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getObjectType() {
|
||||
return FooInterface.class;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
protected interface FooInterface {
|
||||
}
|
||||
|
||||
|
||||
protected static class Foo implements FooInterface {
|
||||
|
||||
private final String value;
|
||||
|
||||
public Foo(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected static class Bar {
|
||||
|
||||
public final FooInterface foo;
|
||||
|
||||
public Bar(FooInterface foo) {
|
||||
this.foo = foo;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue