diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/DefaultAopProxyFactory.java b/spring-aop/src/main/java/org/springframework/aop/framework/DefaultAopProxyFactory.java index 151ef1455b2c..232e3bf13c46 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/DefaultAopProxyFactory.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/DefaultAopProxyFactory.java @@ -60,11 +60,12 @@ public class DefaultAopProxyFactory implements AopProxyFactory, Serializable { public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException { if (config.isOptimize() || config.isProxyTargetClass() || !config.hasUserSuppliedInterfaces()) { Class targetClass = config.getTargetClass(); - if (targetClass == null) { + if (targetClass == null && config.getProxiedInterfaces().length == 0) { throw new AopConfigException("TargetSource cannot determine target class: " + "Either an interface or a target is required for proxy creation."); } - if (targetClass.isInterface() || Proxy.isProxyClass(targetClass) || ClassUtils.isLambdaClass(targetClass)) { + if (targetClass == null || targetClass.isInterface() || + Proxy.isProxyClass(targetClass) || ClassUtils.isLambdaClass(targetClass)) { return new JdkDynamicAopProxy(config); } return new ObjenesisCglibAopProxy(config); diff --git a/spring-aop/src/test/java/org/springframework/aop/framework/ProxyFactoryTests.java b/spring-aop/src/test/java/org/springframework/aop/framework/ProxyFactoryTests.java index 76b736830cf0..476681fee264 100644 --- a/spring-aop/src/test/java/org/springframework/aop/framework/ProxyFactoryTests.java +++ b/spring-aop/src/test/java/org/springframework/aop/framework/ProxyFactoryTests.java @@ -340,6 +340,18 @@ void proxyTargetClassInCaseOfIntroducedInterface() { assertThat(AopProxyUtils.ultimateTargetClass(proxy)).isEqualTo(MyDate.class); } + @Test + void proxyInterfaceInCaseOfIntroducedInterfaceOnly() { + ProxyFactory pf = new ProxyFactory(); + pf.addInterface(TimeStamped.class); + TimestampIntroductionInterceptor ti = new TimestampIntroductionInterceptor(0L); + pf.addAdvisor(new DefaultIntroductionAdvisor(ti, TimeStamped.class)); + Object proxy = pf.getProxy(); + assertThat(AopUtils.isJdkDynamicProxy(proxy)).as("Proxy is a JDK proxy").isTrue(); + assertThat(proxy).isInstanceOf(TimeStamped.class); + assertThat(AopProxyUtils.ultimateTargetClass(proxy)).isEqualTo(proxy.getClass()); + } + @Test void proxyInterfaceInCaseOfNonTargetInterface() { ProxyFactory pf = new ProxyFactory(); 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 64cf81be30a6..1ddddbe82db0 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 @@ -991,54 +991,60 @@ protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, */ @Nullable private FactoryBean getSingletonFactoryBeanForTypeCheck(String beanName, RootBeanDefinition mbd) { - BeanWrapper bw = this.factoryBeanInstanceCache.get(beanName); - if (bw != null) { - return (FactoryBean) bw.getWrappedInstance(); - } - Object beanInstance = getSingleton(beanName, false); - if (beanInstance instanceof FactoryBean factoryBean) { - return factoryBean; - } - if (isSingletonCurrentlyInCreation(beanName) || - (mbd.getFactoryBeanName() != null && isSingletonCurrentlyInCreation(mbd.getFactoryBeanName()))) { - return null; - } - - Object instance; + this.singletonLock.lock(); try { - // Mark this bean as currently in creation, even if just partially. - beforeSingletonCreation(beanName); - // Give BeanPostProcessors a chance to return a proxy instead of the target bean instance. - instance = resolveBeforeInstantiation(beanName, mbd); - if (instance == null) { - bw = createBeanInstance(beanName, mbd, null); - instance = bw.getWrappedInstance(); - this.factoryBeanInstanceCache.put(beanName, bw); + BeanWrapper bw = this.factoryBeanInstanceCache.get(beanName); + if (bw != null) { + return (FactoryBean) bw.getWrappedInstance(); } - } - catch (UnsatisfiedDependencyException ex) { - // Don't swallow, probably misconfiguration... - throw ex; - } - catch (BeanCreationException ex) { - // Don't swallow a linkage error since it contains a full stacktrace on - // first occurrence... and just a plain NoClassDefFoundError afterwards. - if (ex.contains(LinkageError.class)) { + Object beanInstance = getSingleton(beanName, false); + if (beanInstance instanceof FactoryBean factoryBean) { + return factoryBean; + } + if (isSingletonCurrentlyInCreation(beanName) || + (mbd.getFactoryBeanName() != null && isSingletonCurrentlyInCreation(mbd.getFactoryBeanName()))) { + return null; + } + + Object instance; + try { + // Mark this bean as currently in creation, even if just partially. + beforeSingletonCreation(beanName); + // Give BeanPostProcessors a chance to return a proxy instead of the target bean instance. + instance = resolveBeforeInstantiation(beanName, mbd); + if (instance == null) { + bw = createBeanInstance(beanName, mbd, null); + instance = bw.getWrappedInstance(); + this.factoryBeanInstanceCache.put(beanName, bw); + } + } + catch (UnsatisfiedDependencyException ex) { + // Don't swallow, probably misconfiguration... throw ex; } - // Instantiation failure, maybe too early... - if (logger.isDebugEnabled()) { - logger.debug("Bean creation exception on singleton FactoryBean type check: " + ex); + catch (BeanCreationException ex) { + // Don't swallow a linkage error since it contains a full stacktrace on + // first occurrence... and just a plain NoClassDefFoundError afterwards. + if (ex.contains(LinkageError.class)) { + throw ex; + } + // Instantiation failure, maybe too early... + if (logger.isDebugEnabled()) { + logger.debug("Bean creation exception on singleton FactoryBean type check: " + ex); + } + onSuppressedException(ex); + return null; } - onSuppressedException(ex); - return null; + finally { + // Finished partial creation of this bean. + afterSingletonCreation(beanName); + } + + return getFactoryBean(beanName, instance); } finally { - // Finished partial creation of this bean. - afterSingletonCreation(beanName); + this.singletonLock.unlock(); } - - return getFactoryBean(beanName, instance); } /** diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java index 187f22cb54ef..bebcc1154009 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java @@ -76,6 +76,9 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements private static final int SUPPRESSED_EXCEPTIONS_LIMIT = 100; + /** Common lock for singleton creation. */ + final Lock singletonLock = new ReentrantLock(); + /** Cache of singleton objects: bean name to bean instance. */ private final Map singletonObjects = new ConcurrentHashMap<>(256); @@ -91,8 +94,6 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements /** Set of registered singletons, containing the bean names in registration order. */ private final Set registeredSingletons = Collections.synchronizedSet(new LinkedHashSet<>(256)); - private final Lock singletonLock = new ReentrantLock(); - /** Names of beans that are currently in creation. */ private final Set singletonsCurrentlyInCreation = ConcurrentHashMap.newKeySet(16); diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/FactoryBeanRegistrySupport.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/FactoryBeanRegistrySupport.java index 7f4a3375419a..ffcd87bbfbc1 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/FactoryBeanRegistrySupport.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/FactoryBeanRegistrySupport.java @@ -118,39 +118,45 @@ protected Object getCachedObjectForFactoryBean(String beanName) { */ protected Object getObjectFromFactoryBean(FactoryBean factory, String beanName, boolean shouldPostProcess) { if (factory.isSingleton() && containsSingleton(beanName)) { - Object object = this.factoryBeanObjectCache.get(beanName); - if (object == null) { - object = doGetObjectFromFactoryBean(factory, beanName); - // Only post-process and store if not put there already during getObject() call above - // (for example, because of circular reference processing triggered by custom getBean calls) - Object alreadyThere = this.factoryBeanObjectCache.get(beanName); - if (alreadyThere != null) { - object = alreadyThere; - } - else { - if (shouldPostProcess) { - if (isSingletonCurrentlyInCreation(beanName)) { - // Temporarily return non-post-processed object, not storing it yet - return object; - } - beforeSingletonCreation(beanName); - try { - object = postProcessObjectFromFactoryBean(object, beanName); - } - catch (Throwable ex) { - throw new BeanCreationException(beanName, - "Post-processing of FactoryBean's singleton object failed", ex); + this.singletonLock.lock(); + try { + Object object = this.factoryBeanObjectCache.get(beanName); + if (object == null) { + object = doGetObjectFromFactoryBean(factory, beanName); + // Only post-process and store if not put there already during getObject() call above + // (for example, because of circular reference processing triggered by custom getBean calls) + Object alreadyThere = this.factoryBeanObjectCache.get(beanName); + if (alreadyThere != null) { + object = alreadyThere; + } + else { + if (shouldPostProcess) { + if (isSingletonCurrentlyInCreation(beanName)) { + // Temporarily return non-post-processed object, not storing it yet + return object; + } + beforeSingletonCreation(beanName); + try { + object = postProcessObjectFromFactoryBean(object, beanName); + } + catch (Throwable ex) { + throw new BeanCreationException(beanName, + "Post-processing of FactoryBean's singleton object failed", ex); + } + finally { + afterSingletonCreation(beanName); + } } - finally { - afterSingletonCreation(beanName); + if (containsSingleton(beanName)) { + this.factoryBeanObjectCache.put(beanName, object); } } - if (containsSingleton(beanName)) { - this.factoryBeanObjectCache.put(beanName, object); - } } + return object; + } + finally { + this.singletonLock.unlock(); } - return object; } else { Object object = doGetObjectFromFactoryBean(factory, beanName);