Sparm
Sparm

Reputation: 566

Loading properties using @Value into a BeanFactory object using @Bean in Spring Boot

Can anyone help me with a Spring Boot problem?

I want to create a factory bean as part of my application context but I want to be able to instantiate it with injected property values. However it seems that Spring will load FactoryBeans before anything else as demonstrated here:

import java.util.ArrayList;
import java.util.List;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.AbstractFactoryBean;
import org.springframework.beans.factory.config.ListFactoryBean;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;

@EnableAutoConfiguration
public class TestClass
{
    @Value("${test.value}")
    String value;

    @Bean
    public Object test1()
    {
        System.out.println("test.value=" + value );

        List<String> list = new ArrayList<String>();
        ListFactoryBean factory = new ListFactoryBean();
        factory.setSourceList(list);

        return factory;
    }

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

When run with

java -Dtest.value=HELLO -jar myTest.jar

It loads in the value correctly:

test.value=HELLO

However, when I specify that the bean to be loaded is in fact a factory bean, and run it in the same way:

import java.util.ArrayList;
import java.util.List;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.AbstractFactoryBean;
import org.springframework.beans.factory.config.ListFactoryBean;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;

@EnableAutoConfiguration
public class TestClass
{
    @Value("${test.value}")
    String value;

    @Bean
    public AbstractFactoryBean test1()
    {
        System.out.println("test.value=" + value );

        List<String> list = new ArrayList<String>();
        ListFactoryBean factory = new ListFactoryBean();
        factory.setSourceList(list);

        return factory;
    }

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

The value is null because it hasn't been injected yet.

test.value=null

Is there any way around this?

Thanks

Upvotes: 1

Views: 11054

Answers (2)

MariuszS
MariuszS

Reputation: 31595

Take look at Empowering your apps with Spring Boot's property support

There is new annotation @EnableConfigurationProperties in Spring Boot Actuator

The Spring Environment is a collection of name-value pairs taken from (in order of decreasing precedence)

1) the command line,

2) the external configuration file,

3) System properties,

4) the OS environment.

There is also possible to define application properties (external configuration) in YAML format.

Upvotes: 0

Dave Syer
Dave Syer

Reputation: 58134

Spring often has to query bean definitions for the type of object they produce. Factory beans are always problematic because they can cause dependency cascades in a futile attempt to resolve all dynamic information available before asking for the type.

I think ListFactoryBean is insufficiently precise about its product type (getObjectType() can only return a non-generic List.class). You might be able to write your own factory that is parameterized with the correct generic type. Or you might get away with just declaring the @Bean to return a FactoryBean<List<String>.

Another tip is to move the @Bean definition to a separate class (e.g. a nested static one) so that it can be instantiated independently of the rest of the application context. E.g.

@EnableAutoConfiguration
public class TestClass
{

  protected static class NestedConfiguration {

    @Value("${test.value}")
    String value;

    @Bean
    public FactoryBean<Properties> test1()
    {
        System.out.println("test.value=" + value );
        // ...
        return factory;
    }

  }
    ...

}

Not really a Boot question this one so you might consider changing the tags.

Upvotes: 3

Related Questions