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
Closes gh-14825
  • Loading branch information
marcusdacoregio committed Apr 8, 2024
1 parent ef00312 commit 472c9f8
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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;

Expand Down Expand Up @@ -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();
Expand All @@ -78,6 +80,36 @@ 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)) {
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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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 472c9f8

Please sign in to comment.