Enable cglib proxy for configuration classes if necessary

This commit updates code generation to customize the instantiation of
a configuration class that requires a proxy. Rather than instantiating
the raw class, the proxy is used.

Closes gh-29107
This commit is contained in:
Stephane Nicoll 2022-09-13 12:49:03 +02:00
parent f2e9d112b1
commit 2f20d6322b
11 changed files with 259 additions and 45 deletions

View File

@ -43,6 +43,7 @@ import org.springframework.beans.factory.support.BeanDefinitionValueResolver;
import org.springframework.beans.factory.support.InstanceSupplier;
import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.support.SimpleInstantiationStrategy;
import org.springframework.core.CollectionFactory;
import org.springframework.core.MethodParameter;
import org.springframework.lang.Nullable;
@ -207,9 +208,24 @@ public final class BeanInstanceSupplier<T> extends AutowiredElementResolver impl
Executable executable = this.lookup.get(registeredBean);
AutowiredArguments arguments = resolveArguments(registeredBean, executable);
if (this.generator != null) {
return this.generator.apply(registeredBean, arguments);
return invokeBeanSupplier(executable, () ->
this.generator.apply(registeredBean, arguments));
}
return invokeBeanSupplier(executable, () ->
instantiate(registeredBean.getBeanFactory(), executable, arguments.toArray()));
}
private T invokeBeanSupplier(Executable executable, ThrowingSupplier<T> beanSupplier) {
if (!(executable instanceof Method)) {
return beanSupplier.get();
}
try {
SimpleInstantiationStrategy.setCurrentlyInvokedFactoryMethod((Method) executable);
return beanSupplier.get();
}
finally {
SimpleInstantiationStrategy.setCurrentlyInvokedFactoryMethod(null);
}
return instantiate(registeredBean.getBeanFactory(), executable, arguments.toArray());
}
@Nullable

View File

