diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/service/connection/ConnectionDetailsFactories.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/service/connection/ConnectionDetailsFactories.java index d348238f5115..2bbaaec1195b 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/service/connection/ConnectionDetailsFactories.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/service/connection/ConnectionDetailsFactories.java @@ -47,8 +47,26 @@ public class ConnectionDetailsFactories { private final List> registrations = new ArrayList<>(); + /** + * Create a new {@link ConnectionDetailsFactories} instance. This constructor uses the + * class loader of {@link ConnectionDetailsFactory} class to load the factories. + */ public ConnectionDetailsFactories() { - this(SpringFactoriesLoader.forDefaultResourceLocation(ConnectionDetailsFactory.class.getClassLoader())); + this(false); + } + + /** + * Create a new {@link ConnectionDetailsFactories} instance. This constructor takes a + * boolean argument to determine whether the context class loader should be used to + * load the factories. If {@code true} and the context class loader is available it + * will be used otherwise the class loader of {@link ConnectionDetailsFactory} class + * will be used. + * @param useContextClassLoader if {@code true} and the context class loader is + * available it will be used otherwise the class loader of + * {@link ConnectionDetailsFactory} class will be used. + */ + public ConnectionDetailsFactories(boolean useContextClassLoader) { + this(SpringFactoriesLoader.forDefaultResourceLocation(getClassLoader(useContextClassLoader))); } @SuppressWarnings({ "rawtypes", "unchecked" }) @@ -107,6 +125,24 @@ public Map, ConnectionDetails> getConnectionDetails(S source, boole return List.copyOf(result); } + /** + * Return the {@link ClassLoader} to use for loading factories. + *

+ * The default implementation returns the context class loader of the current thread + * or the class loader of this class if the context class loader is {@code null}. + * @param useContextClassLoader if {@code true} and the context class loader is + * available it will be used otherwise the class loader of + * {@link ConnectionDetailsFactory} class will be used + * @return the class loader to use for loading factories + */ + private static ClassLoader getClassLoader(boolean useContextClassLoader) { + if (!useContextClassLoader) { + return ConnectionDetailsFactory.class.getClassLoader(); + } + final ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + return (classLoader != null) ? classLoader : getClassLoader(false); + } + /** * A {@link ConnectionDetailsFactory} registration. * diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/lifecycle/DockerComposeProperties.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/lifecycle/DockerComposeProperties.java index d6e98ec0f0e2..3cdf4d793da1 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/lifecycle/DockerComposeProperties.java +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/lifecycle/DockerComposeProperties.java @@ -46,6 +46,11 @@ public class DockerComposeProperties { */ private boolean enabled = true; + /** + * Whether to try to use the context class loader for connection details factories. + */ + private boolean useContextClassLoader = false; + /** * Arguments to pass to the Docker Compose command. */ @@ -89,10 +94,18 @@ public boolean isEnabled() { return this.enabled; } + public boolean isUseContextClassLoader() { + return this.useContextClassLoader; + } + public void setEnabled(boolean enabled) { this.enabled = enabled; } + public void setUseContextClassLoader(boolean useContextClassLoader) { + this.useContextClassLoader = useContextClassLoader; + } + public List getArguments() { return this.arguments; } @@ -137,7 +150,7 @@ public Readiness getReadiness() { return this.readiness; } - static DockerComposeProperties get(Binder binder) { + public static DockerComposeProperties get(Binder binder) { return binder.bind(NAME, DockerComposeProperties.class).orElseGet(DockerComposeProperties::new); } diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/DockerComposeServiceConnectionsApplicationListener.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/DockerComposeServiceConnectionsApplicationListener.java index 61acfcf99052..ba1390d96f52 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/DockerComposeServiceConnectionsApplicationListener.java +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/DockerComposeServiceConnectionsApplicationListener.java @@ -27,7 +27,9 @@ import org.springframework.boot.autoconfigure.container.ContainerImageMetadata; import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails; import org.springframework.boot.autoconfigure.service.connection.ConnectionDetailsFactories; +import org.springframework.boot.context.properties.bind.Binder; import org.springframework.boot.docker.compose.core.RunningService; +import org.springframework.boot.docker.compose.lifecycle.DockerComposeProperties; import org.springframework.boot.docker.compose.lifecycle.DockerComposeServicesReadyEvent; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationListener; @@ -46,32 +48,30 @@ class DockerComposeServiceConnectionsApplicationListener implements ApplicationListener { - private final ConnectionDetailsFactories factories; - DockerComposeServiceConnectionsApplicationListener() { - this(new ConnectionDetailsFactories()); - } - DockerComposeServiceConnectionsApplicationListener(ConnectionDetailsFactories factories) { - this.factories = factories; } @Override public void onApplicationEvent(DockerComposeServicesReadyEvent event) { ApplicationContext applicationContext = event.getSource(); if (applicationContext instanceof BeanDefinitionRegistry registry) { + Binder binder = Binder.get(applicationContext.getEnvironment()); + DockerComposeProperties properties = DockerComposeProperties.get(binder); + boolean useContextClassLoader = properties.isUseContextClassLoader(); + ConnectionDetailsFactories factories = new ConnectionDetailsFactories(useContextClassLoader); Environment environment = applicationContext.getEnvironment(); - registerConnectionDetails(registry, environment, event.getRunningServices()); + registerConnectionDetails(registry, environment, event.getRunningServices(), factories); } } private void registerConnectionDetails(BeanDefinitionRegistry registry, Environment environment, - List runningServices) { + List runningServices, ConnectionDetailsFactories factories) { for (RunningService runningService : runningServices) { DockerComposeConnectionSource source = new DockerComposeConnectionSource(runningService, environment); - this.factories.getConnectionDetails(source, false).forEach((connectionDetailsType, connectionDetails) -> { + factories.getConnectionDetails(source, false).forEach((connectionDetailsType, connectionDetails) -> { register(registry, runningService, connectionDetailsType, connectionDetails); - this.factories.getConnectionDetails(connectionDetails, false) + factories.getConnectionDetails(connectionDetails, false) .forEach((adaptedType, adaptedDetails) -> register(registry, runningService, adaptedType, adaptedDetails)); });