From a70ad195ca7ae4b7f21c3dcb309acf03d377b55d Mon Sep 17 00:00:00 2001 From: dreis2211 Date: Wed, 3 Nov 2021 22:38:00 +0100 Subject: [PATCH] Avoid duplicate AOP proxy class definition with FilteredClassLoader Fixes gh-28528 --- .../test/context/FilteredClassLoader.java | 16 ++++++++-- .../context/FilteredClassLoaderTests.java | 12 ++++++- .../context/example/ExampleProperties.java | 29 +++++++++++++++++ ...AbstractApplicationContextRunnerTests.java | 31 +++++++++++++++++++ 4 files changed, 85 insertions(+), 3 deletions(-) create mode 100644 spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/example/ExampleProperties.java diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/FilteredClassLoader.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/FilteredClassLoader.java index 903da876d78a..5d6a8236c39f 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/FilteredClassLoader.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/FilteredClassLoader.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-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. @@ -20,12 +20,14 @@ import java.io.InputStream; import java.net.URL; import java.net.URLClassLoader; +import java.security.ProtectionDomain; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; import java.util.function.Predicate; +import org.springframework.core.SmartClassLoader; import org.springframework.core.io.ClassPathResource; /** @@ -37,7 +39,7 @@ * @author Roy Jacobs * @since 2.0.0 */ -public class FilteredClassLoader extends URLClassLoader { +public class FilteredClassLoader extends URLClassLoader implements SmartClassLoader { private final Collection> classesFilters; @@ -129,6 +131,16 @@ public InputStream getResourceAsStream(String name) { return super.getResourceAsStream(name); } + @Override + public Class publicDefineClass(String name, byte[] b, ProtectionDomain protectionDomain) { + for (Predicate filter : this.classesFilters) { + if (filter.test(name)) { + throw new IllegalArgumentException(String.format("Defining class with name %s is not supported", name)); + } + } + return defineClass(name, b, 0, b.length, protectionDomain); + } + /** * Filter to restrict the classes that can be loaded. */ diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/FilteredClassLoaderTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/FilteredClassLoaderTests.java index e18465289986..a488d433e471 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/FilteredClassLoaderTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/FilteredClassLoaderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-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. @@ -26,6 +26,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** * Tests for {@link FilteredClassLoader}. @@ -111,4 +112,13 @@ void loadResourceAsStreamWhenNotFilteredShouldLoadResource() throws Exception { } } + @Test + void publicDefineClassWhenFilteredThrowsException() throws Exception { + Class hiddenClass = FilteredClassLoaderTests.class; + try (FilteredClassLoader classLoader = new FilteredClassLoader(hiddenClass)) { + assertThatIllegalArgumentException() + .isThrownBy(() -> classLoader.publicDefineClass(hiddenClass.getName(), new byte[] {}, null)); + } + } + } diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/example/ExampleProperties.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/example/ExampleProperties.java new file mode 100644 index 000000000000..66f10b908b37 --- /dev/null +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/example/ExampleProperties.java @@ -0,0 +1,29 @@ +/* + * Copyright 2012-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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.test.context.example; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Example properties used in {@code AbstractApplicationContextRunnerTests}. + * + * @author Christoph Dreis + */ +@ConfigurationProperties +public class ExampleProperties { + +} diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/runner/AbstractApplicationContextRunnerTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/runner/AbstractApplicationContextRunnerTests.java index f0fb9208729a..249e2501a0cf 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/runner/AbstractApplicationContextRunnerTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/runner/AbstractApplicationContextRunnerTests.java @@ -28,14 +28,17 @@ import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.context.annotation.UserConfigurations; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.assertj.ApplicationContextAssertProvider; +import org.springframework.boot.test.context.example.ExampleProperties; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Lazy; import org.springframework.core.env.Environment; import org.springframework.core.type.AnnotatedTypeMetadata; import org.springframework.util.ClassUtils; @@ -168,6 +171,15 @@ void runWithClassLoaderShouldSetClassLoaderOnConditionContext() { .run((context) -> assertThat(context).hasSingleBean(ConditionalConfig.class)); } + @Test + void consecutiveRunWithFilteredClassLoaderShouldHaveBeanWithLazyProperties() { + get().withClassLoader(new FilteredClassLoader(Gson.class)).withUserConfiguration(LazyConfig.class) + .run((context) -> assertThat(context).hasSingleBean(ExampleBeanWithLazyProperties.class)); + + get().withClassLoader(new FilteredClassLoader(Gson.class)).withUserConfiguration(LazyConfig.class) + .run((context) -> assertThat(context).hasSingleBean(ExampleBeanWithLazyProperties.class)); + } + @Test void thrownRuleWorksWithCheckedException() { get().run((context) -> assertThatIOException().isThrownBy(() -> throwCheckedException("Expected message")) @@ -260,6 +272,25 @@ static class ConditionalConfig { } + @Configuration(proxyBeanMethods = false) + @EnableConfigurationProperties(ExampleProperties.class) + static class LazyConfig { + + @Bean + ExampleBeanWithLazyProperties exampleBeanWithLazyProperties() { + return new ExampleBeanWithLazyProperties(); + } + + } + + static class ExampleBeanWithLazyProperties { + + @Autowired + @Lazy + ExampleProperties exampleProperties; + + } + static class FilteredClassLoaderCondition implements Condition { @Override