I have loaded the bean definitions in the spring application context pro grammatically. (xml configuration is empty). I need to do the reverse process here. Currently I have the context loaded with the bean definitions. Now I want to convert these to xml configuration files. Anybody has an idea on how to do it ?
Below given is sample method.
static ApplicationContext appContext = new ClassPathXmlApplicationContext("beans.xml"); //beans.xml has no bean definitions
AutowireCapableBeanFactory factory = appContext.getAutowireCapableBeanFactory();
BeanDefinitionRegistrry registry = (BeanDefinitionRegistry) factory;
GenericBeanDefinition beanDefinition;
public void registerBeanDefintions(List<SimpleField> simpleFields) {
if (null != simpleFields) {
String beanId;
for (SimpleField simpleField : simpleFields) {
beanId = simpleField.getDataField().replace(".", "_");
beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(SimpleField.class);
beanDefinition.setAutowireCandidate(true);
MutablePropertyValues values = new MutablePropertyValues();
values.add("engineFields", simpleField.getEngineFields());
values.add("dataField", simpleField.getDataField());
values.add("fieldDataType", simpleField.getFieldDataType());
values.add("setDefaultValueIfNull", simpleField.getSetDefaultValueIfNull());
beanDefinition.setPropertyValues(values);
if (!factory.containsBean(beanId)) {
registry.registerBeanDefinition(beanId, beanDefinition); factory.autowireBeanProperties(this,AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, true);
}}}}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns = "http://www.springframework.org/schema/beans"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation = "http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
</beans>
The loaded beans can be accessed as appContext.getBean("beanName").
Finally, the solution is here!!! Let's check it out.
Problem statement
As we all know, in a spring application we provide configuration information through ‘ApplicationContext’. There are multiple classes provided by spring framework that implements this interface and helps us use configuration information in applications, and ClassPathXmlApplicationContext is one among them. Usually in this case all the bean definitions are configured in a single standalone xml file commonly termed as ‘application-context.xml’.
However, in larger project structures, the best practice is to keep multiple spring configuration files for easy maintainability and modularity than stacking up everything to one single file. In that case the best thing to do is to organize all your Spring bean configuration files into a single XML file, and import the entire files within it. Further, in the application context, we load this single xml file.
Now what if the number of configuration files to be created is outsized? Say for example, close to 100 or more ?
Now that’s a humdrum and a snail-like job! It’s time to find a path to automate this process. Let’s think of a scenario where we have all the required data in an external source, say for instance, an excel sheet. Now we need to read data from the sheet and generate close to 100 spring configuration files spontaneously.
How to automate this process ? JAXB to the rescue
JAXB has a binding compiler. JAXB XJC schema binding compiler transforms, or binds, a source XML schema to a set of JAXB content classes in the Java programming language.
What needs to be done if ‘Property is already defined ‘ error occurs while executing the command ?
It is likely to get an exception that says ‘Property is already defined’. If so, the issue can be resolved by creating a binding file, to override the properties for which the exception was thrown.
Binding file
<jxb:bindings xmlns:jxb="http://java.sun.com/xml/ns/jaxb"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
version="1.0">
<jxb:bindings schemaLocation="http://www.springframework.org/schema/beans/spring-beans-3.0.xsd" node="/xs:schema">
<!-- Resolve:
[ERROR] Property "Ref" is already defined. Use <jaxb:property> to resolve this conflict.
line 975 of file:/home/dw/sandbox/BRZtests/src/jaxb/spring25.xsd
[ERROR] Property "Value" is already defined. Use <jaxb:property> to resolve this conflict.
line 977 of file:/home/dw/sandbox/BRZtests/src/jaxb/spring25.xsd
-->
<jxb:bindings node="//xs:complexType[@name='propertyType']">
<jxb:bindings node=".//xs:attribute[@name='ref']">
<jxb:property name="refAttribute"/>
</jxb:bindings>
<jxb:bindings node=".//xs:attribute[@name='value']">
<jxb:property name="valueAttribute"/>
</jxb:bindings>
</jxb:bindings>
<!--
[ERROR] Property "Ref" is already defined. Use <jaxb:property> to resolve this conflict.
line 577 of file:/home/dw/sandbox/BRZtests/src/jaxb/spring25.xsd
[ERROR] The following location is relevant to the above error
line 606 of file:/home/dw/sandbox/BRZtests/src/jaxb/spring25.xsd
[ERROR] Property "Value" is already defined. Use <jaxb:property> to resolve this conflict.
line 579 of file:/home/dw/sandbox/BRZtests/src/jaxb/spring25.xsd
[ERROR] The following location is relevant to the above error
line 613 of file:/home/dw/sandbox/BRZtests/src/jaxb/spring25.xsd
-->
<jxb:bindings node="//xs:element[@name='constructor-arg']">
<jxb:bindings node=".//xs:attribute[@name='ref']">
<jxb:property name="refAttribute"/>
</jxb:bindings>
<jxb:bindings node=".//xs:attribute[@name='value']">
<jxb:property name="valueAttribute"/>
</jxb:bindings>
</jxb:bindings>
<!--
[ERROR] Property "Key" is already defined. Use <jaxb:property> to resolve this conflict.
line 1063 of file:/home/dw/sandbox/BRZtests/src/jaxb/spring25.xsd
[ERROR] The following location is relevant to the above error
line 1066 of file:/home/dw/sandbox/BRZtests/src/jaxb/spring25.xsd
-->
<jxb:bindings node="//xs:complexType[@name='entryType']">
<jxb:bindings node=".//xs:attribute[@name='key']">
<jxb:property name="keyAttribute"/>
</jxb:bindings>
</jxb:bindings>
</jxb:bindings>
</jxb:bindings>
Use below command to parse the schema and generate the classes.
xjc -b spring25.xjb -verbose -xmlschema http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
All the classes associated with the xsd would be generated.Now create a Bean instance and marshall it. This would generate xml file with the required output.
Sample code - Test class created within the folder of generated classes
package org.springframework.schema.beans;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URISyntaxException;
import java.util.ArrayList;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.namespace.QName;
import org.springframework.util.CollectionUtils;
import com.hrb.leap.bean.SpringConfigGenerator;
public class Test {
public static void main(String[] args) throws JAXBException {
ObjectFactory factory = new ObjectFactory();
JAXBContext context = JAXBContext.newInstance("org.springframework.schema.beans");
/*
* Create the root element 'Beans'
* Set the required schema properties
*/
Beans rootElement = factory.createBeans();
rootElement.getOtherAttributes().put(new QName("xmlns:xsi"), "http://www.w3.org/2001/XMLSchema-instance");
rootElement.getOtherAttributes().put(new QName("xsi:schemaLocation"), "http://www.springframework.org/schema/beans spring-beans-3.2.xsd");
/*
* How to import resources
* How to define list of reference beans
*/
java.util.List<String> resources = new ArrayList<>();
resources.add("ResourceOne");
resources.add("ResourceTwo");
resources.add("ResourceThree");
resources.forEach(resourceName -> {
Import importResource = new Import();
importResource.setResource(resourceName+".xml");
rootElement.getImportOrAliasOrBean().add(importResource);
});
Bean bean = factory.createBean();
bean.setId("id");
bean.setClazz("java.util.ArrayList");
if (!CollectionUtils.isEmpty(resources)) {
ConstructorArg constructorArgs = factory.createConstructorArg();
org.springframework.schema.beans.List listOfResources = new org.springframework.schema.beans.List();
resources.forEach(referenceFormName -> {
Ref refBean = new Ref();
refBean.setBean(referenceFormName);
listOfResources.getBeanOrRefOrIdref().add(refBean);
});
constructorArgs.setList(listOfResources);
bean.getMetaOrConstructorArgOrProperty().add(constructorArgs);
}
rootElement.getImportOrAliasOrBean().add(bean);
/*
* Sample bean definition to show how to store list of values as a property
*/
Bean simpleBean = factory.createBean();
simpleBean.setId("SimpleBean");
simpleBean.setClazz("com.packagename.ClassName");
PropertyType firstProperty= factory.createPropertyType();
firstProperty.setName("listOfValuesDemo");
java.util.List<String> listOfValues = new ArrayList<>();
listOfValues.add("ValueOne");
listOfValues.add("ValueTwo");
listOfValues.add("ValueThree");
org.springframework.schema.beans.List listToStoreValues = new org.springframework.schema.beans.List();
listOfValues.forEach(name -> {
Value value = factory.createValue();
value.getContent().add(name);
listToStoreValues.getBeanOrRefOrIdref().add(value);
});
firstProperty.setList(listToStoreValues);
//Add the property to the bean.
simpleBean.getMetaOrConstructorArgOrProperty().add(factory.createProperty(firstProperty));
// Add the bean under the root element 'beans'
rootElement.getImportOrAliasOrBean().add(simpleBean);
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty("jaxb.formatted.output",Boolean.TRUE);
createXmlConfiguration(marshaller , "output", rootElement);
}
/*
* Method create the xml under the 'src/main/resources' folder in the project.
*/
public static void createXmlConfiguration (Marshaller marshaller , String fileName, Beans rootElement) {
try {
java.net.URL url = SpringConfigGenerator.class.getResource("/");
File fullPathToSubfolder = new File(url.toURI()).getAbsoluteFile();
String projectFolder = fullPathToSubfolder.getAbsolutePath().split("target")[0];
// TODO - Destination folder to be configured
File outputFile = new File(projectFolder + "src/main/resources/" + "/"+fileName+".xml");
if (!outputFile.exists()) {
outputFile.createNewFile();
}
OutputStream os = new FileOutputStream(outputFile);
marshaller.marshal(rootElement, os);
} catch (URISyntaxException uriException) {
} catch (IOException ioException) {
} catch (JAXBException jaxbException) {
}
}
}
Output
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<beans xmlns="http://www.springframework.org/schema/beans" xsi:schemaLocation="http://www.springframework.org/schema/beans spring-beans-3.2.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<import resource="ResourceOne.xml"/>
<import resource="ResourceTwo.xml"/>
<import resource="ResourceThree.xml"/>
<bean class="java.util.ArrayList" id="id">
<constructor-arg>
<list>
<ref bean="ResourceOne"/>
<ref bean="ResourceTwo"/>
<ref bean="ResourceThree"/>
</list>
</constructor-arg>
</bean>
<bean class="com.packagename.ClassName" id="SimpleBean">
<property name="listOfValuesDemo">
<list>
<value>ValueOne</value>
<value>ValueTwo</value>
<value>ValueThree</value>
</list>
</property>
</bean>
</beans>
Follow the below steps to see the sample output
Advantages
Alternative approach
If the number of configuration files is very less, we can try loading the context with all the bean definitions on the fly, without generating the physical xml files. This can be achieved by loading the spring context programmatically, and run the application as and when required.
Drawbacks