|
25 | 25 | import org.junit.jupiter.api.Test;
|
26 | 26 |
|
27 | 27 | import org.springframework.beans.BeanWrapper;
|
| 28 | +import org.springframework.beans.factory.BeanCreationException; |
28 | 29 | import org.springframework.beans.factory.FactoryBean;
|
29 | 30 | import org.springframework.beans.factory.annotation.Qualifier;
|
30 | 31 | import org.springframework.beans.factory.config.BeanDefinition;
|
|
37 | 38 | import org.springframework.core.Ordered;
|
38 | 39 | import org.springframework.core.ResolvableType;
|
39 | 40 | import org.springframework.test.context.MergedContextConfiguration;
|
| 41 | +import org.springframework.test.context.bean.override.convention.TestBean; |
40 | 42 | import org.springframework.test.util.ReflectionTestUtils;
|
41 | 43 | import org.springframework.util.Assert;
|
42 | 44 |
|
43 | 45 | import static org.assertj.core.api.Assertions.assertThat;
|
| 46 | +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; |
44 | 47 | import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
45 | 48 | import static org.assertj.core.api.Assertions.assertThatNoException;
|
46 | 49 | import static org.mockito.Mockito.mock;
|
@@ -212,6 +215,130 @@ void replaceBeanByNameWithMatchingBeanDefinitionWithExplicitSingletonScope() {
|
212 | 215 | assertThat(context.getBean("descriptionBean")).isEqualTo("overridden");
|
213 | 216 | }
|
214 | 217 |
|
| 218 | + @Test |
| 219 | + void replaceBeanByNameWithMatchingBeanDefinitionForClassBasedSingletonFactoryBean() { |
| 220 | + String beanName = "descriptionBean"; |
| 221 | + AnnotationConfigApplicationContext context = createContext(CaseByName.class); |
| 222 | + RootBeanDefinition factoryBeanDefinition = new RootBeanDefinition(SingletonStringFactoryBean.class); |
| 223 | + context.registerBeanDefinition(beanName, factoryBeanDefinition); |
| 224 | + |
| 225 | + assertThatNoException().isThrownBy(context::refresh); |
| 226 | + assertThat(context.isSingleton(beanName)).as("isSingleton").isTrue(); |
| 227 | + assertThat(context.getBean(beanName)).isEqualTo("overridden"); |
| 228 | + } |
| 229 | + |
| 230 | + @Test |
| 231 | + void replaceBeanByNameWithMatchingBeanDefinitionForClassBasedNonSingletonFactoryBean() { |
| 232 | + String beanName = "descriptionBean"; |
| 233 | + AnnotationConfigApplicationContext context = createContext(CaseByName.class); |
| 234 | + RootBeanDefinition factoryBeanDefinition = new RootBeanDefinition(NonSingletonStringFactoryBean.class); |
| 235 | + context.registerBeanDefinition(beanName, factoryBeanDefinition); |
| 236 | + |
| 237 | + assertThatNoException().isThrownBy(context::refresh); |
| 238 | + // Even though the FactoryBean signals it does not manage a singleton, |
| 239 | + // the Bean Override support currently replaces it with a singleton. |
| 240 | + assertThat(context.isSingleton(beanName)).as("isSingleton").isTrue(); |
| 241 | + assertThat(context.getBean(beanName)).isEqualTo("overridden"); |
| 242 | + } |
| 243 | + |
| 244 | + @Test |
| 245 | + void replaceBeanByNameWithMatchingBeanDefinitionForInterfaceBasedSingletonFactoryBean() { |
| 246 | + String beanName = "messageServiceBean"; |
| 247 | + AnnotationConfigApplicationContext context = createContext(MessageServiceTestCase.class); |
| 248 | + RootBeanDefinition factoryBeanDefinition = new RootBeanDefinition(SingletonMessageServiceFactoryBean.class); |
| 249 | + context.registerBeanDefinition(beanName, factoryBeanDefinition); |
| 250 | + |
| 251 | + assertThatNoException().isThrownBy(context::refresh); |
| 252 | + assertThat(context.isSingleton(beanName)).as("isSingleton").isTrue(); |
| 253 | + assertThat(context.getBean(beanName, MessageService.class).getMessage()).isEqualTo("overridden"); |
| 254 | + } |
| 255 | + |
| 256 | + @Test |
| 257 | + void replaceBeanByNameWithMatchingBeanDefinitionForInterfaceBasedNonSingletonFactoryBean() { |
| 258 | + String beanName = "messageServiceBean"; |
| 259 | + AnnotationConfigApplicationContext context = createContext(MessageServiceTestCase.class); |
| 260 | + RootBeanDefinition factoryBeanDefinition = new RootBeanDefinition(NonSingletonMessageServiceFactoryBean.class); |
| 261 | + context.registerBeanDefinition(beanName, factoryBeanDefinition); |
| 262 | + |
| 263 | + assertThatNoException().isThrownBy(context::refresh); |
| 264 | + // Even though the FactoryBean signals it does not manage a singleton, |
| 265 | + // the Bean Override support currently replaces it with a singleton. |
| 266 | + assertThat(context.isSingleton(beanName)).as("isSingleton").isTrue(); |
| 267 | + assertThat(context.getBean(beanName, MessageService.class).getMessage()).isEqualTo("overridden"); |
| 268 | + } |
| 269 | + |
| 270 | + @Test |
| 271 | + void replaceBeanByNameWithMatchingBeanDefinitionWithPrototypeScope() { |
| 272 | + String beanName = "descriptionBean"; |
| 273 | + |
| 274 | + AnnotationConfigApplicationContext context = createContext(CaseByName.class); |
| 275 | + RootBeanDefinition definition = new RootBeanDefinition(String.class, () -> "ORIGINAL"); |
| 276 | + definition.setScope(BeanDefinition.SCOPE_PROTOTYPE); |
| 277 | + context.registerBeanDefinition(beanName, definition); |
| 278 | + |
| 279 | + assertThatNoException().isThrownBy(context::refresh); |
| 280 | + // The Bean Override support currently creates a "dummy" BeanDefinition that |
| 281 | + // retains the prototype scope of the original BeanDefinition. |
| 282 | + assertThat(context.isSingleton(beanName)).as("isSingleton").isFalse(); |
| 283 | + assertThat(context.isPrototype(beanName)).as("isPrototype").isTrue(); |
| 284 | + // Since the "dummy" BeanDefinition has prototype scope, a manual singleton |
| 285 | + // is not registered, and the "dummy" BeanDefinition is used to create a |
| 286 | + // new java.lang.String using the default constructor, which results in an |
| 287 | + // empty string instead of "overridden". In other words, the bean is not |
| 288 | + // actually overridden as expected, and no exception is thrown which |
| 289 | + // silently masks the issue. |
| 290 | + assertThat(context.getBean(beanName)).isEqualTo(""); |
| 291 | + } |
| 292 | + |
| 293 | + @Test |
| 294 | + void replaceBeanByNameWithMatchingBeanDefinitionWithCustomScope() { |
| 295 | + String beanName = "descriptionBean"; |
| 296 | + String scope = "customScope"; |
| 297 | + |
| 298 | + AnnotationConfigApplicationContext context = createContext(CaseByName.class); |
| 299 | + ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); |
| 300 | + beanFactory.registerScope(scope, new SimpleThreadScope()); |
| 301 | + RootBeanDefinition definition = new RootBeanDefinition(String.class, () -> "ORIGINAL"); |
| 302 | + definition.setScope(scope); |
| 303 | + context.registerBeanDefinition(beanName, definition); |
| 304 | + |
| 305 | + assertThatNoException().isThrownBy(context::refresh); |
| 306 | + // The Bean Override support currently creates a "dummy" BeanDefinition that |
| 307 | + // retains the custom scope of the original BeanDefinition. |
| 308 | + assertThat(context.isSingleton(beanName)).as("isSingleton").isFalse(); |
| 309 | + assertThat(context.isPrototype(beanName)).as("isPrototype").isFalse(); |
| 310 | + assertThat(beanFactory.getBeanDefinition(beanName).getScope()).isEqualTo(scope); |
| 311 | + // Since the "dummy" BeanDefinition has a custom scope, a manual singleton |
| 312 | + // is not registered, and the "dummy" BeanDefinition is used to create a |
| 313 | + // new java.lang.String using the default constructor, which results in an |
| 314 | + // empty string instead of "overridden". In other words, the bean is not |
| 315 | + // actually overridden as expected, and no exception is thrown which |
| 316 | + // silently masks the issue. |
| 317 | + assertThat(context.getBean(beanName)).isEqualTo(""); |
| 318 | + } |
| 319 | + |
| 320 | + @Test |
| 321 | + void replaceBeanByNameWithMatchingBeanDefinitionForPrototypeScopedFactoryBean() { |
| 322 | + String beanName = "messageServiceBean"; |
| 323 | + AnnotationConfigApplicationContext context = createContext(MessageServiceTestCase.class); |
| 324 | + RootBeanDefinition factoryBeanDefinition = new RootBeanDefinition(SingletonMessageServiceFactoryBean.class); |
| 325 | + factoryBeanDefinition.setScope(BeanDefinition.SCOPE_PROTOTYPE); |
| 326 | + context.registerBeanDefinition(beanName, factoryBeanDefinition); |
| 327 | + |
| 328 | + assertThatNoException().isThrownBy(context::refresh); |
| 329 | + // The Bean Override support currently creates a "dummy" BeanDefinition that |
| 330 | + // retains the prototype scope of the original BeanDefinition. |
| 331 | + assertThat(context.isSingleton(beanName)).as("isSingleton").isFalse(); |
| 332 | + assertThat(context.isPrototype(beanName)).as("isPrototype").isTrue(); |
| 333 | + // Since the "dummy" BeanDefinition has prototype scope, a manual singleton |
| 334 | + // is not registered, and the "dummy" BeanDefinition is used to create a |
| 335 | + // new MessageService using the default constructor, which results in an |
| 336 | + // error since MessageService is an interface. |
| 337 | + assertThatExceptionOfType(BeanCreationException.class) |
| 338 | + .isThrownBy(() -> context.getBean(beanName)) |
| 339 | + .withMessageContaining("Specified class is an interface"); |
| 340 | + } |
| 341 | + |
215 | 342 | @Test
|
216 | 343 | void replaceBeanByNameWithMatchingBeanDefinitionRetainsPrimaryFallbackAndScopeProperties() {
|
217 | 344 | AnnotationConfigApplicationContext context = createContext(CaseByName.class);
|
@@ -330,6 +457,63 @@ public boolean isSingleton() {
|
330 | 457 | }
|
331 | 458 | }
|
332 | 459 |
|
| 460 | + static class SingletonStringFactoryBean implements FactoryBean<String> { |
| 461 | + |
| 462 | + @Override |
| 463 | + public String getObject() { |
| 464 | + return "test"; |
| 465 | + } |
| 466 | + |
| 467 | + @Override |
| 468 | + public Class<?> getObjectType() { |
| 469 | + return String.class; |
| 470 | + } |
| 471 | + } |
| 472 | + |
| 473 | + static class NonSingletonStringFactoryBean extends SingletonStringFactoryBean { |
| 474 | + |
| 475 | + @Override |
| 476 | + public boolean isSingleton() { |
| 477 | + return false; |
| 478 | + } |
| 479 | + } |
| 480 | + |
| 481 | + static class SingletonMessageServiceFactoryBean implements FactoryBean<MessageService> { |
| 482 | + |
| 483 | + @Override |
| 484 | + public MessageService getObject() { |
| 485 | + return () -> "test"; |
| 486 | + } |
| 487 | + |
| 488 | + @Override |
| 489 | + public Class<?> getObjectType() { |
| 490 | + return MessageService.class; |
| 491 | + } |
| 492 | + } |
| 493 | + |
| 494 | + static class NonSingletonMessageServiceFactoryBean extends SingletonMessageServiceFactoryBean { |
| 495 | + |
| 496 | + @Override |
| 497 | + public boolean isSingleton() { |
| 498 | + return false; |
| 499 | + } |
| 500 | + } |
| 501 | + |
| 502 | + @FunctionalInterface |
| 503 | + interface MessageService { |
| 504 | + String getMessage(); |
| 505 | + } |
| 506 | + |
| 507 | + static class MessageServiceTestCase { |
| 508 | + |
| 509 | + @TestBean(name = "messageServiceBean") |
| 510 | + MessageService messageService; |
| 511 | + |
| 512 | + static MessageService messageService() { |
| 513 | + return () -> "overridden"; |
| 514 | + } |
| 515 | + } |
| 516 | + |
333 | 517 | static class FactoryBeanRegisteringPostProcessor implements BeanFactoryPostProcessor, Ordered {
|
334 | 518 |
|
335 | 519 | @Override
|
|
0 commit comments