In an application there are lots of properties which vary from one environment to another e.g. database used in development will be different from the one used in production, leading to different connection properties (url, user names and password). Applications using Spring Framework usually fallback on PropertyPlaceholderConfigurer for inserting such values from an external properties file.
I would like to have everything at one place instead of externalizing properties into separate file for each environment. Fortunately Spring Framework provides extension points to create user defined XML elements and classes to instantiate beans using those elements. To have environment specific properties in single configuration file along with other bean definitions, I decided to create a custom element named <property>. Next I will try to explain how it works.
Here is the schema for <property> element, which is used to define different property for each environment-
I would like to have everything at one place instead of externalizing properties into separate file for each environment. Fortunately Spring Framework provides extension points to create user defined XML elements and classes to instantiate beans using those elements. To have environment specific properties in single configuration file along with other bean definitions, I decided to create a custom element named <property>. Next I will try to explain how it works.
Here is the schema for <property> element, which is used to define different property for each environment-
<?xml version="1.0" encoding="UTF-8"?>Now create a bean definition parser to parse the <property> element-
<xsd:schema xmlns="http://vinodsingh.com/schema/spring"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://vinodsingh.com/schema/spring">
<xsd:element name="property">
<xsd:complexType>
<xsd:attribute name="id" type="xsd:string" use="required" />
<xsd:attribute name="class" type="xsd:string" use="optional" default="java.lang.String" />
<xsd:attribute name="value" type="xsd:string" use="required" />
<xsd:attribute name="region" use="optional" default="all">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:enumeration value="dev" />
<xsd:enumeration value="test" />
<xsd:enumeration value="integ" />
<xsd:enumeration value="stage" />
<xsd:enumeration value="uat" />
<xsd:enumeration value="prod" />
<xsd:enumeration value="all" />
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
</xsd:schema>
public class PropertyDefinitionParser extends AbstractSingleBeanDefinitionParser {Based on the above-mentioned schema the resulting bean definitions will looks like-
/** Region where bean will be available. */
private String beanRegion = null;
private static String sysRegion = System.getProperty("region");
static {
if (sysRegion == null || sysRegion.length() <= 0)
sysRegion = "all";
}
@Override
protected Class<?> getBeanClass(Element element) {
Class<?> clazz = null;
String className = element.getAttribute("class");
if (className != null && className.length() > 0) {
try {
clazz = Class.forName(className);
} catch (Exception e) {
log.error("Failed to load class: " + className, e);
throw new RuntimeException(e);
}
}
return clazz;
}
@Override
protected void doParse(Element element, BeanDefinitionBuilder bean) {
beanRegion = element.getAttribute("region");
bean.addConstructorArgValue(element.getAttribute("value"));
}
@Override
protected void registerBeanDefinition(BeanDefinitionHolder definition, BeanDefinitionRegistry registry) {
if (beanRegion.equals(sysRegion) || "all".equals(beanRegion))
super.registerBeanDefinition(definition, registry);
}
}
<property id="jdbc.url" value="jdbc:hsqldb:hsql://dev:9002" region="dev" />Here jdbc.url property is defined twice with different region value. In bean definition for dataSource the jdbc.url is used as bean reference. The PropertyDefinitionParser will register only one definition of jdbc.url,which belongs to the current runtime region. The region will be passed as a system property to JVM.
<property id="jdbc.url" value="jdbc:hsqldb:hsql://production:9002" region="prod" />
<bean id="dataSource" destroy-method="close" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="driverClassName"/>
<property name="url" ref="jdbc.url"/>
<property name="username" value="username"/>
<property name="password" value="password"/>
</bean>