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
A bit more involved, but here's one approach:
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();
...
}
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.
<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
.
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.
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).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With