Creating Spring Application Context Using XML

Spring application contexts can be bootstrapped in any environment, including JUnit tests, web application, even standalone application.

Here a sample to load application context in "common" standalone Java application:

public static void main(String[] args) {
    ApplicationContext appContext = new ClassPathXmlApplicationContext(
            "com/dariawan/bankofjakarta/spring-config.xml");

    TxService txService = appContext.getBean("txService", TxService.class);
    try {
        txService.transferFunds(new BigDecimal("200"), "Transfer 200", "5008", "5007");
    } catch (InvalidParameterException|AccountNotFoundException|
            InsufficientFundsException|InsufficientCreditException ex) {
        System.out.println("Exception: " + ex.getMessage());
    } 
}
                    

And here another example using an application context inside a JUnit System Test:

package com.dariawan.bankofjakarta.service.impl;

import com.dariawan.bankofjakarta.domain.Account;
import com.dariawan.bankofjakarta.exception.AccountNotFoundException;
import com.dariawan.bankofjakarta.exception.InsufficientCreditException;
import com.dariawan.bankofjakarta.exception.InsufficientFundsException;
import com.dariawan.bankofjakarta.exception.InvalidParameterException;
import com.dariawan.bankofjakarta.service.AccountService;
import com.dariawan.bankofjakarta.service.TxService;
import java.math.BigDecimal;
import static org.junit.Assert.assertEquals;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TxServiceTest {
    
    protected TxService txService;
    protected AccountService accountService;

    @Before
    public void setUp() {
        // Create the application from the configuration
        ApplicationContext context = new ClassPathXmlApplicationContext(
                "com/dariawan/bankofjakarta/**/spring-config-test.xml");
        // Look up the application service interface
        txService = context.getBean("txService", TxService.class);
        accountService = context.getBean("accountService", AccountService.class);
    }
    
    private Account getAccount(String accountId) {
        try {
            return accountService.getDetails(accountId);
        } catch (InvalidParameterException ex) {
            return null;
        } catch (AccountNotFoundException ex) {
            return null;
        }
    }
    
    @Test
    public void testTransferFunds() throws InvalidParameterException, AccountNotFoundException, InsufficientCreditException, InsufficientFundsException {
        Account accFrom1 = getAccount("5008");
        BigDecimal balanceFromNow = accFrom1.getBalance();
        
        Account accTo1 = getAccount("5007");
        BigDecimal balanceToNow = accTo1.getBalance();
        
        txService.transferFunds(new BigDecimal("200"), "Transfer 200", accFrom1.getAccountId(), accTo1.getAccountId());
        
        Account accFrom2 = getAccount("5008");
        assertEquals(accFrom2.getBalance().toString(), (balanceFromNow.subtract(new BigDecimal("200"))).toString());
        
        Account accTo2 = getAccount("5007");
        // because credit account - subtract
        assertEquals(accTo2.getBalance().toString(), (balanceToNow.subtract(new BigDecimal("200"))).toString());
    }
}
                    

We can load bean definitions is from files which located in:

  • the class path
  • the local file system
  • an environment-relative resource path.

And it's also possible to load from multiple files.

Creating a Spring Application Context from Multiple Files

A Spring application context can be configured from multiple files. We can partition (grouping) bean definitions into logical groups. The best practice is to separate out application beans from infrastructure beans. This is because infrastructure often changes between environments.

Let's check spring configuration in Bank of Jakarta example application. So far, it's a mixed configuration in one file:

<?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"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx 
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
        <property name="driverClassName" value="org.postgresql.Driver" />
        <property name="url" value="jdbc:postgresql://localhost/bankofjakarta" />
        <property name="username" value="duke" />
        <property name="password" value="dariawan" />
    </bean>
    
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- DAOs -->
    <bean id="accountDao" class="com.dariawan.bankofjakarta.dao.impl.AccountDaoImpl">
        <constructor-arg ref="dataSource" />
    </bean>

    <bean id="customerAccountDao" class="com.dariawan.bankofjakarta.dao.impl.CustomerAccountDaoImpl">
        <constructor-arg ref="dataSource" />
    </bean>

    <bean id="customerDao" class="com.dariawan.bankofjakarta.dao.impl.CustomerDaoImpl">
        <constructor-arg ref="dataSource" />
    </bean>

    <bean id="nextIdDao" class="com.dariawan.bankofjakarta.dao.impl.NextIdDaoImpl">
        <constructor-arg ref="dataSource" />
    </bean>

    <bean id="txDao" class="com.dariawan.bankofjakarta.dao.impl.TxDaoImpl">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!-- end DAOs -->

    <!-- Services -->    
    <bean id="accountService" class="com.dariawan.bankofjakarta.service.impl.AccountServiceImpl">
        <constructor-arg ref="accountDao" />
        <constructor-arg ref="customerDao" />
        <constructor-arg ref="nextIdDao" />
        <constructor-arg ref="customerAccountDao" />
        <constructor-arg ref="txDao" />
    </bean>

    <bean id="customerService" class="com.dariawan.bankofjakarta.service.impl.CustomerServiceImpl">
        <constructor-arg ref="customerDao" />
        <constructor-arg ref="accountDao" />        
        <constructor-arg ref="nextIdDao" />
        <constructor-arg ref="customerAccountDao" />
    </bean>

    <bean id="txService" class="com.dariawan.bankofjakarta.service.impl.TxServiceImpl">
        <constructor-arg ref="accountDao" />
        <constructor-arg ref="nextIdDao" />
        <constructor-arg ref="txDao" />       
    </bean>
    <!-- end Services -->   
    
    <!-- the transactional advice (what 'happens'; see the <aop:advisor/> bean below) -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <!-- the transactional semantics... -->
        <tx:attributes>
            <tx:method name="add*" propagation="REQUIRED" />
	    <tx:method name="create*" propagation="REQUIRED" />
	    <tx:method name="remove*" propagation="REQUIRED" />
            <tx:method name="set*" propagation="REQUIRED" />
            <!-- tx -->
            <tx:method name="deposit" propagation="REQUIRED" />
            <tx:method name="make*" propagation="REQUIRED" />
            <tx:method name="transferFunds" propagation="REQUIRED" />
            <tx:method name="withdraw" propagation="REQUIRED" />
            <!-- all methods starting with 'get' are read-only -->
            <tx:method name="get*" read-only="true"/>
        </tx:attributes>
    </tx:advice>
    
    <aop:config>
        <aop:advisor pointcut="execution(* *..*Service.*(..))" advice-ref="txAdvice" />	
    </aop:config>
