I was looking for a way to enrich my domain-model by having dependencies automatically injected into my domain-objects.
There is a solution to this problem using Hibernate. This approach was based on inserting an Interceptor into Hibernate that injected dependencies during object-instantiation. Hibernate allowed the client to insert an instance of an Interceptor. I was hoping to find a similar solution for JPA.
During my search, I found another approach that was not relying on any persistence-mechanism. This approach used a runtime-generated proxy for the appropriate layer that did injections based on configuration. First of all, I was looking for a solution using Spring Framework, and this approach was implemented with Nuts. I also suspect that this approach would trigger lazy-loading in JPA.
I then created my abstraction for enriching my domain-objects:
public interface DependencyInjector {
void performInjection(Object existingBean);
}
I then turned to JPA's EntityListeners. I quickly realised that the persistence-provider was instantiating the EntityListeners, so I settled on having the DependencyInjector as a static instance-variable on my EntityListener.
The EntityListener then looked like:
public class DependencyInjectionEntityListener {
private static DependencyInjector _injector;
public static void setInjector(DependencyInjector injector) {
_injector = injector;
}
@PrePersist
@PostLoad
public void performInjection(Object entity) {
if (_injector == null)
throw new IllegalStateException("EntityListener cannot"
+ " be used without an injector");
_injector.performInjection(entity);
}
}
If I wanted to intercept JPA at other events than @PrePersist and @PostLoad I could add them to the method in my EntityListener.
Now, the static variables in this class is not easy to configure with Spring, so I create this configurer-class:
public class DependencyInjectionEntityListenerConfigurer
implements InitializingBean {
private DependencyInjector injector;
public void setInjector(DependencyInjector injector) {
if (injector == null)
throw new NullPointerException("Injector cannot be null");
this.injector = injector;
}
public void afterPropertiesSet() throws Exception {
DependencyInjectionEntityListener.setInjector(injector);
}
}
To finish this solution I create a simple Autowire-by-type DependencyInjector using Spring:
public class AutowireDependencyInjector
implements DependencyInjector, BeanFactoryAware {
private AutowireCapableBeanFactory beanFactory;
public void setBeanFactory(BeanFactory beanFactory) {
if (!(beanFactory instanceof AutowireCapableBeanFactory))
throw new IllegalArgumentException("BeanFactory must"
+ " be an AutowireCapableBeanFactory");
this.beanFactory = (AutowireCapableBeanFactory) beanFactory;
}
public void performInjection(Object existingBean) {
beanFactory.autowireBeanProperties(existingBean,
AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, false);
}
}
To test this approach I create a simple domain-model with one domain-object and a service.
The domain-object looks like this:
@Entity
@EntityListeners(DependencyInjectionEntityListener.class)
public class DomainObject {
@Id
private long id;
DomainObject() {
}
public DomainObject(long id) {
this.id = id;
}
public long getId() {
return id;
}
@Transient
private SomeService service;
public void setService(SomeService service) {
this.service = service;
}
public boolean hasService() {
return service != null;
}
}
The service has to be marked as @Transient, as it will not be persisted.
The hasService()-method is my way of testing that this approach works.
The service looks like this:
public interface SomeService {
void performService(DomainObject test);
}
I also need a repository for holding my DomainObjects:
public interface DomainObjectRepository {
DomainObject findDomainObject(long id);
void persistDomainObject(DomainObject test);
void flushAndClear();
}
The flushAndClear()-method is there to be able to clear the JPA-cache and make sure that the @PostLoad-callback will be initiated.
I create a simple implementation of this repository with JPA:
public class DomainObjectRepositoryJpaImpl
implements DomainObjectRepository {
@PersistenceContext
private EntityManager em;
public DomainObject findDomainObject(long id) {
return em.find(DomainObject.class, id);
}
public void persistDomainObject(DomainObject domainObject) {
em.persist(domainObject);
}
public void flushAndClear() {
em.flush();
em.clear();
}
}
I create a tiny implementation of my bogus-service, just for fun:
public class SomeServiceImpl implements SomeService {
public void performService(DomainObject domainObject) {
System.out.println("Performing service on domain-object "
+ domainObject.getId());
}
}
My persistence.xml-file lists my DomainObject, and I have a spring-config that wires this this up.
Now, all I have to do is to make an integration-test that verifies that this works. The test looks like this:
public class DomainObjectInjectionTest extends
AbstractTransactionalDataSourceSpringContextTests {
@Override
protected String[] getConfigLocations() {
return new String[] { "classpath:beans.xml" };
}
DomainObjectRepository domainObjectRepository;
public void setDomainObjectRepository(DomainObjectRepository
domainObjectRepository) {
this.domainObjectRepository = domainObjectRepository;
}
public void testInjectionOnPersist() {
DomainObject domainObject = new DomainObject(1L);
assertFalse(domainObject.hasService());
domainObjectRepository.persistDomainObject(domainObject);
assertTrue(domainObject.hasService());
}
public void testInjectionOnLoad() {
DomainObject domainObject = new DomainObject(1L);
domainObjectRepository.persistDomainObject(domainObject);
domainObjectRepository.flushAndClear();
domainObject = null;
domainObject = domainObjectRepository.findDomainObject(1L);
assertTrue(domainObject.hasService());
}
}
The test passes, and I'm happy. The obvious drawback is the fact that the EntityListener must be configured statically, but this is a limitation of the JPA-spec. The solution is, on the other hand, quite simple and doesn't involve any AOP-magic.
You can download this example (see the attached file) as a maven-project, that is ready to unpack and run.
Kommentarer
WOW, this is exactely what I
WOW, this is exactely what I was look for about using Spring and no AOP. Thanks a lot, pal
dissertation writing service || buy essay
I was doing my term paper
I was doing my term paper but I can't stop thinking about fixing my blog and then I saw this article. I find it really interesting but I guess I got to start to write my essay first then I can study how to insert these codes
excellent article. i was
excellent article. i was looking for rich-domain-model solution which is aop-less some time ago without success. reading your article provides a good solution for me which can be easily integrated in an existing application.