Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Actuator throws an exception when using prototype scoped DataSource bean #44706

Closed
BbIKTOP opened this issue Mar 13, 2025 · 9 comments
Closed
Assignees
Labels
type: bug A general bug
Milestone

Comments

@BbIKTOP
Copy link

BbIKTOP commented Mar 13, 2025

Spring Boot 3.4.3:

    <properties>
        <java.version>17</java.version>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.4.3</version>
        <relativePath/>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
            <version>2.5.0</version>
        </dependency>

Test configuration:


@Configuration
public class DbConfig
{
    @Bean
    @ConfigurationProperties("spring.datasource")
    public DataSourceProperties primaryDataSourceProperties()
    {
        return new DataSourceProperties();
    }

    @Bean(name = "cfgDataSource")
    @Primary
    @ConfigurationProperties("spring.datasource.hikari")
    public DataSource primaryDataSource()
    {
        return primaryDataSourceProperties()
                .initializeDataSourceBuilder()
                .type(HikariDataSource.class)
                .build();
    }

    @Bean
    @ConfigurationProperties("app.destination-datasource")
    public DataSourceProperties destinationDataSourceProperties()
    {
        return new DataSourceProperties();
    }

    @Bean(name = "destinationDataSource")
    @ConfigurationProperties("app.destination-datasource.hikari")
    public DataSource destinationDataSource()
    {
        HikariDataSource dataSource = destinationDataSourceProperties()
                .initializeDataSourceBuilder()
                .type(HikariDataSource.class)
                .build();
        return dataSource;
    }

    @Bean
    @ConfigurationProperties("app.source-datasource")
    public DataSourceProperties sourceDataSourceProperties()
    {
        return new DataSourceProperties();
    }

    @Bean(name = "sourceDataSource")
    @Scope("prototype")
    @ConfigurationProperties("app.source-datasource.hikari")
    @Lazy
    public DataSource sourceDataSource(String url, String user, String password)
    {
        DataSourceProperties srcProps = sourceDataSourceProperties();
        srcProps.setUrl(url);
        srcProps.setUsername(user);
        srcProps.setPassword(password);
        DataSource ds = srcProps
                .initializeDataSourceBuilder()
                .build();
        return ds;
    }
}


Exception (org/springframework/boot/actuate/autoconfigure/metrics/jdbc/DataSourcePoolMetricsAutoConfiguration$DataSourcePoolMetadataMetricsConfiguration.class):

2025-03-13T11:15:30.078+02:00 DEBUG 57764 --- [           main] o.s.b.f.s.DefaultListableBeanFactory     : Autowiring by type from bean name 'org.springframework.boot.actuate.autoconfigure.metrics.data.RepositoryMetricsAutoConfiguration' via constructor to bean named 'management.metrics-org.springframework.boot.actuate.autoconfigure.metrics.MetricsProperties'
2025-03-13T11:15:30.079+02:00 DEBUG 57764 --- [           main] o.s.b.f.s.DefaultListableBeanFactory     : Creating shared instance of singleton bean 'repositoryTagsProvider'
2025-03-13T11:15:30.079+02:00 DEBUG 57764 --- [           main] o.s.b.f.s.DefaultListableBeanFactory     : Creating shared instance of singleton bean 'metricsRepositoryMethodInvocationListener'
2025-03-13T11:15:30.080+02:00 DEBUG 57764 --- [           main] o.s.b.f.s.DefaultListableBeanFactory     : Autowiring by type from bean name 'metricsRepositoryMethodInvocationListener' via factory method to bean named 'repositoryTagsProvider'
2025-03-13T11:15:30.081+02:00 DEBUG 57764 --- [           main] o.s.b.f.s.DefaultListableBeanFactory     : Creating shared instance of singleton bean 'org.springframework.boot.actuate.autoconfigure.metrics.integration.IntegrationMetricsAutoConfiguration'
2025-03-13T11:15:30.082+02:00 DEBUG 57764 --- [           main] o.s.b.f.s.DefaultListableBeanFactory     : Creating shared instance of singleton bean 'org.springframework.boot.actuate.autoconfigure.metrics.jdbc.DataSourcePoolMetricsAutoConfiguration$HikariDataSourceMetricsConfiguration'
2025-03-13T11:15:30.082+02:00 DEBUG 57764 --- [           main] o.s.b.f.s.DefaultListableBeanFactory     : Creating shared instance of singleton bean 'hikariDataSourceMeterBinder'
2025-03-13T11:15:30.083+02:00 DEBUG 57764 --- [           main] o.s.b.f.s.DefaultListableBeanFactory     : Creating shared instance of singleton bean 'org.springframework.boot.actuate.autoconfigure.metrics.jdbc.DataSourcePoolMetricsAutoConfiguration$DataSourcePoolMetadataMetricsConfiguration'
2025-03-13T11:15:30.083+02:00 DEBUG 57764 --- [           main] o.s.b.f.s.DefaultListableBeanFactory     : Creating shared instance of singleton bean 'dataSourcePoolMetadataMeterBinder'
2025-03-13T11:15:30.083+02:00 DEBUG 57764 --- [           main] o.s.b.f.s.DefaultListableBeanFactory     : Autowiring by type from bean name 'dataSourcePoolMetadataMeterBinder' via factory method to bean named 'org.springframework.beans.factory.support.DefaultListableBeanFactory@6a988392'

2025-03-13T11:15:30.084+02:00  WARN 57764 --- [           main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataSourcePoolMetadataMeterBinder' defined in class path resource [org/springframework/boot/actuate/autoconfigure/metrics/jdbc/DataSourcePoolMetricsAutoConfiguration$DataSourcePoolMetadataMetricsConfiguration.class]: Failed to instantiate [org.springframework.boot.actuate.autoconfigure.metrics.jdbc.DataSourcePoolMetricsAutoConfiguration$DataSourcePoolMetadataMetricsConfiguration$DataSourcePoolMetadataMeterBinder]: Factory method 'dataSourcePoolMetadataMeterBinder' threw exception with message: Error creating bean with name 'sourceDataSource' defined in class path resource [st/notexi/springtest/config/DbConfig.class]: Unsatisfied dependency expressed through method 'sourceDataSource' parameter 0: No qualifying bean of type 'java.lang.String' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}

2025-03-13T11:15:30.085+02:00 DEBUG 57764 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Unregistering JMX-exposed beans on shutdown
2025-03-13T11:15:30.086+02:00  INFO 57764 --- [           main] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2025-03-13T11:15:30.086+02:00 DEBUG 57764 --- [           main] o.hibernate.internal.SessionFactoryImpl  : HHH000031: Closing
2025-03-13T11:15:30.086+02:00 DEBUG 57764 --- [           main] o.h.type.spi.TypeConfiguration$Scope     : Un-scoping TypeConfiguration [org.hibernate.type.spi.TypeConfiguration$Scope@1444e35f] from SessionFactory [org.hibernate.internal.SessionFactoryImpl@1b83fac3]
2025-03-13T11:15:30.086+02:00 DEBUG 57764 --- [           main] o.h.s.i.AbstractServiceRegistryImpl      : Implicitly destroying ServiceRegistry on de-registration of all child ServiceRegistries
2025-03-13T11:15:30.087+02:00 DEBUG 57764 --- [           main] o.h.b.r.i.BootstrapServiceRegistryImpl   : Implicitly destroying Boot-strap registry on de-registration of all child ServiceRegistries
2025-03-13T11:15:30.087+02:00  INFO 57764 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2025-03-13T11:15:30.087+02:00 DEBUG 57764 --- [           main] com.zaxxer.hikari.pool.HikariPool        : HikariPool-1 - Before shutdown stats (total=10, active=0, idle=10, waiting=0)
2025-03-13T11:15:30.087+02:00 DEBUG 57764 --- [nnection closer] com.zaxxer.hikari.pool.PoolBase          : HikariPool-1 - Closing connection org.postgresql.jdbc.PgConnection@69ce14e6: (connection evicted)
2025-03-13T11:15:30.089+02:00 DEBUG 57764 --- [nnection closer] com.zaxxer.hikari.pool.PoolBase          : HikariPool-1 - Closing connection org.postgresql.jdbc.PgConnection@79479b8c: (connection evicted)
2025-03-13T11:15:30.089+02:00 DEBUG 57764 --- [nnection closer] com.zaxxer.hikari.pool.PoolBase          : HikariPool-1 - Closing connection org.postgresql.jdbc.PgConnection@36bd60ef: (connection evicted)
2025-03-13T11:15:30.089+02:00 DEBUG 57764 --- [nnection closer] com.zaxxer.hikari.pool.PoolBase          : HikariPool-1 - Closing connection org.postgresql.jdbc.PgConnection@b753c39: (connection evicted)
2025-03-13T11:15:30.089+02:00 DEBUG 57764 --- [nnection closer] com.zaxxer.hikari.pool.PoolBase          : HikariPool-1 - Closing connection org.postgresql.jdbc.PgConnection@56929fa0: (connection evicted)
2025-03-13T11:15:30.089+02:00 DEBUG 57764 --- [nnection closer] com.zaxxer.hikari.pool.PoolBase          : HikariPool-1 - Closing connection org.postgresql.jdbc.PgConnection@3da82a53: (connection evicted)
2025-03-13T11:15:30.089+02:00 DEBUG 57764 --- [nnection closer] com.zaxxer.hikari.pool.PoolBase          : HikariPool-1 - Closing connection org.postgresql.jdbc.PgConnection@32472127: (connection evicted)
2025-03-13T11:15:30.089+02:00 DEBUG 57764 --- [nnection closer] com.zaxxer.hikari.pool.PoolBase          : HikariPool-1 - Closing connection org.postgresql.jdbc.PgConnection@107157a2: (connection evicted)
2025-03-13T11:15:30.089+02:00 DEBUG 57764 --- [nnection closer] com.zaxxer.hikari.pool.PoolBase          : HikariPool-1 - Closing connection org.postgresql.jdbc.PgConnection@52dca593: (connection evicted)
2025-03-13T11:15:30.089+02:00 DEBUG 57764 --- [nnection closer] com.zaxxer.hikari.pool.PoolBase          : HikariPool-1 - Closing connection org.postgresql.jdbc.PgConnection@2edaf729: (connection evicted)
2025-03-13T11:15:30.089+02:00 DEBUG 57764 --- [           main] com.zaxxer.hikari.pool.HikariPool        : HikariPool-1 - After shutdown stats (total=0, active=0, idle=0, waiting=0)
2025-03-13T11:15:30.089+02:00  INFO 57764 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.
2025-03-13T11:15:30.090+02:00 DEBUG 57764 --- [           main] o.s.b.f.support.DisposableBeanAdapter    : Custom destroy method 'close' on bean with name 'simpleMeterRegistry' completed
2025-03-13T11:15:30.091+02:00  INFO 57764 --- [           main] o.apache.catalina.core.StandardService   : Stopping service [Tomcat]

App properties:

management:
  health:
    db:
      enabled: false
  endpoint:
    health:
      group:
        readiness:
          include: db
          exclude: db/sourceDataSource
        default:
          include: "*"
          exclude: "sourceDataSource"
  server:
    port: 9091
  endpoints:
    web:
      base-path: /actuator
      path-mapping:
        health: /health
      exposure:
        include: metrics,health,env

Problem:
Spring Boot Actuator is trying to instantiate a parametrized prototype bean ignoring properties.

Fix:
No need to retrieve beans with scopes other than singleton because it leads to the new bin creation. And there's no scenario exists to monitor beans created by the actuator, not by the app.

Usage example in the app:

@Data
public class CredsSource
{
    private String dbUrl;
    private String dbUser;
    private String dbPassword;
}

@Service
public class SomeServiceClass
{
    public void someMethod(@Qualifier("sourceDataSource") ObjectProvider<DataSource> srcDataSourceProvider,
                           CredsSource credsSource)
    {
        DataSource srcDataSource = srcDataSourceProvider.getObject(credsSource.getDbUrl(),
                credsSource.getDbUser(), credsSource.getDbPassword());
    }
}
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Mar 13, 2025
@wilkinsona
Copy link
Member

I'm not sure what you mean by "ignoring properties". Which properties do you believe are being ignored? I would guess it's the properties for the default and readiness health endpoint groups but they will have no effect on DataSource metrics which is where the problem's occurring.

I also don't understand how your application is supposed to work without Actuator. Do you really want a new DataSource instance every time something tries to access the context's DataSource beans? If so, where are the url, user, and password parameters from its @Bean method coming from? It currently requires three String beans to exist in the context and none do which is why the failure's occurring. That failure will occur as soon as any component, not just Actuator, tries to interact with sourceDataSource.

@wilkinsona wilkinsona added the status: waiting-for-feedback We need additional information before we can continue label Mar 13, 2025
@BbIKTOP
Copy link
Author

BbIKTOP commented Mar 13, 2025

I read that management.health.db.enabled = false should disable db support. Cannot find it in the docs right now, but could try if it is required.
Also, I read that I can disable some datasources from being instantiated by the actuator by adding them to groups "exclude", which, as you could see, is also ignored.

Anyways. The main bug is, that Actuator is trying to instantiate this bean and there's no way to disable that. What is the reason to instantiate a prototype scoped parametrized bean? It would definitely fail, what I observe in this example.
I. e. there are two Spring features - parametrized prototype bean and actuator, and they don't work combined together, which is definitely a bug.

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Mar 13, 2025
@BbIKTOP
Copy link
Author

BbIKTOP commented Mar 13, 2025

Sorry, didn't notice the second part of your comment. I just expect/want actuator to ignore this bean as it could not be monitored, of course. Just to support all other metrics except this.

The rest of the app instantiates this datasource as described here and everything is working just fine. Till I add actuator.

@BbIKTOP
Copy link
Author

BbIKTOP commented Mar 13, 2025

Also, you could read more about what Spring devs meant by the "prototype" scope beans here
I suppose, there's no reason to monitor prototype scoped beans at all.

@wilkinsona
Copy link
Member

wilkinsona commented Mar 13, 2025

I just expect/want actuator to ignore this bean as it could not be monitored, of course

Actuator's DataSource metrics support uses SimpleAutowireCandidateResolver.resolveAutowireCandidates(beanFactory, DataSource.class) to ask Framework for all of the DataSource beans in the bean factory. There's no mechanism for excluding beans from that other than making them non-autowireable. Ignoring certain beans, for example because they're in prototype scope, would require a change to Spring Framework.

Given the current capabilities of Framework, your DataSource either can't be a bean at all or it should be defined using @Bean(autowireCandidate = false). If your DataSource needs to be an autowireable bean then what you're trying to do is not currently supported.

The rest of the app instantiates this datasource as described here and everything is working just fine.

I can't see any code in your example using ObjectProvider. I guess you're using its getObject(Object ... args) method? It would be very useful to have a complete yet minimal example here rather than trying to piece things together bit by bit.

Right now, I think your best option may be to not use a @Bean method for your sourceDataSource and instead use some general purpose utility code that creates the DataSource and then uses the Binder API to bind properties to it. A minimal example would make it easier for us to show how this would look.

@wilkinsona wilkinsona added status: waiting-for-feedback We need additional information before we can continue and removed status: feedback-provided Feedback has been provided labels Mar 13, 2025
@BbIKTOP
Copy link
Author

BbIKTOP commented Mar 13, 2025

As I understood from the previous comments and my common understanding of the Spring architecture, there's no reason and no possibility to monitor every instance of the prototype scoped beans at all. Anyways, why actuator is it trying to instantiate it? For what? It would be another instance, not one of those created and used by the app. What is the reason of such a strange behaviour?

"would require a change to Spring Framework." I see two possible options here - to document this bug or to fix it. Second would definitely require a change. As every bug fix. It is just my opinion, of course.

I understand what workarounds I could use. There are many, starting from the decorator and finished with "do not use spring at all". But I have a bug report, not a question about workarounds. And therefore my first question would be: "do you consider it as a bug, when two documented Spring features ain't working together?"

Regarding the mve - my example is focused on the bug, not on the workaround or my app structure. Yes, I do use ObjectProvider#getObject to get instances, but I suppose it is completely unrelated to the bug itself and therefore irrelevant. Although I could add a n example:

@Service
public SomeServiceClass
{
  public someMethod(@Qualifier("sourceDataSource") ObjectProvider<DataSource> srcDataSourceProvider, CredSource credSource, int paremeterTypeId)
  {
     DataSource srcDataSource = srcDataSourceProvider.getObject(credSource.getDbUrl(paremeterTypeId),
                credSource.getDbUser(paremeterTypeId), credSource.getDbPassword(paremeterTypeId));
  }
}

but how could it help?

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Mar 13, 2025
@wilkinsona
Copy link
Member

Anyways, why actuator is it trying to instantiate it?

Actuator is trying to provide metrics for all DataSource beans. It is doing so in the best way that it can within the capabilities that Spring Framework provides. The use of a prototype DataSource bean is a previously unencountered edge case that hasn't been considered until now.

I understand what workarounds I could use. There are many, starting from the decorator and finished with "do not use spring at all". But I have a bug report, not a question about workarounds.

We can't process a bug report without fully understanding the problem. I also thought that us having a full understanding of the problem may help us to suggest some workarounds. You've now made it abundantly clear that you have no interest in us helping you to work around the current limitations so I'll stop wasting my time.

I'll discuss things with the Framework team to see what our options may be for retrieving all non-prototype beans from the context.

@BbIKTOP
Copy link
Author

BbIKTOP commented Mar 13, 2025

Well, I've added an example. Please confirm it is enough.

Actuator is trying to provide metrics for all DataSource beans.

It's clear. I just look a bit dumb, but actually, I'm mostly not. My point is, that there's no sense at all to provide any metrics for prototyped beans instances created internally by actuator, neither parametrized or not. I'd even dare to say that actuator should not create any user objects it has not been asked to.

You've now made it abundantly clear that you have no interest in us helping you to work around the current limitations so I'll stop wasting my time.

Workaround is clear - to retrieve datasource in different way, for example from @Service. As I probably have a kinda OCD, it's really harms me to write such a code. But since there's no other way, I'd maybe stick to that. It's really sad that in this case actuator is the only thing that prevents me to write normal clean code, but why, I use spring for 10+ years already )

