From 5088927f80c076f04b6aa784f8dd09bae815f1b2 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Wed, 23 Jun 2021 23:12:35 -0700 Subject: [PATCH] Pass ClassLoader to Instantiator Update `Instantiator` so that it can accept a `ClassLoader` when creating instances and rework `EnvironmentPostProcessorsFactory` to use the new methods. Prior to this commit we would use the `ClassLoader` to get the class names from `SpringFactories` but not when actually creating the instances. Fixes gh-27043 --- .../env/EnvironmentPostProcessorsFactory.java | 18 +++- ...ctionEnvironmentPostProcessorsFactory.java | 21 +++-- .../boot/util/Instantiator.java | 89 +++++++++++++++++-- ...EnvironmentPostProcessorsFactoryTests.java | 22 ++++- ...EnvironmentPostProcessorsFactoryTests.java | 45 ++++++++-- .../boot/util/InstantiatorTests.java | 28 +++++- .../boot/util/WithDefaultConstructor.java | 26 ++++++ 7 files changed, 219 insertions(+), 30 deletions(-) create mode 100644 spring-boot-project/spring-boot/src/test/java/org/springframework/boot/util/WithDefaultConstructor.java diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/EnvironmentPostProcessorsFactory.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/EnvironmentPostProcessorsFactory.java index dbd3730c9fc..0f49d98a189 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/EnvironmentPostProcessorsFactory.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/EnvironmentPostProcessorsFactory.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. @@ -48,7 +48,7 @@ public interface EnvironmentPostProcessorsFactory { * @return an {@link EnvironmentPostProcessorsFactory} instance */ static EnvironmentPostProcessorsFactory fromSpringFactories(ClassLoader classLoader) { - return new ReflectionEnvironmentPostProcessorsFactory( + return new ReflectionEnvironmentPostProcessorsFactory(classLoader, SpringFactoriesLoader.loadFactoryNames(EnvironmentPostProcessor.class, classLoader)); } @@ -69,7 +69,19 @@ public interface EnvironmentPostProcessorsFactory { * @return an {@link EnvironmentPostProcessorsFactory} instance */ static EnvironmentPostProcessorsFactory of(String... classNames) { - return new ReflectionEnvironmentPostProcessorsFactory(classNames); + return of(null, classNames); + } + + /** + * Return a {@link EnvironmentPostProcessorsFactory} that reflectively creates post + * processors from the given class names. + * @param classLoader the source class loader + * @param classNames the post processor class names + * @return an {@link EnvironmentPostProcessorsFactory} instance + * @since 2.4.8 + */ + static EnvironmentPostProcessorsFactory of(ClassLoader classLoader, String... classNames) { + return new ReflectionEnvironmentPostProcessorsFactory(classLoader, classNames); } } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/ReflectionEnvironmentPostProcessorsFactory.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/ReflectionEnvironmentPostProcessorsFactory.java index 7b984ff3aef..bf920bdbe5a 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/ReflectionEnvironmentPostProcessorsFactory.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/ReflectionEnvironmentPostProcessorsFactory.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. @@ -16,6 +16,7 @@ package org.springframework.boot.env; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -35,17 +36,24 @@ import org.springframework.boot.util.Instantiator; */ class ReflectionEnvironmentPostProcessorsFactory implements EnvironmentPostProcessorsFactory { + private final List> classes; + + private ClassLoader classLoader; + private final List classNames; ReflectionEnvironmentPostProcessorsFactory(Class... classes) { - this(Arrays.stream(classes).map(Class::getName).toArray(String[]::new)); + this.classes = new ArrayList<>(Arrays.asList(classes)); + this.classNames = null; } - ReflectionEnvironmentPostProcessorsFactory(String... classNames) { - this(Arrays.asList(classNames)); + ReflectionEnvironmentPostProcessorsFactory(ClassLoader classLoader, String... classNames) { + this(classLoader, Arrays.asList(classNames)); } - ReflectionEnvironmentPostProcessorsFactory(List classNames) { + ReflectionEnvironmentPostProcessorsFactory(ClassLoader classLoader, List classNames) { + this.classes = null; + this.classLoader = classLoader; this.classNames = classNames; } @@ -60,7 +68,8 @@ class ReflectionEnvironmentPostProcessorsFactory implements EnvironmentPostProce parameters.add(BootstrapContext.class, bootstrapContext); parameters.add(BootstrapRegistry.class, bootstrapContext); }); - return instantiator.instantiate(this.classNames); + return (this.classes != null) ? instantiator.instantiateTypes(this.classes) + : instantiator.instantiate(this.classLoader, this.classNames); } } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/util/Instantiator.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/util/Instantiator.java index e22865192db..377bdce1acc 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/util/Instantiator.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/util/Instantiator.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. @@ -17,7 +17,6 @@ package org.springframework.boot.util; import java.lang.reflect.Constructor; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -27,6 +26,9 @@ import java.util.List; import java.util.Map; import java.util.function.Consumer; import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.util.Assert; @@ -85,22 +87,48 @@ public class Instantiator { * @return a list of instantiated instances */ public List instantiate(Collection names) { - List instances = new ArrayList<>(names.size()); - for (String name : names) { - instances.add(instantiate(name)); - } + return instantiate((ClassLoader) null, names); + } + + /** + * Instantiate the given set of class name, injecting constructor arguments as + * necessary. + * @param classLoader the source classloader + * @param names the class names to instantiate + * @return a list of instantiated instances + * @since 2.4.8 + */ + public List instantiate(ClassLoader classLoader, Collection names) { + Assert.notNull(names, "Names must not be null"); + return instantiate(names.stream().map((name) -> TypeSupplier.forName(classLoader, name))); + } + + /** + * Instantiate the given set of classes, injecting constructor arguments as necessary. + * @param types the types to instantiate + * @return a list of instantiated instances + * @since 2.4.8 + */ + public List instantiateTypes(Collection> types) { + Assert.notNull(types, "Types must not be null"); + return instantiate(types.stream().map((type) -> TypeSupplier.forType(type))); + } + + public List instantiate(Stream typeSuppliers) { + List instances = typeSuppliers.map(this::instantiate).collect(Collectors.toList()); AnnotationAwareOrderComparator.sort(instances); return Collections.unmodifiableList(instances); } - private T instantiate(String name) { + private T instantiate(TypeSupplier typeSupplier) { try { - Class type = ClassUtils.forName(name, null); + Class type = typeSupplier.get(); Assert.isAssignable(this.type, type); return instantiate(type); } catch (Throwable ex) { - throw new IllegalArgumentException("Unable to instantiate " + this.type.getName() + " [" + name + "]", ex); + throw new IllegalArgumentException( + "Unable to instantiate " + this.type.getName() + " [" + typeSupplier.getName() + "]", ex); } } @@ -160,4 +188,47 @@ public class Instantiator { } + /** + * {@link Supplier} that provides a class type. + */ + private interface TypeSupplier { + + String getName(); + + Class get() throws ClassNotFoundException; + + static TypeSupplier forName(ClassLoader classLoader, String name) { + return new TypeSupplier() { + + @Override + public String getName() { + return name; + } + + @Override + public Class get() throws ClassNotFoundException { + return ClassUtils.forName(name, classLoader); + } + + }; + } + + static TypeSupplier forType(Class type) { + return new TypeSupplier() { + + @Override + public String getName() { + return type.getName(); + } + + @Override + public Class get() throws ClassNotFoundException { + return type; + } + + }; + } + + } + } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/EnvironmentPostProcessorsFactoryTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/EnvironmentPostProcessorsFactoryTests.java index fd5e6cd5b85..7682491b8fa 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/EnvironmentPostProcessorsFactoryTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/EnvironmentPostProcessorsFactoryTests.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. @@ -24,6 +24,7 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.DefaultBootstrapContext; import org.springframework.boot.SpringApplication; import org.springframework.boot.logging.DeferredLogFactory; +import org.springframework.core.OverridingClassLoader; import org.springframework.core.env.ConfigurableEnvironment; import static org.assertj.core.api.Assertions.assertThat; @@ -67,6 +68,25 @@ class EnvironmentPostProcessorsFactoryTests { assertThat(processors.get(0)).isInstanceOf(TestEnvironmentPostProcessor.class); } + @Test + void ofClassNamesWithClassLoaderReturnsFactory() { + OverridingClassLoader classLoader = new OverridingClassLoader(getClass().getClassLoader()) { + + @Override + protected boolean isEligibleForOverriding(String className) { + return super.isEligibleForOverriding(className) + && className.equals(TestEnvironmentPostProcessor.class.getName()); + } + + }; + EnvironmentPostProcessorsFactory factory = EnvironmentPostProcessorsFactory.of(classLoader, + TestEnvironmentPostProcessor.class.getName()); + List processors = factory.getEnvironmentPostProcessors(this.logFactory, + this.bootstrapContext); + assertThat(processors).hasSize(1); + assertThat(processors.get(0).getClass().getClassLoader()).isSameAs(classLoader); + } + static class TestEnvironmentPostProcessor implements EnvironmentPostProcessor { TestEnvironmentPostProcessor(DeferredLogFactory logFactory) { diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/ReflectionEnvironmentPostProcessorsFactoryTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/ReflectionEnvironmentPostProcessorsFactoryTests.java index 568ddf59a1b..982224626fd 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/ReflectionEnvironmentPostProcessorsFactoryTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/ReflectionEnvironmentPostProcessorsFactoryTests.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. @@ -28,6 +28,7 @@ import org.springframework.boot.BootstrapRegistry; import org.springframework.boot.DefaultBootstrapContext; import org.springframework.boot.SpringApplication; import org.springframework.boot.logging.DeferredLogFactory; +import org.springframework.core.OverridingClassLoader; import org.springframework.core.env.ConfigurableEnvironment; import static org.assertj.core.api.Assertions.assertThat; @@ -53,49 +54,65 @@ class ReflectionEnvironmentPostProcessorsFactoryTests { @Test void createWithClassNamesArrayCreatesFactory() { - ReflectionEnvironmentPostProcessorsFactory factory = new ReflectionEnvironmentPostProcessorsFactory( + ReflectionEnvironmentPostProcessorsFactory factory = new ReflectionEnvironmentPostProcessorsFactory(null, TestEnvironmentPostProcessor.class.getName()); assertThatFactory(factory).createsSinglePostProcessor(TestEnvironmentPostProcessor.class); } @Test void createWithClassNamesListCreatesFactory() { - ReflectionEnvironmentPostProcessorsFactory factory = new ReflectionEnvironmentPostProcessorsFactory( + ReflectionEnvironmentPostProcessorsFactory factory = new ReflectionEnvironmentPostProcessorsFactory(null, Arrays.asList(TestEnvironmentPostProcessor.class.getName())); assertThatFactory(factory).createsSinglePostProcessor(TestEnvironmentPostProcessor.class); } + @Test + void createWithClassNamesAndClassLoaderListCreatesFactory() { + OverridingClassLoader classLoader = new OverridingClassLoader(getClass().getClassLoader()) { + + @Override + protected boolean isEligibleForOverriding(String className) { + return super.isEligibleForOverriding(className) + && className.equals(TestEnvironmentPostProcessor.class.getName()); + } + + }; + ReflectionEnvironmentPostProcessorsFactory factory = new ReflectionEnvironmentPostProcessorsFactory(classLoader, + Arrays.asList(TestEnvironmentPostProcessor.class.getName())); + assertThatFactory(factory).createsSinglePostProcessorWithClassLoader(classLoader); + } + @Test void getEnvironmentPostProcessorsWhenHasDefaultConstructorCreatesPostProcessors() { - ReflectionEnvironmentPostProcessorsFactory factory = new ReflectionEnvironmentPostProcessorsFactory( + ReflectionEnvironmentPostProcessorsFactory factory = new ReflectionEnvironmentPostProcessorsFactory(null, TestEnvironmentPostProcessor.class.getName()); assertThatFactory(factory).createsSinglePostProcessor(TestEnvironmentPostProcessor.class); } @Test void getEnvironmentPostProcessorsWhenHasLogFactoryConstructorCreatesPostProcessors() { - ReflectionEnvironmentPostProcessorsFactory factory = new ReflectionEnvironmentPostProcessorsFactory( + ReflectionEnvironmentPostProcessorsFactory factory = new ReflectionEnvironmentPostProcessorsFactory(null, TestLogFactoryEnvironmentPostProcessor.class.getName()); assertThatFactory(factory).createsSinglePostProcessor(TestLogFactoryEnvironmentPostProcessor.class); } @Test void getEnvironmentPostProcessorsWhenHasLogConstructorCreatesPostProcessors() { - ReflectionEnvironmentPostProcessorsFactory factory = new ReflectionEnvironmentPostProcessorsFactory( + ReflectionEnvironmentPostProcessorsFactory factory = new ReflectionEnvironmentPostProcessorsFactory(null, TestLogEnvironmentPostProcessor.class.getName()); assertThatFactory(factory).createsSinglePostProcessor(TestLogEnvironmentPostProcessor.class); } @Test void getEnvironmentPostProcessorsWhenHasBootstrapRegistryConstructorCreatesPostProcessors() { - ReflectionEnvironmentPostProcessorsFactory factory = new ReflectionEnvironmentPostProcessorsFactory( + ReflectionEnvironmentPostProcessorsFactory factory = new ReflectionEnvironmentPostProcessorsFactory(null, TestBootstrapRegistryEnvironmentPostProcessor.class.getName()); assertThatFactory(factory).createsSinglePostProcessor(TestBootstrapRegistryEnvironmentPostProcessor.class); } @Test void getEnvironmentPostProcessorsWhenHasNoSuitableConstructorThrowsException() { - ReflectionEnvironmentPostProcessorsFactory factory = new ReflectionEnvironmentPostProcessorsFactory( + ReflectionEnvironmentPostProcessorsFactory factory = new ReflectionEnvironmentPostProcessorsFactory(null, BadEnvironmentPostProcessor.class.getName()); assertThatIllegalArgumentException() .isThrownBy(() -> factory.getEnvironmentPostProcessors(this.logFactory, this.bootstrapContext)) @@ -115,11 +132,21 @@ class ReflectionEnvironmentPostProcessorsFactoryTests { } void createsSinglePostProcessor(Class expectedType) { + EnvironmentPostProcessor processor = getSingleProcessor(); + assertThat(processor).isInstanceOf(expectedType); + } + + void createsSinglePostProcessorWithClassLoader(OverridingClassLoader classLoader) { + EnvironmentPostProcessor processor = getSingleProcessor(); + assertThat(processor.getClass().getClassLoader()).isSameAs(classLoader); + } + + private EnvironmentPostProcessor getSingleProcessor() { List processors = this.factory.getEnvironmentPostProcessors( ReflectionEnvironmentPostProcessorsFactoryTests.this.logFactory, ReflectionEnvironmentPostProcessorsFactoryTests.this.bootstrapContext); assertThat(processors).hasSize(1); - assertThat(processors.get(0)).isInstanceOf(expectedType); + return processors.get(0); } } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/util/InstantiatorTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/util/InstantiatorTests.java index 36aecb33e66..a622f6de824 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/util/InstantiatorTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/util/InstantiatorTests.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. @@ -23,6 +23,7 @@ import java.util.List; import org.junit.jupiter.api.Test; import org.springframework.core.Ordered; +import org.springframework.core.OverridingClassLoader; import org.springframework.core.annotation.Order; import static org.assertj.core.api.Assertions.assertThat; @@ -76,6 +77,29 @@ class InstantiatorTests { assertThat(instance.getParamC()).isEqualTo(this.paramC); } + @Test + void instantiateTypesCreatesInstance() { + WithDefaultConstructor instance = createInstantiator(WithDefaultConstructor.class) + .instantiateTypes(Collections.singleton(WithDefaultConstructor.class)).get(0); + assertThat(instance).isInstanceOf(WithDefaultConstructor.class); + } + + @Test + void instantiateWithClassLoaderCreatesInstance() { + OverridingClassLoader classLoader = new OverridingClassLoader(getClass().getClassLoader()) { + + @Override + protected boolean isEligibleForOverriding(String className) { + return super.isEligibleForOverriding(className) + && className.equals(WithDefaultConstructorSubclass.class.getName()); + } + + }; + WithDefaultConstructor instance = createInstantiator(WithDefaultConstructor.class) + .instantiate(classLoader, Collections.singleton(WithDefaultConstructorSubclass.class.getName())).get(0); + assertThat(instance.getClass().getClassLoader()).isSameAs(classLoader); + } + @Test void createWhenWrongTypeThrowsException() { assertThatIllegalArgumentException() @@ -96,7 +120,7 @@ class InstantiatorTests { }); } - static class WithDefaultConstructor { + static class WithDefaultConstructorSubclass extends WithDefaultConstructor { } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/util/WithDefaultConstructor.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/util/WithDefaultConstructor.java new file mode 100644 index 00000000000..e84e80a67d4 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/util/WithDefaultConstructor.java @@ -0,0 +1,26 @@ +/* + * 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.util; + +/** + * Simple public class with a default constructor. + * + * @author Phillip Webb + */ +public class WithDefaultConstructor { + +}