Generics, Inversion of Control and Repository<T>
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.)