diff --git a/config/src/main/java/org/springframework/security/config/annotation/configuration/AutowireBeanFactoryObjectPostProcessor.java b/config/src/main/java/org/springframework/security/config/annotation/configuration/AutowireBeanFactoryObjectPostProcessor.java index 7792ff44ce4..ef729c5b4a4 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/configuration/AutowireBeanFactoryObjectPostProcessor.java +++ b/config/src/main/java/org/springframework/security/config/annotation/configuration/AutowireBeanFactoryObjectPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2024 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. @@ -22,11 +22,14 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.aop.support.AopUtils; import org.springframework.beans.factory.Aware; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.SmartInitializingSingleton; import org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import org.springframework.core.NativeDetector; import org.springframework.security.config.annotation.ObjectPostProcessor; import org.springframework.util.Assert; @@ -55,14 +58,13 @@ final class AutowireBeanFactoryObjectPostProcessor } @Override - @SuppressWarnings("unchecked") public T postProcess(T object) { if (object == null) { return null; } T result = null; try { - result = (T) this.autowireBeanFactory.initializeBean(object, object.toString()); + result = initializeBeanIfNeeded(object); } catch (RuntimeException ex) { Class type = object.getClass(); @@ -78,6 +80,36 @@ public T postProcess(T object) { return result; } + /** + * Invokes {@link AutowireCapableBeanFactory#initializeBean(Object, String)} only if + * needed, i.e when the application is not a native image or the object is not a CGLIB + * proxy. + * @param object the object to initialize + * @param the type of the object + * @return the initialized bean or an existing bean if the object is a CGLIB proxy and + * the application is a native image + * @see Issue + * gh-14825 + */ + @SuppressWarnings("unchecked") + private T initializeBeanIfNeeded(T object) { + if (!NativeDetector.inNativeImage() || !AopUtils.isCglibProxy(object)) { + return (T) this.autowireBeanFactory.initializeBean(object, object.toString()); + } + ObjectProvider provider = this.autowireBeanFactory.getBeanProvider(object.getClass()); + Object bean = provider.getIfUnique(); + if (bean == null) { + String msg = """ + Failed to resolve an unique bean (single or primary) of type [%s] from the BeanFactory. + Because the object is a CGLIB Proxy, a raw bean cannot be initialized during runtime in a native image. + """ + .formatted(object.getClass()); + throw new IllegalStateException(msg); + } + return (T) bean; + } + @Override public void afterSingletonsInstantiated() { for (SmartInitializingSingleton singleton : this.smartSingletons) { diff --git a/config/src/test/java/org/springframework/security/config/annotation/configuration/AutowireBeanFactoryObjectPostProcessorTests.java b/config/src/test/java/org/springframework/security/config/annotation/configuration/AutowireBeanFactoryObjectPostProcessorTests.java index 2beceb68d17..232f732b5ff 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/configuration/AutowireBeanFactoryObjectPostProcessorTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/configuration/AutowireBeanFactoryObjectPostProcessorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2024 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,9 +16,13 @@ package org.springframework.security.config.annotation.configuration; +import java.lang.reflect.Modifier; + import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mockito; +import org.springframework.aop.framework.ProxyFactory; import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.DisposableBean; @@ -31,13 +35,16 @@ import org.springframework.context.MessageSourceAware; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.NativeDetector; import org.springframework.security.config.annotation.ObjectPostProcessor; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.web.context.ServletContextAware; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatException; import static org.mockito.ArgumentMatchers.isNotNull; +import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -132,6 +139,59 @@ public void autowireBeanFactoryWhenBeanNameAutoProxyCreatorThenWorks() { assertThat(bean.doStuff()).isEqualTo("null"); } + @Test + void postProcessWhenObjectIsCgLibProxyAndInNativeImageThenUseExistingBean() { + try (var detector = Mockito.mockStatic(NativeDetector.class)) { + given(NativeDetector.inNativeImage()).willReturn(true); + + ProxyFactory proxyFactory = new ProxyFactory(new MyClass()); + proxyFactory.setProxyTargetClass(!Modifier.isFinal(MyClass.class.getModifiers())); + MyClass myClass = (MyClass) proxyFactory.getProxy(); + + this.spring.register(Config.class, myClass.getClass()).autowire(); + this.spring.getContext().getBean(myClass.getClass()).setIdentifier("0000"); + + MyClass postProcessed = this.objectObjectPostProcessor.postProcess(myClass); + assertThat(postProcessed.getIdentifier()).isEqualTo("0000"); + } + } + + @Test + void postProcessWhenObjectIsCgLibProxyAndInNativeImageAndBeanDoesNotExistsThenIllegalStateException() { + try (var detector = Mockito.mockStatic(NativeDetector.class)) { + given(NativeDetector.inNativeImage()).willReturn(true); + + ProxyFactory proxyFactory = new ProxyFactory(new MyClass()); + proxyFactory.setProxyTargetClass(!Modifier.isFinal(MyClass.class.getModifiers())); + MyClass myClass = (MyClass) proxyFactory.getProxy(); + + this.spring.register(Config.class).autowire(); + + assertThatException().isThrownBy(() -> this.objectObjectPostProcessor.postProcess(myClass)) + .havingRootCause() + .isInstanceOf(IllegalStateException.class) + .withMessage( + """ + Failed to resolve an unique bean (single or primary) of type [class org.springframework.security.config.annotation.configuration.AutowireBeanFactoryObjectPostProcessorTests$MyClass$$SpringCGLIB$$0] from the BeanFactory. + Because the object is a CGLIB Proxy, a raw bean cannot be initialized during runtime in a native image. + """); + } + } + + static class MyClass { + + private String identifier = "1234"; + + String getIdentifier() { + return this.identifier; + } + + void setIdentifier(String identifier) { + this.identifier = identifier; + } + + } + @Configuration static class Config {