@ -109,8 +109,7 @@ class InstanceSupplierCodeGenerator {
String beanName = registeredBean.getBeanName();
Class<?> beanClass = registeredBean.getBeanClass();
Class<?> declaringClass = ClassUtils
.getUserClass(constructor.getDeclaringClass());
Class<?> declaringClass = constructor.getDeclaringClass();
boolean dependsOnBean = ClassUtils.isInnerClass(declaringClass);
Visibility accessVisibility = getAccessVisibility(registeredBean, constructor);
if (accessVisibility != Visibility.PRIVATE) {

View File

@ -53,6 +53,14 @@ public class SimpleInstantiationStrategy implements InstantiationStrategy {
return currentlyInvokedFactoryMethod.get();
}
/**
* Set the factory method currently being invoked or {@code null} to reset.
* @param method the factory method currently being invoked or {@code null}
*/
public static void setCurrentlyInvokedFactoryMethod(@Nullable Method method) {
currentlyInvokedFactoryMethod.set(method);
}
@Override
public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {

View File

@ -73,7 +73,7 @@ import org.springframework.util.ReflectionUtils;
class ConfigurationClassEnhancer {
// The callbacks to use. Note that these callbacks must be stateless.
private static final Callback[] CALLBACKS = new Callback[] {
static final Callback[] CALLBACKS = new Callback[] {
new BeanMethodInterceptor(),
new BeanFactoryAwareMethodInterceptor(),
NoOp.INSTANCE

View File

@ -18,6 +18,8 @@ package org.springframework.context.annotation;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
@ -26,6 +28,7 @@ import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
import java.util.function.Supplier;
import javax.lang.model.element.Modifier;
@ -46,6 +49,11 @@ import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution;
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor;
import org.springframework.beans.factory.aot.BeanFactoryInitializationCode;
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.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
@ -61,6 +69,7 @@ import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.BeanNameGenerator;
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.ApplicationStartupAware;
import org.springframework.context.EnvironmentAware;
@ -110,8 +119,8 @@ import org.springframework.util.CollectionUtils;
* @since 3.0
*/
public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor,
BeanFactoryInitializationAotProcessor, PriorityOrdered, ResourceLoaderAware, ApplicationStartupAware,
BeanClassLoaderAware, EnvironmentAware {
BeanRegistrationAotProcessor, BeanFactoryInitializationAotProcessor, PriorityOrdered,
ResourceLoaderAware, ApplicationStartupAware, BeanClassLoaderAware, EnvironmentAware {
/**
* A {@code BeanNameGenerator} using fully qualified class names as default bean names.
@ -294,6 +303,19 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
}
@Nullable
@Override
public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {
Object configClassAttr = registeredBean.getMergedBeanDefinition()
.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE);
if (ConfigurationClassUtils.CONFIGURATION_CLASS_FULL.equals(configClassAttr)) {
Class<?> proxyClass = registeredBean.getBeanType().toClass();
return BeanRegistrationAotContribution.withCustomCodeFragments(codeFragments ->
new ConfigurationClassProxyBeanRegistrationCodeFragments(codeFragments, proxyClass));
}
return null;
}
@Override
public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) {
boolean hasPropertySourceDescriptors = !CollectionUtils.isEmpty(this.propertySourceDescriptors);
@ -692,4 +714,47 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
}
private static class ConfigurationClassProxyBeanRegistrationCodeFragments extends BeanRegistrationCodeFragmentsDecorator {
private final Class<?> proxyClass;
public ConfigurationClassProxyBeanRegistrationCodeFragments(BeanRegistrationCodeFragments codeFragments,
Class<?> proxyClass) {
super(codeFragments);
this.proxyClass = proxyClass;
}
@Override
public CodeBlock generateSetBeanDefinitionPropertiesCode(GenerationContext generationContext,
BeanRegistrationCode beanRegistrationCode, RootBeanDefinition beanDefinition, Predicate<String> attributeFilter) {
CodeBlock.Builder code = CodeBlock.builder();
code.add(super.generateSetBeanDefinitionPropertiesCode(generationContext,
beanRegistrationCode, beanDefinition, attributeFilter));
code.addStatement("$T.initializeConfigurationClass($T.class)",
ConfigurationClassUtils.class, ClassUtils.getUserClass(this.proxyClass));
return code.build();
}
@Override
public CodeBlock generateInstanceSupplierCode(GenerationContext generationContext,
BeanRegistrationCode beanRegistrationCode, Executable constructorOrFactoryMethod,
boolean allowDirectSupplierShortcut) {
return super.generateInstanceSupplierCode(generationContext, beanRegistrationCode,
proxyExecutable(constructorOrFactoryMethod), allowDirectSupplierShortcut);
}
private Executable proxyExecutable(Executable rawClassExecutable) {
if (rawClassExecutable instanceof Constructor<?>) {
try {
return this.proxyClass.getConstructor(rawClassExecutable.getParameterTypes());
}
catch (NoSuchMethodException ex) {
throw new IllegalStateException("No matching constructor found on proxy " + this.proxyClass, ex);
}
}
return rawClassExecutable;
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-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.
@ -30,6 +30,7 @@ import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.context.event.EventListenerFactory;
import org.springframework.core.Conventions;
import org.springframework.core.Ordered;
@ -42,23 +43,24 @@ import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
/**
* Utilities for identifying {@link Configuration} classes.
* Utilities for identifying and configuring {@link Configuration} classes.
*
* @author Chris Beams
* @author Juergen Hoeller
* @author Sam Brannen
* @since 3.1
* @author Stephane Nicoll
* @since 6.0
*/
abstract class ConfigurationClassUtils {
public abstract class ConfigurationClassUtils {
public static final String CONFIGURATION_CLASS_FULL = "full";
static final String CONFIGURATION_CLASS_FULL = "full";
public static final String CONFIGURATION_CLASS_LITE = "lite";
static final String CONFIGURATION_CLASS_LITE = "lite";
public static final String CONFIGURATION_CLASS_ATTRIBUTE =
static final String CONFIGURATION_CLASS_ATTRIBUTE =
Conventions.getQualifiedAttributeName(ConfigurationClassPostProcessor.class, "configurationClass");
private static final String ORDER_ATTRIBUTE =
static final String ORDER_ATTRIBUTE =
Conventions.getQualifiedAttributeName(ConfigurationClassPostProcessor.class, "order");
@ -73,6 +75,17 @@ abstract class ConfigurationClassUtils {
candidateIndicators.add(ImportResource.class.getName());
}
/**
* Initialize a configuration class proxy for the specified class.
* @param userClass the configuration class to initialize
*/
@SuppressWarnings("unused") // Used by AOT-optimized generated code
public static Class<?> initializeConfigurationClass(Class<?> userClass) {
Class<?> configurationClass = new ConfigurationClassEnhancer().enhance(userClass, null);
Enhancer.registerStaticCallbacks(configurationClass, ConfigurationClassEnhancer.CALLBACKS);
return configurationClass;
}
/**
* Check whether the given bean definition is a candidate for a configuration class
@ -82,7 +95,7 @@ abstract class ConfigurationClassUtils {
* @param metadataReaderFactory the current factory in use by the caller
* @return whether the candidate qualifies as (any kind of) configuration class
*/
public static boolean checkConfigurationClassCandidate(
static boolean checkConfigurationClassCandidate(
BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {
String className = beanDef.getBeanClassName();
@ -149,7 +162,7 @@ abstract class ConfigurationClassUtils {
* @return {@code true} if the given class is to be registered for
* configuration class processing; {@code false} otherwise
*/
public static boolean isConfigurationCandidate(AnnotationMetadata metadata) {
static boolean isConfigurationCandidate(AnnotationMetadata metadata) {
// Do not consider an interface or an annotation...
if (metadata.isInterface()) {
return false;

View File

@ -36,14 +36,17 @@ import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.testfixture.beans.factory.aot.MockBeanFactoryInitializationCode;
import org.springframework.beans.testfixture.beans.factory.generator.SimpleConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.context.testfixture.context.generator.SimpleComponent;
import org.springframework.context.testfixture.context.generator.annotation.CglibConfiguration;
import org.springframework.context.testfixture.context.generator.annotation.ImportAwareConfiguration;
import org.springframework.context.testfixture.context.generator.annotation.ImportConfiguration;
import org.springframework.context.testfixture.context.generator.annotation.SimpleConfiguration;
import org.springframework.core.Ordered;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.io.ResourceLoader;
@ -78,7 +81,7 @@ class ConfigurationClassPostProcessorAotContributionTests {
@Test
void processAheadOfTimeWhenNoImportAwareConfigurationReturnsNull() {
assertThat(getContribution(SimpleConfiguration.class)).isNull();
assertThat(getContribution(SimpleComponent.class)).isNull();
}
@Test
@ -323,6 +326,34 @@ class ConfigurationClassPostProcessorAotContributionTests {
}
@Nested
class ConfigurationClassProxyTests {
private final DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
private final ConfigurationClassPostProcessor processor = new ConfigurationClassPostProcessor();
@Test
void processAheadOfTimeRegularConfigurationClass() {
assertThat(this.processor.processAheadOfTime(
getRegisteredBean(SimpleConfiguration.class))).isNull();
}
@Test
void processAheadOfTimeFullConfigurationClass() {
assertThat(this.processor.processAheadOfTime(
getRegisteredBean(CglibConfiguration.class))).isNotNull();
}
private RegisteredBean getRegisteredBean(Class<?> bean) {
this.beanFactory.registerBeanDefinition("test", new RootBeanDefinition(bean));
this.processor.postProcessBeanFactory(this.beanFactory);
return RegisteredBean.of(this.beanFactory, "test");
}
}
@Nullable
private BeanFactoryInitializationAotContribution getContribution(Class<?>... types) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();

View File

@ -22,6 +22,7 @@ import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.aot.generate.GeneratedFiles.Kind;
@ -54,6 +55,7 @@ import org.springframework.context.support.GenericApplicationContext;
import org.springframework.context.testfixture.context.generator.SimpleComponent;
import org.springframework.context.testfixture.context.generator.annotation.AutowiredComponent;
import org.springframework.context.testfixture.context.generator.annotation.CglibConfiguration;
import org.springframework.context.testfixture.context.generator.annotation.ConfigurableCglibConfiguration;
import org.springframework.context.testfixture.context.generator.annotation.InitDestroyComponent;
import org.springframework.context.testfixture.context.generator.annotation.LazyAutowiredFieldComponent;
import org.springframework.context.testfixture.context.generator.annotation.LazyAutowiredMethodComponent;
@ -64,8 +66,10 @@ import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.test.tools.CompileWithForkedClassLoader;
import org.springframework.core.test.tools.Compiled;
import org.springframework.core.test.tools.TestCompiler;
import org.springframework.mock.env.MockEnvironment;
import static org.assertj.core.api.Assertions.assertThat;
@ -93,13 +97,13 @@ class ApplicationContextAotGeneratorTests {
GenericApplicationContext applicationContext = new GenericApplicationContext();
applicationContext.registerBeanDefinition(AnnotationConfigUtils.AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME,
BeanDefinitionBuilder
.rootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class)
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE).getBeanDefinition());
.rootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class)
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE).getBeanDefinition());
applicationContext.registerBeanDefinition("autowiredComponent", new RootBeanDefinition(AutowiredComponent.class));
applicationContext.registerBeanDefinition("number",
BeanDefinitionBuilder
.rootBeanDefinition(Integer.class, "valueOf")
.addConstructorArgValue("42").getBeanDefinition());
.rootBeanDefinition(Integer.class, "valueOf")
.addConstructorArgValue("42").getBeanDefinition());
testCompiledResult(applicationContext, (initializer, compiled) -> {
GenericApplicationContext freshApplicationContext = toFreshApplicationContext(initializer);
assertThat(freshApplicationContext.getBeanDefinitionNames()).containsOnly("autowiredComponent", "number");
@ -200,8 +204,8 @@ class ApplicationContextAotGeneratorTests {
applicationContext.registerBeanDefinition(
AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME,
BeanDefinitionBuilder
.rootBeanDefinition(CommonAnnotationBeanPostProcessor.class)
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE).getBeanDefinition());
.rootBeanDefinition(CommonAnnotationBeanPostProcessor.class)
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE).getBeanDefinition());
applicationContext.registerBeanDefinition("initDestroyComponent",
new RootBeanDefinition(InitDestroyComponent.class));
testCompiledResult(applicationContext, (initializer, compiled) -> {
@ -220,8 +224,8 @@ class ApplicationContextAotGeneratorTests {
applicationContext.registerBeanDefinition(
AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME,
BeanDefinitionBuilder
.rootBeanDefinition(CommonAnnotationBeanPostProcessor.class)
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE).getBeanDefinition());
.rootBeanDefinition(CommonAnnotationBeanPostProcessor.class)
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE).getBeanDefinition());
RootBeanDefinition beanDefinition = new RootBeanDefinition(InitDestroyComponent.class);
beanDefinition.setInitMethodName("customInit");
beanDefinition.setDestroyMethodName("customDestroy");
@ -270,17 +274,6 @@ class ApplicationContextAotGeneratorTests {
});
}
@Test
void processAheadOfTimeWhenHasCglibProxyWriteProxyAndGenerateReflectionHints() throws IOException {
GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.registerBean(CglibConfiguration.class);
TestGenerationContext context = processAheadOfTime(applicationContext);
String proxyClassName = CglibConfiguration.class.getName() + "$$SpringCGLIB$$0";
assertThat(context.getGeneratedFiles()
.getGeneratedFileContent(Kind.CLASS, proxyClassName.replace('.', '/') + ".class")).isNotNull();
assertThat(RuntimeHintsPredicates.reflection().onType(TypeReference.of(proxyClassName))
.withMemberCategory(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)).accepts(context.getRuntimeHints());
}
@Test
void processAheadOfTimeWithPropertySource() {
@ -295,8 +288,51 @@ class ApplicationContextAotGeneratorTests {
});
}
@Nested
@CompileWithForkedClassLoader
class ConfigurationClassCglibProxy {
@Test
void processAheadOfTimeWhenHasCglibProxyWriteProxyAndGenerateReflectionHints() throws IOException {
GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.registerBean(CglibConfiguration.class);
TestGenerationContext context = processAheadOfTime(applicationContext);
String proxyClassName = CglibConfiguration.class.getName() + "$$SpringCGLIB$$0";
assertThat(context.getGeneratedFiles()
.getGeneratedFileContent(Kind.CLASS, proxyClassName.replace('.', '/') + ".class")).isNotNull();
assertThat(RuntimeHintsPredicates.reflection().onType(TypeReference.of(proxyClassName))
.withMemberCategory(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)).accepts(context.getRuntimeHints());
}
@Test
void processAheadOfTimeWhenHasCglibProxyUseProxy() {
GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.registerBean(CglibConfiguration.class);
testCompiledResult(applicationContext, (initializer, compiled) -> {
GenericApplicationContext freshApplicationContext = toFreshApplicationContext(initializer);
assertThat(freshApplicationContext.getBean("prefix", String.class)).isEqualTo("Hello0");
assertThat(freshApplicationContext.getBean("text", String.class)).isEqualTo("Hello0 World");
});
}
@Test
void processAheadOfTimeWhenHasCglibProxyWithArgumentsUseProxy() {
GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.registerBean(ConfigurableCglibConfiguration.class);
testCompiledResult(applicationContext, (initializer, compiled) -> {
GenericApplicationContext freshApplicationContext = createFreshApplicationContext(initializer);
freshApplicationContext.setEnvironment(new MockEnvironment().withProperty("test.prefix", "Hi"));
freshApplicationContext.refresh();
assertThat(freshApplicationContext.getBean("prefix", String.class)).isEqualTo("Hi0");
assertThat(freshApplicationContext.getBean("text", String.class)).isEqualTo("Hi0 World");
});
}
}
private Consumer<List<? extends JdkProxyHint>> doesNotHaveProxyFor(Class<?> target) {
return hints -> assertThat(hints).noneMatch(hint -> hint.getProxiedInterfaces().get(0).equals(target));
return hints -> assertThat(hints).noneMatch(hint ->
hint.getProxiedInterfaces().get(0).equals(TypeReference.of(target)));
}
private static TestGenerationContext processAheadOfTime(GenericApplicationContext applicationContext) {
@ -307,23 +343,29 @@ class ApplicationContextAotGeneratorTests {
return generationContext;
}
private void testCompiledResult(GenericApplicationContext applicationContext,
private static void testCompiledResult(GenericApplicationContext applicationContext,
BiConsumer<ApplicationContextInitializer<GenericApplicationContext>, Compiled> result) {
testCompiledResult(processAheadOfTime(applicationContext), result);
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private void testCompiledResult(TestGenerationContext generationContext,
private static void testCompiledResult(TestGenerationContext generationContext,
BiConsumer<ApplicationContextInitializer<GenericApplicationContext>, Compiled> result) {
TestCompiler.forSystem().with(generationContext).compile(compiled ->
result.accept(compiled.getInstance(ApplicationContextInitializer.class), compiled));
}
private GenericApplicationContext toFreshApplicationContext(
private static GenericApplicationContext toFreshApplicationContext(
ApplicationContextInitializer<GenericApplicationContext> initializer) {
GenericApplicationContext freshApplicationContext = createFreshApplicationContext(initializer);
freshApplicationContext.refresh();
return freshApplicationContext;
}
private static GenericApplicationContext createFreshApplicationContext(
ApplicationContextInitializer<GenericApplicationContext> initializer) {
GenericApplicationContext freshApplicationContext = new GenericApplicationContext();
initializer.initialize(freshApplicationContext);
freshApplicationContext.refresh();
return freshApplicationContext;
}

View File

@ -28,7 +28,7 @@ public class CglibConfiguration {
@Bean
public String prefix() {
return "Hello" + counter.getAndIncrement();
return getPrefix() + counter.getAndIncrement();
}
@Bean
@ -36,4 +36,8 @@ public class CglibConfiguration {
return prefix() + " World";
}
protected String getPrefix() {
return "Hello";
}
}

View File

@ -0,0 +1,36 @@
/*
* Copyright 2002-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.context.testfixture.context.generator.annotation;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
@Configuration
public class ConfigurableCglibConfiguration extends CglibConfiguration {
private final Environment environment;
public ConfigurableCglibConfiguration(Environment environment) {
this.environment = environment;
}
@Override
protected String getPrefix() {
return this.environment.getProperty("test.prefix", String.class, "Howdy");
}
}

View File

@ -31,7 +31,7 @@ import org.springframework.transaction.PlatformTransactionManager;
* @author Sam Brannen
* @since 4.1
*/
@Configuration(proxyBeanMethods = false)
@Configuration
public class EmptyDatabaseConfig {
@Bean