Include @Bean method annotations in ContainerConnectionSource

Update `ServiceConnectionAutoConfigurationRegistrar` to include
annotations from `@Bean` methods.

See gh-50033

Signed-off-by: Daeho Kwon <trewq231@naver.com>
This commit is contained in:
Daeho Kwon
2026-04-13 15:03:26 -07:00
committed by Phillip Webb
parent 019eb637ac
commit fe5424b7e1
6 changed files with 180 additions and 1 deletions
@@ -16,6 +16,7 @@
package org.springframework.boot.testcontainers.service.connection;
import java.lang.reflect.Method;
import java.util.LinkedHashSet;
import java.util.Set;
@@ -26,6 +27,7 @@ import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.boot.autoconfigure.service.connection.ConnectionDetailsFactories;
import org.springframework.boot.origin.Origin;
import org.springframework.boot.testcontainers.beans.TestcontainerBeanDefinition;
@@ -39,6 +41,7 @@ import org.springframework.core.type.AnnotationMetadata;
* {@link ServiceConnectionAutoConfiguration}.
*
* @author Phillip Webb
* @author Daeho Kwon
*/
class ServiceConnectionAutoConfigurationRegistrar implements ImportBeanDefinitionRegistrar {
@@ -61,7 +64,7 @@ class ServiceConnectionAutoConfigurationRegistrar implements ImportBeanDefinitio
for (String beanName : beanFactory.getBeanNamesForType(Container.class)) {
BeanDefinition beanDefinition = getBeanDefinition(beanFactory, beanName);
MergedAnnotations annotations = (beanDefinition instanceof TestcontainerBeanDefinition testcontainerBeanDefinition)
? testcontainerBeanDefinition.getAnnotations() : null;
? testcontainerBeanDefinition.getAnnotations() : getAnnotationsFromFactoryMethod(beanDefinition);
for (ServiceConnection serviceConnection : getServiceConnections(beanFactory, beanName, annotations)) {
ContainerConnectionSource<?> source = createSource(beanFactory, beanName, beanDefinition, annotations,
serviceConnection);
@@ -70,6 +73,16 @@ class ServiceConnectionAutoConfigurationRegistrar implements ImportBeanDefinitio
}
}
private MergedAnnotations getAnnotationsFromFactoryMethod(BeanDefinition beanDefinition) {
if (beanDefinition instanceof RootBeanDefinition rootBeanDefinition) {
Method factoryMethod = rootBeanDefinition.getResolvedFactoryMethod();
if (factoryMethod != null) {
return MergedAnnotations.from(factoryMethod, MergedAnnotations.SearchStrategy.DIRECT);
}
}
return null;
}
private Set<ServiceConnection> getServiceConnections(ConfigurableListableBeanFactory beanFactory, String beanName,
MergedAnnotations annotations) {
Set<ServiceConnection> serviceConnections = beanFactory.findAllAnnotationsOnBean(beanName,
@@ -0,0 +1,25 @@
/*
* Copyright 2012-present 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.testcontainers.service.connection;
import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails;
public interface DatabaseConnectionDetails extends ConnectionDetails {
String getJdbcUrl();
}
@@ -0,0 +1,30 @@
/*
* Copyright 2012-present 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.testcontainers.service.connection;
import org.testcontainers.containers.JdbcDatabaseContainer;
public class DatabaseContainerDatabaseConnectionDetails
extends ContainerConnectionDetailsFactory<JdbcDatabaseContainer<?>, DatabaseConnectionDetails> {
@Override
protected DatabaseConnectionDetails getContainerConnectionDetails(
ContainerConnectionSource<JdbcDatabaseContainer<?>> source) {
return new TestDatabaseConnectionDetails(source);
}
}
@@ -0,0 +1,69 @@
/*
* Copyright 2012-present 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.testcontainers.service.connection;
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.PostgreSQLContainer;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory.ContainerConnectionDetails;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link ServiceConnectionAutoConfigurationRegistrar} to verify that
* annotations on {@link Bean @Bean} methods are available in
* {@link ContainerConnectionSource}.
*
* @author Daeho Kwon
*/
class ServiceConnectionAutoConfigurationRegistrarTests {
@Test
void sslAnnotationOnBeanMethodShouldBeDetectedInContainerConnectionSource() {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
context.register(WithServiceConnectionAutoConfiguration.class, ContainerConfiguration.class);
context.refresh();
ContainerConnectionDetails<?> details = (ContainerConnectionDetails<?>) context
.getBean(DatabaseConnectionDetails.class);
assertThat(details.hasAnnotation(Ssl.class)).isTrue();
}
}
@Configuration(proxyBeanMethods = false)
@ImportAutoConfiguration(ServiceConnectionAutoConfiguration.class)
static class WithServiceConnectionAutoConfiguration {
}
@Configuration(proxyBeanMethods = false)
static class ContainerConfiguration {
@Bean
@ServiceConnection
@Ssl
PostgreSQLContainer container() {
return mock(PostgreSQLContainer.class);
}
}
}
@@ -0,0 +1,39 @@
/*
* Copyright 2012-present 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.testcontainers.service.connection;
import org.testcontainers.containers.JdbcDatabaseContainer;
import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory.ContainerConnectionDetails;
class TestDatabaseConnectionDetails extends ContainerConnectionDetails<JdbcDatabaseContainer<?>>
implements DatabaseConnectionDetails {
TestDatabaseConnectionDetails(ContainerConnectionSource<JdbcDatabaseContainer<?>> source) {
super(source);
}
@Override
public String getJdbcUrl() {
return getContainer().getJdbcUrl();
}
JdbcDatabaseContainer<?> callGetContainer() {
return super.getContainer();
}
}
@@ -0,0 +1,3 @@
# Test Connection Details Factories
org.springframework.boot.autoconfigure.service.connection.ConnectionDetailsFactory=\
org.springframework.boot.testcontainers.service.connection.DatabaseContainerDatabaseConnectionDetails