简体   繁体   中英

How to create spring xml configuration files, from the application context programmaticaly?

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);
}}}}

beans.xml

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

  1. 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 .
  2. Hit this url in browser to get the xsd. Now save it in some location(like spring.xsd).
  3. Download jaxbri package and the set the environment path variable .In case of maven projects, include depenendencies for jaxb-ri and jaxb-core.
  4. 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'. 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 &lt;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 &lt;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 &lt;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 &lt;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 &lt;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

  1. Hit the schema url in browser to get the xsd. Now save it in some location (eg:- spring.xsd).
  2. Download jaxbri package and the set the environment path variable .
  3. Set classpath of the jaxb libraries and current path(dot) like example (do this command prompt or equalant in IDE) set classpath=C:\\folder_name\\jaxb-ri\\lib;.;
  4. After extracting the generated classes to a folder run below command (from the extracted folder in cmd), compile the 'Test' class. javac folder_name\\Test.java
  5. Finally run the program. java folder_name.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. Considering the complexity of bean definitions I had to configure manually,a single person would have taken minimum of 20-25 days to configure close to 85 files (considering 9 hours/day),which would have accounted to 225 hours. However, the above tool will 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
  • Performance
  • Minor mistake in data source would have big impact.
  • Risky to run in production environment, on the fly.
  • Not advised, if the data source remains almost untouched.
  • Will be difficult to troubleshoot.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM