Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

dynamically injecting property into spring

I have a spring config xml file which creates multiple beans and autowires one into others

Eg

<bean id="a" class="com.xyz.A">
        <property name="prop1" value="?" />
   </bean>

   <bean id="b" class="com.xyz.B">
        <property name="prop2" ref="a" />   
   </bean>

My creates application context by reading the above spring file. But the value of prop1 of A is dynamically known at run time.How do i inject this property dynamically?By dynamic i mean when the application starts.For example some properties come as command line inputs to the application. And this property should then be set as property of beans

I had given this example to simplify the problem.In real my dynamic parameter is database server url and i want to create connection pool dynamically

like image 751
user93796 Avatar asked Aug 21 '14 09:08

user93796


3 Answers

A bit more involved, but here's one approach:

  • in your main() method define another application context along the one you are already creating. The idea is that you are defining a List where the command line arguments will be stored and define this list as a bean (with the id args) in this application context:
public static void main(String[] args) {
    DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
    BeanDefinition beanDefinition = BeanDefinitionBuilder
            .rootBeanDefinition(Arrays.class, "asList")
            .addConstructorArgValue(args)
            .getBeanDefinition();
    beanFactory.registerBeanDefinition("args", beanDefinition);

    GenericApplicationContext parentContext= new GenericApplicationContext(beanFactory);
    parentContext.refresh();
    ...
}
  • Use the previously defined application context as a parent for the application context you already have in your main() method. Assuming you are creating a ClassPathXmlApplicationContext this would be the code:
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
            new String[] { "app-context.xml" }, parentContext);

where parentContext is the context created previously to hold the List of arguments.

  • then, in your xml, you'd use SPeL to easily access the list of arguments:
    <bean id="a" class="com.foo.bar.ClassA">
        <property name="prop1" value="#{args[0]}" />
    </bean>

    <bean id="b" class="com.foo.bar.ClassB">
        <property name="prop2" ref="a" />
    </bean>

notice in xml the name of the argument used in the SPeL expression is args which is the exact name used to define the List in the parentContext.

like image 80
Andrei Stefan Avatar answered Sep 24 '22 01:09

Andrei Stefan


Here is another way to inject property. Mine is similar to that geoand suggested, since I also use java configuration than xml. But little more simpler by using SimpleCommandLinePropertySource and also little more flexible. Below my example does work with or without using spring boot.

Main class

public static void main(String [] args) {
    SimpleCommandLinePropertySource ps = new SimpleCommandLinePropertySource(args);
    @SuppressWarnings("resource")
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.getEnvironment().getPropertySources().addFirst(ps);
    ctx.register(ApplicationConfig.class);
    ctx.refresh();
}

ApplicationConfig.java

This is java configuration class and read application.properties inside of package as default, but also try to read external property file from command line option --config.location=/some/path/app.properties If external property file is not given, then it will be gracefully ignored by ignoreResourceNotFound = true

@Configuration
@EnableScheduling
@ComponentScan("com.mycompany.package")
@PropertySource(
        value = {"classpath:/application.properties", "file:${config.location}"},
        ignoreResourceNotFound = true
    )
public class ApplicationConfig {

    @Bean
    public static PropertySourcesPlaceholderConfigurer propertyConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }
}

Property Injection Annotation

Now any place has @Value annotation or other property injection annotation like @Scheduled expect the value to be bound by the property value given on external file or command line option both.

@Component
public class MyComponent {

    @Value("${my.property.data}")
    private String myPropertyData;


    @Scheduled(fixedDelayString = "${schedule.delay.period}")
    public void run() {
         :
    }
}

So, you can put below property on external file and give the file through command line option --config.location=/some/path/app.properties

app.properties

my.property.data=test
schedule.delay.period=60000

or you can inject each property value separately via command line

--my.property.data=test --schedule.delay.period=60000

FYI, spring-boot uses --spring.config.location command line option for external property file already.

like image 39
Steve Park Avatar answered Sep 22 '22 01:09

Steve Park


Since what you are looking to do is to inject command line arguments into beans, I suggest you take a look at this.

The essence of what is mentioned there is that you can add a CommandLinePropertySource (in the exact same way you would use a property source for reading arguments from a properties file) and then just to refer to them using regular Spring methods (either getting them through the environment or referring to them using Spring EL).

For completeness I am posting the code that is found in the first link

public class Main {
    public static void main(String... args) {
        //initialize the command line parsing stuff
        OptionParser parser = new OptionParser();
        parser.accepts("greeting").withRequiredArg();
        OptionSet options = parser.parse(args);

        //create the actual Spring PropertySource
        PropertySource<?> ps = new JOptCommandLinePropertySource(options);

        //setup the Spring context
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
        ctx.getEnvironment().getPropertySources().addLast(ps); //register the property source with the environment

        ctx.register(Greeter.class);
        ctx.refresh();
        Greeter greeter = ctx.getBean(Greeter.class);
        greeter.sayGreeting();
    }
}

@Component
class Greeter {
    @Inject private Environment env;


    //the following would also work
    //@Value("${greeting}")
    //private String greeting;        

    /**
     * Print out the 'greeting' property if it exists, and otherwise, "Welcome!".
     */
    public void sayGreeting() {
        System.out.println(env.getProperty("greeting", "Welcome!"));
    }
}

In your case, if you use the CommandLinePropertySource the xml configuration would look like:

   <bean id="a" class="com.xyz.A">
        <property name="prop1" value="${db.url}" />
   </bean>

   <bean id="b" class="com.xyz.B">
        <property name="prop2" ref="a" />   
   </bean>

On a final note, if you are using Spring Boot, then Spring Boot will automatically add the command line properties to the Spring environment which means that you don't have to register the PropertySource on your own (check this for more details).

like image 20
geoand Avatar answered Sep 24 '22 01:09

geoand