Browse Source

Apply InstanceSupplier post-processing when bypassed by explicit args

When getBean(name, args) is called with explicit constructor arguments,
the InstanceSupplier is intentionally bypassed (gh-32657). However, in
AOT mode, @Autowired setter/field injection is baked into the
InstanceSupplier's andThen() chain and AutowiredAnnotationBeanPostProcessor
is excluded from runtime registration. This means the autowiring
post-processing is lost when the supplier is bypassed.

Add InstanceSupplier.postProcessInstance() to allow applying only the
post-processing steps (from andThen()) to an already-created instance
without re-invoking creation. Call this from doCreateBean() when the
instance supplier was bypassed due to explicit args.

Closes gh-35871

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Vladimir Sitnikov <sitnikov.vladimir@gmail.com>
pull/36462/head
Vladimir Sitnikov 5 days ago
parent
commit
a6099fadf4
  1. 31
      spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java
  2. 20
      spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java
  3. 25
      spring-beans/src/main/java/org/springframework/beans/factory/support/InstanceSupplier.java
  4. 744
      spring-context/src/test/java/org/springframework/context/aot/AotDiEquivalenceTests.java

31
spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java

@ -598,6 +598,20 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac @@ -598,6 +598,20 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
// Initialize the bean instance.
Object exposedObject = bean;
// If the instance supplier was bypassed (e.g. explicit args were provided),
// apply any post-processing that was registered via InstanceSupplier.andThen().
// Note: this runs after early singleton caching. For the typical case where
// post-processing returns the same instance, this is safe. If post-processing
// wraps the instance, circular reference detection at the end of this method
// will detect the mismatch and throw BeanCurrentlyInCreationException.
if (args != null && mbd.getInstanceSupplier() instanceof InstanceSupplier<?>) {
exposedObject = applyInstanceSupplierPostProcessing(exposedObject, beanName, mbd);
if (exposedObject != instanceWrapper.getWrappedInstance()) {
instanceWrapper = new BeanWrapperImpl(exposedObject);
initBeanWrapper(instanceWrapper);
}
}
try {
populateBean(beanName, mbd, instanceWrapper);
exposedObject = initializeBean(beanName, exposedObject, mbd);
@ -1285,6 +1299,23 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac @@ -1285,6 +1299,23 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
return supplier.get();
}
/**
* Apply any post-processing from the bean definition's
* {@link InstanceSupplier} to an already-created instance. This is called
* when the instance supplier was bypassed during creation (for example,
* when explicit constructor arguments were provided) but the post-processing
* registered via {@link InstanceSupplier#andThen} still needs to be applied.
* @param bean the already-created bean instance
* @param beanName the name of the bean
* @param mbd the bean definition for the bean
* @return the post-processed bean instance
* @since 7.0
* @see InstanceSupplier#postProcessInstance
*/
protected Object applyInstanceSupplierPostProcessing(Object bean, String beanName, RootBeanDefinition mbd) {
return bean;
}
/**
* Overridden in order to implicitly register the currently created bean as
* dependent on further beans getting programmatically retrieved during a

20
spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java

@ -1026,6 +1026,26 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto @@ -1026,6 +1026,26 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
return super.obtainInstanceFromSupplier(supplier, beanName, mbd);
}
@Override
@SuppressWarnings("unchecked")
protected Object applyInstanceSupplierPostProcessing(Object bean, String beanName, RootBeanDefinition mbd) {
InstanceSupplier<?> instanceSupplier = (InstanceSupplier<?>) mbd.getInstanceSupplier();
if (instanceSupplier != null) {
try {
return ((InstanceSupplier<Object>) instanceSupplier)
.postProcessInstance(RegisteredBean.of(this, beanName, mbd), bean);
}
catch (RuntimeException ex) {
throw ex;
}
catch (Exception ex) {
throw new BeanCreationException(beanName,
"Post-processing of instance supplier failed", ex);
}
}
return bean;
}
@Override
protected void cacheMergedBeanDefinition(RootBeanDefinition mbd, String beanName) {
super.cacheMergedBeanDefinition(mbd, beanName);

25
spring-beans/src/main/java/org/springframework/beans/factory/support/InstanceSupplier.java

@ -64,6 +64,25 @@ public interface InstanceSupplier<T> extends ThrowingSupplier<T> { @@ -64,6 +64,25 @@ public interface InstanceSupplier<T> extends ThrowingSupplier<T> {
return null;
}
/**
* Apply only the post-processing steps of this supplier to an
* already-created instance, without invoking the instance creation itself.
* <p>This is used when the instance was created through a different path
* (for example, when explicit constructor arguments bypass the instance
* supplier) but post-processing registered via {@link #andThen} still
* needs to be applied.
* @param registeredBean the registered bean
* @param instance the already-created instance to post-process
* @return the post-processed instance
* @throws Exception on error
* @since 7.0
* @see #andThen
*/
@SuppressWarnings("unchecked")
default T postProcessInstance(RegisteredBean registeredBean, T instance) throws Exception {
return instance;
}
/**
* Return a composed instance supplier that first obtains the instance from
* this supplier and then applies the {@code after} function to obtain the
@ -83,6 +102,12 @@ public interface InstanceSupplier<T> extends ThrowingSupplier<T> { @@ -83,6 +102,12 @@ public interface InstanceSupplier<T> extends ThrowingSupplier<T> {
return after.applyWithException(registeredBean, InstanceSupplier.this.get(registeredBean));
}
@Override
@SuppressWarnings("unchecked")
public V postProcessInstance(RegisteredBean registeredBean, V instance) throws Exception {
T postProcessed = InstanceSupplier.this.postProcessInstance(registeredBean, (T) instance);
return after.applyWithException(registeredBean, postProcessed);
}
@Override
public @Nullable Method getFactoryMethod() {
return InstanceSupplier.this.getFactoryMethod();
}

744
spring-context/src/test/java/org/springframework/context/aot/AotDiEquivalenceTests.java

@ -0,0 +1,744 @@ @@ -0,0 +1,744 @@
/*
* Copyright 2002-present 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.aot;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Stream;
import jakarta.annotation.PostConstruct;
import jakarta.inject.Provider;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.TestFactory;
import org.springframework.aot.test.generate.TestGenerationContext;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.annotation.AnnotationConfigUtils;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.test.tools.TestCompiler;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Equivalence tests verifying that Spring DI produces the same observable bean
* state in both regular and AOT modes.
*
* <p>The core invariant: for any valid DI configuration, {@code getBean()} in
* regular mode and AOT mode should produce beans with the same observable state.
*
* <p>Tests are organized by scenario groups covering key combinations of DI
* dimensions: injection style, scope, retrieval method, target type, lifecycle,
* and lazy resolution. Each fixture bean implements {@link Verifiable} to
* produce a comparable snapshot of its state.
*
* @author Spring Framework Contributors
* @since 7.0
* @see <a href="https://github.com/spring-projects/spring-framework/issues/35871">gh-35871</a>
*/
class AotDiEquivalenceTests {
private static final String TARGET = "target";
private static final String[] ALL_SCOPES = {
BeanDefinition.SCOPE_SINGLETON,
BeanDefinition.SCOPE_PROTOTYPE };
@TestFactory
Stream<DynamicTest> regularAndAotModesProduceSameBeanState() {
return Stream.of(
explicitArgsScenarios(),
injectionStyleScenarios(),
targetTypeScenarios(),
lifecycleScenarios(),
lazyScenarios(),
crossCuttingScenarios()
).flatMap(Function.identity());
}
// --- Scenario Groups ---
/**
* getBean(name, args) with various injection styles (prototype scope).
* This scenario group catches gh-35871-class regressions where
* InstanceSupplier.andThen() post-processing was not applied when
* the supplier was bypassed due to explicit constructor args.
*/
private Stream<DynamicTest> explicitArgsScenarios() {
List<Class<? extends Verifiable>> beanClasses = List.of(
SetterInjectedWithArg.class,
FieldInjectedWithArg.class,
MethodInjectedWithArg.class);
return beanClasses.stream().map(beanClass -> scenario(
"[prototype] %s, getBean(name, args)".formatted(beanClass.getSimpleName()),
ctx -> {
registerDependency(ctx);
registerBean(ctx, TARGET, beanClass, BeanDefinition.SCOPE_PROTOTYPE);
},
ctx -> ((Verifiable) ctx.getBean(TARGET, "testValue")).snapshot()));
}
/**
* Standard getBean(name) with each injection style x scope.
*/
private Stream<DynamicTest> injectionStyleScenarios() {
record Case(String style, Class<? extends Verifiable> beanClass) {}
List<Case> cases = List.of(
new Case("constructor", ConstructorInjectedBean.class),
new Case("setter", SetterInjectedBean.class),
new Case("field", FieldInjectedBean.class),
new Case("method", MethodInjectedBean.class));
return cases.stream().flatMap(c ->
Arrays.stream(ALL_SCOPES).map(scope ->
scenario("[%s] %s injection".formatted(scope, c.style),
ctx -> {
registerDependency(ctx);
registerBean(ctx, TARGET, c.beanClass, scope);
},
ctx -> ((Verifiable) ctx.getBean(TARGET)).snapshot())));
}
/**
* Injection target type variations x scope.
*/
private Stream<DynamicTest> targetTypeScenarios() {
record Case(String type, Class<? extends Verifiable> beanClass) {}
List<Case> cases = List.of(
new Case("Optional<T>", OptionalInjectedBean.class),
new Case("ObjectProvider<T>", ObjectProviderInjectedBean.class),
new Case("Provider<T>", ProviderInjectedBean.class),
new Case("List<T>", ListInjectedBean.class));
return cases.stream().flatMap(c ->
Arrays.stream(ALL_SCOPES).map(scope ->
scenario("[%s] %s field injection".formatted(scope, c.type),
ctx -> {
registerDependency(ctx);
registerBean(ctx, TARGET, c.beanClass, scope);
},
ctx -> ((Verifiable) ctx.getBean(TARGET)).snapshot())));
}
/**
* Lifecycle callbacks x scope.
*/
private Stream<DynamicTest> lifecycleScenarios() {
record Case(String label, Class<? extends Verifiable> beanClass) {}
List<Case> cases = List.of(
new Case("@PostConstruct + BeanNameAware", PostConstructBean.class),
new Case("InitializingBean", InitializingBeanImpl.class));
return cases.stream().flatMap(c ->
Arrays.stream(ALL_SCOPES).map(scope ->
scenario("[%s] %s".formatted(scope, c.label),
ctx -> {
registerDependency(ctx);
registerBean(ctx, TARGET, c.beanClass, scope);
},
ctx -> ((Verifiable) ctx.getBean(TARGET)).snapshot())));
}
/**
* @Lazy injection x scope.
*/
private Stream<DynamicTest> lazyScenarios() {
record Case(String style, Class<? extends Verifiable> beanClass) {}
List<Case> cases = List.of(
new Case("@Lazy field", LazyFieldInjectedBean.class),
new Case("@Lazy setter", LazySetterInjectedBean.class));
return cases.stream().flatMap(c ->
Arrays.stream(ALL_SCOPES).map(scope ->
scenario("[%s] %s injection".formatted(scope, c.style),
ctx -> {
registerDependency(ctx);
registerBean(ctx, TARGET, c.beanClass, scope);
},
ctx -> ((Verifiable) ctx.getBean(TARGET)).snapshot())));
}
/**
* Cross-cutting scenarios combining multiple DI features on the same bean.
* Bugs like gh-35871 hide at the intersection of dimensions, so these
* tests exercise feature combinations rather than individual axes.
*/
private Stream<DynamicTest> crossCuttingScenarios() {
// Feature combinations with explicit constructor args
List<Class<? extends Verifiable>> withArgBeans = List.of(
FullFeaturedWithArg.class,
LazyAndEagerWithArg.class,
OptionalSetterWithArg.class);
Stream<DynamicTest> withArgs = withArgBeans.stream().map(beanClass -> scenario(
"[prototype] %s, getBean(name, args)".formatted(beanClass.getSimpleName()),
ctx -> {
registerDependency(ctx);
registerBean(ctx, TARGET, beanClass, BeanDefinition.SCOPE_PROTOTYPE);
},
ctx -> ((Verifiable) ctx.getBean(TARGET, "testValue")).snapshot()));
// Feature combinations with standard retrieval x scope
record Case(String label, Class<? extends Verifiable> beanClass) {}
List<Case> standardCases = List.of(
new Case("setter+field+@PostConstruct+BeanNameAware",
MultiInjectionWithLifecycle.class),
new Case("@Lazy field+setter+@PostConstruct",
LazyWithLifecycle.class),
new Case("Optional+Provider+@PostConstruct",
TargetTypeMixWithLifecycle.class));
Stream<DynamicTest> standard = standardCases.stream().flatMap(c ->
Arrays.stream(ALL_SCOPES).map(scope ->
scenario("[%s] %s".formatted(scope, c.label),
ctx -> {
registerDependency(ctx);
registerBean(ctx, TARGET, c.beanClass, scope);
},
ctx -> ((Verifiable) ctx.getBean(TARGET)).snapshot())));
return Stream.concat(withArgs, standard);
}
// --- Test infrastructure ---
private static DynamicTest scenario(String name,
Consumer<GenericApplicationContext> setup,
Function<GenericApplicationContext, Object> stateExtractor) {
return DynamicTest.dynamicTest(name,
() -> assertAotEquivalent(setup, stateExtractor));
}
/**
* Assert that the given context setup produces beans with the same
* observable state in regular mode and AOT-compiled mode.
*/
@SuppressWarnings("unchecked")
private static void assertAotEquivalent(
Consumer<GenericApplicationContext> setup,
Function<GenericApplicationContext, Object> stateExtractor) {
// Regular mode
GenericApplicationContext regular = new GenericApplicationContext();
AnnotationConfigUtils.registerAnnotationConfigProcessors(regular);
setup.accept(regular);
regular.refresh();
Object regularState = stateExtractor.apply(regular);
regular.close();
// AOT mode
GenericApplicationContext aotSource = new GenericApplicationContext();
AnnotationConfigUtils.registerAnnotationConfigProcessors(aotSource);
setup.accept(aotSource);
TestGenerationContext genCtx = processAheadOfTime(aotSource);
TestCompiler.forSystem().with(genCtx).compile(compiled -> {
ApplicationContextInitializer<GenericApplicationContext> initializer =
compiled.getInstance(ApplicationContextInitializer.class);
GenericApplicationContext aot = new GenericApplicationContext();
initializer.initialize(aot);
aot.refresh();
Object aotState = stateExtractor.apply(aot);
assertThat(aotState)
.as("AOT mode should produce the same bean state as regular mode")
.isEqualTo(regularState);
aot.close();
});
}
private static TestGenerationContext processAheadOfTime(
GenericApplicationContext applicationContext) {
ApplicationContextAotGenerator generator = new ApplicationContextAotGenerator();
TestGenerationContext generationContext = new TestGenerationContext();
generator.processAheadOfTime(applicationContext, generationContext);
generationContext.writeGeneratedContent();
return generationContext;
}
private static void registerDependency(GenericApplicationContext ctx) {
ctx.registerBeanDefinition("service",
new RootBeanDefinition(ServiceBean.class));
}
private static void registerBean(GenericApplicationContext ctx,
String name, Class<?> beanClass, String scope) {
RootBeanDefinition bd = new RootBeanDefinition(beanClass);
if (!BeanDefinition.SCOPE_SINGLETON.equals(scope)) {
bd.setScope(scope);
}
ctx.registerBeanDefinition(name, bd);
}
// --- Verifiable contract ---
interface Verifiable {
Map<String, Object> snapshot();
}
// --- Fixture beans ---
public static class ServiceBean {
}
// Injection with explicit constructor args (for getBean(name, args) scenarios)
public static class SetterInjectedWithArg implements Verifiable {
private final String name;
private ServiceBean service;
public SetterInjectedWithArg(String name) {
this.name = name;
}
@Autowired
public void setService(ServiceBean service) {
this.service = service;
}
@Override
public Map<String, Object> snapshot() {
return Map.of("name", name, "serviceInjected", service != null);
}
}
public static class FieldInjectedWithArg implements Verifiable {
private final String name;
@Autowired
private ServiceBean service;
public FieldInjectedWithArg(String name) {
this.name = name;
}
@Override
public Map<String, Object> snapshot() {
return Map.of("name", name, "serviceInjected", service != null);
}
}
public static class MethodInjectedWithArg implements Verifiable {
private final String name;
private ServiceBean service;
public MethodInjectedWithArg(String name) {
this.name = name;
}
@Autowired
public void configure(ServiceBean service) {
this.service = service;
}
@Override
public Map<String, Object> snapshot() {
return Map.of("name", name, "serviceInjected", service != null);
}
}
// Standard injection styles (for getBean(name) scenarios)
public static class ConstructorInjectedBean implements Verifiable {
private final ServiceBean service;
public ConstructorInjectedBean(ServiceBean service) {
this.service = service;
}
@Override
public Map<String, Object> snapshot() {
return Map.of("serviceInjected", service != null);
}
}
public static class SetterInjectedBean implements Verifiable {
private ServiceBean service;
@Autowired
public void setService(ServiceBean service) {
this.service = service;
}
@Override
public Map<String, Object> snapshot() {
return Map.of("serviceInjected", service != null);
}
}
public static class FieldInjectedBean implements Verifiable {
@Autowired
private ServiceBean service;
@Override
public Map<String, Object> snapshot() {
return Map.of("serviceInjected", service != null);
}
}
public static class MethodInjectedBean implements Verifiable {
private ServiceBean service;
@Autowired
public void configure(ServiceBean service) {
this.service = service;
}
@Override
public Map<String, Object> snapshot() {
return Map.of("serviceInjected", service != null);
}
}
// Target type variations
public static class OptionalInjectedBean implements Verifiable {
@Autowired
private Optional<ServiceBean> service;
@Override
public Map<String, Object> snapshot() {
return Map.of("servicePresent", service != null && service.isPresent());
}
}
public static class ObjectProviderInjectedBean implements Verifiable {
@Autowired
private ObjectProvider<ServiceBean> service;
@Override
public Map<String, Object> snapshot() {
return Map.of("serviceAvailable",
service != null && service.getIfAvailable() != null);
}
}
public static class ProviderInjectedBean implements Verifiable {
@Autowired
private Provider<ServiceBean> service;
@Override
public Map<String, Object> snapshot() {
return Map.of("serviceAvailable",
service != null && service.get() != null);
}
}
public static class ListInjectedBean implements Verifiable {
@Autowired
private List<ServiceBean> services;
@Override
public Map<String, Object> snapshot() {
return Map.of("servicesCount", services != null ? services.size() : 0);
}
}
// Lifecycle
public static class PostConstructBean implements BeanNameAware, Verifiable {
@Autowired
private ServiceBean service;
private String beanName;
private boolean initialized;
@PostConstruct
public void init() {
this.initialized = true;
}
@Override
public void setBeanName(String name) {
this.beanName = name;
}
@Override
public Map<String, Object> snapshot() {
return Map.of(
"serviceInjected", service != null,
"beanName", (beanName != null ? beanName : ""),
"initialized", initialized);
}
}
public static class InitializingBeanImpl implements InitializingBean, Verifiable {
@Autowired
private ServiceBean service;
private boolean initialized;
@Override
public void afterPropertiesSet() {
this.initialized = true;
}
@Override
public Map<String, Object> snapshot() {
return Map.of(
"serviceInjected", service != null,
"initialized", initialized);
}
}
// Lazy
public static class LazyFieldInjectedBean implements Verifiable {
@Lazy
@Autowired
private ServiceBean service;
@Override
public Map<String, Object> snapshot() {
return Map.of("serviceInjected", service != null);
}
}
public static class LazySetterInjectedBean implements Verifiable {
private ServiceBean service;
@Autowired
public void setService(@Lazy ServiceBean service) {
this.service = service;
}
@Override
public Map<String, Object> snapshot() {
return Map.of("serviceInjected", service != null);
}
}
// Cross-cutting: explicit args + multiple features
public static class FullFeaturedWithArg implements BeanNameAware, Verifiable {
private final String name;
@Autowired
private ServiceBean fieldService;
private ServiceBean setterService;
private String beanName;
private boolean initialized;
public FullFeaturedWithArg(String name) {
this.name = name;
}
@Autowired
public void setSetterService(ServiceBean service) {
this.setterService = service;
}
@PostConstruct
public void init() {
this.initialized = true;
}
@Override
public void setBeanName(String name) {
this.beanName = name;
}
@Override
public Map<String, Object> snapshot() {
return Map.of(
"name", name,
"fieldInjected", fieldService != null,
"setterInjected", setterService != null,
"beanName", (beanName != null ? beanName : ""),
"initialized", initialized);
}
}
public static class LazyAndEagerWithArg implements Verifiable {
private final String name;
@Lazy
@Autowired
private ServiceBean lazyService;
private ServiceBean eagerService;
public LazyAndEagerWithArg(String name) {
this.name = name;
}
@Autowired
public void setEagerService(ServiceBean service) {
this.eagerService = service;
}
@Override
public Map<String, Object> snapshot() {
return Map.of(
"name", name,
"lazyInjected", lazyService != null,
"eagerInjected", eagerService != null);
}
}
public static class OptionalSetterWithArg implements Verifiable {
private final String name;
private Optional<ServiceBean> service = Optional.empty();
public OptionalSetterWithArg(String name) {
this.name = name;
}
@Autowired
public void setService(Optional<ServiceBean> service) {
this.service = service;
}
@Override
public Map<String, Object> snapshot() {
return Map.of(
"name", name,
"servicePresent", service.isPresent());
}
}
// Cross-cutting: multiple features without explicit args
public static class MultiInjectionWithLifecycle
implements BeanNameAware, Verifiable {
@Autowired
private ServiceBean fieldService;
private ServiceBean setterService;
private String beanName;
private boolean initialized;
@Autowired
public void setSetterService(ServiceBean service) {
this.setterService = service;
}
@PostConstruct
public void init() {
this.initialized = true;
}
@Override
public void setBeanName(String name) {
this.beanName = name;
}
@Override
public Map<String, Object> snapshot() {
return Map.of(
"fieldInjected", fieldService != null,
"setterInjected", setterService != null,
"beanName", (beanName != null ? beanName : ""),
"initialized", initialized);
}
}
public static class LazyWithLifecycle implements Verifiable {
@Lazy
@Autowired
private ServiceBean lazyService;
private ServiceBean eagerService;
private boolean initialized;
@Autowired
public void setEagerService(ServiceBean service) {
this.eagerService = service;
}
@PostConstruct
public void init() {
this.initialized = true;
}
@Override
public Map<String, Object> snapshot() {
return Map.of(
"lazyInjected", lazyService != null,
"eagerInjected", eagerService != null,
"initialized", initialized);
}
}
public static class TargetTypeMixWithLifecycle implements Verifiable {
@Autowired
private Optional<ServiceBean> optionalService;
private Provider<ServiceBean> providerService;
private boolean initialized;
@Autowired
public void setProviderService(Provider<ServiceBean> service) {
this.providerService = service;
}
@PostConstruct
public void init() {
this.initialized = true;
}
@Override
public Map<String, Object> snapshot() {
return Map.of(
"optionalPresent", optionalService != null && optionalService.isPresent(),
"providerAvailable", providerService != null && providerService.get() != null,
"initialized", initialized);
}
}
}
Loading…
Cancel
Save