Skip to content

Use thread's context class loader for spring factories loader on connection details factory #45010

Closed as not planned
@lengors

Description

@lengors

Proposal

At the moment, ConnectionDetailsFactories initializes a SpringFactoriesLoader using its own class loader (ConnectionDetailsFactories.class.getClassLoader()). I propose instead that the SpringFactoriesLoader be initialized with the thread's context class loader (Thread.currentThread().getContextClassLoader()) by default - falling back to the previous behavior if the context class loader is missing.

Motivation

When defining our own ConnectionDetails and ConnectionDetailsFactory in a project that uses spring-boot-devtools, the ConnectionDetails instance created by the factory fails to be autowired. This happens because the class loader used by the factory differs from the one used to wire the bean.

Example

src/main/java/com/example/custom/demo/CustomPGConnectionDetails.java
public interface CustomPGConnectionDetails extends ConnectionDetails {
  String getHost();
}
src/main/java/com/example/custom/demo/CustomPGDockerComposeConnectionDetailsFactory.java
class CustomPGDockerComposeConnectionDetailsFactory
    extends DockerComposeConnectionDetailsFactory<CustomPGConnectionDetails> {
  private static final String[] CUSTOM_PG_CONTAINER_NAMES = { "postgres" };

  CustomPGDockerComposeConnectionDetailsFactory() {
    super(CUSTOM_PG_CONTAINER_NAMES);
  }

  @Override
  protected CustomPGConnectionDetails getDockerComposeConnectionDetails(final DockerComposeConnectionSource source) {
    return new CustomPGDockerComposeConnectionDetails(source.getRunningService());
  }

  static class CustomPGDockerComposeConnectionDetails extends DockerComposeConnectionDetails
      implements CustomPGConnectionDetails {
    private final String host;

    private CustomPGDockerComposeConnectionDetails(final RunningService runningService) {
      super(runningService);
      this.host = runningService.host();
    }

    @Override
    public String getHost() {
      return host;
    }
  }
}
src/main/java/com/example/custom/demo/CustomPGClient.java
package com.example.demo.custom;

import org.springframework.stereotype.Service;

@Service
class CustomPGClient {
  private final CustomPGConnectionDetails customPGConnectionDetails;

  public CustomPGClient(final CustomPGConnectionDetails customPGConnectionDetails) {
    this.customPGConnectionDetails = customPGConnectionDetails;
  }
}
src/main/resources/META-INF/spring.factories
org.springframework.boot.autoconfigure.service.connection.ConnectionDetailsFactory=com.example.demo.custom.CustomPGDockerComposeConnectionDetailsFactory

In this example, the application will fail to start with the following error:

Parameter 0 of constructor in com.example.demo.custom.CustomPGClient required a bean of type 'com.example.demo.custom.CustomPGConnectionDetails' that could not be found.

Disclaimer

My understanding of the inner workings of Spring is limited, so there might be a good reason this isn't already done.

If that's the case, please let me know so we can explore alternatives or consider opening a broader issue to address the underlying problem.

Metadata

Metadata

Assignees

No one assigned

    Labels

    status: supersededAn issue that has been superseded by another

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions