Skip to content

Commit

Permalink
Avoid initializing raw bean during runtime in native-images
Browse files Browse the repository at this point in the history
  • Loading branch information
marcusdacoregio committed Apr 5, 2024
1 parent 57884a7 commit c94cb82
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jetbrains.annotations.NotNull;

import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.Aware;
Expand Down Expand Up @@ -81,6 +80,18 @@ public <T> 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 <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)) {
Expand All @@ -92,7 +103,8 @@ private <T> T initializeBeanIfNeeded(T object) {
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());
"""
.formatted(object.getClass());
throw new IllegalStateException(msg);
}
return (T) bean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -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 {

Expand Down

0 comments on commit c94cb82

Please sign in to comment.