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’. In larger project structures, the best practice is to keep multiple spring configuration files for easy maintainability and modularity. 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 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.
- Look for the xml schema that needs to be referred in the xml to be generated -http://www.springframework.org/schema/beans/spring-beans-3.0.xsd.
- Hit this url in browser to get the xsd. Now save it in some location (like spring.xsd).
- Download jaxbri package and the set the environment path variable .In case of maven projects, include dependencies for jaxb-ri and jaxb-core.
- Execute the command xjc.exe spring.xsd, from the folder where spring.xsd exists. This would generate the classes associated to the xsd.
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’, as shown in the below screenshot. If so, the issue can be resolved by creating a binding file, to override the properties for which the exception was thrown. Binding file spring25 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
Generated classes org Now create a Bean instance and marshall it. This would generate xml file with the required output. Sample code Note:- Test class is created under org\springframework\schema\beans.
Java
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 Output Follow the below steps to see the sample output:
- Hit the schema url in browser to get the xsd. Now save it in some location (eg:- spring.xsd).
- Download jaxbri package and the set the environment path variable .
- Set classpath of the jaxb libraries and current path(dot) like example (do this command prompt or equivalent in IDE)
set classpath=C:\folder_name\jaxb-ri\lib;.;
- After extracting the attached to a folder run below command (from the extracted folder in cmd), compile the ‘Test’ class.
javac org\springframework\schema\beans\Test.java
- Finally run the program.
java org.springframework.schema.beans.Test
- Note:- Output will print in the console, if ‘createXmlConfiguration(marshaller , “output”, rootElement);’ is replaces by ‘marshaller.marshal(rootElement, System.out);’
Advantages
- Effort is reduced considerably. A single person might take minimum of 20-25 days to configure close to 85 files (considering 9 hours/day), it would account to 225 hours. The above tool would take less than 3 seconds to do so.
- If there are minor configuration changes to be done in future, we can take the specific file alone and make the corrections.
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
- Memory Consumption high
- Performance low
- Minor mistake in data source would have big impact.
- Risky to run in production environment, on the fly.
- Not an advised approach, if the data source remains almost untouched
- Will be difficult to troubleshoot.