如何以编程方式从应用程序上下文创建 spring xml 配置文件?

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

我已经以编程方式将 bean 定义加载到 spring 应用程序上下文中。 (xml 配置为空)。我需要在这里做相反的过程。目前我已经加载了 bean 定义的上下文。现在我想将它们转换为 xml 配置文件。 有人知道怎么做吗?

下面给出了示例方法。

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>

加载的 bean 可以作为 appContext.getBean("beanName").

访问

终于,解决方案来了!!!我们来看看吧。

问题陈述

众所周知,在 spring 应用程序中,我们通过“ApplicationContext”提供配置信息。 spring框架提供了多个classes来实现这个接口,帮助我们在应用中使用配置信息,ClassPathXmlApplicationContext就是其中之一。通常在这种情况下,所有 bean 定义都配置在一个独立的 xml 文件中,通常称为“application-context.xml”。

但是,在较大的项目结构中,最好的做法是保留多个 spring 配置文件,以便于维护和模块化,而不是将所有内容堆叠到一个文件中。在这种情况下,最好的办法是将所有 Spring bean 配置文件组织到一个 XML 文件中,然后将整个文件导入其中。此外,在应用程序上下文中,我们加载了这个 xml 文件。

现在要创建的配置文件多了怎么办?比如说,接近 100 或更多 ?

现在这是一项单调乏味、蜗牛般的工作!是时候找到一条途径来自动化这个过程了。 让我们考虑一个场景,我们在外部源中拥有所有必需的数据,例如 excel sheet。现在我们需要从 sheet 中读取数据并自发地生成近 100 个 spring 配置文件。

如何使这个过程自动化? JAXB 来拯救

JAXB 有一个绑定编译器。 JAXB XJC 模式绑定编译器将源 XML 模式转换或绑定到 Java 编程语言中的一组 JAXB 内容 classes。

  1. 在待生成的xml中寻找需要引用的xml模式-http://www.springframework.org/schema/beans/spring-beans-3.0.xsd.
  2. 在浏览器中点击 url 以获得 xsd。现在将其保存在某个位置(如 spring.xsd)。
  3. 下载 jaxbri 包并设置环境路径变量。对于 maven 项目,包括 jaxb-ri 和 jaxb-core 的依赖项。
  4. 从 spring.xsd 所在的文件夹执行命令 xjc.exe spring.xsd。 这将生成与 xsd 关联的 classes。

如果'属性 is already defined'执行命令时出错怎么办?

很可能会得到一个异常,提示‘属性 is already defined’。如果是这样,可以通过创建绑定文件来解决问题,以覆盖引发异常的属性。

绑定文件

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

使用下面的命令解析架构并生成 classes.

xjc -b spring25.xjb -verbose -xmlschema http://www.springframework.org/schema/beans/spring-beans-3.0.xsd

与 xsd 关联的所有 classes 将 generated.Now 创建一个 Bean 实例并编组它。这将生成具有所需输出的 ​​xml 文件。

示例代码 - 在生成的 classes

文件夹中创建测试 class
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) {

        }

    }
}

输出

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

按照以下步骤查看示例输出

  1. 在浏览器中点击架构 url 以获取 xsd。现在将其保存在某个位置(例如:- spring.xsd)。
  2. 下载jaxbri包并设置环境路径变量。
  3. 设置 classjaxb 库的路径和当前路径(点),例如示例(执行此命令提示符或等同于 IDE) 设置 classpath=C:\folder_name\jaxb-ri\lib;.;
  4. 将生成的 classes 解压缩到命令下的文件夹 运行 后(从 cmd 中解压缩的文件夹),编译“测试”class。 javac folder_name\Test.java
  5. 终于运行节目了。 java folder_name.Test 注意:- 如果“createXmlConfiguration(marshaller, "output", rootElement);”被替换为“marshaller.marshal(rootElement, System.out);”[=92=,输出将打印在控制台中]

优势

  • 工作量大大减少。考虑到我必须手动配置的 bean 定义的复杂性,一个人至少需要 20-25 天来配置近 85 个文件(考虑 9 hours/day),这将占 225 小时。但是,上述工具将花费不到 3 秒的时间。
  • 如果以后要进行较小的配置更改,我们可以单独获取特定文件并进行更正。

替代方法

如果配置文件的数量非常少,我们可以尝试动态加载包含所有 bean 定义的上下文,而不生成物理 xml 文件。这可以通过以编程方式加载 spring 上下文,并在需要时 运行 应用程序来实现。

缺点

  • 内存
  • 性能
  • 数据源中的小错误会产生很大的影响。
  • 在生产环境中,即时 运行 存在风险。
  • 不建议,如果数据源几乎保持不变。
  • 将很难排除故障。