spring load dynamically beans from xml which use property injection - java

I need to load bean definitions from XML. The file is in a remote location (http) which is configured by properties. The properties should also be loaded from a remote location (a spring cloud config server)
constraints:
Spring 4.3.14 (not boot)
should be in runtime and after my properties already loaded
beans defined in xml are referencing properties in context
server URI (to fetch the xml from) should be in properties and not environment variable or profile depandant
The current setup I have works well when MY_XML_URI is passed as environment variable:
${SPRING_CONFIG_URI}/master/application-${spring.profiles.active}.properties
<import resource="${MY_XML_URI}/myBeans.xml"/>
And in the remote location, myBeans.xml with lots of beans e.g.
<bean name="mySpecialBean" class="com.example.MyGenericBean">
<constructor-arg value="mySpecialBean"/>
<constructor-arg value="${special.bean.config.expression}"/>
</bean>
However trouble starts when I want to get MY_XML_URI from the properties context, it doesn't resolve
…
I have tried several approaches e.g:
java configuration class with #ImportResource({"${xml.server.uri}"})
but the properties are not loaded yet so it not converting to real value of xml.server.uri .
#Configuration
#ImportResource({"${xml.server.uri:http://localhost:8888}/myBeans.xml"})
public class MyConfiguration {}
expose dummy bean which fetch xml as resource and load the beans to parent applicationcontext - i must have it available to other beans dependant on those beans defined in xml. This solution was not injecting the properties context to my beans so failed to init them.
#Configuration
public class RiskConfig {
#Value("${xml.server.uri}")
private String xmlUri;
#Autowired
#Bean
public Object myBean(ApplicationContext applicationContext) {
Resource resource = applicationContext.getResource(xmlUri + "myBeans.xml");
// not working since its not loading the beans to the main context
// GenericApplicationContext genericApplicationContext = new GenericApplicationContext(applicationContext);
// XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(genericApplicationContext);
// reader.loadBeanDefinitions(resource);
AutowireCapableBeanFactory factory = applicationContext.getAutowireCapableBeanFactory();
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) factory;
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(registry);
reader.loadBeanDefinitions(resource);
return new Object();
}
}
Finally - is there a way to load the beans from xml programmatically to parent application context (which is already exist) though they are injected with properties.

Related

How can i get my Application Context in xml defination while working in Spring Boot

What is the xml equivalent of the
#Autowired
private ApplicationContext appContext;
?
P.S.: When I try to google it I got a million results about how ApplicationContext works but not how to get it in the xml definition. The project is all writen using xml definition so I need to find a way how to do it without anotations.
First, configure your spring beans in file applicationContext.xml For example:-
<bean id="beanId"
class="com.java.spring.MyClassName">
</bean>
load the spring configuration file and retrieve bean from spring container
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("applicationContext.xml");
MyClass myBean = context.getBean("beanId",MyClass.class);

Spring property initialization

I have an xml configurable Spring context with the following property placeholders:
<context:property-placeholder
properties-ref="dbProperties" location="classpath:logmessages.properties" order="2"/>
<context:property-placeholder location="classpath:application.properties" order="1"/>
DB properties configuration bean is as follows:
<bean id="dbProperties"
class="com.example.DatabasePropertiesLoader">
<property name="path" value="${db.path}"/>
</bean>
As it comes from the name, this bean loads some properties from the database, for example endpoints and credentials to other services. However, to access the database that keeps this properties it also needs credentials, which are kept in application.properties:
public class DatabasePropertiesLoader extends AbstractFactoryBean<Properties> {
private String path;
#Override
protected Properties createInstance() throws Exception {
// logic loading properties
}
#Override
public Class<Properties> getObjectType() {
return Properties.class;
}
}
Path property kept in application.properties file:
db.path=localhost:7777
As you see, this bean requires "path" property to be injected to be created.
However, it can't be done, because injected value is null. I guess that Spring only knows about application.properties file, not about its contents. Is there any way to solve this?
You need to tell spring to use the properties as below.
#Value("${db.path}")
private String path;

SpringFramework: set bean name programmatically