</beans>
                    

Now, we partition above configuration into separated files:

  • spring-tx-config.xml: infrastructure beans, handling database connection and transaction manager
  • spring-dao-config.xml: application beans, handling DAOs configuration
  • spring-service-config.xml: application beans, handling Services configuration
  • spring-aop-config.xml: application beans, AOP and point-cuts
  • spring-config.xml: main configuration, imports for the other four xml.
spring-tx-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"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx 
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context.xsd">

    <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
        <property name="driverClassName" value="org.postgresql.Driver" />
        <property name="url" value="jdbc:postgresql://localhost/bankofjakarta" />
        <property name="username" value="duke" />
        <property name="password" value="dariawan" />
    </bean>
    
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
</beans>
                    

spring-dao-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.xsd
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- DAOs -->
    <bean id="accountDao" class="com.dariawan.bankofjakarta.dao.impl.AccountDaoImpl">
        <constructor-arg ref="dataSource" />
    </bean>

    <bean id="customerAccountDao" class="com.dariawan.bankofjakarta.dao.impl.CustomerAccountDaoImpl">
        <constructor-arg ref="dataSource" />
    </bean>

    <bean id="customerDao" class="com.dariawan.bankofjakarta.dao.impl.CustomerDaoImpl">
        <constructor-arg ref="dataSource" />
    </bean>

    <bean id="nextIdDao" class="com.dariawan.bankofjakarta.dao.impl.NextIdDaoImpl">
        <constructor-arg ref="dataSource" />
    </bean>

    <bean id="txDao" class="com.dariawan.bankofjakarta.dao.impl.TxDaoImpl">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!-- end DAOs -->
</beans>
                    

spring-service-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.xsd
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context.xsd">    

    <!-- Services -->    
    <bean id="accountService" class="com.dariawan.bankofjakarta.service.impl.AccountServiceImpl">
        <constructor-arg ref="accountDao" />
        <constructor-arg ref="customerDao" />
        <constructor-arg ref="nextIdDao" />
        <constructor-arg ref="customerAccountDao" />
        <constructor-arg ref="txDao" />
    </bean>

    <bean id="customerService" class="com.dariawan.bankofjakarta.service.impl.CustomerServiceImpl">
        <constructor-arg ref="customerDao" />
        <constructor-arg ref="accountDao" />        
        <constructor-arg ref="nextIdDao" />
        <constructor-arg ref="customerAccountDao" />
    </bean>

    <bean id="txService" class="com.dariawan.bankofjakarta.service.impl.TxServiceImpl">
        <constructor-arg ref="accountDao" />
        <constructor-arg ref="nextIdDao" />
        <constructor-arg ref="txDao" />       
    </bean>
    <!-- end Services -->
</beans>
                    

spring-aop-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"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx 
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">  

    <!-- the transactional advice (what 'happens'; see the <aop:advisor/> bean below) -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <!-- the transactional semantics... -->
        <tx:attributes>
            <tx:method name="add*" propagation="REQUIRED" />
	    <tx:method name="create*" propagation="REQUIRED" />
	    <tx:method name="remove*" propagation="REQUIRED" />
            <tx:method name="set*" propagation="REQUIRED" />
            <!-- tx -->
            <tx:method name="deposit" propagation="REQUIRED" />
            <tx:method name="make*" propagation="REQUIRED" />
            <tx:method name="transferFunds" propagation="REQUIRED" />
            <tx:method name="withdraw" propagation="REQUIRED" />
            <!-- all methods starting with 'get' are read-only -->
            <tx:method name="get*" read-only="true"/>
        </tx:attributes>
    </tx:advice>
    
    <aop:config>
        <aop:advisor pointcut="execution(* *..*Service.*(..))" advice-ref="txAdvice" />	
    </aop:config>
