Merge pull request #50033 from kwondh5217

* pr/50033:
  Polish 'Include @Bean method annotations in ContainerConnectionSource'
  Include @Bean method annotations in ContainerConnectionSource

Closes gh-50033
This commit is contained in:
Phillip Webb
2026-04-13 15:25:18 -07:00
6 changed files with 172 additions and 2 deletions
@@ -23,6 +23,7 @@ import org.testcontainers.containers.Container;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
@@ -39,6 +40,7 @@ import org.springframework.core.type.AnnotationMetadata;
* {@link ServiceConnectionAutoConfiguration}.
*
* @author Phillip Webb
* @author Daeho Kwon
*/
class ServiceConnectionAutoConfigurationRegistrar implements ImportBeanDefinitionRegistrar {
@@ -60,8 +62,7 @@ class ServiceConnectionAutoConfigurationRegistrar implements ImportBeanDefinitio
new ConnectionDetailsFactories(null));
for (String beanName : beanFactory.getBeanNamesForType(Container.class)) {
BeanDefinition beanDefinition = getBeanDefinition(beanFactory, beanName);
MergedAnnotations annotations = (beanDefinition instanceof TestcontainerBeanDefinition testcontainerBeanDefinition)
? testcontainerBeanDefinition.getAnnotations() : null;
MergedAnnotations annotations = getAnnotations(beanDefinition);
for (ServiceConnection serviceConnection : getServiceConnections(beanFactory, beanName, annotations)) {
ContainerConnectionSource<?> source = createSource(beanFactory, beanName, beanDefinition, annotations,
serviceConnection);
@@ -92,6 +93,16 @@ class ServiceConnectionAutoConfigurationRegistrar implements ImportBeanDefinitio
}
}
private MergedAnnotations getAnnotations(BeanDefinition beanDefinition) {
if (beanDefinition instanceof TestcontainerBeanDefinition testcontainerBeanDefinition) {
return testcontainerBeanDefinition.getAnnotations();
}
if (beanDefinition instanceof AnnotatedBeanDefinition annotatedBeanDefinition) {
return annotatedBeanDefinition.getFactoryMethodMetadata().getAnnotations();
}
return null;
}
@SuppressWarnings("unchecked")
private <C extends Container<?>> ContainerConnectionSource<C> createSource(
ConfigurableListableBeanFactory beanFactory, String beanName, BeanDefinition beanDefinition,
@@ -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,62 @@
/*
* 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.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory.ContainerConnectionDetails;
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}.
*
* @author Daeho Kwon
*/
class ServiceConnectionAutoConfigurationRegistrarTests {
@Test
void sslAnnotationOnBeanMethodShouldBeDetectedInContainerConnectionSource() {
new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(ServiceConnectionAutoConfiguration.class))
.withUserConfiguration(ContainerConfiguration.class)
.run((context) -> {
ContainerConnectionDetails<?> connectionDetails = (ContainerConnectionDetails<?>) context
.getBean(DatabaseConnectionDetails.class);
assertThat(connectionDetails.hasAnnotation(Ssl.class)).isTrue();
});
}
@Configuration(proxyBeanMethods = false)
static class ContainerConfiguration {
@Bean
@ServiceConnection
@Ssl
PostgreSQLContainer<?> container() {
return mock();
}
}
}
@@ -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