Generics, Inversion of Control and Repository<T>

Published 03 April 08 07:32 AM | andersnoras 

Whenever I stray off the beaten path of Java generics, I instantly miss C#’s generics implementation. Earlier today, Java’s type erasure erased a few good hours of productivity whilst I was doing a spike on bringing the IRepository<T> experience to Java. In C# you can get the class of any generic type argument in a straight forward manner.

public void DoStuff(T thing) 
{
Type thingsType = typeof(T);
}

Because of Java’s type erasure, things aren’t as simple. The Java equivalent of this C# example won’t compile.

public <T> void doStuff(T thing) {
Class thingsType = T.class;
}

Still, all is not lost. Even if you’ve been told otherwise, some information about generic type arguments is available at runtime. While the JVM doesn’t track the type arguments for an instance of a generic class, it actually does this for subclasses of that generic class. To work around this limitation you can add the required behavior to infer a class’ type arguments to an abstract base class. Ian Robertson has a good write up about this at Artima.Since many of the features of any repository are common, regardless of its final implementation, my initial design already had an abstract repository class and a repository interface, so using the “extend and infer” technique described in Ian’s post was OK. Basically, this allows you to do this:

public abstract class AbstractRepository<T> implements Repository<T> {
private Class entityClass;
public final Class getEntityClass() {
if (entityClass == null) {
ReflectionHelper reflectionHelper = new ReflectionHelper();
entityClass = reflectionHelper.getTypeArguments(
AbstractRepository.class,getClass()
).get(0);
}
return entityClass;
}
void setEntityClass(Class entityClass) {
this.entityClass = entityClass;
}
// Implementations of common repository features like
// load, save, delete and so on go here...
}

public class ProductRepository extends AbstractRepository<Product> {}

Since ProductRepository specifically extends the AbstractRepository, the type information will be available at runtime and the following test will pass.

Repository<Product> repository = new ProductRepository();
Class inferredEntityClass = repository.getEntityClass();
Assert.assertSame(Product.class,inferredEntityClass);

But apart form implementing an abstract class, the ProductRepository serves no real purpose. In a real-life scenario, you would probably add some operations specific to Product entities to the repository, but there is no need to do this. So, I’d really like to have a way to create instances of a repository for any entity without creating a separate implementation of the repository for each entity class.Because of the trick we’re using to infer type arguments, we cannot turn the AbstractRepository into a concrete class, but we can extend it with a concrete generic class.

public class ConcreteRepository<T> extends AbstractRepository<T> {  }

Ayende uses this approach to support different object relational mappers in his implementation of this pattern for .NET. However, if we try to use this approach with Java, type erasure comes in and screw things up again.

Repository<Product> repository = new ConcreteRepository<Product>();
Class inferredEntityClass = repository.getEntityClass();
// This assertion fails because the type argument (Product) has been
// erased and replaced with java.lang.Object...
Assert.assertSame(Product.class,inferredEntityClass);

To work around this, we can stick with our abstract repository and employ Neil Gafter’s “gadget” aka Super Type Tokens.

Repository<Product> repository = new AbstractRepository<Product>() {};
// Did you notice this method in the AbstractRepository<T> class?
repository.setEntityClass(Product.class);
Class inferredEntityClass = repository.getEntityClass();
Assert.assertSame(Product.class,inferredEntityClass);

This works, but it makes the client code ugly. So you probably should cover the ugliness with some factory “makeup”.

public static <T> Repository<T> create(Class<? extends T> entityClass) {
AbstractRepository<T> repository = new AbstractRepository<T>() {};
repository.setEntityClass(entityClass);
return repository;
}

Now we can instantiate generic repositories this way…

Repository<Product> repository = RepositoryFactory.create(Product.class);
Class inferredEntityClass = repository.getEntityClass();
Assert.assertSame(Product.class,inferredEntityClass);

…and IMHO, that looks quite alright.

Having a good implementation of a generic repository in place opens the doors to creating adaptive domain models using the patterns and design techniques Ayende and Udi have been writing quite a lot about lately. The missing piece to the puzzle is better generics support from the dependency injection containers available for Java. I’m a bit surprised that there aren’t (AFIK) any containers available that support generics in a similar fashion to what Spring.NET or Windsor do. To get this piece in place, I actually started something I promised myself that I’d never do again - I started to build my own inversion of control framework. Nothing is ready to be made public yet, but I’ll provide you with some examples of its very limited features.You can register components by specifying its interface and implementation like this (No XML configuration for me, baby!)…

IoC.register(Repository.class, ProductRepository.class);

…and you can resolve a dependency at a later stage by specifying the interface and its generic type argument(s).

Repository<Product> repository = IoC.resolve(Repository.class,Product.class);
Assert.assertTrue(repository instanceof ProductRepository);

Before you start screaming about the static methods, I’ll inform you that the IoC class is just an accessor that makes the IoC framework readily available. This is an approach I’ve been using for quite some time, and I recommend trying it for yourself. Its very convenient.

So what do you guys think? Do we need another dependency injection framework? (No worries, I’m just doing this to support some demo code I’m writing and I have no intentions making it a fully fledged framework at the time being.)

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

# Terje Sten Bjerkseth said on April 3, 2008 6:50 PM:

Did you check Google Guice and its TypeLiteral?

# Martin said on April 6, 2008 9:59 AM:

@ Terie:

Google Guice's class TypeLiteral employs Neal Gafter's idea of Super Type Tokens as far as I know...

# andersnoras said on April 6, 2008 3:56 PM:

@Terje & Martin;

You're right that Guice's TypeLiteral (which indeed is an implementation of "Gafter's gadget") can be used in some scenarios, and it will be sufficient in the IoC example above. You could setup your module like this:

public class RepositoriesModule implements Module {

   public void configure(Binder binder) {

       binder.bind(new TypeLiteral<Repository<Product>>() {}).to(ProductRepository.class);

   }

}

...and resolve the dependency like this:

Injector injector = Guice.createInjector(new RepositoriesModule());

Repository<Product> repository = injector.getInstance(Key.get(new TypeLiteral<Repository<Product>>() {}));

However, I want to have a way to bind the interface to a class with a type argument so that I can create a concrete instance based on the type arguments requested for the interface. This would be similar to the factory example given above. So for instance, I could install just the Repository<T> interface with a binding to HibernateRepository<T> and have concrete the concrete implementation HibernateRepository<Product> resolved in the Guice example above.

I think it is doable, because the TypeLiteral<Repository<Product>> has all the information need to get the type arguments needed. What would be required in addition is a way to install components with wildcards akin to this:

binder.bind(new TypeLiteral<Repository<?>>() {}).toProvider(new RepositoryProvider());

..which of course does not work for two reasons;

1) The compiler does not allow wildcard arguments here.

2) Guice won't be able to resolve the provider because of key ambiguity.

# Anders Norås' Blog said on April 7, 2008 6:29 AM:

My last post on Java generics and the Repository pattern got a bit of interest. Judging from the feedback

# Confluence: Enterprise Architecture said on August 20, 2008 1:01 AM:

http://wiki.community.objectware....

# Confluence: Enterprise Architecture said on August 20, 2008 9:47 AM:

Intro The Emerging Technologies the future of enterprise POJOS is the first in a series of open seminars/workshops. The idea is to facilitate discussions between thought leaders and peers on important aspects of the software development....

Leave a Comment

(required) 
(optional)
(required) 
Enter the code you see below