Avoid initializing raw bean during runtime in native-images

Closes gh-14825
This commit is contained in:
Marcus Hert Da Coregio 2024-04-03 14:05:08 -03:00
parent ef00312991
commit 472c9f8275
2 changed files with 96 additions and 4 deletions

View File

@ -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 java.util.List;
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> 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 @@ final class AutowireBeanFactoryObjectPostProcessor
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 <T> 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 <a href=
* "https://github.com/spring-projects/spring-security/issues/14825">Issue
* gh-14825</a>
*/
@SuppressWarnings("unchecked")
private <T> 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) {

View File

@ -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.EnvironmentAware;
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 class AutowireBeanFactoryObjectPostProcessorTests {
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 {