dynamically injecting property into spring - java

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:
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.

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).

You can use the Supplier pattern (comes with Java 8 or Guava library for example)
<bean id="a" class="com.xyz.A">
<property name="prop1" value="prop1Supplier" />
</bean>
<bean id="b" class="com.xyz.B">
<property name="prop2" ref="a" />
</bean>
<bean id="prop1Supplier" class="com.xyz.Prop1Supplier"/>
Then you'll need to use prop1.get() in order to retrieve the value at runtime

Related

SpEL: get current bean name during bean instantiation

I am attempting to use SpEL to get the name of the bean currently being instantiated to allow multiple beans of same class to be created with different properties supplied by #PropertySource. I am hoping for something like the following:
public class SampleBean {
#Value("${#{CurrentBeanName}.val}")
private String val
}
Other bean:
public class OtherBean {
#Autowired
#Qualifier(name="BeanA")
SampleBean beanA;
#Autowired
#Qualifier(name="BeanB")
SampleBean beanB;
}
properties file:
BeanA.val=VALUE A
BeanB.val=VALUE B
If I add beanName=BeanA to my properties file, I am able to get this to work with
#Value("${${beanName}.val}")
Any ideas on what to do for #{BeanName}? If this is impossible then so be it, but if it works it would be much cleaner than my current solution.
EDIT:
Or any way to pass a constant from the xml bean definition to SpEL? example:
<bean id="BeanA" class="...">
<property name="prefix" value="BeanA"/>
</bean>
java:
public class SampleBean {
#Value("${#{prefix}.val}")
private String val
}
Any sort of attribute or anything would work
EDIT2:
This is trivial in old XML based config
spring.xml:
<bean id="beanA" class="SampleBean">
<property name="val" value="${BeanA.val}"/>
</bean>
<bean id="beanB" class="SampleBean">
<property name="val" value="${BeanB.val}"/>
</bean>
SampleBean.java:
public class SampleBean {
private String val;
public void setVal (String val) {
this.val = val;
}
}
However when switching to the new #Value annotations to get rid of all the setters, it seems non-singletons with diff properties aren't supported (i.e. no way to dynamically filter #Value arguments on bean creation)
No; it is not possible to reference the current bean.
EDIT
To address your comment below, the Java Configuration equivalent of
<bean id="BeanA" class="com.my.Foo">
<property name="prefix" value="BeanA"/>
</bean>
is
#Bean
public Foo BeanA() {
Foo a = new Foo();
a.setPrefix("BeanA");
}
although, by convention, you'd probably name it beanA.
If you have singleton bean types you could just use a static final variable for the name and then reference that. But the bigger issue is that you will be breaking the Spring inversion of control principals if you begin depending on Spring bean names, which is why this sort of thing isn't done. Pretty much want to focus on creating modules and domains for your project. If you begin accessing components coming from the Spring Context directly (such as the bean name) you will find that your modules will become brittle, hard to change and very hard to reason about as they begin to depend on behaviour from seemingly unrelated modules, such as the Spring Dependency Injection Framework. Although you may have a valid use-case for doing this you just need to be very very careful.

Use of SystemPropertyInitializer to set System Property before setting property placeholder

According to this answer, you can use the Spring Batch class org.springframework.batch.support.SystemPropertyInitializer to set a System Property during startup of a Spring Context.
In particular, I was hoping to be able to use it to set ENVIRONMENT because part of Spring Batch config reads:
<bean id="placeholderProperties" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:/org/springframework/batch/admin/bootstrap/batch.properties</value>
<value>classpath:batch-default.properties</value>
<value>classpath:batch-${ENVIRONMENT:hsql}.properties</value>
</list>
</property>
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" />
<property name="ignoreResourceNotFound" value="true" />
<property name="ignoreUnresolvablePlaceholders" value="false" />
<property name="order" value="1" />
</bean>
But SystemPropertyInitializer uses afterPropertiesSet() to set the System Property, and apparently this happens after the configuration of PropertyPlaceholderConfigurer.
Is it possible to achieve this?
The easiest solution would be to pass in the environment property as a command-line argument, so it can be resolved as a system property.
If that's not an option you can implement a ApplicationContextInitializer that promotes environment properties to system properties.
public class EnvironmentPropertyInitializer implements
ApplicationContextInitializer<ConfigurableApplicationContext> {
boolean override = false; //change if you prefer envionment over command line args
#Override
public void initialize(final ConfigurableApplicationContext applicationContext) {
for (Entry<String, String> environmentProp : System.getenv().entrySet()) {
String key = environmentProp.getKey();
if (override || System.getProperty(key) == null) {
System.setProperty(key, environmentProp.getValue());
}
}
}
}
Here it looks like you're using Spring Batch Admin, so you can register your initializer with a slight addition to the web.xml file:
<context-param>
<param-name>contextInitializerClasses</param-name>
<param-value>org.your.package.EnvironmentPropertyInitializer</param-value>
</context-param>
Adding Background Since a Comment Didn't Seem Sufficient: Here's the relevant classes and the order in which they are called/evaluated.
The ApplicationContextInitializer tells the Spring Application how to load an application context and can be used to set bean profiles, and change other aspects of the context. This gets executed before the context gets completely created.
The PropertyPlaceholderConfigurer is a BeanFactoryPostProcessor and calls postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory). This modifies the BeanFactory to allow for resolution of properties like ${my.property:some.default} when setting the properties of a bean as it is created by the BeanFactory.
The SystemPropertyInitializer implements InitializingBean and calls afterPropertiesSet(). This method runs after a bean is instantiated and the properties have been set.
So you're right that the SystemPropertyInitializer will not help here since it evaluates after the properties are set on the PropertyPlaceholderConfigurer. The ApplicationContextInitializer, however, will be able to promote those environment properties to system properties so they can be interpreted by the XML.
And one more note that I forgot to mention, one of the first declared beans will need to be:
<context:property-placeholder/>
Though it seems redundant, it will allow your PropertyPlaceholderConfigurer bean to evaluate ${ENVIRONMENT:hsql} correctly by using the environment properties you just promoted.

