Skip to content

Commit 7fb0ad3

Browse files
committed
ConfigurationClassEnhancer explicitly handles non-interceptable FactoryBeans
Issue: SPR-15275
1 parent 6108ab1 commit 7fb0ad3

File tree

2 files changed

+379
-70
lines changed

2 files changed

+379
-70
lines changed

spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java

Lines changed: 129 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2016 the original author or authors.
2+
* Copyright 2002-2017 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,7 +17,10 @@
1717
package org.springframework.context.annotation;
1818

1919
import java.lang.reflect.Field;
20+
import java.lang.reflect.InvocationHandler;
2021
import java.lang.reflect.Method;
22+
import java.lang.reflect.Modifier;
23+
import java.lang.reflect.Proxy;
2124
import java.util.Arrays;
2225

2326
import org.apache.commons.logging.Log;
@@ -330,12 +333,11 @@ public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object
330333
factoryContainsBean(beanFactory, beanName)) {
331334
Object factoryBean = beanFactory.getBean(BeanFactory.FACTORY_BEAN_PREFIX + beanName);
332335
if (factoryBean instanceof ScopedProxyFactoryBean) {
333-
// Pass through - scoped proxy factory beans are a special case and should not
334-
// be further proxied
336+
// Scoped proxy factory beans are a special case and should not be further proxied
335337
}
336338
else {
337339
// It is a candidate FactoryBean - go ahead with enhancement
338-
return enhanceFactoryBean(factoryBean, beanFactory, beanName);
340+
return enhanceFactoryBean(factoryBean, beanMethod.getReturnType(), beanFactory, beanName);
339341
}
340342
}
341343

