One of the most interesting aspects (no pun intended) of Spring 2.0 that was discussed this past week at The Spring Experience was the idea of letting Spring configure beans post-instantiation and regardless of how the bean became instantiated. This Spring 2.0 feature helps avoid the Anemic Domain Model" anti-pattern as described by Martin Fowler.
It's very common in Spring to build applications where service objects are injected with DAO objects and use those DAO objects to handle persistence of domain objects. The domain objects themselves, however, are little more than dumb data holders. The problem with this approach is that the interaction between the service object and the DAO object is very procedural. The service object makes one or more calls to the DAO, passing the domain object around like cargo.
Ideally, the domain object would contain behavior to handle its own persistence. If domain objects offered such behavior, then the service object could deal directly with the domain object in a very object-oriented way. Instead of telling a DAO to persist a customer, the service would tell the customer to persist itself. There will still be a DAO, but the domain object will do its own dealing with the DAO, unbeknownst to the service object. In effect, the domain object and the DAO swap positions with relation to the service object.
If the domain object is responsible for dealing with the DAO, then the domain object must have access to the DAO. In Spring, we'd like to do this through dependency injection. But domain objects are typically instantiated outside of Spring (e.g. in Hibernate, iBATIS, or some other persistence mechanism). How can Spring inject a DAO into a domain object when Spring isn't the one instantiating that domain object?
AspectJ to the rescue
If there was any common theme expressed during The Spring Experience last week it was that AspectJ is going to play a huge part in Spring 2.0. Indeed, the addition of Adrian Colyer to the Spring team has triggered a large number of AspectJ-related enhancements. While Spring AOP is nice, AspectJ offers a great deal of potential that cannot be found in proxy-based AOP.
One of the AspectJ-powered enhancements is the inclusion of
org.springframework.beans.factory.aspectj.BeanConfigurer
.
BeanConfigurer
is an AspectJ aspect that performs
dependency injection on objects after instantiation...even objects
that aren't instantiated by Spring. Let's take a look at
BeanConfigurer
in action.
Imagine an application that (among other things) maintains a
database of customers. In such an application, you may have a
Customer
domain object that looks like this:
public class Customer { private Integer id; private String name; public Customer() {} public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
(NOTE: I've purposefully kept the Customer
class simple with
only ID and name properties so as not to clutter the example with
unnecessary noise. In a real-world application, this class would
likely have many more properties.)
Notice that the Customer
class doesn't have any real
functionality--It's merely a data holder. This is what the
Customer
class might look like in a pre-2.0 Spring
application. It's assumed that instances of Customer
will
be passed around to a CustomerDao
implementation that
will handle the persistence of the object.
But in Spring 2.0, we can give the Customer
a bit more
power:
import org.springframework.beans.factory.aspectj.SpringConfigured; @SpringConfigured("customer") public class Customer { private Integer id; private String name; public Customer() {} public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } // business functions public void save() { dao.save(this); } // injected DAO private CustomerDao dao; public void setDao(CustomerDao dao) { this.dao = dao; } }
The first thing you'll notice is the use of a
@SpringConfigured
annotation. @SpringConfigured
tells the
BeanConfigurer
aspect that you wish for Spring to perform
dependency injection on instances of this class
post-instantiation. The value passed to @SpringConfigured
is the name of a <bean>
in the Spring application
context that will serve as a template for injection.
The dependency injection that will be done to instances of
Customer
is a CustomerDao
. Note that
Customer
also has a new save()
method that
uses the injected CustomerDao
to save itself.
Now let's look at the XML for the Spring application context:
<?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:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <aop:spring-configured/> <bean id="customerDao" class="com.habuma.account.CustomerDaoImpl"/> <bean id="customer" class="com.habuma.account.Customer" lazy-init="true"> <property name="dao" ref="customerDao" /> </bean> </beans>
The first thing about this XML file that may strike you odd is that
instead of using a DTD, XML Schemas are being used to validate the
XML. There are a handful of good reasons for going with schemas
instead of DTD, but for the purposes this discussion, the main reason
is that the "http://www.springframework.org/schema/aop" schema gives
us the <aop:spring-configured/>
XML element. This
element is just one example of some of the XML simplifications coming
in Spring 2.0. Using <aop:spring-configured/>
is
roughly equivalent to the following chunk of XML:
<bean id="beanConfigurer" class="org.springframework.beans.factory.aspectj.BeanConfigurer" factory-method="aspectOf" />
Aside from being more terse, the purpose of
<aop:spring-configured/>
is a bit more clear. In
short, it loads the BeanConfigurer
aspect. Once loaded,
BeanConfigurer
will keep an eye out for instantiations of
any class that is annotated with @SpringConfigured
and
inject them as prescribed in the application context.
In the case of Customer
, the
@SpringConfigured
annotation tells BeanConfigurer
to use the bean
whose id
is "customer" as its guide for injecting new
instances of Customer
. As you can see, the "customer"
bean is configured to be injected with an instance of
CustomerDaoImpl
.
You'll also note that the "customer" bean is configured with
lazy-init="true"
. Since Spring will only use this bean as
a template, setting lazy-init
to "true" tells Spring to
not bother creating an instance when the Spring container is
loaded.
To try all of this out, let's create a service object:
public class CustomerServiceImpl implements CustomerService { public CustomerServiceImpl() {} public void copyAndTweakCustomer(Integer id) { Customer original = dao.load(id); Customer duplicate = new Customer(); duplicate.setId(original.getId() + 1); duplicate.setName(original.getName()); original.setName("Michael Walls"); original.save(); duplicate.save(); } private CustomerDao dao; public void setDao(CustomerDao dao) { this.dao = dao; } }
The copyAndTweakCustomer
is a rather contrived method
that loads a customer given an ID, makes a copy of the customer,
changes the name of the customer, then saves both the original and the
duplicate customer object. Take note that the code that makes up
copyAndTweakCustomer()
is very OO. For comparison,
consider what this may have looked like if Customer
was
more anemic:
public void copyAndTweakCustomer(Integer id) { Customer original = dao.load(id); Customer duplicate = new Customer(); duplicate.setId(original.getId() + 1); duplicate.setName(original.getName()); original.setName("Michael Walls"); dao.save(original); dao.save(duplicate); }
The code is only slightly different. But the difference is important. In an anemic domain model, the service layer code is more procedural, whereas in a strong domain model the service layer code is more object oriented.
You'll notice that the service object is also injected with a
CustomerDao
. This is so that it can load a
previously-persisted Customer
. This illustrates an
important point: Just because the domain object is now primarily
responsible for its own persistence, that does not mean that service
objects can't also access the DAO to load previously-persisted
instances.
Speaking of which, we also need to add the
CustomerServiceImpl
configuration to the Spring
configuration:
<?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:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <aop:spring-configured/> <bean id="customerDao" class="com.habuma.account.CustomerDaoImpl"/> <bean id="customer" class="com.habuma.account.Customer" lazy-init="true"> <property name="dao" ref="customerDao" /> </bean> <bean id="customerService" class="com.habuma.account.CustomerServiceImpl"> <property name="dao" ref="customerDao" /> </bean> </beans>
The actual implementation of CustomerDaoImpl
is up to
you. It could be Hibernate-based, iBATIS-based, OJB-based,
TopLink-based, or (in Spring 2.0), it could be based on the Java
Persistance API. For purposes of this example, I've implemented a dummy
implementation of CustomerDao
that looks like this:
public class CustomerDaoImpl implements CustomerDao { public CustomerDaoImpl() {} public void save(Customer customer) { System.out.println("SAVING A CUSTOMER:"); System.out.println(" customer.id = " + customer.getId()); System.out.println(" customer.name = " + customer.getName()); } public Customer load(Integer id) { System.out.println("LOADING A CUSTOMER WITH ID: " + id); Customer customer = new Customer(); customer.setId(id); customer.setName("Craig Walls"); return customer; } }
Now let's give it a try. Create a class with a main()
method that looks like this:
public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("com/habuma/account/applicationContext.xml"); CustomerService service = (CustomerService) ctx.getBean("customerService"); service.copyAndTweakCustomer(new Integer(5)); }
Using my dummy CustomerDaoImpl
, you should see the
following output on STDOUT:
LOADING A CUSTOMER WITH ID: 5 SAVING A CUSTOMER: customer.id = 5 customer.name = Michael Walls SAVING A CUSTOMER: customer.id = 6 customer.name = Craig Walls
@SpringConfigured
and <aop:spring-configured/>
together represent just one of several awesome improvements being made in Spring 2.0. In this article, you also saw a hint of some XML simplification that's coming. And Spring 2.0 will also come with the 1.0 release of Spring Web Flow, support for the Java Persistence API, and many more goodies to watch out for. Keep an eye on this blog. As I have time and as I try out the new Spring 2.0 features, I'll tell you about them here.
What if it doesn't work?
The use of BeanConfigurer
assumes several things:
- The use of Spring 2.0 (M1 will be out any day now, but you can download a fairly recent version from the nightly builds). You'll need spring.jar and commons-logging.jar in the classpath.
- It requires Java 5 due to the use of the
@SpringConfigured
annotation. - The code should be compiled with AspectJ's "ajc" compiler. This
is so that AspectJ gets a chance to weave
BeanConfigurer
intoCustomer
. Once compiled, it can be run using any Java 5 JVM.