Skip to content

Commit 472c9f8

Browse files
Avoid initializing raw bean during runtime in native-images
Closes gh-14825
1 parent ef00312 commit 472c9f8

File tree

2 files changed

+96
-4
lines changed

2 files changed

+96
-4
lines changed

config/src/main/java/org/springframework/security/config/annotation/configuration/AutowireBeanFactoryObjectPostProcessor.java

+35-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -22,11 +22,14 @@
2222
import org.apache.commons.logging.Log;
2323
import org.apache.commons.logging.LogFactory;
2424

25+
import org.springframework.aop.support.AopUtils;
2526
import org.springframework.beans.factory.Aware;
2627
import org.springframework.beans.factory.DisposableBean;
2728
import org.springframework.beans.factory.InitializingBean;
29+
import org.springframework.beans.factory.ObjectProvider;
2830
import org.springframework.beans.factory.SmartInitializingSingleton;
2931
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
32+
import org.springframework.core.NativeDetector;
3033
import org.springframework.security.config.annotation.ObjectPostProcessor;
3134
import org.springframework.util.Assert;
3235

@@ -55,14 +58,13 @@ final class AutowireBeanFactoryObjectPostProcessor
5558
}
5659

5760
@Override
58-
@SuppressWarnings("unchecked")
5961
public <T> T postProcess(T object) {
6062
if (object == null) {
6163
return null;
6264
}
6365
T result = null;
6466
try {
65-
result = (T) this.autowireBeanFactory.initializeBean(object, object.toString());
67+
result = initializeBeanIfNeeded(object);
6668
}
6769
catch (RuntimeException ex) {
6870
Class<?> type = object.getClass();
@@ -78,6 +80,36 @@ public <T> T postProcess(T object) {
7880
return result;
7981
}
8082

83+
/**
84+
* Invokes {@link AutowireCapableBeanFactory#initializeBean(Object, String)} only if
85+
* needed, i.e when the application is not a native image or the object is not a CGLIB
86+
* proxy.
87+
* @param object the object to initialize
88+
* @param <T> the type of the object
89+
* @return the initialized bean or an existing bean if the object is a CGLIB proxy and
90+
* the application is a native image
91+
* @see <a href=
92+
* "https://github.com/spring-projects/spring-security/issues/14825">Issue
93+
* gh-14825</a>
94+
*/
95+
@SuppressWarnings("unchecked")
96+
private <T> T initializeBeanIfNeeded(T object) {
97+
if (!NativeDetector.inNativeImage() || !AopUtils.isCglibProxy(object)) {
98+
return (T) this.autowireBeanFactory.initializeBean(object, object.toString());
99+
}
100+
ObjectProvider<?> provider = this.autowireBeanFactory.getBeanProvider(object.getClass());
101+
Object bean = provider.getIfUnique();
102+
if (bean == null) {
103+
String msg = """
104+
Failed to resolve an unique bean (single or primary) of type [%s] from the BeanFactory.
105+
Because the object is a CGLIB Proxy, a raw bean cannot be initialized during runtime in a native image.
106+
"""
107+
.formatted(object.getClass());
108+
throw new IllegalStateException(msg);
109+
}
110+
return (T) bean;
111+
}
112+
81113
@Override
82114
public void afterSingletonsInstantiated() {
83115
for (SmartInitializingSingleton singleton : this.smartSingletons) {

config/src/test/java/org/springframework/security/config/annotation/configuration/AutowireBeanFactoryObjectPostProcessorTests.java

+61-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,9 +16,13 @@
1616

1717
package org.springframework.security.config.annotation.configuration;
1818

19+
import java.lang.reflect.Modifier;
20+
1921
import org.junit.jupiter.api.Test;
2022
import org.junit.jupiter.api.extension.ExtendWith;
23+
import org.mockito.Mockito;
2124

25+
import org.springframework.aop.framework.ProxyFactory;
2226
import org.springframework.beans.factory.BeanClassLoaderAware;
2327
import org.springframework.beans.factory.BeanFactoryAware;
2428
import org.springframework.beans.factory.DisposableBean;
@@ -31,13 +35,16 @@
3135
import org.springframework.context.MessageSourceAware;
3236
import org.springframework.context.annotation.Bean;
3337
import org.springframework.context.annotation.Configuration;
38+
import org.springframework.core.NativeDetector;
3439
import org.springframework.security.config.annotation.ObjectPostProcessor;
3540
import org.springframework.security.config.test.SpringTestContext;
3641
import org.springframework.security.config.test.SpringTestContextExtension;
3742
import org.springframework.web.context.ServletContextAware;
3843

3944
import static org.assertj.core.api.Assertions.assertThat;
45+
import static org.assertj.core.api.Assertions.assertThatException;
4046
import static org.mockito.ArgumentMatchers.isNotNull;
47+
import static org.mockito.BDDMockito.given;
4148
import static org.mockito.Mockito.mock;
4249
import static org.mockito.Mockito.verify;
4350

@@ -132,6 +139,59 @@ public void autowireBeanFactoryWhenBeanNameAutoProxyCreatorThenWorks() {
132139
assertThat(bean.doStuff()).isEqualTo("null");
133140
}
134141

142+
@Test
143+
void postProcessWhenObjectIsCgLibProxyAndInNativeImageThenUseExistingBean() {
144+
try (var detector = Mockito.mockStatic(NativeDetector.class)) {
145+
given(NativeDetector.inNativeImage()).willReturn(true);
146+
147+
ProxyFactory proxyFactory = new ProxyFactory(new MyClass());
148+
proxyFactory.setProxyTargetClass(!Modifier.isFinal(MyClass.class.getModifiers()));
149+
MyClass myClass = (MyClass) proxyFactory.getProxy();
150+
151+
this.spring.register(Config.class, myClass.getClass()).autowire();
152+
this.spring.getContext().getBean(myClass.getClass()).setIdentifier("0000");
153+
154+
MyClass postProcessed = this.objectObjectPostProcessor.postProcess(myClass);
155+
assertThat(postProcessed.getIdentifier()).isEqualTo("0000");
156+
}
157+
}
158+
159+
@Test
160+
void postProcessWhenObjectIsCgLibProxyAndInNativeImageAndBeanDoesNotExistsThenIllegalStateException() {
161+
try (var detector = Mockito.mockStatic(NativeDetector.class)) {
162+
given(NativeDetector.inNativeImage()).willReturn(true);
163+
164+
ProxyFactory proxyFactory = new ProxyFactory(new MyClass());
165+
proxyFactory.setProxyTargetClass(!Modifier.isFinal(MyClass.class.getModifiers()));
166+
MyClass myClass = (MyClass) proxyFactory.getProxy();
167+
168+
this.spring.register(Config.class).autowire();
169+
170+
assertThatException().isThrownBy(() -> this.objectObjectPostProcessor.postProcess(myClass))
171+
.havingRootCause()
172+
.isInstanceOf(IllegalStateException.class)
173+
.withMessage(
174+
"""
175+
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.
176+
Because the object is a CGLIB Proxy, a raw bean cannot be initialized during runtime in a native image.
177+
""");
178+
}
179+
}
180+
181+
static class MyClass {
182+
183+
private String identifier = "1234";
184+
185+
String getIdentifier() {
186+
return this.identifier;
187+
}
188+
189+
void setIdentifier(String identifier) {
190+
this.identifier = identifier;
191+
}
192+
193+
}
194+
135195
@Configuration
136196
static class Config {
137197

0 commit comments

Comments
 (0)