diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java index bd42f89d30..2065f6b6e9 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 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. @@ -628,45 +628,58 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA Field field = (Field) this.member; Object value; if (this.cached) { - value = resolvedCachedArgument(beanName, this.cachedFieldValue); + try { + value = resolvedCachedArgument(beanName, this.cachedFieldValue); + } + catch (NoSuchBeanDefinitionException ex) { + // Unexpected removal of target bean for cached argument -> re-resolve + value = resolveFieldValue(field, bean, beanName); + } } else { - DependencyDescriptor desc = new DependencyDescriptor(field, this.required); - desc.setContainingClass(bean.getClass()); - Set autowiredBeanNames = new LinkedHashSet<>(1); - Assert.state(beanFactory != null, "No BeanFactory available"); - TypeConverter typeConverter = beanFactory.getTypeConverter(); - try { - value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter); - } - catch (BeansException ex) { - throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex); - } - synchronized (this) { - if (!this.cached) { - Object cachedFieldValue = null; - if (value != null || this.required) { - cachedFieldValue = desc; - registerDependentBeans(beanName, autowiredBeanNames); - if (autowiredBeanNames.size() == 1) { - String autowiredBeanName = autowiredBeanNames.iterator().next(); - if (beanFactory.containsBean(autowiredBeanName) && - beanFactory.isTypeMatch(autowiredBeanName, field.getType())) { - cachedFieldValue = new ShortcutDependencyDescriptor( - desc, autowiredBeanName, field.getType()); - } - } - } - this.cachedFieldValue = cachedFieldValue; - this.cached = true; - } - } + value = resolveFieldValue(field, bean, beanName); } if (value != null) { ReflectionUtils.makeAccessible(field); field.set(bean, value); } } + + @Nullable + private Object resolveFieldValue(Field field, Object bean, @Nullable String beanName) { + DependencyDescriptor desc = new DependencyDescriptor(field, this.required); + desc.setContainingClass(bean.getClass()); + Set autowiredBeanNames = new LinkedHashSet<>(1); + Assert.state(beanFactory != null, "No BeanFactory available"); + TypeConverter typeConverter = beanFactory.getTypeConverter(); + Object value; + try { + value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter); + } + catch (BeansException ex) { + throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex); + } + synchronized (this) { + if (!this.cached) { + Object cachedFieldValue = null; + if (value != null || this.required) { + cachedFieldValue = desc; + registerDependentBeans(beanName, autowiredBeanNames); + if (autowiredBeanNames.size() == 1) { + String autowiredBeanName = autowiredBeanNames.iterator().next(); + if (beanFactory.containsBean(autowiredBeanName) && + beanFactory.isTypeMatch(autowiredBeanName, field.getType())) { + cachedFieldValue = new ShortcutDependencyDescriptor( + desc, autowiredBeanName, field.getType()); + } + } + } + this.cachedFieldValue = cachedFieldValue; + this.cached = true; + } + } + return value; + } } @@ -695,58 +708,16 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA Method method = (Method) this.member; Object[] arguments; if (this.cached) { - // Shortcut for avoiding synchronization... - arguments = resolveCachedArguments(beanName); + try { + arguments = resolveCachedArguments(beanName); + } + catch (NoSuchBeanDefinitionException ex) { + // Unexpected removal of target bean for cached argument -> re-resolve + arguments = resolveMethodArguments(method, bean, beanName); + } } else { - int argumentCount = method.getParameterCount(); - arguments = new Object[argumentCount]; - DependencyDescriptor[] descriptors = new DependencyDescriptor[argumentCount]; - Set autowiredBeans = new LinkedHashSet<>(argumentCount); - Assert.state(beanFactory != null, "No BeanFactory available"); - TypeConverter typeConverter = beanFactory.getTypeConverter(); - for (int i = 0; i < arguments.length; i++) { - MethodParameter methodParam = new MethodParameter(method, i); - DependencyDescriptor currDesc = new DependencyDescriptor(methodParam, this.required); - currDesc.setContainingClass(bean.getClass()); - descriptors[i] = currDesc; - try { - Object arg = beanFactory.resolveDependency(currDesc, beanName, autowiredBeans, typeConverter); - if (arg == null && !this.required) { - arguments = null; - break; - } - arguments[i] = arg; - } - catch (BeansException ex) { - throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(methodParam), ex); - } - } - synchronized (this) { - if (!this.cached) { - if (arguments != null) { - DependencyDescriptor[] cachedMethodArguments = Arrays.copyOf(descriptors, arguments.length); - registerDependentBeans(beanName, autowiredBeans); - if (autowiredBeans.size() == argumentCount) { - Iterator it = autowiredBeans.iterator(); - Class[] paramTypes = method.getParameterTypes(); - for (int i = 0; i < paramTypes.length; i++) { - String autowiredBeanName = it.next(); - if (beanFactory.containsBean(autowiredBeanName) && - beanFactory.isTypeMatch(autowiredBeanName, paramTypes[i])) { - cachedMethodArguments[i] = new ShortcutDependencyDescriptor( - descriptors[i], autowiredBeanName, paramTypes[i]); - } - } - } - this.cachedMethodArguments = cachedMethodArguments; - } - else { - this.cachedMethodArguments = null; - } - this.cached = true; - } - } + arguments = resolveMethodArguments(method, bean, beanName); } if (arguments != null) { try { @@ -771,6 +742,59 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA } return arguments; } + + @Nullable + private Object[] resolveMethodArguments(Method method, Object bean, @Nullable String beanName) { + int argumentCount = method.getParameterCount(); + Object[] arguments = new Object[argumentCount]; + DependencyDescriptor[] descriptors = new DependencyDescriptor[argumentCount]; + Set autowiredBeans = new LinkedHashSet<>(argumentCount); + Assert.state(beanFactory != null, "No BeanFactory available"); + TypeConverter typeConverter = beanFactory.getTypeConverter(); + for (int i = 0; i < arguments.length; i++) { + MethodParameter methodParam = new MethodParameter(method, i); + DependencyDescriptor currDesc = new DependencyDescriptor(methodParam, this.required); + currDesc.setContainingClass(bean.getClass()); + descriptors[i] = currDesc; + try { + Object arg = beanFactory.resolveDependency(currDesc, beanName, autowiredBeans, typeConverter); + if (arg == null && !this.required) { + arguments = null; + break; + } + arguments[i] = arg; + } + catch (BeansException ex) { + throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(methodParam), ex); + } + } + synchronized (this) { + if (!this.cached) { + if (arguments != null) { + DependencyDescriptor[] cachedMethodArguments = Arrays.copyOf(descriptors, arguments.length); + registerDependentBeans(beanName, autowiredBeans); + if (autowiredBeans.size() == argumentCount) { + Iterator it = autowiredBeans.iterator(); + Class[] paramTypes = method.getParameterTypes(); + for (int i = 0; i < paramTypes.length; i++) { + String autowiredBeanName = it.next(); + if (beanFactory.containsBean(autowiredBeanName) && + beanFactory.isTypeMatch(autowiredBeanName, paramTypes[i])) { + cachedMethodArguments[i] = new ShortcutDependencyDescriptor( + descriptors[i], autowiredBeanName, paramTypes[i]); + } + } + } + this.cachedMethodArguments = cachedMethodArguments; + } + else { + this.cachedMethodArguments = null; + } + this.cached = true; + } + } + return arguments; + } } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java index 0493c7f54f..c746f21949 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 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. @@ -297,6 +297,121 @@ public class AutowiredAnnotationBeanPostProcessorTests { assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2); } + @Test + public void testOptionalResourceInjectionWithSingletonRemoval() { + RootBeanDefinition rbd = new RootBeanDefinition(OptionalResourceInjectionBean.class); + rbd.setScope(BeanDefinition.SCOPE_PROTOTYPE); + bf.registerBeanDefinition("annotatedBean", rbd); + TestBean tb = new TestBean(); + bf.registerSingleton("testBean", tb); + IndexedTestBean itb = new IndexedTestBean(); + bf.registerSingleton("indexedTestBean", itb); + NestedTestBean ntb1 = new NestedTestBean(); + bf.registerSingleton("nestedTestBean1", ntb1); + NestedTestBean ntb2 = new NestedTestBean(); + bf.registerSingleton("nestedTestBean2", ntb2); + + OptionalResourceInjectionBean bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean"); + assertThat(bean.getTestBean()).isSameAs(tb); + assertThat(bean.getTestBean2()).isSameAs(tb); + assertThat(bean.getTestBean3()).isSameAs(tb); + assertThat(bean.getTestBean4()).isSameAs(tb); + assertThat(bean.getIndexedTestBean()).isSameAs(itb); + assertThat(bean.getNestedTestBeans().length).isEqualTo(2); + assertThat(bean.getNestedTestBeans()[0]).isSameAs(ntb1); + assertThat(bean.getNestedTestBeans()[1]).isSameAs(ntb2); + assertThat(bean.nestedTestBeansField.length).isEqualTo(2); + assertThat(bean.nestedTestBeansField[0]).isSameAs(ntb1); + assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2); + + bf.destroySingleton("testBean"); + + bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean"); + assertThat(bean.getTestBean()).isNull(); + assertThat(bean.getTestBean2()).isNull(); + assertThat(bean.getTestBean3()).isNull(); + assertThat(bean.getTestBean4()).isNull(); + assertThat(bean.getIndexedTestBean()).isSameAs(itb); + assertThat(bean.getNestedTestBeans().length).isEqualTo(2); + assertThat(bean.getNestedTestBeans()[0]).isSameAs(ntb1); + assertThat(bean.getNestedTestBeans()[1]).isSameAs(ntb2); + assertThat(bean.nestedTestBeansField.length).isEqualTo(2); + assertThat(bean.nestedTestBeansField[0]).isSameAs(ntb1); + assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2); + + bf.registerSingleton("testBean", tb); + + bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean"); + assertThat(bean.getTestBean()).isSameAs(tb); + assertThat(bean.getTestBean2()).isSameAs(tb); + assertThat(bean.getTestBean3()).isSameAs(tb); + assertThat(bean.getTestBean4()).isSameAs(tb); + assertThat(bean.getIndexedTestBean()).isSameAs(itb); + assertThat(bean.getNestedTestBeans().length).isEqualTo(2); + assertThat(bean.getNestedTestBeans()[0]).isSameAs(ntb1); + assertThat(bean.getNestedTestBeans()[1]).isSameAs(ntb2); + assertThat(bean.nestedTestBeansField.length).isEqualTo(2); + assertThat(bean.nestedTestBeansField[0]).isSameAs(ntb1); + assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2); + } + + @Test + public void testOptionalResourceInjectionWithBeanDefinitionRemoval() { + RootBeanDefinition rbd = new RootBeanDefinition(OptionalResourceInjectionBean.class); + rbd.setScope(BeanDefinition.SCOPE_PROTOTYPE); + bf.registerBeanDefinition("annotatedBean", rbd); + bf.registerBeanDefinition("testBean", new RootBeanDefinition(TestBean.class)); + IndexedTestBean itb = new IndexedTestBean(); + bf.registerSingleton("indexedTestBean", itb); + NestedTestBean ntb1 = new NestedTestBean(); + bf.registerSingleton("nestedTestBean1", ntb1); + NestedTestBean ntb2 = new NestedTestBean(); + bf.registerSingleton("nestedTestBean2", ntb2); + + OptionalResourceInjectionBean bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean"); + assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean")); + assertThat(bean.getTestBean2()).isSameAs(bf.getBean("testBean")); + assertThat(bean.getTestBean3()).isSameAs(bf.getBean("testBean")); + assertThat(bean.getTestBean4()).isSameAs(bf.getBean("testBean")); + assertThat(bean.getIndexedTestBean()).isSameAs(itb); + assertThat(bean.getNestedTestBeans().length).isEqualTo(2); + assertThat(bean.getNestedTestBeans()[0]).isSameAs(ntb1); + assertThat(bean.getNestedTestBeans()[1]).isSameAs(ntb2); + assertThat(bean.nestedTestBeansField.length).isEqualTo(2); + assertThat(bean.nestedTestBeansField[0]).isSameAs(ntb1); + assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2); + + bf.removeBeanDefinition("testBean"); + + bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean"); + assertThat(bean.getTestBean()).isNull(); + assertThat(bean.getTestBean2()).isNull(); + assertThat(bean.getTestBean3()).isNull(); + assertThat(bean.getTestBean4()).isNull(); + assertThat(bean.getIndexedTestBean()).isSameAs(itb); + assertThat(bean.getNestedTestBeans().length).isEqualTo(2); + assertThat(bean.getNestedTestBeans()[0]).isSameAs(ntb1); + assertThat(bean.getNestedTestBeans()[1]).isSameAs(ntb2); + assertThat(bean.nestedTestBeansField.length).isEqualTo(2); + assertThat(bean.nestedTestBeansField[0]).isSameAs(ntb1); + assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2); + + bf.registerBeanDefinition("testBean", new RootBeanDefinition(TestBean.class)); + + bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean"); + assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean")); + assertThat(bean.getTestBean2()).isSameAs(bf.getBean("testBean")); + assertThat(bean.getTestBean3()).isSameAs(bf.getBean("testBean")); + assertThat(bean.getTestBean4()).isSameAs(bf.getBean("testBean")); + assertThat(bean.getIndexedTestBean()).isSameAs(itb); + assertThat(bean.getNestedTestBeans().length).isEqualTo(2); + assertThat(bean.getNestedTestBeans()[0]).isSameAs(ntb1); + assertThat(bean.getNestedTestBeans()[1]).isSameAs(ntb2); + assertThat(bean.nestedTestBeansField.length).isEqualTo(2); + assertThat(bean.nestedTestBeansField[0]).isSameAs(ntb1); + assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2); + } + @Test public void testOptionalCollectionResourceInjection() { RootBeanDefinition rbd = new RootBeanDefinition(OptionalCollectionResourceInjectionBean.class); @@ -533,6 +648,83 @@ public class AutowiredAnnotationBeanPostProcessorTests { assertThat(bean.getBeanFactory()).isSameAs(bf); } + @Test + public void testConstructorResourceInjectionWithSingletonRemoval() { + RootBeanDefinition bd = new RootBeanDefinition(ConstructorResourceInjectionBean.class); + bd.setScope(BeanDefinition.SCOPE_PROTOTYPE); + bf.registerBeanDefinition("annotatedBean", bd); + TestBean tb = new TestBean(); + bf.registerSingleton("testBean", tb); + NestedTestBean ntb = new NestedTestBean(); + bf.registerSingleton("nestedTestBean", ntb); + + ConstructorResourceInjectionBean bean = (ConstructorResourceInjectionBean) bf.getBean("annotatedBean"); + assertThat(bean.getTestBean()).isSameAs(tb); + assertThat(bean.getTestBean2()).isSameAs(tb); + assertThat(bean.getTestBean3()).isSameAs(tb); + assertThat(bean.getTestBean4()).isSameAs(tb); + assertThat(bean.getNestedTestBean()).isSameAs(ntb); + assertThat(bean.getBeanFactory()).isSameAs(bf); + + bf.destroySingleton("nestedTestBean"); + + bean = (ConstructorResourceInjectionBean) bf.getBean("annotatedBean"); + assertThat(bean.getTestBean()).isSameAs(tb); + assertThat(bean.getTestBean2()).isSameAs(tb); + assertThat(bean.getTestBean3()).isSameAs(tb); + assertThat(bean.getTestBean4()).isSameAs(tb); + assertThat(bean.getNestedTestBean()).isNull(); + assertThat(bean.getBeanFactory()).isSameAs(bf); + + bf.registerSingleton("nestedTestBean", ntb); + + bean = (ConstructorResourceInjectionBean) bf.getBean("annotatedBean"); + assertThat(bean.getTestBean()).isSameAs(tb); + assertThat(bean.getTestBean2()).isSameAs(tb); + assertThat(bean.getTestBean3()).isSameAs(tb); + assertThat(bean.getTestBean4()).isSameAs(tb); + assertThat(bean.getNestedTestBean()).isSameAs(ntb); + assertThat(bean.getBeanFactory()).isSameAs(bf); + } + + @Test + public void testConstructorResourceInjectionWithBeanDefinitionRemoval() { + RootBeanDefinition bd = new RootBeanDefinition(ConstructorResourceInjectionBean.class); + bd.setScope(BeanDefinition.SCOPE_PROTOTYPE); + bf.registerBeanDefinition("annotatedBean", bd); + TestBean tb = new TestBean(); + bf.registerSingleton("testBean", tb); + bf.registerBeanDefinition("nestedTestBean", new RootBeanDefinition(NestedTestBean.class)); + + ConstructorResourceInjectionBean bean = (ConstructorResourceInjectionBean) bf.getBean("annotatedBean"); + assertThat(bean.getTestBean()).isSameAs(tb); + assertThat(bean.getTestBean2()).isSameAs(tb); + assertThat(bean.getTestBean3()).isSameAs(tb); + assertThat(bean.getTestBean4()).isSameAs(tb); + assertThat(bean.getNestedTestBean()).isSameAs(bf.getBean("nestedTestBean")); + assertThat(bean.getBeanFactory()).isSameAs(bf); + + bf.removeBeanDefinition("nestedTestBean"); + + bean = (ConstructorResourceInjectionBean) bf.getBean("annotatedBean"); + assertThat(bean.getTestBean()).isSameAs(tb); + assertThat(bean.getTestBean2()).isSameAs(tb); + assertThat(bean.getTestBean3()).isSameAs(tb); + assertThat(bean.getTestBean4()).isSameAs(tb); + assertThat(bean.getNestedTestBean()).isNull(); + assertThat(bean.getBeanFactory()).isSameAs(bf); + + bf.registerBeanDefinition("nestedTestBean", new RootBeanDefinition(NestedTestBean.class)); + + bean = (ConstructorResourceInjectionBean) bf.getBean("annotatedBean"); + assertThat(bean.getTestBean()).isSameAs(tb); + assertThat(bean.getTestBean2()).isSameAs(tb); + assertThat(bean.getTestBean3()).isSameAs(tb); + assertThat(bean.getTestBean4()).isSameAs(tb); + assertThat(bean.getNestedTestBean()).isSameAs(bf.getBean("nestedTestBean")); + assertThat(bean.getBeanFactory()).isSameAs(bf); + } + @Test public void testConstructorResourceInjectionWithNullFromFactoryBean() { RootBeanDefinition bd = new RootBeanDefinition(ConstructorResourceInjectionBean.class);