</beans>
                    

spring-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"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx 
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- DB & Transaction Configurations -->
    <import resource="spring-tx-config.xml"/>

    <!-- DAO Configurations -->
    <import resource="spring-dao-config.xml"/>

    <!-- Service Configurations -->
    <import resource="spring-service-config.xml"/>
    
    <!-- AOP Configurations -->
    <import resource="spring-aop-config.xml"/>
</beans>
                    

From the example above, we can import resources from another XML using import resource

<import resource="spring-tx-config.xml"/>

Previously, we have test configuration that only importing to spring-config.xml

spring-config-test.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"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx 
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context.xsd">

    <import resource="classpath*:com/dariawan/bankofjakarta/spring-config.xml" />
    
</beans>
                    

Now, we can make it more flexible. Let say, we have different data source for test environment. So we can create spring-tx-config-test.xml (assuming with different database connection) like below:

spring-tx-config-test.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"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx 
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context.xsd">

    <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
        <property name="driverClassName" value="org.postgresql.Driver" />
        <property name="url" value="jdbc:postgresql://localhost/testbank" />
        <property name="username" value="testmarquis" />
        <property name="password" value="ariawand" />
    </bean>
    
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
</beans>
                    

And spring-config-test.xml is more independent from spring-config.xml

spring-config-test.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"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx 
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- DB & Transaction Configurations -->
    <import resource="spring-tx-config-test.xml"/>

    <!-- DAO Configurations -->
    <import resource="classpath*:com/dariawan/bankofjakarta/spring-dao-config.xml"/>

    <!-- Service Configurations -->
    <import resource="classpath*:com/dariawan/bankofjakarta/spring-service-config.xml"/>
    
    <!-- AOP Configurations -->
    <import resource="classpath*:com/dariawan/bankofjakarta/spring-aop-config.xml"/>
    
</beans>
                    

Bootstrapping in Each Environment

Besides combine all XML in one master XML (like spring-config.xml or spring-config-test.xml), we can load each XML configurations separately. Here an example in Java standalone program:

public static void main(String[] args) {
    // Create the application from the multiple configuration
    ApplicationContext appContext = new ClassPathXmlApplicationContext(
            "com/dariawan/bankofjakarta/spring-tx-config.xml",
            "com/dariawan/bankofjakarta/spring-dao-config.xml",
            "com/dariawan/bankofjakarta/spring-service-config.xml",
            "com/dariawan/bankofjakarta/spring-aop-config.xml");

    AccountService accountService = appContext.getBean("accountService", AccountService.class);
    try {
        Account account = accountService.getDetails("5007");
        System.out.println(account.getBalance());
    } catch (InvalidParameterException|AccountNotFoundException ex) {
        System.out.println("Exception: " + ex.getMessage());
    }
}
                    

Similarly, for test environment we only need to replace spring-tx-config.xml to spring-tx-config-test.xml

ApplicationContext appContext = new ClassPathXmlApplicationContext(
        "com/dariawan/bankofjakarta/spring-tx-config-test.xml",
        "com/dariawan/bankofjakarta/spring-dao-config.xml",
        "com/dariawan/bankofjakarta/spring-service-config.xml",
        "com/dariawan/bankofjakarta/spring-aop-config.xml");
                    

Spring's Flexible Resource Loading Mechanism

ApplicationContext implementations have default resource loading rules

// $CLASSPATH/com/dariawan/application-config.xml new ClassPathXmlApplicationContext("com/dariawan/application-config.xml"); // absolute path: D:/Users/dariawan/application-config.xml new FileSystemXmlApplicationContext("D:/Users/dariawan/application-config.xml"); // path relative to the JVM working directory new FileSystemXmlApplicationContext("../config/application-config.xml"); // XmlWebApplicationContext is also available // - The path is relative to the Web application // - Usually created indirectly via a declaration in web.xml XmlWebApplicationContext appContext = new XmlWebApplicationContext(); appContext.setConfigLocations("file:src/main/webapp/WEB-INF/configuration/application-config.xml");

Working with prefixes

But the configuration is flexible. Default rules can be overridden. As example, you can use file: prefix in ClassPathXmlApplicationContext.

new ClassPathXmlApplicationContext( "config/spring-dao-config.xml", "config/spring-service-config.xml", "file:/app/dariawan/server-config.xml" );

Not just in constructor args to application context, prefixes can be used anywhere Spring needs to deal with resources. Below is the table for various prefixes with example and explanation:

PrefixExampleExplanation
classpath:classpath:com/dariawan/application-config.xmlLoaded from the classpath.
file:file:/app/dariawan/application-config.xmlLoaded as a URL, from the filesystem.
http:http://localhost/secure/application-config.xmlLoaded as a URL
(none)/config/application-config.xmlDepends on the underlying ApplicationContext.

To understand more about how to deal resources, visit Spring documentation for details.