Is there a PropertyPlaceholderConfigurer-like class for use with Spring that accepts XML?

后端 未结 4 2076
轻奢々
轻奢々 2020-12-30 11:01

Spring has a very handy convenience class called PropertyPlaceholderConfigurer, which takes a standard .properties file and injects values from it into your bean.xml config.

相关标签:
4条回答
  • 2020-12-30 11:39

    Found out that Spring Modules provide integration between Spring and Commons Configuration, which has a hierarchial XML configuration style. This ties straight into PropertyPlaceholderConfigurer, which is exactly what I wanted.

    0 讨论(0)
  • 2020-12-30 11:45

    Been trying to come up with a nice solution to this myself that

    1. Revolves around creating an XSD for the config file - since in my mind the whole benefit of using XML is that you can strongly type the config file, in terms of datatypes, and which fields are mandatory/optional
    2. Will validate the XML against the XSD, so if a value is missing it'll throw an error out rather than your bean being injected with a 'null'
    3. Doesn't rely on spring annotations (like @Value - in my mind that's giving beans knowledge about their container + relationship with other beans, so breaks IOC)
    4. Will validate the spring XML against the XSD, so if you try to reference an XML field not present in the XSD, it'll throw out an error too
    5. The bean has no knowledge that its property values are being injected from XML (i.e. I want to inject individual properties, and not the XML object as a whole)

    What I came up with is as below, apologies this is quite long winded, but I like it as a solution since I believe it covers everything. Hopefully this might prove useful to someone. Trivial pieces first:

    The bean I want property values injected into:

    package com.ndg.xmlpropertyinjectionexample;
    
    public final class MyBean
    {
        private String firstMessage;
        private String secondMessage;
    
        public final String getFirstMessage ()
        {
            return firstMessage;
        }
    
        public final void setFirstMessage (String firstMessage)
        {
            this.firstMessage = firstMessage;
        }
    
        public final String getSecondMessage ()
        {
            return secondMessage;
        }
    
        public final void setSecondMessage (String secondMessage)
        {
            this.secondMessage = secondMessage;
        }
    }
    

    Test class to create the above bean and dump out the property values it got:

    package com.ndg.xmlpropertyinjectionexample;
    
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public final class Main
    {
        public final static void main (String [] args)
        {
            try
            {
                final ApplicationContext ctx = new ClassPathXmlApplicationContext ("spring-beans.xml");
                final MyBean bean = (MyBean) ctx.getBean ("myBean");
                System.out.println (bean.getFirstMessage ());
                System.out.println (bean.getSecondMessage ());
            }
            catch (final Exception e)
            {
                e.printStackTrace ();
            }
        }
    
    }
    

    MyConfig.xsd:

    <?xml version="1.0" encoding="UTF-8"?>
    <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:myconfig="http://ndg.com/xmlpropertyinjectionexample/config" targetNamespace="http://ndg.com/xmlpropertyinjectionexample/config">
    
        <xsd:element name="myConfig">
            <xsd:complexType>
                <xsd:sequence>
                    <xsd:element minOccurs="1" maxOccurs="1" name="someConfigValue" type="xsd:normalizedString" />
                    <xsd:element minOccurs="1" maxOccurs="1" name="someOtherConfigValue" type="xsd:normalizedString" />
                </xsd:sequence>
            </xsd:complexType>
        </xsd:element>
    
    </xsd:schema>
    

    Sample MyConfig.xml file based on the XSD:

    <?xml version="1.0" encoding="UTF-8"?>
    <config:myConfig xmlns:config="http://ndg.com/xmlpropertyinjectionexample/config">
        <someConfigValue>First value from XML file</someConfigValue>
        <someOtherConfigValue>Second value from XML file</someOtherConfigValue>
    </config:myConfig>
    

    Snippet of pom.xml file to run xsd2java (wasn't much else in here besides setting to Java 1.6, and spring-context dependency):

            <plugin>
                <groupId>org.jvnet.jaxb2.maven2</groupId>
                <artifactId>maven-jaxb2-plugin</artifactId>
                <executions>
                    <execution>
                        <id>main-xjc-generate</id>
                        <phase>generate-sources</phase>
                        <goals><goal>generate</goal></goals>
                    </execution>
                </executions>
            </plugin>
    

    Now the spring XML itself. This creates a schema/validator, then uses JAXB to create an unmarshaller to create a POJO from the XML file, then uses spring # annotation to inject property values by quering the POJO:

    <?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.2.xsd" >
    
        <!-- Set up schema to validate the XML -->
    
        <bean id="schemaFactory" class="javax.xml.validation.SchemaFactory" factory-method="newInstance">
            <constructor-arg value="http://www.w3.org/2001/XMLSchema"/>
        </bean> 
    
        <bean id="configSchema" class="javax.xml.validation.Schema" factory-bean="schemaFactory" factory-method="newSchema">
            <constructor-arg value="MyConfig.xsd"/>
        </bean>
    
        <!-- Load config XML -->
    
        <bean id="configJaxbContext" class="javax.xml.bind.JAXBContext" factory-method="newInstance">
            <constructor-arg>
                <list>
                    <value>com.ndg.xmlpropertyinjectionexample.config.MyConfig</value>
                </list>
            </constructor-arg>
        </bean>
    
        <bean id="configUnmarshaller" class="javax.xml.bind.Unmarshaller" factory-bean="configJaxbContext" factory-method="createUnmarshaller">
            <property name="schema" ref="configSchema" />
        </bean>
    
        <bean id="myConfig" class="com.ndg.xmlpropertyinjectionexample.config.MyConfig" factory-bean="configUnmarshaller" factory-method="unmarshal">
            <constructor-arg value="MyConfig.xml" />
        </bean>
    
        <!-- Example bean that we want config properties injected into -->
    
        <bean id="myBean" class="com.ndg.xmlpropertyinjectionexample.MyBean">
            <property name="firstMessage" value="#{myConfig.someConfigValue}" />
            <property name="secondMessage" value="#{myConfig.someOtherConfigValue}" />
        </bean>
    
    </beans>
    
    0 讨论(0)
  • 2020-12-30 11:50

    I just tested this, and it should just work.

    PropertiesPlaceholderConfigurer contains a setPropertiesPersister method, so you can use your own subclass of PropertiesPersister. The default PropertiesPersister already supports properties in XML format.

    Just to show you the fully working code:

    JUnit 4.4 test case:

    package org.nkl;
    
    import static org.junit.Assert.assertEquals;
    import static org.junit.Assert.assertNotNull;
    
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    
    @ContextConfiguration(locations = { "classpath:/org/nkl/test-config.xml" })
    @RunWith(SpringJUnit4ClassRunner.class)
    public class PropertyTest {
    
        @Autowired
        private Bean bean;
    
        @Test
        public void testPropertyPlaceholderConfigurer() {
            assertNotNull(bean);
            assertEquals("fred", bean.getName());
        }
    }
    

    The spring config file test-config.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"
      xmlns:context="http://www.springframework.org/schema/context"
      xsi:schemaLocation="
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-2.5.xsd
    ">
      <context:property-placeholder 
          location="classpath:/org/nkl/properties.xml" />
      <bean id="bean" class="org.nkl.Bean">
        <property name="name" value="${org.nkl.name}" />
      </bean>
    </beans>
    

    The XML properties file properties.xml - see here for description of usage.

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
    <properties>
      <entry key="org.nkl.name">fred</entry>
    </properties>
    

    And finally the bean:

    package org.nkl;
    
    public class Bean {
        private String name;
        public String getName() { return name; }
        public void setName(String name) { this.name = name; }
    }
    

    Hope this helps...

    0 讨论(0)
  • 2020-12-30 11:57

    I'm not sure about the Apache digester-style config files, but I found a solution that was not that hard to implement and suitable for my xml config-file.

    You can use the normal PropertyPlaceholderConfigurer from spring, but to read your custom config you have to create your own PropertiesPersister, where you can parse the xml (with XPath) and set the required properties yourself.

    Here's a small example:

    First create your own PropertiesPersister by extending the default one:

    public class CustomXMLPropertiesPersister extends DefaultPropertiesPersister {
                private XPath dbPath;
                private XPath dbName;
                private XPath dbUsername;
                private XPath dbPassword;
    
                public CustomXMLPropertiesPersister() throws JDOMException  {
                    super();
    
                dbPath = XPath.newInstance("//Configuration/Database/Path");
                dbName = XPath.newInstance("//Configuration/Database/Filename");
                dbUsername = XPath.newInstance("//Configuration/Database/User");
                dbPassword = XPath.newInstance("//Configuration/Database/Password");
            }
    
            public void loadFromXml(Properties props, InputStream is)
            {
                Element rootElem = inputStreamToElement(is);
    
                String path = "";
                String name = "";
                String user = "";
                String password = "";
    
                try
                {
                    path = ((Element) dbPath.selectSingleNode(rootElem)).getValue();
                    name = ((Element) dbName.selectSingleNode(rootElem)).getValue();
                    user = ((Element) dbUsername.selectSingleNode(rootElem)).getValue();
                    password = ((Element) dbPassword.selectSingleNode(rootElem)).getValue();
                }
                catch (JDOMException e)
                {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
    
                props.setProperty("db.path", path);
                props.setProperty("db.name", name);
                props.setProperty("db.user", user);
                props.setProperty("db.password", password);
            }
    
            public Element inputStreamToElement(InputStream is)
            {       
                ...
            }
    
            public void storeToXml(Properties props, OutputStream os, String header)
            {
                ...
            }
        }
    

    Then inject the CustomPropertiesPersister to the PropertyPlaceholderConfigurer in the application context:

    <beans ...>
        <bean id="customXMLPropertiesPersister" class="some.package.CustomXMLPropertiesPersister" />
    
        <bean id="propertyPlaceholderConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
            <property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_FALLBACK" />
            <property name="location" value="file:/location/of/the/config/file" />
            <property name="propertiesPersister" ref="customXMLPropertiesPersister" />
        </bean> 
    </beans>
    

    After that you can use your properties like this:

    <bean id="someid" class="some.example.class">
      <property name="someValue" value="$(db.name)" />
    </bean>
    
    0 讨论(0)
提交回复
热议问题