Spring Basics Wiring Beans With Xml Configuration
If you have to work with legacy Spring applications, chances are you will have to know how XML-based configuration works. Although Java configuration is preferred for new applications, sometimes you just don’t have a choice, so you’d better be comfortable with it.
The Code
You can find the code from this blog post on GitLab.
Dependencies
The only dependency you need to get a Spring container running is
spring-context
. Add in Maven:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
Declaring Beans in XML
The following class will become a bean:
package com.relentlesscoding.wirebeans;
import java.util.List;
public class Running implements Habit {
private final String name = "Running";
private final String description = "Run 10 km every day";
private List<Streak> streaks;
public String getName() {
return name;
}
public String getDescription() {
return description;
}
public List<Streak> getStreaks() {
return streaks;
}
public void setStreaks(List<Streak> streaks) {
this.streaks = streaks;
}
public void addStreak(Streak streak) {
streaks.add(streak);
}
}
Create an XML file, give it any name (I would call it
applicationContext.xml
) and place it in your src/main/resources
.
<?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.xsd ">
<bean id="running" class="com.relentlesscoding.wirebeans.Running"/>
</beans>
This would wire a bean with the id running
that is of type Running
(which is a Habit
). You can see why this is much more of a hassle to
set up than using a simple @Component
annotation on a class or a
@Bean
annotation in a @Configuration
class. Those XML namespaces are
nasty, but luckily most IDEs will help you with them.
Injecting Beans
To inject dependencies, we have two choices:
- Constructor injection
- Setter injection
Unlike with Java configuration, we cannot insert into fields when using XML-based configuration.
Constructor Injection
Suppose we want to insert one bean into another bean, for instance a
HabitRepository
that persists habits to the database into a
HabitService
. By using the <constructor-arg />
element and the ref
property, we can accomplish this:
<bean id="habitRepository" class="com.relentlesscoding.wirebeans.HabitRepository" />
<bean id="habitService" class="com.relentlesscoding.wirebeans.HabitService">
<constructor-arg ref="habitRepository" />
</bean>
If we wanted to pass more arguments to the HabitService
constructor,
we must keep on eye on the order: it must be the same as the order in
which they are declared in the class.
To ease working with constructor arguments and as a way to curtail the
verbosity of the XML configuration, Spring offers the c
XML namespace
to help wire beans without the need to create a sub-element
<constructor-arg />
:
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:c="http://www.springframework.org/schema/c"
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.xsd">
<bean id="currentTime" class="java.time.LocalDate" factory-method="now" />
<bean id="streak"
class="com.relentlesscoding.wirebeans.PositiveStreak"
c:startTime-ref="currentTime" />
We define a bean of type java.time.LocalDate
and we use the static
factory method now()
to get an instance of it. We then use the c
namespace to pass it to the constructor of our PositiveStreak
bean.
c:startTime-ref="currentTime"
should be read as: pass the reference to
the bean with id currentTime
to the constructor argument that has the
name startTime
.
So we can reference constructor arguments by name. We can also reference
them by position. c:_0-ref="currentTime"
would do the exact same
thing. XML does not allow a digit as the first character of an
attribute, so we have to use an underscore. If there is only a single
argument to the constructor, we can even use the shorthand
c:_-ref="currentTime"
. I would not want to promote this as readable,
but it’s good to know it exists and might be used in the wild.
Read more about the c
namespace
here.
Setter Injection
To use setter injection with XML-based configuration, you use the
<property>
element. If you look back at the Running
class above, you
see it has a method setStreak
that takes a List<Streak>
:
<bean id="startTime" class="java.time.LocalDate" factory-method="now" />
<bean id="streak"
class="com.relentlesscoding.wirebeans.PositiveStreak"
c:_0-ref="startTime" />
<bean id="running" class="com.relentlesscoding.wirebeans.Running">
<property name="streaks">
<list>
<ref bean="streak" />
</list>
</property>
</bean>
The <property>
element has an attribute name
that refers to the
field name of the bean being set. In the current case, class Running
has a field named streaks
. As a child element of <property>
we
define a list of Streak
references.
For a list of literal String values, this would have looked like:
<property name="listOfStrings">
<list>
<value>string value 1</value>
<value>string value 1</value>
<value><null/></value>
</list>
</property>
The list is wired with literal String
s and even a literal null
.
Spring also provides the p
namespace to make this more convenient
(less verbose):
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:p="http://www.springframework.org/schema/p"
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.xsd">
<bean id="startTime" class="java.time.LocalDate" factory-method="now"/>
<bean id="streak"
class="com.relentlesscoding.wirebeans.PositiveStreak"
c:_0-ref="startTime" />
<bean id="running"
class="com.relentlesscoding.wirebeans.Running"
p:streaks-ref="streak" />
</beans>
The usage of the p
namespace is a lot like that of the c
namespace.
In this case, p:streaks-ref="streak"
tells spring to wire a property
named streaks
with the bean that is referenced by the id streak
.
Now, the property streaks
takes a List
. If we pass only a single
element to that list, the current syntax works and Spring will happily
insert the single reference to streak
into a List
for us and pass
that to the setter method. If we want to pass more than one element in a
list, however, we have to create the list separately first, and then
pass the id of that reference to the p
property:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util"
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.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd">
<bean id="startTime" class="java.time.LocalDate" factory-method="now"/>
<bean id="firstStreak" class="com.relentlesscoding.wirebeans.PositiveStreak" c:_0-ref="startTime"/>
<bean id="secondStreak" class="com.relentlesscoding.wirebeans.PositiveStreak" c:_0-ref="startTime"/>
<util:list id="myRunningStreak">
<ref bean="firstStreak"/>
<ref bean="secondStreak"/>
</util:list>
<bean id="running" class="com.relentlesscoding.wirebeans.Running" p:streaks-ref="myRunningStreak"/>
</beans>
We need to add the util
namespace and the location of the util
schema definition to get this to work. You see that the XML becomes
quite verbose the more you try to do with it. The util
namespace
allows us to create collections of literal values or beans. These
collections can then be referenced by their id
.
More information:
Taking the App for a Test Ride
package com.relentlesscoding.wirebeans;
import org.junit.Assert;
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.SpringRunner;
@RunWith(SpringRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class HabitTest {
@Autowired
Habit runningHabit;
@Test
public void runningHabitIsNotNull() {
Assert.assertNotNull(runningHabit);
}
@Test
public void runningHabitHasSingleStreak() {
Assert.assertEquals(2, runningHabit.getStreaks().size());
}
}
To run integration tests where the Spring context is available, you need the following dependency:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.5.RELEASE</version>
<scope>test</scope>
</dependency>
spring-test
contains the SpringRunner
JUnit runner, and the
@ContextConfiguration
that will tell Spring where to look for the
application context that contains the beans that need to be wired. In
this case, we tell it to look at the applicationContext.xml
that we
put in src/main/resources
, so we can reference it by looking at the
root of our classpath with the attribute locations = "classpath:applicationContext.xml"
.