You know, as a developer, I always try to fix my bugs as soon as possible. However, I’m quite familiar with another kind of developers -those who try to prove that "it's not a bug," "there are workarounds," or "provide us with an app that reproduces the bug, with at least 10,000 lines of code," etc. I see no point in wasting time arguing.

We can't process a bug report without fully understanding the problem.

The code I initially provided is enough to track the bug. This bug exists even if this prototyped bean is never instantiated, meaning I could provide an empty file as a usage example. But ok, I'll follow. Here's the example.

Well.
I have reported a bug. It’s up to the developers to decide whether to fix it or not. That’s why I asked the first and most important question: do you consider this a bug at all? From my perspective, it is, and I explained why. If you, as a developer, disagree, then there’s no reason to continue this discussion and waste time. However, if you agree that the current behavior is at least meaningless and leads to bugs (as demonstrated in the MVE I provided), I’d be happy to provide additional details, including those that may not seem directly related to the issue.

Regarding possible fix: I see here is a good place :

		@Bean
		DataSourcePoolMetadataMeterBinder dataSourcePoolMetadataMeterBinder(ConfigurableListableBeanFactory beanFactory,
				ObjectProvider<DataSourcePoolMetadataProvider> metadataProviders) {
			return new DataSourcePoolMetadataMeterBinder(
					SimpleAutowireCandidateResolver.resolveAutowireCandidates(beanFactory, DataSource.class),
					metadataProviders);
		}

It would be probably the simplest solution to allow to replace this bean with the one provided by the user. So I could filter the map returned by the resolveAutowireCandidates by the name or scope or anything else. Just a proposal.

@wilkinsona wilkinsona added type: bug A general bug and removed status: waiting-for-triage An issue we've not yet triaged status: feedback-provided Feedback has been provided labels Mar 18, 2025
@wilkinsona wilkinsona added this to the 3.4.x milestone Mar 18, 2025
@wilkinsona wilkinsona self-assigned this Mar 18, 2025
@wilkinsona wilkinsona changed the title Actuator throws an exception when using prototype scoped datasource bean Actuator throws an exception when using prototype scoped DataSource bean Mar 18, 2025
@wilkinsona wilkinsona modified the milestones: 3.4.x, 3.4.4 Mar 18, 2025
@BbIKTOP
Copy link
Author

BbIKTOP commented Mar 19, 2025

Thank you so much Andy!
btw I refactored my code just yesterday. Will try new actuator now, thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: bug A general bug
Projects
None yet
Development

No branches or pull requests

3 participants