• spring boot version: 3.2.3

DbConfig.java

@Configuration
public class DbConfig {


    @Bean
    @ConfigurationProperties(value = "spring.datasource.log")
    public DataSource logDatasource(){
        return new DruidDataSource();
    }

    @Bean
    @ConfigurationProperties(value = "spring.datasource.user")
    public DataSource userDatasource(){
        return new DruidDataSource();
    }

}

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>example</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.3</version>
        <relativePath />
    </parent>
    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>com.zaxxer</groupId>
                    <artifactId>HikariCP</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.20</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-tomcat</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jetty</artifactId>
        </dependency>
    </dependencies>

</project>

exception log:

2024-03-06T11:18:41.252+08:00 ERROR 22188 --- [           main] com.alibaba.druid.pool.DruidDataSource   : {dataSource-1} init error

java.sql.SQLException: url not set
    at com.alibaba.druid.pool.DruidDataSource.resolveDriver(DruidDataSource.java:1273) ~[druid-1.2.20.jar:na]
    at com.alibaba.druid.pool.DruidDataSource.init(DruidDataSource.java:898) ~[druid-1.2.20.jar:na]
    at com.alibaba.druid.pool.DruidDataSource.getConnection(DruidDataSource.java:1462) ~[druid-1.2.20.jar:na]
    at com.alibaba.druid.pool.DruidDataSource.getConnection(DruidDataSource.java:1458) ~[druid-1.2.20.jar:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
    at org.springframework.boot.context.properties.bind.JavaBeanBinder$BeanProperty.lambda$getValue$0(JavaBeanBinder.java:383) ~[spring-boot-3.2.3.jar:3.2.3]
    at org.springframework.boot.context.properties.bind.DefaultBindConstructorProvider.getBindConstructor(DefaultBindConstructorProvider.java:46) ~[spring-boot-3.2.3.jar:3.2.3]
    at org.springframework.boot.context.properties.bind.ValueObjectBinder$ValueObject.get(ValueObjectBinder.java:208) ~[spring-boot-3.2.3.jar:3.2.3]
    at org.springframework.boot.context.properties.bind.ValueObjectBinder.bind(ValueObjectBinder.java:75) ~[spring-boot-3.2.3.jar:3.2.3]
    at org.springframework.boot.context.properties.bind.Binder.lambda$bindDataObject$6(Binder.java:480) ~[spring-boot-3.2.3.jar:3.2.3]
    at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197) ~[na:na]
    at java.base/java.util.AbstractList$RandomAccessSpliterator.tryAdvance(AbstractList.java:706) ~[na:na]
    at java.base/java.util.stream.ReferencePipeline.forEachWithCancel(ReferencePipeline.java:129) ~[na:na]
    at java.base/java.util.stream.AbstractPipeline.copyIntoWithCancel(AbstractPipeline.java:527) ~[na:na]
    at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:513) ~[na:na]
    at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) ~[na:na]
    at java.base/java.util.stream.FindOps$FindOp.evaluateSequential(FindOps.java:150) ~[na:na]
    at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[na:na]
    at java.base/java.util.stream.ReferencePipeline.findFirst(ReferencePipeline.java:647) ~[na:na]
    at org.springframework.boot.context.properties.bind.Binder.fromDataObjectBinders(Binder.java:488) ~[spring-boot-3.2.3.jar:3.2.3]
    at org.springframework.boot.context.properties.bind.Binder.lambda$bindDataObject$7(Binder.java:479) ~[spring-boot-3.2.3.jar:3.2.3]
    at org.springframework.boot.context.properties.bind.Binder$Context.withIncreasedDepth(Binder.java:597) ~[spring-boot-3.2.3.jar:3.2.3]
    at org.springframework.boot.context.properties.bind.Binder$Context.withDataObject(Binder.java:583) ~[spring-boot-3.2.3.jar:3.2.3]
    at org.springframework.boot.context.properties.bind.Binder.bindDataObject(Binder.java:479) ~[spring-boot-3.2.3.jar:3.2.3]
    at org.springframework.boot.context.properties.bind.Binder.bindObject(Binder.java:418) ~[spring-boot-3.2.3.jar:3.2.3]
    at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:350) ~[spring-boot-3.2.3.jar:3.2.3]
    at org.springframework.boot.context.properties.bind.Binder.lambda$bindDataObject$5(Binder.java:477) ~[spring-boot-3.2.3.jar:3.2.3]
    at org.springframework.boot.context.properties.bind.JavaBeanBinder.bind(JavaBeanBinder.java:99) ~[spring-boot-3.2.3.jar:3.2.3]
    at org.springframework.boot.context.properties.bind.JavaBeanBinder.bind(JavaBeanBinder.java:87) ~[spring-boot-3.2.3.jar:3.2.3]
    at org.springframework.boot.context.properties.bind.JavaBeanBinder.bind(JavaBeanBinder.java:63) ~[spring-boot-3.2.3.jar:3.2.3]
    at org.springframework.boot.context.properties.bind.Binder.lambda$bindDataObject$6(Binder.java:480) ~[spring-boot-3.2.3.jar:3.2.3]
    at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197) ~[na:na]
    at java.base/java.util.AbstractList$RandomAccessSpliterator.tryAdvance(AbstractList.java:706) ~[na:na]
    at java.base/java.util.stream.ReferencePipeline.forEachWithCancel(ReferencePipeline.java:129) ~[na:na]
    at java.base/java.util.stream.AbstractPipeline.copyIntoWithCancel(AbstractPipeline.java:527) ~[na:na]
    at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:513) ~[na:na]
    at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) ~[na:na]
    at java.base/java.util.stream.FindOps$FindOp.evaluateSequential(FindOps.java:150) ~[na:na]
    at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[na:na]
    at java.base/java.util.stream.ReferencePipeline.findFirst(ReferencePipeline.java:647) ~[na:na]
    at org.springframework.boot.context.properties.bind.Binder.fromDataObjectBinders(Binder.java:488) ~[spring-boot-3.2.3.jar:3.2.3]
    at org.springframework.boot.context.properties.bind.Binder.lambda$bindDataObject$7(Binder.java:479) ~[spring-boot-3.2.3.jar:3.2.3]
    at org.springframework.boot.context.properties.bind.Binder$Context.withIncreasedDepth(Binder.java:597) ~[spring-boot-3.2.3.jar:3.2.3]
    at org.springframework.boot.context.properties.bind.Binder$Context.withDataObject(Binder.java:583) ~[spring-boot-3.2.3.jar:3.2.3]
    at org.springframework.boot.context.properties.bind.Binder.bindDataObject(Binder.java:479) ~[spring-boot-3.2.3.jar:3.2.3]
    at org.springframework.boot.context.properties.bind.Binder.bindObject(Binder.java:418) ~[spring-boot-3.2.3.jar:3.2.3]
    at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:350) ~[spring-boot-3.2.3.jar:3.2.3]
    at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:339) ~[spring-boot-3.2.3.jar:3.2.3]
    at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:269) ~[spring-boot-3.2.3.jar:3.2.3]
    at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:256) ~[spring-boot-3.2.3.jar:3.2.3]
    at org.springframework.boot.context.properties.ConfigurationPropertiesBinder.bind(ConfigurationPropertiesBinder.java:94) ~[spring-boot-3.2.3.jar:3.2.3]
    at org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor.bind(ConfigurationPropertiesBindingPostProcessor.java:96) ~[spring-boot-3.2.3.jar:3.2.3]
    at org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor.postProcessBeforeInitialization(ConfigurationPropertiesBindingPostProcessor.java:79) ~[spring-boot-3.2.3.jar:3.2.3]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:422) ~[spring-beans-6.1.4.jar:6.1.4]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1778) ~[spring-beans-6.1.4.jar:6.1.4]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:600) ~[spring-beans-6.1.4.jar:6.1.4]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:522) ~[spring-beans-6.1.4.jar:6.1.4]
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:325) ~[spring-beans-6.1.4.jar:6.1.4]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-6.1.4.jar:6.1.4]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:323) ~[spring-beans-6.1.4.jar:6.1.4]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-6.1.4.jar:6.1.4]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:975) ~[spring-beans-6.1.4.jar:6.1.4]
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:959) ~[spring-context-6.1.4.jar:6.1.4]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:624) ~[spring-context-6.1.4.jar:6.1.4]
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) ~[spring-boot-3.2.3.jar:3.2.3]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:754) ~[spring-boot-3.2.3.jar:3.2.3]
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:456) ~[spring-boot-3.2.3.jar:3.2.3]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:334) ~[spring-boot-3.2.3.jar:3.2.3]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1354) ~[spring-boot-3.2.3.jar:3.2.3]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1343) ~[spring-boot-3.2.3.jar:3.2.3]
    at org.example.App.main(App.java:20) ~[classes/:na]