Auto-wire third party library in my spring managed component

I am using a third party library in my application to do some task. They have provided a wrapper that I've added in my project using maven. For using this wrapper we have to give an access key to their client class in order to use it's functionality. For ex:
final WeatherApiService was = new WeatherApiServiceImpl(accessKey);
final WeatherApiClient weatherApiClient = new WeatherApiClient(was);
What I want is to remove the above code (Since it's kind of Singleton and should be registered in spring context when the application is being started) and do something so that I can just autowire the WeatherApiClient and we are good to go. (wrapper isn't using spring FYI). Below is what I did is in my spring context I registered two beans and put the access-key is web.xml.
spring-context.xml
<bean id="was" class="my.librarypath.WeatherApiService ">
<constructor-arg type="java.lang.String" value="${accessKeyFromWebXml}"/>
</bean>
<bean id="weatherApiClient" class="my.librarypath.WeatherApiClient">
<constructor-arg type="my.librarypath.WeatherApiService" value="was"/>
</bean>
my component that will use the third party library
#Component("myComponent")
public class MyComponent IComponent {
#Resource(name = "weatherApiClient") // <--- getting Error here i.e: Couldn't aurtowire, bean should be of String type
private String weatherApiClient;
public void myFunction() {
weatherApiClient.getWeather();
}
}
Can someone confirm if I'm doing it right or is there any best practices options available !?
Ther were two issues:
<bean id="weatherApiClient" class="my.librarypath.WeatherApiClient">
<constructor-arg type="my.librarypath.WeatherApiService" value="was"/>
// ^---- should be ref
</bean>
Secondly, I was using String instead of WeatherApiClient. MY BAD :/
#Resource(name = "weatherApiClient")
private String weatherApiClient;
// ^---- this one should have to be WeatherApiClient

What is the meaning of <property> tag in [servlet-name]-servlet.xml of Spring Web MVC?

I got the following bean declearation in my [servlet-name]-servlet.xml of my Dynamic Web Project using Spring Web MVC?
I have read quite a lot of documents but stil couldnt understand what is the purpose of having those property tags?
<bean name="abcController" parent="defController"
class="abcController">
<constructor-arg ref="staticService" />
<property name="commandClass" value="abcCommand" />
<property name="property2" value="search" />
<property name="property3" value="true" />
<property name="formView" value="/someValue" />
</bean>
I know that the property could a field in abcController class, but there is no such a field named formView in abcController class!
Does anyone could help me out?
That xml file is used to create fields without having those fields coded in the file itself.
// This is used to Start the ApplicationContext Container and to get the Bean of AbcCotroller
ApplicationContext context =
new ClassPathXmlApplicationContext("[servlet-name]-servlet.xml");
abcController obj = (abcController) context.getBean("abcController");
You could later on use the beans in your code:
obj.getFormView(); //this will return '/somevalue'
//Bean.java
public class SampleBean{
private String message;
public void setMessage(String message){
this.message=message; //setter injection
}
public void ShowMessage(){
system.out.println("Message"+message);
}
}
//Main.java
Class Main{
public Static Void Main(String args[]){
//TO Start the ApplicationContext container
ApplicationContext applicationcontext = new ClassPathXmlApplicationCOntext("Spring.xml");
//To Read the SampleBean
Object o=applicationcontext .getBean("SampleBean");
SampleBean sampleBean=(SampleBean)o;
//Invoke the Method
sampleBean.ShowMessage();
}
}
//Spring.Xml
// you need to configure some more nameSpace which are required for Spring and Xml
<bean id="sampleBean" class="SampleBean">
<property name="message" value="this is the use of property Tag"/>
</bean>
//output :This is the use or Property Tag
explanation: when we want to perform Setter Injection we go for the Property
Tag
In spring we have some dependency injections like setter,constructor,interface,lookup method injection
when we use the Setter Injection first Dependent class object is create and next the dependency class object is created

