Avoid initializing raw bean during runtime in native-images
Closes gh-14825
This commit is contained in:
parent
ef00312991
commit
472c9f8275
|
@ -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) {
|
||||
|
|
|
@ -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 {
|
||||
|
||||
|
|
Loading…
Reference in New Issue