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.
<?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>
<?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>
<?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>
<?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>
<?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
<?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:
<?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
<?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:
Prefix | Example | Explanation |
---|---|---|
classpath: | classpath:com/dariawan/application-config.xml | Loaded from the classpath. |
file: | file:/app/dariawan/application-config.xml | Loaded as a URL, from the filesystem. |
http: | http://localhost/secure/application-config.xml | Loaded as a URL |
(none) | /config/application-config.xml | Depends on the underlying ApplicationContext. |
To understand more about how to deal resources, visit Spring documentation for details.