diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/ReflectiveAspectJAdvisorFactory.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/ReflectiveAspectJAdvisorFactory.java index 9c7f6161da1f..34081d6a727f 100644 --- a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/ReflectiveAspectJAdvisorFactory.java +++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/ReflectiveAspectJAdvisorFactory.java @@ -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 @@ private List getAdvisorMethods(Class aspectClass) { if (AnnotationUtils.getAnnotation(method, Pointcut.class) == null) { methods.add(method); } - }); + }, ReflectionUtils.USER_DECLARED_METHODS); methods.sort(METHOD_COMPARATOR); return methods; } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java index 5c46f86a6bb3..fc48260c9193 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java @@ -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 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 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 ? @@ -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); } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java index 6e977750b3af..695ddd65e49c 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java @@ -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.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 @@ 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 candidateList = new ArrayList<>(); - for (Method candidate : rawCandidates) { - if (Modifier.isStatic(candidate.getModifiers()) == isStatic && mbd.isFactoryMethod(candidate)) { - candidateList.add(candidate); + List 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); + } } } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java index f28dc09bdeca..01afeb7860be 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java @@ -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 RootBeanDefinition(RootBeanDefinition original) { this.allowCaching = original.allowCaching; this.isFactoryMethodUnique = original.isFactoryMethodUnique; this.targetType = original.targetType; + this.factoryMethodToIntrospect = original.factoryMethodToIntrospect; } /** @@ -361,6 +362,16 @@ 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. */ @@ -368,6 +379,15 @@ 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 diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java index 501b55a3cff5..5719ed3eabda 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java @@ -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.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 @@ 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); @@ -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 @@ -403,6 +424,7 @@ public AnnotationMetadata getMetadata() { } @Override + @NonNull public MethodMetadata getFactoryMethodMetadata() { return this.factoryMethodMetadata; } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java index 20284b8a93c6..255e49bc50ad 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java @@ -33,6 +33,7 @@ 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.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 void setMetadataReaderFactory(MetadataReaderFactory metadataReaderFactory * and a variant thereof for imported configuration classes (using unique fully-qualified * class names instead of standard component overriding). *

Note that this strategy does not apply to {@link Bean} methods. - *

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 - * } element. Any bean name generator specified against - * the application context will take precedence over any value set here. + *

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 } 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 void processConfigBeanDefinitions(BeanDefinitionRegistry registry) { 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 @@ else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this. 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 void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFact Map 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 @@ else if (logger.isInfoEnabled() && beanFactory.containsSingleton(beanName)) { 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); } } } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassUtils.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassUtils.java index 94e1c9c3511c..5c8ef87f10e4 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassUtils.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassUtils.java @@ -51,11 +51,11 @@ */ 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 @@ public static boolean isConfigurationCandidate(AnnotationMetadata metadata) { } } - /** - * 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 diff --git a/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorTests.java b/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorTests.java index 17cac5fc53e0..ec316c73c2af 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorTests.java @@ -97,6 +97,19 @@ public void enhancementIsPresentBecauseSingletonSemanticsAreRespected() { 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 void enhancementIsNotPresentForProxyBeanMethodsFlagSetToFalse() { 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 @@ static class NonEnhancedSingletonBeanConfig { } } + @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 { diff --git a/spring-core/src/main/java/org/springframework/util/ReflectionUtils.java b/spring-core/src/main/java/org/springframework/util/ReflectionUtils.java index 6198cbbbbed8..56c2d4fcee9f 100644 --- a/spring-core/src/main/java/org/springframework/util/ReflectionUtils.java +++ b/spring-core/src/main/java/org/springframework/util/ReflectionUtils.java @@ -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 static void doWithMethods(Class clazz, MethodCallback mc, @Nullable Me 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 static Method[] getAllDeclaredMethods(Class leafClass) { * @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 methods = new ArrayList<>(32); doWithMethods(leafClass, method -> { boolean knownSignature = false; @@ -590,7 +603,7 @@ public static Method[] getUniqueDeclaredMethods(Class leafClass) { if (!knownSignature && !isCglibRenamedMethod(method)) { methods.add(method); } - }); + }, mf); return methods.toArray(new Method[0]); } diff --git a/spring-test/src/main/java/org/springframework/test/context/transaction/TransactionalTestExecutionListener.java b/spring-test/src/main/java/org/springframework/test/context/transaction/TransactionalTestExecutionListener.java index b842f32ac903..e72ae969abc9 100644 --- a/spring-test/src/main/java/org/springframework/test/context/transaction/TransactionalTestExecutionListener.java +++ b/spring-test/src/main/java/org/springframework/test/context/transaction/TransactionalTestExecutionListener.java @@ -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 @@ protected final boolean isRollback(TestContext testContext) throws Exception { * as well as annotated interface default methods */ private List getAnnotatedMethods(Class clazz, Class 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()); }