diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java index 9f598f2c00..d44f96017e 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java @@ -514,8 +514,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp * to check whether the bean with the given name matches the specified type. Allow * additional constraints to be applied to ensure that beans are not created early. * @param name the name of the bean to query - * @param typeToMatch the type to match against (as a - * {@code ResolvableType}) + * @param typeToMatch the type to match against (as a {@code ResolvableType}) * @return {@code true} if the bean type matches, {@code false} if it * doesn't match or cannot be determined yet * @throws NoSuchBeanDefinitionException if there is no bean with the given name diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java index 33ca3421fb..a097350a63 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java @@ -60,6 +60,7 @@ import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.BeanNotOfRequiredTypeException; import org.springframework.beans.factory.CannotLoadBeanClassException; +import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.InjectionPoint; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.NoUniqueBeanDefinitionException; @@ -191,8 +192,8 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto /** Map from bean name to merged BeanDefinitionHolder. */ private final Map mergedBeanDefinitionHolders = new ConcurrentHashMap<>(256); - /** Set of bean definition names with a primary marker. */ - private final Set primaryBeanNames = ConcurrentHashMap.newKeySet(16); + /** Map of bean definition names with a primary marker plus corresponding type. */ + private final Map> primaryBeanNamesWithType = new ConcurrentHashMap<>(16); /** Map of singleton and non-singleton bean names, keyed by dependency type. */ private final Map, String[]> allBeanNamesByType = new ConcurrentHashMap<>(64); @@ -1024,7 +1025,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto protected void cacheMergedBeanDefinition(RootBeanDefinition mbd, String beanName) { super.cacheMergedBeanDefinition(mbd, beanName); if (mbd.isPrimary()) { - this.primaryBeanNames.add(beanName); + this.primaryBeanNamesWithType.put(beanName, Void.class); } } @@ -1309,7 +1310,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto // Cache a primary marker for the given bean. if (beanDefinition.isPrimary()) { - this.primaryBeanNames.add(beanName); + this.primaryBeanNamesWithType.put(beanName, Void.class); } } @@ -1401,7 +1402,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto destroySingleton(beanName); // Remove a cached primary marker for the given bean. - this.primaryBeanNames.remove(beanName); + this.primaryBeanNamesWithType.remove(beanName); // Notify all post-processors that the specified bean definition has been reset. for (MergedBeanDefinitionPostProcessor processor : getBeanPostProcessorCache().mergedDefinition) { @@ -1454,9 +1455,18 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto @Override protected void addSingleton(String beanName, Object singletonObject) { super.addSingleton(beanName, singletonObject); + Predicate> filter = (beanType -> beanType != Object.class && beanType.isInstance(singletonObject)); this.allBeanNamesByType.keySet().removeIf(filter); this.singletonBeanNamesByType.keySet().removeIf(filter); + + if (this.primaryBeanNamesWithType.containsKey(beanName) && singletonObject.getClass() != NullBean.class) { + Class beanType = (singletonObject instanceof FactoryBean fb ? + getTypeForFactoryBean(fb) : singletonObject.getClass()); + if (beanType != null) { + this.primaryBeanNamesWithType.put(beanName, beanType); + } + } } @Override @@ -2245,8 +2255,12 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto * not matching the given bean name. */ private boolean hasPrimaryConflict(String beanName, Class dependencyType) { - for (String candidate : this.primaryBeanNames) { - if (isTypeMatch(candidate, dependencyType) && !candidate.equals(beanName)) { + for (Map.Entry> candidate : this.primaryBeanNamesWithType.entrySet()) { + String candidateName = candidate.getKey(); + Class candidateType = candidate.getValue(); + if (!candidateName.equals(beanName) && (candidateType != Void.class ? + dependencyType.isAssignableFrom(candidateType) : // cached singleton class for primary bean + isTypeMatch(candidateName, dependencyType))) { // not instantiated yet or not a singleton return true; } } diff --git a/spring-context/src/test/java/org/springframework/cache/interceptor/CacheOperationExpressionEvaluatorTests.java b/spring-context/src/test/java/org/springframework/cache/interceptor/CacheOperationExpressionEvaluatorTests.java index ec99775c40..7ef419955d 100644 --- a/spring-context/src/test/java/org/springframework/cache/interceptor/CacheOperationExpressionEvaluatorTests.java +++ b/spring-context/src/test/java/org/springframework/cache/interceptor/CacheOperationExpressionEvaluatorTests.java @@ -60,12 +60,6 @@ class CacheOperationExpressionEvaluatorTests { private final AnnotationCacheOperationSource source = new AnnotationCacheOperationSource(); - private Collection getOps(String name) { - Method method = ReflectionUtils.findMethod(AnnotatedClass.class, name, Object.class, Object.class); - return this.source.getCacheOperations(method, AnnotatedClass.class); - } - - @Test void testMultipleCachingSource() { Collection ops = getOps("multipleCaching"); @@ -144,6 +138,12 @@ class CacheOperationExpressionEvaluatorTests { assertThat(value).isEqualTo(String.class.getName()); } + + private Collection getOps(String name) { + Method method = ReflectionUtils.findMethod(AnnotatedClass.class, name, Object.class, Object.class); + return this.source.getCacheOperations(method, AnnotatedClass.class); + } + private EvaluationContext createEvaluationContext(Object result) { return createEvaluationContext(result, null); } diff --git a/spring-context/src/test/java/org/springframework/cache/interceptor/CachePutEvaluationTests.java b/spring-context/src/test/java/org/springframework/cache/interceptor/CachePutEvaluationTests.java index 50ad24d6f5..31811b0cd4 100644 --- a/spring-context/src/test/java/org/springframework/cache/interceptor/CachePutEvaluationTests.java +++ b/spring-context/src/test/java/org/springframework/cache/interceptor/CachePutEvaluationTests.java @@ -104,6 +104,7 @@ class CachePutEvaluationTests { assertThat(this.cache.get(anotherValue + 100).get()).as("Wrong value for @CachePut key").isEqualTo(anotherValue); } + @Configuration @EnableCaching static class Config implements CachingConfigurer { @@ -121,8 +122,10 @@ class CachePutEvaluationTests { } + @CacheConfig("test") public static class SimpleService { + private AtomicLong counter = new AtomicLong(); /** @@ -144,4 +147,5 @@ class CachePutEvaluationTests { return this.counter.getAndIncrement(); } } + } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/support/StandardEvaluationContext.java b/spring-expression/src/main/java/org/springframework/expression/spel/support/StandardEvaluationContext.java index 21c3f3f65b..6fc154dcad 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/support/StandardEvaluationContext.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/support/StandardEvaluationContext.java @@ -122,34 +122,72 @@ public class StandardEvaluationContext implements EvaluationContext { } + /** + * Specify the default root context object (including a type descriptor) + * against which unqualified properties, methods, etc. should be resolved. + * @param rootObject the root object to use + * @param typeDescriptor a corresponding type descriptor + */ public void setRootObject(@Nullable Object rootObject, TypeDescriptor typeDescriptor) { this.rootObject = new TypedValue(rootObject, typeDescriptor); } + /** + * Specify the default root context object against which unqualified + * properties, methods, etc. should be resolved. + * @param rootObject the root object to use + */ public void setRootObject(@Nullable Object rootObject) { this.rootObject = (rootObject != null ? new TypedValue(rootObject) : TypedValue.NULL); } + /** + * Return the configured default root context object against which unqualified + * properties, methods, etc. should be resolved (can be {@link TypedValue#NULL}). + */ @Override public TypedValue getRootObject() { return this.rootObject; } + /** + * Set the list of property accessors to use in this evaluation context. + *

Replaces any previously configured property accessors. + */ public void setPropertyAccessors(List propertyAccessors) { this.propertyAccessors = propertyAccessors; } + /** + * Get the list of property accessors configured in this evaluation context. + */ @Override public List getPropertyAccessors() { return initPropertyAccessors(); } - public void addPropertyAccessor(PropertyAccessor accessor) { - addBeforeDefault(initPropertyAccessors(), accessor); + /** + * Add the supplied property accessor to this evaluation context. + * @param propertyAccessor the property accessor to add + * @see #getPropertyAccessors() + * @see #setPropertyAccessors(List) + * @see #removePropertyAccessor(PropertyAccessor) + */ + public void addPropertyAccessor(PropertyAccessor propertyAccessor) { + addBeforeDefault(initPropertyAccessors(), propertyAccessor); } - public boolean removePropertyAccessor(PropertyAccessor accessor) { - return initPropertyAccessors().remove(accessor); + /** + * Remove the supplied property accessor from this evaluation context. + * @param propertyAccessor the property accessor to remove + * @return {@code true} if the property accessor was removed, {@code false} + * if the property accessor was not configured in this evaluation context + * @see #getPropertyAccessors() + * @see #setPropertyAccessors(List) + * @see #addPropertyAccessor(PropertyAccessor) + */ + public boolean removePropertyAccessor(PropertyAccessor propertyAccessor) { + return initPropertyAccessors().remove(propertyAccessor); } /** @@ -191,8 +229,8 @@ public class StandardEvaluationContext implements EvaluationContext { /** * Remove the supplied index accessor from this evaluation context. * @param indexAccessor the index accessor to remove - * @return {@code true} if the index accessor was removed, {@code false} if - * the index accessor was not configured in this evaluation context + * @return {@code true} if the index accessor was removed, {@code false} + * if the index accessor was not configured in this evaluation context * @since 6.2 * @see #getIndexAccessors() * @see #setIndexAccessors(List) @@ -202,44 +240,96 @@ public class StandardEvaluationContext implements EvaluationContext { return initIndexAccessors().remove(indexAccessor); } + /** + * Set the list of constructor resolvers to use in this evaluation context. + *

Replaces any previously configured constructor resolvers. + */ public void setConstructorResolvers(List constructorResolvers) { this.constructorResolvers = constructorResolvers; } + /** + * Get the list of constructor resolvers to use in this evaluation context. + */ @Override public List getConstructorResolvers() { return initConstructorResolvers(); } - public void addConstructorResolver(ConstructorResolver resolver) { - addBeforeDefault(initConstructorResolvers(), resolver); + /** + * Add the supplied constructor resolver to this evaluation context. + * @param constructorResolver the constructor resolver to add + * @see #getConstructorResolvers() + * @see #setConstructorResolvers(List) + * @see #removeConstructorResolver(ConstructorResolver) + */ + public void addConstructorResolver(ConstructorResolver constructorResolver) { + addBeforeDefault(initConstructorResolvers(), constructorResolver); } - public boolean removeConstructorResolver(ConstructorResolver resolver) { - return initConstructorResolvers().remove(resolver); + /** + * Remove the supplied constructor resolver from this evaluation context. + * @param constructorResolver the constructor resolver to remove + * @return {@code true} if the constructor resolver was removed, {@code false} + * if the constructor resolver was not configured in this evaluation context +< * @see #getConstructorResolvers() + * @see #setConstructorResolvers(List) + * @see #addConstructorResolver(ConstructorResolver) + */ + public boolean removeConstructorResolver(ConstructorResolver constructorResolver) { + return initConstructorResolvers().remove(constructorResolver); } + /** + * Set the list of method resolvers to use in this evaluation context. + *

Replaces any previously configured method resolvers. + */ public void setMethodResolvers(List methodResolvers) { this.methodResolvers = methodResolvers; } + /** + * Get the list of method resolvers to use in this evaluation context. + */ @Override public List getMethodResolvers() { return initMethodResolvers(); } - public void addMethodResolver(MethodResolver resolver) { - addBeforeDefault(initMethodResolvers(), resolver); + /** + * Add the supplied method resolver to this evaluation context. + * @param methodResolver the method resolver to add + * @see #getMethodResolvers() + * @see #setMethodResolvers(List) + * @see #removeMethodResolver(MethodResolver) + */ + public void addMethodResolver(MethodResolver methodResolver) { + addBeforeDefault(initMethodResolvers(), methodResolver); } + /** + * Remove the supplied method resolver from this evaluation context. + * @param methodResolver the method resolver to remove + * @return {@code true} if the method resolver was removed, {@code false} + * if the method resolver was not configured in this evaluation context + * @see #getMethodResolvers() + * @see #setMethodResolvers(List) + * @see #addMethodResolver(MethodResolver) + */ public boolean removeMethodResolver(MethodResolver methodResolver) { return initMethodResolvers().remove(methodResolver); } - public void setBeanResolver(BeanResolver beanResolver) { + /** + * Set the {@link BeanResolver} to use for looking up beans, if any. + */ + public void setBeanResolver(@Nullable BeanResolver beanResolver) { this.beanResolver = beanResolver; } + /** + * Get the configured {@link BeanResolver} for looking up beans, if any. + */ @Override public @Nullable BeanResolver getBeanResolver() { return this.beanResolver; @@ -276,11 +366,17 @@ public class StandardEvaluationContext implements EvaluationContext { return this.typeLocator; } + /** + * Set the {@link TypeConverter} for value conversion. + */ public void setTypeConverter(TypeConverter typeConverter) { Assert.notNull(typeConverter, "TypeConverter must not be null"); this.typeConverter = typeConverter; } + /** + * Get the configured {@link TypeConverter} for value conversion. + */ @Override public TypeConverter getTypeConverter() { if (this.typeConverter == null) { @@ -289,21 +385,33 @@ public class StandardEvaluationContext implements EvaluationContext { return this.typeConverter; } + /** + * Set the {@link TypeComparator} for comparing pairs of objects. + */ public void setTypeComparator(TypeComparator typeComparator) { Assert.notNull(typeComparator, "TypeComparator must not be null"); this.typeComparator = typeComparator; } + /** + * Get the configured {@link TypeComparator} for comparing pairs of objects. + */ @Override public TypeComparator getTypeComparator() { return this.typeComparator; } + /** + * Set the {@link OperatorOverloader} for mathematical operations. + */ public void setOperatorOverloader(OperatorOverloader operatorOverloader) { Assert.notNull(operatorOverloader, "OperatorOverloader must not be null"); this.operatorOverloader = operatorOverloader; } + /** + * Get the configured {@link OperatorOverloader} for mathematical operations. + */ @Override public OperatorOverloader getOperatorOverloader() { return this.operatorOverloader;