Disable @ProjectGenerationContext bean overriding by default

This commit extends ProjectGenerator so that the configuration of the
ProjectGenerationContext can be externalized if necessary. By default,
a context that does not allow bean overriding is provided.

Closes gh-999
This commit is contained in:
Stephane Nicoll
2019-09-27 12:31:11 +02:00
parent 7da1db9329
commit c89b399956
2 changed files with 79 additions and 10 deletions

View File

@@ -24,6 +24,7 @@ import java.util.function.Supplier;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.core.type.AnnotationMetadata;
@@ -35,20 +36,62 @@ import org.springframework.core.type.AnnotationMetadata;
*/
public class ProjectGenerator {
private final Consumer<ProjectGenerationContext> projectGenerationContext;
private final Consumer<ProjectGenerationContext> contextConsumer;
private final Supplier<? extends ProjectGenerationContext> contextFactory;
/**
* Create an instance with a customizer for the project generator application context.
* @param projectGenerationContext a consumer of the project generation context before
* it is refreshed.
* Create an instance with a customizer for the project generator application context
* and a factory for the {@link ProjectGenerationContext}.
* @param contextConsumer a consumer of the project generation context after
* contributors and the {@link ProjectDescription} have been registered but before it
* is refreshed
* @param contextFactory the factory to use to create {@link ProjectGenerationContext}
* instances
*/
public ProjectGenerator(Consumer<ProjectGenerationContext> projectGenerationContext) {
this.projectGenerationContext = projectGenerationContext;
public ProjectGenerator(Consumer<ProjectGenerationContext> contextConsumer,
Supplier<? extends ProjectGenerationContext> contextFactory) {
this.contextConsumer = contextConsumer;
this.contextFactory = contextFactory;
}
/**
* Generate project assets using the specified {@link ProjectAssetGenerator}. Invokes
* known {@link ProjectDescriptionCustomizer} if applicable prior to asset generation.
* Create an instance with a customizer for the {@link ProjectGenerationContext} and a
* default factory for the {@link ProjectGenerationContext} that disables bean
* definition overriding.
* @param contextConsumer a consumer of the project generation context after
* contributors and the {@link ProjectDescription} have been registered but before it
* is refreshed
* @see GenericApplicationContext#setAllowBeanDefinitionOverriding(boolean)
*/
public ProjectGenerator(Consumer<ProjectGenerationContext> contextConsumer) {
this(contextConsumer, defaultContextFactory());
}
private static Supplier<ProjectGenerationContext> defaultContextFactory() {
return () -> {
ProjectGenerationContext context = new ProjectGenerationContext();
context.setAllowBeanDefinitionOverriding(false);
return context;
};
}
/**
* Generate project assets using the specified {@link ProjectAssetGenerator} for the
* specified {@link ProjectDescription}.
* <p>
* Create a dedicated {@link ProjectGenerationContext} with the following
* characteristics:
* <ul>
* <li>{@linkplain ProjectGenerationContext#setAllowBeanDefinitionOverriding(boolean)
* Bean overriding} disabled by default.</li>
* <li>Register a {@link ProjectDescription} bean based on the given
* {@code description} post-processed by available
* {@link ProjectDescriptionCustomizer} beans.</li>
* <li>Process all registered {@link ProjectGenerationConfiguration} classes.</li>
* </ul>
* Before the context is refreshed, it can be further customized using the customizer
* registered {@linkplain #ProjectGenerator(Consumer) on this instance}.
* @param description the description of the project to generate
* @param projectAssetGenerator the {@link ProjectAssetGenerator} to invoke
* @param <T> the type that gathers the project assets
@@ -57,10 +100,10 @@ public class ProjectGenerator {
*/
public <T> T generate(ProjectDescription description, ProjectAssetGenerator<T> projectAssetGenerator)
throws ProjectGenerationException {
try (ProjectGenerationContext context = new ProjectGenerationContext()) {
try (ProjectGenerationContext context = this.contextFactory.get()) {
context.registerBean(ProjectDescription.class, resolve(description, context));
context.register(CoreConfiguration.class);
this.projectGenerationContext.accept(context);
this.contextConsumer.accept(context);
context.refresh();
try {
return projectAssetGenerator.generate(context);

View File

@@ -17,13 +17,17 @@
package io.spring.initializr.generator.project;
import java.io.IOException;
import java.util.Map;
import java.util.function.Consumer;
import org.junit.jupiter.api.Test;
import org.mockito.InOrder;
import org.springframework.beans.factory.support.BeanDefinitionOverrideException;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.Assertions.entry;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.inOrder;
@@ -112,6 +116,28 @@ public class ProjectGeneratorTests {
.isInstanceOf(ProjectGenerationException.class).hasCause(exception);
}
@Test
void generateDoesNotAllowBeanDefinitionOverridingByDefault() {
ProjectGenerator generator = new ProjectGenerator((context) -> {
context.registerBean("testBean", String.class, () -> "test");
context.registerBean("testBean", String.class, () -> "duplicate");
});
ProjectAssetGenerator<?> assetGenerator = mock(ProjectAssetGenerator.class);
assertThatThrownBy(() -> generator.generate(new MutableProjectDescription(), assetGenerator))
.isInstanceOf(BeanDefinitionOverrideException.class).hasMessageContaining("testBean");
}
@Test
void generateCanBeConfiguredToAllowBeanDefinitionOverriding() {
ProjectGenerator generator = new ProjectGenerator((context) -> {
context.registerBean("testBean", String.class, () -> "test");
context.registerBean("testBean", String.class, () -> "duplicate");
}, ProjectGenerationContext::new);
Map<String, String> candidates = generator.generate(new MutableProjectDescription(),
(context) -> context.getBeansOfType(String.class));
assertThat(candidates).containsOnly(entry("testBean", "duplicate"));
}
@SuppressWarnings("unchecked")
private Consumer<ProjectGenerationContext> mockContextInitializr() {
return mock(Consumer.class);