Early resolution of unique factory methods in configuration classes
Includes consistent bean class resolution in the enhancement step as well as general reflection optimizations for user-declared methods. Closes gh-22420
This commit is contained in:
parent
a35adc6ea6
commit
40c62139ae
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2019 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.
|
||||
|
@ -153,7 +153,7 @@ public class ReflectiveAspectJAdvisorFactory extends AbstractAspectJAdvisorFacto
|
|||
if (AnnotationUtils.getAnnotation(method, Pointcut.class) == null) {
|
||||
methods.add(method);
|
||||
}
|
||||
});
|
||||
}, ReflectionUtils.USER_DECLARED_METHODS);
|
||||
methods.sort(METHOD_COMPARATOR);
|
||||
return methods;
|
||||
}
|
||||
|
|
|
@ -706,96 +706,100 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
|
|||
return cachedReturnType.resolve();
|
||||
}
|
||||
|
||||
Class<?> factoryClass;
|
||||
boolean isStatic = true;
|
||||
|
||||
String factoryBeanName = mbd.getFactoryBeanName();
|
||||
if (factoryBeanName != null) {
|
||||
if (factoryBeanName.equals(beanName)) {
|
||||
throw new BeanDefinitionStoreException(mbd.getResourceDescription(), beanName,
|
||||
"factory-bean reference points back to the same bean definition");
|
||||
}
|
||||
// Check declared factory method return type on factory class.
|
||||
factoryClass = getType(factoryBeanName);
|
||||
isStatic = false;
|
||||
}
|
||||
else {
|
||||
// Check declared factory method return type on bean class.
|
||||
factoryClass = resolveBeanClass(mbd, beanName, typesToMatch);
|
||||
}
|
||||
|
||||
if (factoryClass == null) {
|
||||
return null;
|
||||
}
|
||||
factoryClass = ClassUtils.getUserClass(factoryClass);
|
||||
|
||||
// If all factory methods have the same return type, return that type.
|
||||
// Can't clearly figure out exact method due to type converting / autowiring!
|
||||
Class<?> commonType = null;
|
||||
Method uniqueCandidate = null;
|
||||
int minNrOfArgs =
|
||||
(mbd.hasConstructorArgumentValues() ? mbd.getConstructorArgumentValues().getArgumentCount() : 0);
|
||||
Method[] candidates = this.factoryMethodCandidateCache.computeIfAbsent(
|
||||
factoryClass, ReflectionUtils::getUniqueDeclaredMethods);
|
||||
Method uniqueCandidate = mbd.factoryMethodToIntrospect;
|
||||
|
||||
for (Method candidate : candidates) {
|
||||
if (Modifier.isStatic(candidate.getModifiers()) == isStatic && mbd.isFactoryMethod(candidate) &&
|
||||
candidate.getParameterCount() >= minNrOfArgs) {
|
||||
// Declared type variables to inspect?
|
||||
if (candidate.getTypeParameters().length > 0) {
|
||||
try {
|
||||
// Fully resolve parameter names and argument values.
|
||||
Class<?>[] paramTypes = candidate.getParameterTypes();
|
||||
String[] paramNames = null;
|
||||
ParameterNameDiscoverer pnd = getParameterNameDiscoverer();
|
||||
if (pnd != null) {
|
||||
paramNames = pnd.getParameterNames(candidate);
|
||||
}
|
||||
ConstructorArgumentValues cav = mbd.getConstructorArgumentValues();
|
||||
Set<ConstructorArgumentValues.ValueHolder> usedValueHolders = new HashSet<>(paramTypes.length);
|
||||
Object[] args = new Object[paramTypes.length];
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
ConstructorArgumentValues.ValueHolder valueHolder = cav.getArgumentValue(
|
||||
i, paramTypes[i], (paramNames != null ? paramNames[i] : null), usedValueHolders);
|
||||
if (valueHolder == null) {
|
||||
valueHolder = cav.getGenericArgumentValue(null, null, usedValueHolders);
|
||||
if (uniqueCandidate == null) {
|
||||
Class<?> factoryClass;
|
||||
boolean isStatic = true;
|
||||
|
||||
String factoryBeanName = mbd.getFactoryBeanName();
|
||||
if (factoryBeanName != null) {
|
||||
if (factoryBeanName.equals(beanName)) {
|
||||
throw new BeanDefinitionStoreException(mbd.getResourceDescription(), beanName,
|
||||
"factory-bean reference points back to the same bean definition");
|
||||
}
|
||||
// Check declared factory method return type on factory class.
|
||||
factoryClass = getType(factoryBeanName);
|
||||
isStatic = false;
|
||||
}
|
||||
else {
|
||||
// Check declared factory method return type on bean class.
|
||||
factoryClass = resolveBeanClass(mbd, beanName, typesToMatch);
|
||||
}
|
||||
|
||||
if (factoryClass == null) {
|
||||
return null;
|
||||
}
|
||||
factoryClass = ClassUtils.getUserClass(factoryClass);
|
||||
|
||||
// If all factory methods have the same return type, return that type.
|
||||
// Can't clearly figure out exact method due to type converting / autowiring!
|
||||
int minNrOfArgs =
|
||||
(mbd.hasConstructorArgumentValues() ? mbd.getConstructorArgumentValues().getArgumentCount() : 0);
|
||||
Method[] candidates = this.factoryMethodCandidateCache.computeIfAbsent(factoryClass,
|
||||
clazz -> ReflectionUtils.getUniqueDeclaredMethods(clazz, ReflectionUtils.USER_DECLARED_METHODS));
|
||||
|
||||
for (Method candidate : candidates) {
|
||||
if (Modifier.isStatic(candidate.getModifiers()) == isStatic && mbd.isFactoryMethod(candidate) &&
|
||||
candidate.getParameterCount() >= minNrOfArgs) {
|
||||
// Declared type variables to inspect?
|
||||
if (candidate.getTypeParameters().length > 0) {
|
||||
try {
|
||||
// Fully resolve parameter names and argument values.
|
||||
Class<?>[] paramTypes = candidate.getParameterTypes();
|
||||
String[] paramNames = null;
|
||||
ParameterNameDiscoverer pnd = getParameterNameDiscoverer();
|
||||
if (pnd != null) {
|
||||
paramNames = pnd.getParameterNames(candidate);
|
||||
}
|
||||
if (valueHolder != null) {
|
||||
args[i] = valueHolder.getValue();
|
||||
usedValueHolders.add(valueHolder);
|
||||
ConstructorArgumentValues cav = mbd.getConstructorArgumentValues();
|
||||
Set<ConstructorArgumentValues.ValueHolder> usedValueHolders = new HashSet<>(paramTypes.length);
|
||||
Object[] args = new Object[paramTypes.length];
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
ConstructorArgumentValues.ValueHolder valueHolder = cav.getArgumentValue(
|
||||
i, paramTypes[i], (paramNames != null ? paramNames[i] : null), usedValueHolders);
|
||||
if (valueHolder == null) {
|
||||
valueHolder = cav.getGenericArgumentValue(null, null, usedValueHolders);
|
||||
}
|
||||
if (valueHolder != null) {
|
||||
args[i] = valueHolder.getValue();
|
||||
usedValueHolders.add(valueHolder);
|
||||
}
|
||||
}
|
||||
Class<?> returnType = AutowireUtils.resolveReturnTypeForFactoryMethod(
|
||||
candidate, args, getBeanClassLoader());
|
||||
uniqueCandidate = (commonType == null && returnType == candidate.getReturnType() ?
|
||||
candidate : null);
|
||||
commonType = ClassUtils.determineCommonAncestor(returnType, commonType);
|
||||
if (commonType == null) {
|
||||
// Ambiguous return types found: return null to indicate "not determinable".
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Class<?> returnType = AutowireUtils.resolveReturnTypeForFactoryMethod(
|
||||
candidate, args, getBeanClassLoader());
|
||||
uniqueCandidate = (commonType == null && returnType == candidate.getReturnType() ?
|
||||
candidate : null);
|
||||
commonType = ClassUtils.determineCommonAncestor(returnType, commonType);
|
||||
catch (Throwable ex) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Failed to resolve generic return type for factory method: " + ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
uniqueCandidate = (commonType == null ? candidate : null);
|
||||
commonType = ClassUtils.determineCommonAncestor(candidate.getReturnType(), commonType);
|
||||
if (commonType == null) {
|
||||
// Ambiguous return types found: return null to indicate "not determinable".
|
||||
return null;
|
||||
}
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Failed to resolve generic return type for factory method: " + ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
uniqueCandidate = (commonType == null ? candidate : null);
|
||||
commonType = ClassUtils.determineCommonAncestor(candidate.getReturnType(), commonType);
|
||||
if (commonType == null) {
|
||||
// Ambiguous return types found: return null to indicate "not determinable".
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mbd.factoryMethodToIntrospect = uniqueCandidate;
|
||||
if (commonType == null) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
mbd.factoryMethodToIntrospect = uniqueCandidate;
|
||||
if (commonType == null) {
|
||||
return null;
|
||||
}
|
||||
// Common return type found: all factory methods return same type. For a non-parameterized
|
||||
// unique candidate, cache the full type declaration context of the target factory method.
|
||||
cachedReturnType = (uniqueCandidate != null ?
|
||||
|
@ -926,7 +930,7 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
|
|||
objectType.value = ClassUtils.determineCommonAncestor(currentType, objectType.value);
|
||||
}
|
||||
}
|
||||
});
|
||||
}, ReflectionUtils.USER_DECLARED_METHODS);
|
||||
|
||||
return (objectType.value != null && Object.class != objectType.value ? objectType.value : null);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2019 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.
|
||||
|
@ -26,6 +26,7 @@ import java.security.AccessController;
|
|||
import java.security.PrivilegedAction;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.LinkedList;
|
||||
|
@ -436,11 +437,22 @@ class ConstructorResolver {
|
|||
// Try all methods with this name to see if they match the given arguments.
|
||||
factoryClass = ClassUtils.getUserClass(factoryClass);
|
||||
|
||||
Method[] rawCandidates = getCandidateMethods(factoryClass, mbd);
|
||||
List<Method> candidateList = new ArrayList<>();
|
||||
for (Method candidate : rawCandidates) {
|
||||
if (Modifier.isStatic(candidate.getModifiers()) == isStatic && mbd.isFactoryMethod(candidate)) {
|
||||
candidateList.add(candidate);
|
||||
List<Method> candidateList = null;
|
||||
if (mbd.isFactoryMethodUnique) {
|
||||
if (factoryMethodToUse == null) {
|
||||
factoryMethodToUse = mbd.getResolvedFactoryMethod();
|
||||
}
|
||||
if (factoryMethodToUse != null) {
|
||||
candidateList = Collections.singletonList(factoryMethodToUse);
|
||||
}
|
||||
}
|
||||
if (candidateList == null) {
|
||||
candidateList = new ArrayList<>();
|
||||
Method[] rawCandidates = getCandidateMethods(factoryClass, mbd);
|
||||
for (Method candidate : rawCandidates) {
|
||||
if (Modifier.isStatic(candidate.getModifiers()) == isStatic && mbd.isFactoryMethod(candidate)) {
|
||||
candidateList.add(candidate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2019 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.
|
||||
|
@ -237,6 +237,7 @@ public class RootBeanDefinition extends AbstractBeanDefinition {
|
|||
this.allowCaching = original.allowCaching;
|
||||
this.isFactoryMethodUnique = original.isFactoryMethodUnique;
|
||||
this.targetType = original.targetType;
|
||||
this.factoryMethodToIntrospect = original.factoryMethodToIntrospect;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -361,6 +362,16 @@ public class RootBeanDefinition extends AbstractBeanDefinition {
|
|||
this.isFactoryMethodUnique = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify a factory method name that refers to an overloaded method.
|
||||
* @since 5.2
|
||||
*/
|
||||
public void setNonUniqueFactoryMethodName(String name) {
|
||||
Assert.hasText(name, "Factory method name must not be empty");
|
||||
setFactoryMethodName(name);
|
||||
this.isFactoryMethodUnique = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the given candidate qualifies as a factory method.
|
||||
*/
|
||||
|
@ -368,6 +379,15 @@ public class RootBeanDefinition extends AbstractBeanDefinition {
|
|||
return candidate.getName().equals(getFactoryMethodName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a resolved Java Method for the factory method on this bean definition.
|
||||
* @param method the resolved factory method, or {@code null} to reset it
|
||||
* @since 5.2
|
||||
*/
|
||||
public void setResolvedFactoryMethod(@Nullable Method method) {
|
||||
this.factoryMethodToIntrospect = method;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the resolved factory method as a Java Method object, if available.
|
||||
* @return the factory method, or {@code null} if not found or not resolved yet
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2019 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.
|
||||
|
@ -50,6 +50,9 @@ import org.springframework.core.io.Resource;
|
|||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.core.type.MethodMetadata;
|
||||
import org.springframework.core.type.StandardAnnotationMetadata;
|
||||
import org.springframework.core.type.StandardMethodMetadata;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
|
@ -214,14 +217,24 @@ class ConfigurationClassBeanDefinitionReader {
|
|||
|
||||
if (metadata.isStatic()) {
|
||||
// static @Bean method
|
||||
beanDef.setBeanClassName(configClass.getMetadata().getClassName());
|
||||
beanDef.setFactoryMethodName(methodName);
|
||||
if (configClass.getMetadata() instanceof StandardAnnotationMetadata) {
|
||||
beanDef.setBeanClass(((StandardAnnotationMetadata) configClass.getMetadata()).getIntrospectedClass());
|
||||
}
|
||||
else {
|
||||
beanDef.setBeanClassName(configClass.getMetadata().getClassName());
|
||||
}
|
||||
beanDef.setUniqueFactoryMethodName(methodName);
|
||||
}
|
||||
else {
|
||||
// instance @Bean method
|
||||
beanDef.setFactoryBeanName(configClass.getBeanName());
|
||||
beanDef.setUniqueFactoryMethodName(methodName);
|
||||
}
|
||||
|
||||
if (metadata instanceof StandardMethodMetadata) {
|
||||
beanDef.setResolvedFactoryMethod(((StandardMethodMetadata) metadata).getIntrospectedMethod());
|
||||
}
|
||||
|
||||
beanDef.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);
|
||||
beanDef.setAttribute(org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor.
|
||||
SKIP_REQUIRED_CHECK_ATTRIBUTE, Boolean.TRUE);
|
||||
|
@ -286,8 +299,16 @@ class ConfigurationClassBeanDefinitionReader {
|
|||
// preserve the existing bean definition.
|
||||
if (existingBeanDef instanceof ConfigurationClassBeanDefinition) {
|
||||
ConfigurationClassBeanDefinition ccbd = (ConfigurationClassBeanDefinition) existingBeanDef;
|
||||
return ccbd.getMetadata().getClassName().equals(
|
||||
beanMethod.getConfigurationClass().getMetadata().getClassName());
|
||||
if (ccbd.getMetadata().getClassName().equals(
|
||||
beanMethod.getConfigurationClass().getMetadata().getClassName())) {
|
||||
if (ccbd.getFactoryMethodMetadata().getMethodName().equals(ccbd.getFactoryMethodName())) {
|
||||
ccbd.setNonUniqueFactoryMethodName(ccbd.getFactoryMethodMetadata().getMethodName());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// A bean definition resulting from a component scan can be silently overridden
|
||||
|
@ -403,6 +424,7 @@ class ConfigurationClassBeanDefinitionReader {
|
|||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public MethodMetadata getFactoryMethodMetadata() {
|
||||
return this.factoryMethodMetadata;
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ import org.springframework.beans.PropertyValues;
|
|||
import org.springframework.beans.factory.BeanClassLoaderAware;
|
||||
import org.springframework.beans.factory.BeanDefinitionStoreException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.BeanDefinitionHolder;
|
||||
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
|
||||
|
@ -57,14 +58,13 @@ import org.springframework.core.env.StandardEnvironment;
|
|||
import org.springframework.core.io.DefaultResourceLoader;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.core.type.MethodMetadata;
|
||||
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
|
||||
import org.springframework.core.type.classreading.MetadataReaderFactory;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
import static org.springframework.context.annotation.AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR;
|
||||
|
||||
/**
|
||||
* {@link BeanFactoryPostProcessor} used for bootstrapping processing of
|
||||
* {@link Configuration @Configuration} classes.
|
||||
|
@ -173,11 +173,10 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
|
|||
* and a variant thereof for imported configuration classes (using unique fully-qualified
|
||||
* class names instead of standard component overriding).
|
||||
* <p>Note that this strategy does <em>not</em> apply to {@link Bean} methods.
|
||||
* <p>This setter is typically only appropriate when configuring the post-processor as
|
||||
* a standalone bean definition in XML, e.g. not using the dedicated
|
||||
* {@code AnnotationConfig*} application contexts or the {@code
|
||||
* <context:annotation-config>} element. Any bean name generator specified against
|
||||
* the application context will take precedence over any value set here.
|
||||
* <p>This setter is typically only appropriate when configuring the post-processor as a
|
||||
* standalone bean definition in XML, e.g. not using the dedicated {@code AnnotationConfig*}
|
||||
* application contexts or the {@code <context:annotation-config>} element. Any bean name
|
||||
* generator specified against the application context will take precedence over any set here.
|
||||
* @since 3.1.1
|
||||
* @see AnnotationConfigApplicationContext#setBeanNameGenerator(BeanNameGenerator)
|
||||
* @see AnnotationConfigUtils#CONFIGURATION_BEAN_NAME_GENERATOR
|
||||
|
@ -264,8 +263,7 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
|
|||
|
||||
for (String beanName : candidateNames) {
|
||||
BeanDefinition beanDef = registry.getBeanDefinition(beanName);
|
||||
if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) ||
|
||||
ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {
|
||||
if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
|
||||
}
|
||||
|
@ -292,7 +290,8 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
|
|||
if (registry instanceof SingletonBeanRegistry) {
|
||||
sbr = (SingletonBeanRegistry) registry;
|
||||
if (!this.localBeanNameGeneratorSet) {
|
||||
BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(CONFIGURATION_BEAN_NAME_GENERATOR);
|
||||
BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(
|
||||
AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);
|
||||
if (generator != null) {
|
||||
this.componentScanBeanNameGenerator = generator;
|
||||
this.importBeanNameGenerator = generator;
|
||||
|
@ -371,7 +370,26 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
|
|||
Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<>();
|
||||
for (String beanName : beanFactory.getBeanDefinitionNames()) {
|
||||
BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
|
||||
if (ConfigurationClassUtils.isFullConfigurationClass(beanDef)) {
|
||||
Object configClassAttr = beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE);
|
||||
MethodMetadata methodMetadata = null;
|
||||
if (beanDef instanceof AnnotatedBeanDefinition) {
|
||||
methodMetadata = ((AnnotatedBeanDefinition) beanDef).getFactoryMethodMetadata();
|
||||
}
|
||||
if ((configClassAttr != null || methodMetadata != null) && beanDef instanceof AbstractBeanDefinition) {
|
||||
// Configuration class (full or lite) or a configuration-derived @Bean method
|
||||
// -> resolve bean class at this point...
|
||||
AbstractBeanDefinition abd = (AbstractBeanDefinition) beanDef;
|
||||
if (!abd.hasBeanClass()) {
|
||||
try {
|
||||
abd.resolveBeanClass(this.beanClassLoader);
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
throw new IllegalStateException(
|
||||
"Cannot load configuration class: " + beanDef.getBeanClassName(), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ConfigurationClassUtils.CONFIGURATION_CLASS_FULL.equals(configClassAttr)) {
|
||||
if (!(beanDef instanceof AbstractBeanDefinition)) {
|
||||
throw new BeanDefinitionStoreException("Cannot enhance @Configuration bean definition '" +
|
||||
beanName + "' since it is not stored in an AbstractBeanDefinition subclass");
|
||||
|
@ -395,22 +413,15 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
|
|||
AbstractBeanDefinition beanDef = entry.getValue();
|
||||
// If a @Configuration class gets proxied, always proxy the target class
|
||||
beanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
|
||||
try {
|
||||
// Set enhanced subclass of the user-specified bean class
|
||||
Class<?> configClass = beanDef.resolveBeanClass(this.beanClassLoader);
|
||||
if (configClass != null) {
|
||||
Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
|
||||
if (configClass != enhancedClass) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace(String.format("Replacing bean definition '%s' existing class '%s' with " +
|
||||
"enhanced class '%s'", entry.getKey(), configClass.getName(), enhancedClass.getName()));
|
||||
}
|
||||
beanDef.setBeanClass(enhancedClass);
|
||||
}
|
||||
// Set enhanced subclass of the user-specified bean class
|
||||
Class<?> configClass = beanDef.getBeanClass();
|
||||
Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
|
||||
if (configClass != enhancedClass) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace(String.format("Replacing bean definition '%s' existing class '%s' with " +
|
||||
"enhanced class '%s'", entry.getKey(), configClass.getName(), enhancedClass.getName()));
|
||||
}
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
throw new IllegalStateException("Cannot load configuration class: " + beanDef.getBeanClassName(), ex);
|
||||
beanDef.setBeanClass(enhancedClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,11 +51,11 @@ import org.springframework.stereotype.Component;
|
|||
*/
|
||||
abstract class ConfigurationClassUtils {
|
||||
|
||||
private static final String CONFIGURATION_CLASS_FULL = "full";
|
||||
public static final String CONFIGURATION_CLASS_FULL = "full";
|
||||
|
||||
private static final String CONFIGURATION_CLASS_LITE = "lite";
|
||||
public static final String CONFIGURATION_CLASS_LITE = "lite";
|
||||
|
||||
private static final String CONFIGURATION_CLASS_ATTRIBUTE =
|
||||
public static final String CONFIGURATION_CLASS_ATTRIBUTE =
|
||||
Conventions.getQualifiedAttributeName(ConfigurationClassPostProcessor.class, "configurationClass");
|
||||
|
||||
private static final String ORDER_ATTRIBUTE =
|
||||
|
@ -174,22 +174,6 @@ abstract class ConfigurationClassUtils {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the given bean definition indicates a full {@code @Configuration}
|
||||
* class, through checking {@link #checkConfigurationClassCandidate}'s metadata marker.
|
||||
*/
|
||||
public static boolean isFullConfigurationClass(BeanDefinition beanDef) {
|
||||
return CONFIGURATION_CLASS_FULL.equals(beanDef.getAttribute(CONFIGURATION_CLASS_ATTRIBUTE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the given bean definition indicates a lite {@code @Configuration}
|
||||
* class, through checking {@link #checkConfigurationClassCandidate}'s metadata marker.
|
||||
*/
|
||||
public static boolean isLiteConfigurationClass(BeanDefinition beanDef) {
|
||||
return CONFIGURATION_CLASS_LITE.equals(beanDef.getAttribute(CONFIGURATION_CLASS_ATTRIBUTE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the order for the given configuration class metadata.
|
||||
* @param metadata the metadata of the annotated class
|
||||
|
|
|
@ -97,6 +97,19 @@ public class ConfigurationClassPostProcessorTests {
|
|||
beanFactory.registerBeanDefinition("config", new RootBeanDefinition(SingletonBeanConfig.class));
|
||||
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
|
||||
pp.postProcessBeanFactory(beanFactory);
|
||||
assertTrue(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).hasBeanClass());
|
||||
Foo foo = beanFactory.getBean("foo", Foo.class);
|
||||
Bar bar = beanFactory.getBean("bar", Bar.class);
|
||||
assertSame(foo, bar.foo);
|
||||
assertTrue(Arrays.asList(beanFactory.getDependentBeans("foo")).contains("bar"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void enhancementIsPresentBecauseSingletonSemanticsAreRespectedUsingAsm() {
|
||||
beanFactory.registerBeanDefinition("config", new RootBeanDefinition(SingletonBeanConfig.class.getName()));
|
||||
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
|
||||
pp.postProcessBeanFactory(beanFactory);
|
||||
assertTrue(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).hasBeanClass());
|
||||
Foo foo = beanFactory.getBean("foo", Foo.class);
|
||||
Bar bar = beanFactory.getBean("bar", Bar.class);
|
||||
assertSame(foo, bar.foo);
|
||||
|
@ -108,6 +121,44 @@ public class ConfigurationClassPostProcessorTests {
|
|||
beanFactory.registerBeanDefinition("config", new RootBeanDefinition(NonEnhancedSingletonBeanConfig.class));
|
||||
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
|
||||
pp.postProcessBeanFactory(beanFactory);
|
||||
assertTrue(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).hasBeanClass());
|
||||
Foo foo = beanFactory.getBean("foo", Foo.class);
|
||||
Bar bar = beanFactory.getBean("bar", Bar.class);
|
||||
assertNotSame(foo, bar.foo);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void enhancementIsNotPresentForProxyBeanMethodsFlagSetToFalseUsingAsm() {
|
||||
beanFactory.registerBeanDefinition("config", new RootBeanDefinition(NonEnhancedSingletonBeanConfig.class.getName()));
|
||||
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
|
||||
pp.postProcessBeanFactory(beanFactory);
|
||||
assertTrue(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).hasBeanClass());
|
||||
Foo foo = beanFactory.getBean("foo", Foo.class);
|
||||
Bar bar = beanFactory.getBean("bar", Bar.class);
|
||||
assertNotSame(foo, bar.foo);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void enhancementIsNotPresentForStaticMethods() {
|
||||
beanFactory.registerBeanDefinition("config", new RootBeanDefinition(StaticSingletonBeanConfig.class));
|
||||
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
|
||||
pp.postProcessBeanFactory(beanFactory);
|
||||
assertTrue(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).hasBeanClass());
|
||||
assertTrue(((RootBeanDefinition) beanFactory.getBeanDefinition("foo")).hasBeanClass());
|
||||
assertTrue(((RootBeanDefinition) beanFactory.getBeanDefinition("bar")).hasBeanClass());
|
||||
Foo foo = beanFactory.getBean("foo", Foo.class);
|
||||
Bar bar = beanFactory.getBean("bar", Bar.class);
|
||||
assertNotSame(foo, bar.foo);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void enhancementIsNotPresentForStaticMethodsUsingAsm() {
|
||||
beanFactory.registerBeanDefinition("config", new RootBeanDefinition(StaticSingletonBeanConfig.class.getName()));
|
||||
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
|
||||
pp.postProcessBeanFactory(beanFactory);
|
||||
assertTrue(((RootBeanDefinition) beanFactory.getBeanDefinition("config")).hasBeanClass());
|
||||
assertTrue(((RootBeanDefinition) beanFactory.getBeanDefinition("foo")).hasBeanClass());
|
||||
assertTrue(((RootBeanDefinition) beanFactory.getBeanDefinition("bar")).hasBeanClass());
|
||||
Foo foo = beanFactory.getBean("foo", Foo.class);
|
||||
Bar bar = beanFactory.getBean("bar", Bar.class);
|
||||
assertNotSame(foo, bar.foo);
|
||||
|
@ -1090,6 +1141,18 @@ public class ConfigurationClassPostProcessorTests {
|
|||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class StaticSingletonBeanConfig {
|
||||
|
||||
public static @Bean Foo foo() {
|
||||
return new Foo();
|
||||
}
|
||||
|
||||
public static @Bean Bar bar() {
|
||||
return new Bar(foo());
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@Order(2)
|
||||
static class OverridingSingletonBeanConfig {
|
||||
|
|
|
@ -51,13 +51,13 @@ public abstract class ReflectionUtils {
|
|||
* @since 3.0.5
|
||||
*/
|
||||
public static final MethodFilter USER_DECLARED_METHODS =
|
||||
(method -> (!method.isBridge() && !method.isSynthetic() && method.getDeclaringClass() != Object.class));
|
||||
(method -> !method.isBridge() && !method.isSynthetic());
|
||||
|
||||
/**
|
||||
* Pre-built FieldFilter that matches all non-static, non-final fields.
|
||||
*/
|
||||
public static final FieldFilter COPYABLE_FIELDS =
|
||||
field -> !(Modifier.isStatic(field.getModifiers()) || Modifier.isFinal(field.getModifiers()));
|
||||
(field -> !(Modifier.isStatic(field.getModifiers()) || Modifier.isFinal(field.getModifiers())));
|
||||
|
||||
|
||||
/**
|
||||
|
@ -536,7 +536,7 @@ public abstract class ReflectionUtils {
|
|||
throw new IllegalStateException("Not allowed to access method '" + method.getName() + "': " + ex);
|
||||
}
|
||||
}
|
||||
if (clazz.getSuperclass() != null) {
|
||||
if (clazz.getSuperclass() != null && (mf != USER_DECLARED_METHODS || clazz.getSuperclass() != Object.class)) {
|
||||
doWithMethods(clazz.getSuperclass(), mc, mf);
|
||||
}
|
||||
else if (clazz.isInterface()) {
|
||||
|
@ -566,6 +566,19 @@ public abstract class ReflectionUtils {
|
|||
* @throws IllegalStateException if introspection fails
|
||||
*/
|
||||
public static Method[] getUniqueDeclaredMethods(Class<?> leafClass) {
|
||||
return getUniqueDeclaredMethods(leafClass, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the unique set of declared methods on the leaf class and all superclasses.
|
||||
* Leaf class methods are included first and while traversing the superclass hierarchy
|
||||
* any methods found with signatures matching a method already included are filtered out.
|
||||
* @param leafClass the class to introspect
|
||||
* @param mf the filter that determines the methods to take into account
|
||||
* @throws IllegalStateException if introspection fails
|
||||
* @since 5.2
|
||||
*/
|
||||
public static Method[] getUniqueDeclaredMethods(Class<?> leafClass, @Nullable MethodFilter mf) {
|
||||
final List<Method> methods = new ArrayList<>(32);
|
||||
doWithMethods(leafClass, method -> {
|
||||
boolean knownSignature = false;
|
||||
|
@ -590,7 +603,7 @@ public abstract class ReflectionUtils {
|
|||
if (!knownSignature && !isCglibRenamedMethod(method)) {
|
||||
methods.add(method);
|
||||
}
|
||||
});
|
||||
}, mf);
|
||||
return methods.toArray(new Method[0]);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2019 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.
|
||||
|
@ -423,7 +423,7 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
|
|||
* as well as annotated interface default methods
|
||||
*/
|
||||
private List<Method> getAnnotatedMethods(Class<?> clazz, Class<? extends Annotation> annotationType) {
|
||||
return Arrays.stream(ReflectionUtils.getUniqueDeclaredMethods(clazz))
|
||||
return Arrays.stream(ReflectionUtils.getUniqueDeclaredMethods(clazz, ReflectionUtils.USER_DECLARED_METHODS))
|
||||
.filter(method -> AnnotatedElementUtils.hasAnnotation(method, annotationType))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue