Add Flyway native-image support

The ResourceProviderCustomizer, which is used by FlywayAutoConfiguration
gets replaced with NativeImageResourceProviderCustomizer when running
in AOT mode. The NativeImageResourceProvider does the heavy lifting when
running in a native image: it uses PathMatchingResourcePatternResolver
to find the migration files.

Closes gh-31999
This commit is contained in:
Moritz Halbritter 2022-10-18 11:56:50 +02:00
parent 3acdf590b7
commit b986a9b12e
10 changed files with 528 additions and 2 deletions

View File

@ -234,6 +234,7 @@ dependencies {
testImplementation("org.mockito:mockito-junit-jupiter")
testImplementation("org.skyscreamer:jsonassert")
testImplementation("org.springframework:spring-test")
testImplementation("org.springframework:spring-core-test")
testImplementation("org.springframework.graphql:spring-graphql-test")
testImplementation("org.springframework.kafka:spring-kafka-test")
testImplementation("org.springframework.security:spring-security-test")
@ -258,4 +259,4 @@ tasks.named("checkSpringConfigurationMetadata").configure {
"spring.datasource.tomcat.*",
"spring.groovy.template.configuration.*"
]
}
}

View File

@ -34,6 +34,8 @@ import org.flywaydb.core.api.configuration.FluentConfiguration;
import org.flywaydb.core.api.migration.JavaMigration;
import org.flywaydb.database.sqlserver.SQLServerConfigurationExtension;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
@ -42,6 +44,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration.FlywayAutoConfigurationRuntimeHints;
import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration.FlywayDataSourceCondition;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration;
@ -56,6 +59,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportRuntimeHints;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.GenericConverter;
import org.springframework.core.io.ResourceLoader;
@ -81,6 +85,7 @@ import org.springframework.util.StringUtils;
* @author András Deák
* @author Semyon Danilov
* @author Chris Bono
* @author Moritz Halbritter
* @since 1.1.0
*/
@AutoConfiguration(after = { DataSourceAutoConfiguration.class, JdbcTemplateAutoConfiguration.class,
@ -89,6 +94,7 @@ import org.springframework.util.StringUtils;
@Conditional(FlywayDataSourceCondition.class)
@ConditionalOnProperty(prefix = "spring.flyway", name = "enabled", matchIfMissing = true)
@Import(DatabaseInitializationDependencyConfigurer.class)
@ImportRuntimeHints(FlywayAutoConfigurationRuntimeHints.class)
public class FlywayAutoConfiguration {
@Bean
@ -108,17 +114,24 @@ public class FlywayAutoConfiguration {
@EnableConfigurationProperties(FlywayProperties.class)
public static class FlywayConfiguration {
@Bean
ResourceProviderCustomizer resourceProviderCustomizer() {
return new ResourceProviderCustomizer();
}
@Bean
public Flyway flyway(FlywayProperties properties, ResourceLoader resourceLoader,
ObjectProvider<DataSource> dataSource, @FlywayDataSource ObjectProvider<DataSource> flywayDataSource,
ObjectProvider<FlywayConfigurationCustomizer> fluentConfigurationCustomizers,
ObjectProvider<JavaMigration> javaMigrations, ObjectProvider<Callback> callbacks) {
ObjectProvider<JavaMigration> javaMigrations, ObjectProvider<Callback> callbacks,
ResourceProviderCustomizer resourceProviderCustomizer) {
FluentConfiguration configuration = new FluentConfiguration(resourceLoader.getClassLoader());
configureDataSource(configuration, properties, flywayDataSource.getIfAvailable(), dataSource.getIfUnique());
configureProperties(configuration, properties);
configureCallbacks(configuration, callbacks.orderedStream().toList());
configureJavaMigrations(configuration, javaMigrations.orderedStream().toList());
fluentConfigurationCustomizers.orderedStream().forEach((customizer) -> customizer.customize(configuration));
resourceProviderCustomizer.customize(configuration);
return configuration.load();
}
@ -349,4 +362,13 @@ public class FlywayAutoConfiguration {
}
static class FlywayAutoConfigurationRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
hints.resources().registerPattern("db/migration/*");
}
}
}

View File

@ -0,0 +1,146 @@
/*
* Copyright 2012-2022 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.autoconfigure.flyway;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.flywaydb.core.api.FlywayException;
import org.flywaydb.core.api.Location;
import org.flywaydb.core.api.ResourceProvider;
import org.flywaydb.core.api.resource.LoadableResource;
import org.flywaydb.core.internal.resource.classpath.ClassPathResource;
import org.flywaydb.core.internal.scanner.Scanner;
import org.flywaydb.core.internal.util.StringUtils;
import org.springframework.core.NativeDetector;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
/**
* A Flyway {@link ResourceProvider} which supports GraalVM native-image.
* <p>
* It delegates work to Flyways {@link Scanner}, and additionally uses
* {@link PathMatchingResourcePatternResolver} to find migration files in a native image.
*
* @author Moritz Halbritter
*/
class NativeImageResourceProvider implements ResourceProvider {
private final Scanner<?> scanner;
private final ClassLoader classLoader;
private final Collection<Location> locations;
private final Charset encoding;
private final boolean failOnMissingLocations;
private final List<ResourceWithLocation> resources = new ArrayList<>();
private final Lock lock = new ReentrantLock();
private boolean initialized;
NativeImageResourceProvider(Scanner<?> scanner, ClassLoader classLoader, Collection<Location> locations,
Charset encoding, boolean failOnMissingLocations) {
this.scanner = scanner;
this.classLoader = classLoader;
this.locations = locations;
this.encoding = encoding;
this.failOnMissingLocations = failOnMissingLocations;
}
@Override
public LoadableResource getResource(String name) {
if (!NativeDetector.inNativeImage()) {
return this.scanner.getResource(name);
}
LoadableResource resource = this.scanner.getResource(name);
if (resource != null) {
return resource;
}
if (this.classLoader.getResource(name) == null) {
return null;
}
return new ClassPathResource(null, name, this.classLoader, this.encoding);
}
@Override
public Collection<LoadableResource> getResources(String prefix, String[] suffixes) {
if (!NativeDetector.inNativeImage()) {
return this.scanner.getResources(prefix, suffixes);
}
ensureInitialized();
List<LoadableResource> result = new ArrayList<>(this.scanner.getResources(prefix, suffixes));
this.resources.stream().filter((r) -> StringUtils.startsAndEndsWith(r.resource.getFilename(), prefix, suffixes))
.map((r) -> (LoadableResource) new ClassPathResource(r.location(),
r.location().getPath() + "/" + r.resource().getFilename(), this.classLoader, this.encoding))
.forEach(result::add);
return result;
}
private void ensureInitialized() {
this.lock.lock();
try {
if (!this.initialized) {
initialize();
this.initialized = true;
}
}
finally {
this.lock.unlock();
}
}
private void initialize() {
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
for (Location location : this.locations) {
if (!location.isClassPath()) {
continue;
}
Resource root = resolver.getResource(location.getDescriptor());
if (!root.exists()) {
if (this.failOnMissingLocations) {
throw new FlywayException("Location " + location.getDescriptor() + " doesn't exist");
}
continue;
}
Resource[] resources;
try {
resources = resolver.getResources(root.getURI() + "/*");
}
catch (IOException ex) {
throw new UncheckedIOException("Failed to list resources for " + location.getDescriptor(), ex);
}
for (Resource resource : resources) {
this.resources.add(new ResourceWithLocation(resource, location));
}
}
}
private record ResourceWithLocation(Resource resource, Location location) {
}
}

View File

@ -0,0 +1,49 @@
/*
* Copyright 2012-2022 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.autoconfigure.flyway;
import java.util.Arrays;
import org.flywaydb.core.api.configuration.FluentConfiguration;
import org.flywaydb.core.api.migration.JavaMigration;
import org.flywaydb.core.internal.scanner.LocationScannerCache;
import org.flywaydb.core.internal.scanner.ResourceNameCache;
import org.flywaydb.core.internal.scanner.Scanner;
/**
* Registers {@link NativeImageResourceProvider} as a Flyway
* {@link org.flywaydb.core.api.ResourceProvider}.
*
* @author Moritz Halbritter
*/
class NativeImageResourceProviderCustomizer extends ResourceProviderCustomizer {
@Override
public void customize(FluentConfiguration configuration) {
if (configuration.getResourceProvider() == null) {
Scanner<JavaMigration> scanner = new Scanner<>(JavaMigration.class,
Arrays.asList(configuration.getLocations()), configuration.getClassLoader(),
configuration.getEncoding(), configuration.isDetectEncoding(), false, new ResourceNameCache(),
new LocationScannerCache(), configuration.isFailOnMissingLocations());
NativeImageResourceProvider resourceProvider = new NativeImageResourceProvider(scanner,
configuration.getClassLoader(), Arrays.asList(configuration.getLocations()),
configuration.getEncoding(), configuration.isFailOnMissingLocations());
configuration.resourceProvider(resourceProvider);
}
}
}

View File

@ -0,0 +1,32 @@
/*
* Copyright 2012-2022 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.autoconfigure.flyway;
import org.flywaydb.core.api.configuration.FluentConfiguration;
/**
* A Flyway customizer which gets replaced with
* {@link NativeImageResourceProviderCustomizer} when running in a native image.
*
* @author Moritz Halbritter
*/
class ResourceProviderCustomizer {
void customize(FluentConfiguration configuration) {
}
}

View File

@ -0,0 +1,76 @@
/*
* Copyright 2012-2022 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.autoconfigure.flyway;
import java.lang.reflect.Executable;
import javax.lang.model.element.Modifier;
import org.springframework.aot.generate.GeneratedMethod;
import org.springframework.aot.generate.GenerationContext;
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor;
import org.springframework.beans.factory.aot.BeanRegistrationCode;
import org.springframework.beans.factory.aot.BeanRegistrationCodeFragments;
import org.springframework.beans.factory.aot.BeanRegistrationCodeFragmentsDecorator;
import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.javapoet.CodeBlock;
/**
* Replaces the {@link ResourceProviderCustomizer} bean with a
* {@link NativeImageResourceProviderCustomizer} bean.
*
* @author Moritz Halbritter
*/
class ResourceProviderCustomizerBeanRegistrationAotProcessor implements BeanRegistrationAotProcessor {
@Override
public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {
if (registeredBean.getBeanClass().equals(ResourceProviderCustomizer.class)) {
return BeanRegistrationAotContribution
.withCustomCodeFragments((codeFragments) -> new AotContribution(codeFragments, registeredBean));
}
return null;
}
private static class AotContribution extends BeanRegistrationCodeFragmentsDecorator {
private final RegisteredBean registeredBean;
protected AotContribution(BeanRegistrationCodeFragments delegate, RegisteredBean registeredBean) {
super(delegate);
this.registeredBean = registeredBean;
}
@Override
public CodeBlock generateInstanceSupplierCode(GenerationContext generationContext,
BeanRegistrationCode beanRegistrationCode, Executable constructorOrFactoryMethod,
boolean allowDirectSupplierShortcut) {
GeneratedMethod generatedMethod = beanRegistrationCode.getMethods().add("getInstance", (method) -> {
method.addJavadoc("Get the bean instance for '$L'.", this.registeredBean.getBeanName());
method.addModifiers(Modifier.PRIVATE, Modifier.STATIC);
method.returns(NativeImageResourceProviderCustomizer.class);
CodeBlock.Builder code = CodeBlock.builder();
code.addStatement("return new $T()", NativeImageResourceProviderCustomizer.class);
method.addCode(code.build());
});
return generatedMethod.toMethodReference().toCodeBlock();
}
}
}

View File

@ -3,3 +3,6 @@ org.springframework.boot.autoconfigure.template.TemplateRuntimeHints
org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor=\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingProcessor
org.springframework.beans.factory.aot.BeanRegistrationAotProcessor=\
org.springframework.boot.autoconfigure.flyway.ResourceProviderCustomizerBeanRegistrationAotProcessor

View File

@ -25,6 +25,7 @@ import java.util.UUID;
import javax.sql.DataSource;
import org.assertj.core.api.Assertions;
import org.flywaydb.core.Flyway;
import org.flywaydb.core.api.Location;
import org.flywaydb.core.api.MigrationVersion;
@ -41,9 +42,12 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InOrder;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration.FlywayAutoConfigurationRuntimeHints;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@ -94,6 +98,7 @@ import static org.mockito.Mockito.mock;
* @author András Deák
* @author Takaaki Shimbo
* @author Chris Bono
* @author Moritz Halbritter
*/
@ExtendWith(OutputCaptureExtension.class)
class FlywayAutoConfigurationTests {
@ -656,6 +661,21 @@ class FlywayAutoConfigurationTests {
.isEqualTo("SPS"));
}
@Test
void containsResourceProviderCustomizer() {
this.contextRunner.withPropertyValues("spring.flyway.url:jdbc:hsqldb:mem:" + UUID.randomUUID())
.run((context) -> assertThat(context).hasSingleBean(ResourceProviderCustomizer.class));
}
@Test
void shouldRegisterResourceHints() {
RuntimeHints runtimeHints = new RuntimeHints();
new FlywayAutoConfigurationRuntimeHints().registerHints(runtimeHints, getClass().getClassLoader());
Assertions.assertThat(RuntimeHintsPredicates.resource().forResource("db/migration/")).accepts(runtimeHints);
Assertions.assertThat(RuntimeHintsPredicates.resource().forResource("db/migration/V1__init.sql"))
.accepts(runtimeHints);
}
private ContextConsumer<AssertableApplicationContext> validateFlywayTeamsPropertyOnly(String propertyName) {
return (context) -> {
assertThat(context).hasFailed();

View File

@ -0,0 +1,64 @@
/*
* Copyright 2012-2022 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.autoconfigure.flyway;
import java.util.Collection;
import org.flywaydb.core.api.ResourceProvider;
import org.flywaydb.core.api.configuration.FluentConfiguration;
import org.flywaydb.core.api.resource.LoadableResource;
import org.flywaydb.core.internal.resource.NoopResourceProvider;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link NativeImageResourceProviderCustomizer}.
*
* @author Moritz Halbritter
*/
class NativeImageResourceProviderCustomizerTests {
private final NativeImageResourceProviderCustomizer customizer = new NativeImageResourceProviderCustomizer();
@Test
void shouldInstallNativeImageResourceProvider() {
FluentConfiguration configuration = new FluentConfiguration();
assertThat(configuration.getResourceProvider()).isNull();
this.customizer.customize(configuration);
assertThat(configuration.getResourceProvider()).isInstanceOf(NativeImageResourceProvider.class);
}
@Test
void nativeImageResourceProviderShouldFindMigrations() {
FluentConfiguration configuration = new FluentConfiguration();
this.customizer.customize(configuration);
ResourceProvider resourceProvider = configuration.getResourceProvider();
Collection<LoadableResource> migrations = resourceProvider.getResources("V", new String[] { ".sql" });
LoadableResource migration = resourceProvider.getResource("V1__init.sql");
assertThat(migrations).containsExactly(migration);
}
@Test
void shouldBackOffOnCustomResourceProvider() {
FluentConfiguration configuration = new FluentConfiguration();
configuration.resourceProvider(NoopResourceProvider.INSTANCE);
this.customizer.customize(configuration);
assertThat(configuration.getResourceProvider()).isEqualTo(NoopResourceProvider.INSTANCE);
}
}

View File

@ -0,0 +1,113 @@
/*
* Copyright 2012-2022 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.autoconfigure.flyway;
import java.util.Arrays;
import java.util.function.Consumer;
import org.junit.jupiter.api.Test;
import org.springframework.aot.test.generate.TestGenerationContext;
import org.springframework.beans.factory.aot.AotServices;
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.aot.ApplicationContextAotGenerator;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.test.tools.CompileWithForkedClassLoader;
import org.springframework.core.test.tools.TestCompiler;
import org.springframework.javapoet.ClassName;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ResourceProviderCustomizerBeanRegistrationAotProcessor}.
*
* @author Moritz Halbritter
*/
class ResourceProviderCustomizerBeanRegistrationAotProcessorTests {
private final DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
private final ResourceProviderCustomizerBeanRegistrationAotProcessor processor = new ResourceProviderCustomizerBeanRegistrationAotProcessor();
@Test
void beanRegistrationAotProcessorIsRegistered() {
assertThat(AotServices.factories().load(BeanRegistrationAotProcessor.class))
.anyMatch(ResourceProviderCustomizerBeanRegistrationAotProcessor.class::isInstance);
}
@Test
void shouldIgnoreNonResourceProviderCustomizerBeans() {
RootBeanDefinition beanDefinition = new RootBeanDefinition(String.class);
this.beanFactory.registerBeanDefinition("test", beanDefinition);
BeanRegistrationAotContribution contribution = this.processor
.processAheadOfTime(RegisteredBean.of(this.beanFactory, "test"));
assertThat(contribution).isNull();
}
@Test
@CompileWithForkedClassLoader
void shouldReplaceResourceProviderCustomizer() {
compile(createContext(ResourceProviderCustomizerConfiguration.class), (freshContext) -> {
freshContext.refresh();
ResourceProviderCustomizer bean = freshContext.getBean(ResourceProviderCustomizer.class);
assertThat(bean).isInstanceOf(NativeImageResourceProviderCustomizer.class);
});
}
private GenericApplicationContext createContext(Class<?>... types) {
GenericApplicationContext context = new AnnotationConfigApplicationContext();
Arrays.stream(types).forEach((type) -> context.registerBean(type));
return context;
}
@SuppressWarnings("unchecked")
private void compile(GenericApplicationContext context, Consumer<GenericApplicationContext> freshContext) {
TestGenerationContext generationContext = new TestGenerationContext(TestTarget.class);
ClassName className = new ApplicationContextAotGenerator().processAheadOfTime(context, generationContext);
generationContext.writeGeneratedContent();
TestCompiler.forSystem().with(generationContext).compile((compiled) -> {
GenericApplicationContext freshApplicationContext = new GenericApplicationContext();
ApplicationContextInitializer<GenericApplicationContext> initializer = compiled
.getInstance(ApplicationContextInitializer.class, className.toString());
initializer.initialize(freshApplicationContext);
freshContext.accept(freshApplicationContext);
});
}
static class TestTarget {
}
@Configuration(proxyBeanMethods = false)
static class ResourceProviderCustomizerConfiguration {
@Bean
ResourceProviderCustomizer resourceProviderCustomizer() {
return new ResourceProviderCustomizer();
}
}
}