After jetty is introduced, the DruidDataSource.getConnection method will be called when the @ConfigurationProperties property is filled.

Causes DruidDataSource.init method initialization exception

After removing jetty, the DruidDataSource.getConnection method is no longer called, which is weird.

 <!-- <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-jetty</artifactId>
  </dependency> -->

Reproduce project address: example

Comment From: wilkinsona

This is quite similar to https://github.com/spring-projects/spring-boot/issues/38514 which the underlying cause being the same non-enumerable property source that comes from JNDI being available. You can work around the problem by adding a spring.properties file to src/main/resources that contains spring.jndi.ignore=true.

The failure's occurring because DruidDataSource isn't entirely compatible with configuration property binding.

When all property sources are enumerable, binding can be optimised to only try to bind properties that are present in the environment. For example, if there are no spring.datasource.log.connection.* properties in the environment, the binder will never need to call getConnection() on the DruidDataSource.

When there's a non-enumerable property source in the environment, the binder does not know all of the properties that are available so it has to try to bind everything. As part of trying to bind everything, the binder's calling getConnection() so that it can then introspect its result for setter methods and determine which properties need to be bound to it. This call fails as it's happening before the URL had been set. Even if the URL had been set, the call would still be unwanted as it would leak a connection.

Hopefully the workaround above is enough to get you past the problem. I'll leave this open for now as I'm not sure what, if anything, we can do to address it. One option would be to provide a way to indicate properties that should be ignored when binding to a third-party class. Another would be for you to bind properties to a safe class of your own and then use Boot's PropertyMapper to apply those to the DruidDataSource instance. Lastly, you could explore binding properties to Boot's DataSourceProperties class and then building the DataSource from those properties:

java properties.initializeDataSourceBuilder().type(DruidDataSource.class).build();

Comment From: quaff

I know it's risky, but can we ignore JndiPropertySource by default since it's vulnerable and not much useful in practice?

Comment From: quaff

I requested Spring Framework team to disable JndiPropertySource by default. see https://github.com/spring-projects/spring-framework/issues/18598#issuecomment-1982234283

Comment From: philwebb

We've opened #39932 to deal with the Jetty JNDI issue. Once that has been done this issue is effectively a duplicate of #34616