Skip to content

Commit

Permalink
Early resolution of unique factory methods in configuration classes
Browse files Browse the repository at this point in the history
Includes consistent bean class resolution in the enhancement step as well as general reflection optimizations for user-declared methods.

Closes gh-22420
  • Loading branch information
jhoeller committed Mar 12, 2019
1 parent a35adc6 commit 40c6213
Show file tree
Hide file tree
Showing 10 changed files with 270 additions and 141 deletions.
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -153,7 +153,7 @@ private List<Method> getAdvisorMethods(Class<?> aspectClass) {
if (AnnotationUtils.getAnnotation(method, Pointcut.class) == null) {
methods.add(method);
}
});
}, ReflectionUtils.USER_DECLARED_METHODS);
methods.sort(METHOD_COMPARATOR);
return methods;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -706,96 +706,100 @@ protected Class<?> getTypeForFactoryMethod(String beanName, RootBeanDefinition m
return cachedReturnType.resolve();
}

Class<?> factoryClass;
boolean isStatic = true;
Class<?> commonType = null;
Method uniqueCandidate = mbd.factoryMethodToIntrospect;

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 (uniqueCandidate == null) {
Class<?> factoryClass;
boolean isStatic = true;

if (factoryClass == null) {
return null;
}
factoryClass = ClassUtils.getUserClass(factoryClass);
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 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);

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 (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);
}
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);
}
}
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;
}
}
catch (Throwable ex) {
if (logger.isDebugEnabled()) {
logger.debug("Failed to resolve generic return type for factory method: " + ex);
}
}
Class<?> returnType = AutowireUtils.resolveReturnTypeForFactoryMethod(
candidate, args, getBeanClassLoader());
uniqueCandidate = (commonType == null && returnType == candidate.getReturnType() ?
candidate : null);
commonType = ClassUtils.determineCommonAncestor(returnType, commonType);
}
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 ?
Expand Down Expand Up @@ -926,7 +930,7 @@ class Holder {
objectType.value = ClassUtils.determineCommonAncestor(currentType, objectType.value);
}
}
});
}, ReflectionUtils.USER_DECLARED_METHODS);

return (objectType.value != null && Object.class != objectType.value ? objectType.value : null);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -26,6 +26,7 @@
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;
Expand Down Expand Up @@ -436,11 +437,22 @@ public BeanWrapper instantiateUsingFactoryMethod(
// 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);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -237,6 +237,7 @@ public RootBeanDefinition(RootBeanDefinition original) {
this.allowCaching = original.allowCaching;
this.isFactoryMethodUnique = original.isFactoryMethodUnique;
this.targetType = original.targetType;
this.factoryMethodToIntrospect = original.factoryMethodToIntrospect;
}

/**
Expand Down Expand Up @@ -361,13 +362,32 @@ public void setUniqueFactoryMethodName(String name) {
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.
*/
public boolean isFactoryMethod(Method candidate) {
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
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -50,6 +50,9 @@
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;

Expand Down Expand Up @@ -214,14 +217,24 @@ private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) {

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);
Expand Down Expand Up @@ -286,8 +299,16 @@ protected boolean isOverriddenByExistingDefinition(BeanMethod beanMethod, String
// 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
Expand Down Expand Up @@ -403,6 +424,7 @@ public AnnotationMetadata getMetadata() {
}

@Override
@NonNull
public MethodMetadata getFactoryMethodMetadata() {
return this.factoryMethodMetadata;
}
Expand Down
Loading

0 comments on commit 40c6213

Please sign in to comment.