Inject Into Domain Objects

June 7, 2005 – 22:08 | aop, java, spring

There is an ongoing discussion on the Spring forums around DAO Reference Inside an Entity Domain Object?. Putting aside the debate on whether the idea is sensible from a design perspective, a practical issue in implementing this approach is that, in a sophisticated application, domain objects, unlike those stateless business service objects or DAOs, are usually instantiated by the data access layer, e.g., an ORM tool such as Hibernate. Therefore it's usually hard to inject dependencies into these domain objects through Spring. In this forum post, Rod Johnson and other guys described a way to inject into domain objects by handling Hibernate object lifecycle events, but as of Spring 1.2, the actually implementation is still in the Sandbox. In this blog entry, I will describe another AOP-based approach that "virtually" injects a DAO reference (or any other Spring bean in general) into a domain object.

For the sake of discussion, let's say we have a domain object, DomainObject, and its DAO interface, IDomainObjectDAO. The basic idea is actually very simple - we declare a method in DomainObject:

JAVA:
  1. public class DomainObject {
  2.     protected IDomainObjectDAO getDomainObjectDAO() {
  3.         throw new UnsupportedOperationException(
  4.         "This method must be either AOP-intercepted, or overridden by a subclass, in order to provide a concrete implementation.");
  5.     }
  6. }

What we put in the method body does not matter, because the execution of this method will be intercepted with a Spring managed advice that actually gets the DAO instance from a Spring context and returns it on behalf of the method at runtime.

There can be many ways to implement the idea. The one described below uses Aspectwerkz's offline weaving, and relies on the AppContextCollector, and Spring-manage Aspectwerkz Aspects ideas, so it might be beneficial to read those two blog entries first.

First we need a holder bean class as the runtime bridge to the target DAO reference:

JAVA:
  1. package com.digizen.commons.aop;
  2.  
  3. import org.springframework.beans.BeansException;
  4. import org.springframework.context.ApplicationContext;
  5. import org.springframework.context.ApplicationContextAware;
  6.  
  7. public class BeanReferenceAdvice implements ApplicationContextAware {
  8.  
  9.     private ApplicationContext contextRef_;
  10.     private String beanName_;
  11.  
  12.     public void setApplicationContext(ApplicationContext context) throws BeansException {
  13.         contextRef_ = context;
  14.     }
  15.  
  16.     public String getBeanName() {
  17.         return beanName_;
  18.     }
  19.  
  20.     public void setBeanName(String beanName) {
  21.         beanName_ = beanName;
  22.     }
  23.  
  24.     public Object getBean() {
  25.         return contextRef_.getBean(beanName_);
  26.     }
  27. }

The context definition file (with only the relevant parts):

XML:
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
  3. <beans>
  4.   <bean id="domainObjectDAO" class="com.digizen.dao.DomainObjectDAOHibernateImpl">
  5.     <property name="sessionFactory"><ref local="mySessionFactory"/></property>
  6.   </bean>
  7.  
  8.   <!-- this declares the context collector instance.  Note the contextKey, which will be referenced in the aop.xml below. -->
  9.   <bean id="contextCollector" class="com.digizen.commons.spring.AppContextCollector" singleton="true">
  10.     <property name="contextKey"><value>appContext</value></property>
  11.   </bean>
  12.  
  13.   <!-- configure the advice bean.  The beanName references the dao bean declared above.
  14.           We don't use the bean reference itself because we don't want this advice to hold on to any bean references.
  15.    -->
  16.   <bean id="domainObjectDAORef" class="com.digizen.commons.aop.BeanReferenceAdvice" singleton="true">
  17.     <property name="beanName"><value>domainObjectDAO</value></property>
  18.   </bean>
  19. </beans>

Now in the aop.xml, just add:

XML:
  1. <aspect name="appContext:domainObjectDAORef" class="com.digizen.commons.aop.BeanReferenceAdvice" container="com.digizen.commons.aop.SpringBeanAspectContainer">
  2.   <pointcut name="getDomainObjectDAOPC">execution(* com.digizen.domain.DomainObject.getDomainObjectDAO())</pointcut>>
  3.   <advice name="getBean" type="around" bind-to="getDomainObjectDAOPC"/>
  4. </aspect>

Once this is weaved into the DomainObject class, getDomainObjectDAO will be intercepted at runtime and will retrieve the DAO bean instance dynamically. Because it does not cache the bean reference, this implementation is not limited to inject singleton beans, but also prototypes.

On first sight, this approach may cause some concerns in the readability of the codebase. However as long as this is used consistently across the application, and becomes a pattern, it is not hard to realize the getter method is being injected - not harder than any average AOP intercepted code anyway. Another plus of it is it does not rely on any specific ORM tool feature (such as the Hibernate events). Finally, if a different way of providing the DAO is needed in the future, a subclass can always override this method.

Trackback from your site, or follow the comments in RSS.

Post a Comment