@@ -346,68 +348,88 @@ public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object
346348
if (logger.isWarnEnabled() &&
347349
BeanFactoryPostProcessor.class.isAssignableFrom(beanMethod.getReturnType())) {
348350
logger.warn(String.format("@Bean method %s.%s is non-static and returns an object " +
349-
"assignable to Spring's BeanFactoryPostProcessor interface. This will " +
350-
"result in a failure to process annotations such as @Autowired, " +
351-
"@Resource and @PostConstruct within the method's declaring " +
352-
"@Configuration class. Add the 'static' modifier to this method to avoid " +
353-
"these container lifecycle issues; see @Bean javadoc for complete details.",
351+
"assignable to Spring's BeanFactoryPostProcessor interface. This will " +
352+
"result in a failure to process annotations such as @Autowired, " +
353+
"@Resource and @PostConstruct within the method's declaring " +
354+
"@Configuration class. Add the 'static' modifier to this method to avoid " +
355+
"these container lifecycle issues; see @Bean javadoc for complete details.",
354356
beanMethod.getDeclaringClass().getSimpleName(), beanMethod.getName()));
355357
}
356358
return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
357359
}
358-
else {
359-
// The user (i.e. not the factory) is requesting this bean through a call to
360-
// the bean method, direct or indirect. The bean may have already been marked
361-
// as 'in creation' in certain autowiring scenarios; if so, temporarily set
362-
// the in-creation status to false in order to avoid an exception.
363-
boolean alreadyInCreation = beanFactory.isCurrentlyInCreation(beanName);
364-
try {
365-
if (alreadyInCreation) {
366-
beanFactory.setCurrentlyInCreation(beanName, false);
367-
}
368-
boolean useArgs = !ObjectUtils.isEmpty(beanMethodArgs);
369-
if (useArgs && beanFactory.isSingleton(beanName)) {
370-
// Stubbed null arguments just for reference purposes,
371-
// expecting them to be autowired for regular singleton references?
372-
// A safe assumption since @Bean singleton arguments cannot be optional...
373-
for (Object arg : beanMethodArgs) {
374-
if (arg == null) {
375-
useArgs = false;
376-
break;
377-
}
360+
361+
return obtainBeanInstanceFromFactory(beanMethod, beanMethodArgs, beanFactory, beanName);
362+
}
363+
364+
private Object obtainBeanInstanceFromFactory(Method beanMethod, Object[] beanMethodArgs,
365+
ConfigurableBeanFactory beanFactory, String beanName) {
366+
367+
// The user (i.e. not the factory) is requesting this bean through a call to
368+
// the bean method, direct or indirect. The bean may have already been marked
369+
// as 'in creation' in certain autowiring scenarios; if so, temporarily set
370+
// the in-creation status to false in order to avoid an exception.
371+
boolean alreadyInCreation = beanFactory.isCurrentlyInCreation(beanName);
372+
try {
373+
if (alreadyInCreation) {
374+
beanFactory.setCurrentlyInCreation(beanName, false);
375+
}
376+
boolean useArgs = !ObjectUtils.isEmpty(beanMethodArgs);
377+
if (useArgs && beanFactory.isSingleton(beanName)) {
378+
// Stubbed null arguments just for reference purposes,
379+
// expecting them to be autowired for regular singleton references?
380+
// A safe assumption since @Bean singleton arguments cannot be optional...
381+
for (Object arg : beanMethodArgs) {
382+
if (arg == null) {
383+
useArgs = false;
384+
break;
378385
}
379386
}
380-
Object beanInstance = (useArgs ? beanFactory.getBean(beanName, beanMethodArgs) :
381-
beanFactory.getBean(beanName));
382-
if (beanInstance != null && !ClassUtils.isAssignableValue(beanMethod.getReturnType(), beanInstance)) {
383-
String msg = String.format("@Bean method %s.%s called as a bean reference " +
384-
"for type [%s] but overridden by non-compatible bean instance of type [%s].",
385-
beanMethod.getDeclaringClass().getSimpleName(), beanMethod.getName(),
386-
beanMethod.getReturnType().getName(), beanInstance.getClass().getName());
387-
try {
388-
BeanDefinition beanDefinition = beanFactory.getMergedBeanDefinition(beanName);
389-
msg += " Overriding bean of same name declared in: " + beanDefinition.getResourceDescription();
390-
}
391-
catch (NoSuchBeanDefinitionException ex) {
392-
// Ignore - simply no detailed message then.
393-
}
394-
throw new IllegalStateException(msg);
387+
}
388+
Object beanInstance = (useArgs ? beanFactory.getBean(beanName, beanMethodArgs) :
389+
beanFactory.getBean(beanName));
390+
if (beanInstance != null && !ClassUtils.isAssignableValue(beanMethod.getReturnType(), beanInstance)) {
391+
String msg = String.format("@Bean method %s.%s called as a bean reference " +
392+
"for type [%s] but overridden by non-compatible bean instance of type [%s].",
393+
beanMethod.getDeclaringClass().getSimpleName(), beanMethod.getName(),
394+
beanMethod.getReturnType().getName(), beanInstance.getClass().getName());
395+
try {
396+
BeanDefinition beanDefinition = beanFactory.getMergedBeanDefinition(beanName);
397+
msg += " Overriding bean of same name declared in: " + beanDefinition.getResourceDescription();
395398
}
396-
Method currentlyInvoked = SimpleInstantiationStrategy.getCurrentlyInvokedFactoryMethod();
397-
if (currentlyInvoked != null) {
398-
String outerBeanName = BeanAnnotationHelper.determineBeanNameFor(currentlyInvoked);
399-
beanFactory.registerDependentBean(beanName, outerBeanName);
399+
catch (NoSuchBeanDefinitionException ex) {
400+
// Ignore - simply no detailed message then.
400401
}
401-
return beanInstance;
402+
throw new IllegalStateException(msg);
402403
}
403-
finally {
404-
if (alreadyInCreation) {
405-
beanFactory.setCurrentlyInCreation(beanName, true);
406-
}
404+
Method currentlyInvoked = SimpleInstantiationStrategy.getCurrentlyInvokedFactoryMethod();
405+
if (currentlyInvoked != null) {
406+
String outerBeanName = BeanAnnotationHelper.determineBeanNameFor(currentlyInvoked);
407+
beanFactory.registerDependentBean(beanName, outerBeanName);
408+
}
409+
return beanInstance;
410+
}
411+
finally {
412+
if (alreadyInCreation) {
413+
beanFactory.setCurrentlyInCreation(beanName, true);
407414
}
408415
}
409416
}
410417

418+
@Override
419+
public boolean isMatch(Method candidateMethod) {
420+
return BeanAnnotationHelper.isBeanAnnotated(candidateMethod);
421+
}
422+
423+
private ConfigurableBeanFactory getBeanFactory(Object enhancedConfigInstance) {
424+
Field field = ReflectionUtils.findField(enhancedConfigInstance.getClass(), BEAN_FACTORY_FIELD);
425+
Assert.state(field != null, "Unable to find generated bean factory field");
426+
Object beanFactory = ReflectionUtils.getField(field, enhancedConfigInstance);
427+
Assert.state(beanFactory != null, "BeanFactory has not been injected into @Configuration class");
428+
Assert.state(beanFactory instanceof ConfigurableBeanFactory,
429+
"Injected BeanFactory is not a ConfigurableBeanFactory");
430+
return (ConfigurableBeanFactory) beanFactory;
431+
}
432+
411433
/**
412434
* Check the BeanFactory to see whether the bean named <var>beanName</var> already
413435
* exists. Accounts for the fact that the requested bean may be "in creation", i.e.:
@@ -444,8 +466,60 @@ private boolean isCurrentlyInvokedFactoryMethod(Method method) {
444466
* instance directly. If a FactoryBean instance is fetched through the container via &-dereferencing,
445467
* it will not be proxied. This too is aligned with the way XML configuration works.
446468
*/
447-
private Object enhanceFactoryBean(final Object factoryBean, final ConfigurableBeanFactory beanFactory,
448-
final String beanName) {
469+
private Object enhanceFactoryBean(final Object factoryBean, Class<?> exposedType,
470+
final ConfigurableBeanFactory beanFactory, final String beanName) {
471+
472+
try {
473+
Class<?> clazz = factoryBean.getClass();
474+
boolean finalClass = Modifier.isFinal(clazz.getModifiers());
475+
boolean finalMethod = Modifier.isFinal(clazz.getMethod("getObject").getModifiers());
476+
if (finalClass || finalMethod) {
477+
if (exposedType.isInterface()) {
478+
if (logger.isDebugEnabled()) {
479+
logger.debug("Creating interface proxy for FactoryBean '" + beanName + "' of type [" +
480+
clazz.getName() + "] for use within another @Bean method because its " +
481+
(finalClass ? "implementation class" : "getObject() method") +
482+
" is final: Otherwise a getObject() call would not be routed to the factory.");
483+
}
484+
return createInterfaceProxyForFactoryBean(factoryBean, exposedType, beanFactory, beanName);
485+
}
486+
else {
487+
if (logger.isInfoEnabled()) {
488+
logger.info("Unable to proxy FactoryBean '" + beanName + "' of type [" +
489+
clazz.getName() + "] for use within another @Bean method because its " +
490+
(finalClass ? "implementation class" : "getObject() method") +
491+
" is final: A getObject() call will NOT be routed to the factory. " +
492+
"Consider declaring the return type as a FactoryBean interface.");
493+
}
494+
return factoryBean;
495+
}
496+
}
497+
}
498+
catch (NoSuchMethodException ex) {
499+
// No getObject() method -> shouldn't happen, but as long as nobody is trying to call it...
500+
}
501+
502+
return createCglibProxyForFactoryBean(factoryBean, beanFactory, beanName);
503+
}
504+
505+
private Object createInterfaceProxyForFactoryBean(final Object factoryBean, Class<?> interfaceType,
506+
final ConfigurableBeanFactory beanFactory, final String beanName) {
507+
508+
return Proxy.newProxyInstance(
509+
factoryBean.getClass().getClassLoader(), new Class<?>[] {interfaceType},
510+
new InvocationHandler() {
511+
@Override
512+
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
513+
if (method.getName().equals("getObject") && args == null) {
514+
return beanFactory.getBean(beanName);
515+
}
516+
return ReflectionUtils.invokeMethod(method, factoryBean, args);
517+
}
518+
});
519+
}
520+
521+
private Object createCglibProxyForFactoryBean(final Object factoryBean,
522+
final ConfigurableBeanFactory beanFactory, final String beanName) {
449523

450524
Enhancer enhancer = new Enhancer();
451525
enhancer.setSuperclass(factoryBean.getClass());
@@ -489,21 +563,6 @@ public Object intercept(Object obj, Method method, Object[] args, MethodProxy pr
489563

490564
return fbProxy;
491565
}
492-
493-
private ConfigurableBeanFactory getBeanFactory(Object enhancedConfigInstance) {
494-
Field field = ReflectionUtils.findField(enhancedConfigInstance.getClass(), BEAN_FACTORY_FIELD);
495-
Assert.state(field != null, "Unable to find generated bean factory field");
496-
Object beanFactory = ReflectionUtils.getField(field, enhancedConfigInstance);
497-
Assert.state(beanFactory != null, "BeanFactory has not been injected into @Configuration class");
498-
Assert.state(beanFactory instanceof ConfigurableBeanFactory,
499-
"Injected BeanFactory is not a ConfigurableBeanFactory");
500-
return (ConfigurableBeanFactory) beanFactory;
501-
}
502-
503-
@Override
504-
public boolean isMatch(Method candidateMethod) {
505-
return BeanAnnotationHelper.isBeanAnnotated(candidateMethod);
506-
}
507566
}
508567

509568
}

0 commit comments

Comments
 (0)