For springframwork based application, when using xml to declare beans, bean id can be configured by passing a unique value and even a parameter and then solve the value at runtime.
Now I hope to replace all xml configuration to java annotation.
Say I want to create two database beans with different id.
bean.xml
<bean id="A.database" class="org.apache.commons.dbcp.BasicDataSource">
<!-- collaborators and configuration for this bean go here -->
</bean>
<bean id="B.database" class="org.apache.commons.dbcp.BasicDataSource">
<!-- collaborators and configuration for this bean go here -->
</bean>
Then I optimize the upper code to one bean with two property file for two application
bean.xml
<bean id="${appName.database}" class="org.apache.commons.dbcp.BasicDataSource">
<!-- collaborators and configuration for this bean go here -->
</bean>
applicationA.properties
appName.database=A.database
applicationB.properties
appName.database=B.database
The whole application is composed of "framework" module which provides beans common for each application, like database bean, jdbcTemplate bean, and "application" module which provides property value for placeholder and initializes beans with unique id. So even if I start multiple application at the same time, they will find corresponding bean from the context.
Generally speaking, I hope to do
#Bean(name = "${beanName}")
public ABean getBean() {}
and resolve ${beanName} at application level.
By reading SpringFramwork document, I found the answer: BeanNameGenerator
NameGenerator.class
public class NameGenerator implements BeanNameGenerator{
#Override
public String generateBeanName(BeanDefinition definition,
BeanDefinitionRegistry registry) {
if(definition.getBeanClassName().contains("Toto")) {
return "toto";
}
return return definition.getBeanClassName();
}
}
AppConfiguration.class
#Configuration
#ComponentScan(basePackages = {"com.example.domain"}, nameGenerator = NameGenerator.class)
public class Config {
}
Domain class with #Component
#Component
public class Toto {
private int id;
}
BootApplication with domain bean name : toto
#SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ConfigurableApplicationContext ctx = SpringApplication.run(
DemoApplication.class, args);
for (String name : ctx.getBeanNamesForType(Toto.class)) {
System.out.println(name);
}
}
}
If you want to follow that type of approach create multiple configuration classes you annotated with different Spring profiles.
At start up you can pass a parameter on which profile to use and hence what beans to load within the associated profile.
A more efficient way to do it is use the same property naming convention across all application .properties files. Set a parameter placeholder for the file name which resolves to a JVM arg passed at runtime which is loaded by the#PropertySource annotation.
There's no need to have duplicate beans defined for different environments if it's just properties that are changing.

Spring XML and java configuration together

I have a project with XML based spring configuration and want to define some new beans but in java class based configuration.
How to implement this so that i can also refer beans of java configuration in my XML config file.
You can import your xml config into java configuration, like so:
#Configuration
#ImportResource("classpath:pl/rav/springtest/resources/app.xml")
public class AppConfig {
#Bean(name="myMessageService")
MessageService mockMessageService() {
return new MessageServiceImpl();
}
}
When you want to refer from xml to the bean you just point to its name:
<property name="msgSrv">
<ref bean="myMessageService"/>
</property>
Then use ApplicationContext based on your java configuration.
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
As you started with xml config, you may be interested in other way around (importing java config into xml config) which I think was explained here

Define '*.properties' file location in Spring

I have Spring context with several beans, like:
<bean id="anyBean" class="com.my.app.AnyBean"
p:test_user="${any1}"
p:test_pass="${any2}">
</bean>
To resolve these placeholders (${any1} and ${any2}) I use:
<bean id="propertyPlaceholderConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer" p:location="my.properties" />
It works fine, but I need to define location of the 'my.properties' from 'main' method of the main class. Like this:
ApplicationContext context = new ClassPathXmlApplicationContext(my_xml_config.xml);
PropertyPlaceholderConfigurer ppc = context.getBean("propertyPlaceholderConfigurer", PropertyPlaceholderConfigurer.class);
Resource resource = context.getResource("path/to/my.properties");
ppc.setLocation(resource);
But when I try to launch it:
Exception in thread "main"
org.springframework.beans.factory.BeanDefinitionStoreException:
Invalid bean definition with name 'AnyBean' defined in class path
resource [my_xml_config.xml]: Could not resolve placeholder 'any1' in
string value "${any1}"
Could you hint is there any way to resolve this problem?
By the time you try to get a bean
PropertyPlaceholderConfigurer ppc = context.getBean("propertyPlaceholderConfigurer", PropertyPlaceholderConfigurer.class);
Spring has already tried to refresh your context and failed since the property isn't present. You need to prevent Spring from doing this by specifying it in the constructor
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"my_xml_config.xml"}, false);
Since the ApplicationContext is not refreshed, the PropertyPlaceholderConfigurer bean doesn't exist.
For this to work, you need to use a PropertySourcesPlaceholderConfigurer instead of a PropertyPlaceholderConfigurer (thanks M.Deinum). You can declare it in the XML in the same way as you did for the PropertyPlaceholderConfigurer bean, or use
<context:property-placeholder />
You need to take advantage of the fact that PropertySourcesPlaceholderConfigurer has its own locations, but also uses the PropertySource instances registered in the ApplicationContext's Environment.
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"your_config.xml"}, false);
// all sorts of constructors, many options for finding the resource
ResourcePropertySource properties = new ResourcePropertySource("path/to/my.properties");
context.getEnvironment().getPropertySources().addLast(properties);
context.refresh();

Categories