Distinguishing between beans loaded from different spring configuration files

our project is based on Spring IoC which provides easy extensibility, meaning the functionality of our project is extended by what we call extensions (or plug-ins) that provide a few additional Spring xml configuration files + new code.
The problem is I have to somehow distinguish between beans that have been loaded from different xml files.
Example: two extensions (let's call them A and B) installed two additional spring files: A.xml and B.xml. Both these files define beans of the same Java class. The framework loads all beans of this class (using autowiring) to some factory and later uses this list of beans in specific algorithms. But it should be able to know that what extension a bean is defined by.
Of course I can add an additional required property (for example name or code of the extension) for the beans and then extensions developers will have to fill it for every bean definition, but this is error prone: the property should be the same for all the beans of some particular extension, while it is filled for every bean. I'm looking for a more elegant solution that is less verbose.
Thanks.
It sounds like each of your extensions should be defined within its own application context. Each of these contexts would share a single parent context, which would be your application "core".
This shoulkd give you an easier way of knowing which bean came from what, since you'd have to go through each context to obtain the beans to start with. Also, by isolating each extension in its own context, you reduce the possibility of beans clashing.
Configure each extension in a separate application context. In each application context, define a BeanPostProcessor which saves every bean the application context defines to a registry that maps beans to application contexts.
Configure an application context which will be the parent for each extension application context. In this configuration file, define a bean which maps bean names to application context identifiers.
<!-- parent.xml -->
<beans>
<bean
id="beanNameToApplicationContextIdMap"
class="java.util.HashMap"/>
</beans>
The extension application context configuration file defines an instance of BeanNameToApplicationContextIdMapInserter, which is a custom class implementing the BeanPostProcessor interface. The applicationContextId property is configured to a string identifying the application context. The map property configures the instance to update the map defined in the parent application context.
<!-- alpha.xml -->
<beans>
<bean class="com.example.BeanNameToApplicationContextIdMapInserter">
<property name="applicationContextId" value="alpha"/>
<property name="map" ref="beanNameToApplicationContextIdMap"/>
</bean>
<bean id="alphaService" class="...">
</bean>
</beans>
The BeanNameToApplicationContextIdMapInserter source code:
public class BeanNameToApplicationContextIdMapInserter implements BeanPostProcessor {
private String applicationContextId;
private HashMap<String, String> map;
public void setApplicationContextId(String id) {
this.applicationContextId = id;
}
public void setMap(HashMap<String, String> map) {
this.map = map;
}
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException
{
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException
{
map.put(beanName, applicationContextId);
return bean;
}
}
You can use SingletonBeanFactoryLocator to configure the parent application context and each extension application context.
<!-- beanRefFactory.xml -->
<beans>
<bean
id="parentApplicationContext"
class="org.springframework.context.support.ClassPathXmlApplicationContext">
<constructor-arg>
<value>parent.xml</value>
</constructor-arg>
</bean>
<bean
id="alphaApplicationContext"
class="org.springframework.context.support.ClassPathXmlApplicationContext">
<constructor-arg>
<value>alpha.xml</value>
</constructor-arg>
<constructor-arg>
<ref bean="parentApplicationContext"/>
</constructor-arg>
</bean>
<bean
id="bravoApplicationContext"
class="org.springframework.context.support.ClassPathXmlApplicationContext">
<constructor-arg>
<value>bravo.xml</value>
</constructor-arg>
<constructor-arg>
<ref bean="parentApplicationContext"/>
</constructor-arg>
</bean>
</beans>
Here is example code that reads the bean to application context map.
public class Main {
private static ApplicationContext getApplicationContext(String name) {
BeanFactoryLocator bfl = SingletonBeanFactoryLocator.getInstance();
BeanFactoryReference bfr = bfl.useBeanFactory(name);
return (ApplicationContext) bfr.getFactory();
}
public static void main(String[] args) {
ApplicationContext parentApplicationContext =
getApplicationContext("parentApplicationContext");
HashMap<String, String> map = (HashMap<String, String>)
parentApplicationContext.getBean("beanNameToApplicationContextIdMap");
System.out.println(map.get("alphaService"));
System.out.println(map.get("bravoService"));
}
}

Categories

Resources