|
29 | 29 | import com.zaxxer.hikari.HikariDataSource; |
30 | 30 | import org.junit.jupiter.api.Test; |
31 | 31 |
|
| 32 | +import org.springframework.aop.TargetSource; |
| 33 | +import org.springframework.aop.framework.ProxyFactory; |
32 | 34 | import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ConfigurationPropertiesBeanDescriptor; |
33 | 35 | import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ConfigurationPropertiesDescriptor; |
| 36 | +import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ContextConfigurationPropertiesDescriptor; |
34 | 37 | import org.springframework.boot.actuate.endpoint.Show; |
35 | 38 | import org.springframework.boot.context.properties.ConfigurationProperties; |
36 | 39 | import org.springframework.boot.context.properties.ConfigurationPropertiesBinding; |
37 | 40 | import org.springframework.boot.context.properties.EnableConfigurationProperties; |
| 41 | +import org.springframework.boot.test.context.assertj.AssertableApplicationContext; |
38 | 42 | import org.springframework.boot.test.context.runner.ApplicationContextRunner; |
39 | 43 | import org.springframework.context.annotation.Bean; |
40 | 44 | import org.springframework.context.annotation.Configuration; |
@@ -253,6 +257,55 @@ void testInitializedMapAndList() { |
253 | 257 | }); |
254 | 258 | } |
255 | 259 |
|
| 260 | + @Test |
| 261 | + void aopProxyDoesNotLeakAdvisedInternals() { |
| 262 | + ApplicationContextRunner contextRunner = new ApplicationContextRunner() |
| 263 | + .withUserConfiguration(CglibProxiedFooConfig.class) |
| 264 | + .withPropertyValues("foo.name:test"); |
| 265 | + contextRunner.run((context) -> { |
| 266 | + ConfigurationPropertiesReportEndpoint endpoint = context |
| 267 | + .getBean(ConfigurationPropertiesReportEndpoint.class); |
| 268 | + ConfigurationPropertiesDescriptor applicationProperties = endpoint.configurationProperties(); |
| 269 | + ConfigurationPropertiesBeanDescriptor foo = getContextDescriptor(context, applicationProperties).getBeans() |
| 270 | + .get("foo"); |
| 271 | + assertThat(foo).isNotNull(); |
| 272 | + Map<String, Object> map = foo.getProperties(); |
| 273 | + assertThat(map).containsOnlyKeys("name", "bar"); |
| 274 | + assertThat(map).containsEntry("name", "test"); |
| 275 | + }); |
| 276 | + } |
| 277 | + |
| 278 | + @Test |
| 279 | + void aopProxyTargetingAnotherProxyIsUnwrapped() { |
| 280 | + ApplicationContextRunner contextRunner = new ApplicationContextRunner() |
| 281 | + .withUserConfiguration(NestedCglibProxiedFooConfig.class) |
| 282 | + .withPropertyValues("foo.name:nested"); |
| 283 | + contextRunner.run((context) -> { |
| 284 | + ConfigurationPropertiesReportEndpoint endpoint = context |
| 285 | + .getBean(ConfigurationPropertiesReportEndpoint.class); |
| 286 | + ConfigurationPropertiesDescriptor applicationProperties = endpoint.configurationProperties(); |
| 287 | + ConfigurationPropertiesBeanDescriptor foo = getContextDescriptor(context, applicationProperties).getBeans() |
| 288 | + .get("foo"); |
| 289 | + assertThat(foo).isNotNull(); |
| 290 | + assertThat(foo.getProperties()).containsOnlyKeys("name", "bar"); |
| 291 | + assertThat(foo.getProperties()).containsEntry("name", "nested"); |
| 292 | + }); |
| 293 | + } |
| 294 | + |
| 295 | + @Test |
| 296 | + void aopProxyWithUnresolvableTarget() { |
| 297 | + ApplicationContextRunner contextRunner = new ApplicationContextRunner() |
| 298 | + .withUserConfiguration(UnresolvableTargetFooConfig.class); |
| 299 | + contextRunner.run((context) -> { |
| 300 | + ConfigurationPropertiesReportEndpoint endpoint = context |
| 301 | + .getBean(ConfigurationPropertiesReportEndpoint.class); |
| 302 | + ConfigurationPropertiesDescriptor applicationProperties = endpoint.configurationProperties(); |
| 303 | + assertThat(getContextDescriptor(context, applicationProperties).getBeans()).containsKey("foo") |
| 304 | + .satisfies((beans) -> assertThat(beans.get("foo").getProperties()).containsEntry("error", |
| 305 | + "Cannot serialize 'foo'")); |
| 306 | + }); |
| 307 | + } |
| 308 | + |
256 | 309 | @Test |
257 | 310 | void hikariDataSourceConfigurationPropertiesBeanCanBeSerialized() { |
258 | 311 | ApplicationContextRunner contextRunner = new ApplicationContextRunner() |
@@ -313,6 +366,14 @@ void endpointResponseUsesPlaceholderForComplexValueAsPropertyValue() { |
313 | 366 | }); |
314 | 367 | } |
315 | 368 |
|
| 369 | + private ContextConfigurationPropertiesDescriptor getContextDescriptor(AssertableApplicationContext context, |
| 370 | + ConfigurationPropertiesDescriptor applicationProperties) { |
| 371 | + ContextConfigurationPropertiesDescriptor contextDescriptor = applicationProperties.getContexts() |
| 372 | + .get(context.getId()); |
| 373 | + assertThat(contextDescriptor).isNotNull(); |
| 374 | + return contextDescriptor; |
| 375 | + } |
| 376 | + |
316 | 377 | @Configuration(proxyBeanMethods = false) |
317 | 378 | @EnableConfigurationProperties |
318 | 379 | static class Base { |
@@ -576,6 +637,68 @@ Cycle cycle() { |
576 | 637 |
|
577 | 638 | } |
578 | 639 |
|
| 640 | + @Configuration(proxyBeanMethods = false) |
| 641 | + @Import(Base.class) |
| 642 | + static class CglibProxiedFooConfig { |
| 643 | + |
| 644 | + @Bean |
| 645 | + @ConfigurationProperties("foo") |
| 646 | + Foo foo() { |
| 647 | + ProxyFactory proxyFactory = new ProxyFactory(new Foo()); |
| 648 | + proxyFactory.setProxyTargetClass(true); |
| 649 | + return (Foo) proxyFactory.getProxy(); |
| 650 | + } |
| 651 | + |
| 652 | + } |
| 653 | + |
| 654 | + @Configuration(proxyBeanMethods = false) |
| 655 | + @Import(Base.class) |
| 656 | + static class NestedCglibProxiedFooConfig { |
| 657 | + |
| 658 | + @Bean |
| 659 | + @ConfigurationProperties("foo") |
| 660 | + Foo foo() { |
| 661 | + ProxyFactory inner = new ProxyFactory(new Foo()); |
| 662 | + inner.setProxyTargetClass(true); |
| 663 | + ProxyFactory outer = new ProxyFactory(inner.getProxy()); |
| 664 | + outer.setProxyTargetClass(true); |
| 665 | + return (Foo) outer.getProxy(); |
| 666 | + } |
| 667 | + |
| 668 | + } |
| 669 | + |
| 670 | + @Configuration(proxyBeanMethods = false) |
| 671 | + @Import(Base.class) |
| 672 | + static class UnresolvableTargetFooConfig { |
| 673 | + |
| 674 | + @Bean |
| 675 | + @ConfigurationProperties("foo") |
| 676 | + Foo foo() { |
| 677 | + ProxyFactory proxyFactory = new ProxyFactory(); |
| 678 | + proxyFactory.setProxyTargetClass(true); |
| 679 | + proxyFactory.setTargetSource(new TargetSource() { |
| 680 | + |
| 681 | + @Override |
| 682 | + public Class<?> getTargetClass() { |
| 683 | + return Foo.class; |
| 684 | + } |
| 685 | + |
| 686 | + @Override |
| 687 | + public boolean isStatic() { |
| 688 | + return false; |
| 689 | + } |
| 690 | + |
| 691 | + @Override |
| 692 | + public Object getTarget() throws Exception { |
| 693 | + throw new IllegalStateException("no target"); |
| 694 | + } |
| 695 | + |
| 696 | + }); |
| 697 | + return (Foo) proxyFactory.getProxy(); |
| 698 | + } |
| 699 | + |
| 700 | + } |
| 701 | + |
579 | 702 | @Configuration(proxyBeanMethods = false) |
580 | 703 | @EnableConfigurationProperties |
581 | 704 | static class HikariDataSourceConfig { |
|
0 commit comments