Nate Glenn
Nate Glenn

Reputation: 6744

Spring fails to autoinject constructor parameters

I am a complete Spring beginner and I am trying to get basic configuration with Spring Boot working. I would like to use constructor injection wherever possible. However, Spring is throwing exceptions that I do not understand. I've shortened the problematic code for easier reading:

My config YAML file (I have snake yaml on the classpath):

database:
    inactive_timeout: 100
    active_jdbc_connections:
        # this is a list with one property in each element
        - name: foo
        - name: bar
        - name: baz
        - name: qux

The application code:

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {
    private final DBProperties dbProperties;
    DBProperties getDbProperties() {
        return dbProperties;
    }

    public DemoApplication(DBProperties dbProperties) {
        this.dbProperties = dbProperties;
    }

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

And the class that Spring is failing to wire properly:

package com.example.demo;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.List;

@ConfigurationProperties(prefix = "database")
@Component
public class DBProperties {
    private final List<ConnectionProperties> activeJdbcConnections;
    private int inactiveTimeout;

    // ERROR: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'int' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
    public DBProperties(List<ConnectionProperties> activeJdbcConnections, int inactiveTimeout) {
        this.activeJdbcConnections = activeJdbcConnections;
        this.inactiveTimeout = inactiveTimeout;
    }

    public List<ConnectionProperties> getActiveJdbcConnections() {
        return activeJdbcConnections;
    }

    public int getInactiveTimeout() {
        return inactiveTimeout;
    }

    @Component
    public static class ConnectionProperties {
        private String name;

        // ERROR: Parameter 0 of constructor in com.example.demo.DBProperties$ConnectionProperties required a bean of type 'java.lang.String' that could not be found.
        public ConnectionProperties(String name){
            this.name = name;
        }

        public String getName() {
            return name;
        }
    }
}

There are two separate errors. First, in the DBProperties constructor Spring is not able to wire the integer inactiveTimeout, even though it has no trouble wiring the parameter activeJdbcConnections. This can be solved using the @Value parameter, but this is undesirable because @Value does not recognize the prefix specified by @ConfigurationParameters, and so the prefix must be repeated for each @Value annotation.

Second, Spring cannot wire the name parameter of ConnectionProperties. Spring is able to work with a setter if it is added, but as stated above I want to work with constructor injection. As the name of the parameter matches the property I want wired, I do not understand the problem here. As a side note, I was unable to solve this with an @Value annotation, as I do not know the syntax for specifying a property from the current list element.

How can I get Spring to properly instantiate my configuration classes using constructor injection?

Upvotes: 2

Views: 5657

Answers (1)

Plog
Plog

Reputation: 9622

There are a few issues with your approach I think. Firstly you can't define beans of type int and String (even if you did try to define them somewhere) so the name and inactiveTimeout will never be available as beans to autowire through constructor injection.

Secondly, @Components when scanned are Spring singleton beans by default which means that there is only one instance of them per Spring context. It doesn't really then make sense for ConnectionProperties to be a component if you're passing it in a list for the DBProperties constructor since there can only be one instatiated ConnectionProperties so it would be a bit of a short list!

If you want DBProperties to be a bean with some parameters provided in its constructor then you probably want to define it as a @Bean directly and call the constructor yourself. Spring can't work out what parameters you want to put in there! Something like:

@Bean
public DBProperties dbProperties() {
    ...
    return new DBProperties(myArray, myNumber);
}

Then this DBProperties bean will be available to be Constructor injected anywhere else in your Spring boot application.

Upvotes: 1

Related Questions