01 January 2012

So, I had a use case the other day - I wanted to support a custom annotation for denoting injection at the class level. Spring users will know that Spring already supports Spring's native @Autowired, JSR 330's @javax.inject.Inject, and JSR 250's '@javax.resource.Resource. These annotations, when placed on a setter, or a field, signal to Spring that a value should be injected at the site of the annotation. These annotations are synonyms for the same thing. Spring also supports other annotations - like the JPA 1.0 @javax.persistence.PersistenceContext annotation. The annotation is used in a special circumstance, when you want Spring to inject an EntityManager or an EntityManagerFactory. Here are some examples.


	@javax.inject.Inject 
	private DataSource dataSource ;
	
	@Autowired  
	public void setDataSource(DataSource ds){
	  this.datSource = ds ;
	}
	
	@PersistenceContext 
	private EntityManager entityManager; 	

To "teach" Spring what to do with a custom annotation like @PersistenceContext is fairly easy once you know the actors involved. The work is typically done inside of a special BeanPostProcessor. What we want is a framework hook to inspect the beans after the bean's have been created, but before they've been populated with properties. The BeanPostProcessor interface is powerful, but not quite what we need. What we actually need is a combination of the the more specialized InstantiationAwareBeanPostProcessor and MergedBeanDefinitionPostProcessor. InstantiationAwareBeanPostProcessor provides the following callback method:

PropertyValues postProcessPropertyValues( PropertyValues pvs, PropertyDescriptor[] pds, 
		                       Object bean, String beanName) throws BeansException;
The callback hook lets you stipulate a value for a properties on the bean. We can use that to inject custom values. It turns out that Spring already provides a convenient framework object that you can use to handle the duty of injection called InjectionMetadata. The metadata represents information about the injection sites. You can extend this class and provide specific metadata about the call site - perhaps by caching the contents of the annotation that you're dealing with - and then use that to actually perform the injection. It is up to you to crawl the beans - including the super classes and so on - to gather the fields and property setters and then provide the relevant InjectionMetadata. There's a lot of expectations here, but it's pretty simple to implement. I have provided a reusable base class (below) that generalizes all but the specific concerns. This code is written in Scala, but it can be reused from Java, once compiled. Alternatively, it wouldn't be hard to translate to Java. The only thing that wouldn't map nicely into Java are the uses of callback methods, which can be expressed nicely in Scala, but would require an object with a callback method. (I probably should re-write it in Java and provide callback interfaces in lieu of the various callback methods or provide an abstract base class with well known, abstract callback methods) Anyway, you can basically ignore the implementation - it's presented so you can include it in your project (under the Apache 2 license, of course). See you after the example to see how to implement it.


	package com.joshlong.spring.util 
	import org.springframework.beans.factory.support.{RootBeanDefinition, MergedBeanDefinitionPostProcessor}
	import org.springframework.beans.factory.annotation.InjectionMetadata
	import org.springframework.beans.factory.BeanCreationException
	import java.beans.PropertyDescriptor
	import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor
	import org.springframework.beans.PropertyValues
	import java.util.LinkedList
	import java.lang.{String, Class}
	import java.lang.reflect.{Method, Field, Member, Modifier}
	import org.apache.commons.logging.LogFactory
	class AnnotatedSiteInjectionPostProcessor [T <: AnyRef, X <: java.lang.annotation.Annotation]
	  (annotation: Class[X], fieldMetadataCallback: (X, Field) => InjectionMetadata.InjectedElement, methodMetadataCallback: (X, Method) => InjectionMetadata.InjectedElement)
	  extends InstantiationAwareBeanPostProcessor with MergedBeanDefinitionPostProcessor {
	  val logger = LogFactory.getLog(getClass)
	  def postProcessBeforeInstantiation(beanClass: Class[_], beanName: String) = null
	  def postProcessBeforeInitialization(bean: AnyRef, beanName: String) = bean
	  def postProcessAfterInstantiation(bean: AnyRef, beanName: String) = true
	  def postProcessAfterInitialization(bean: AnyRef, beanName: String) = bean
	  def postProcessMergedBeanDefinition(beanDefinition: RootBeanDefinition, beanType: Class[_], beanName: String) {
	    if (beanType != null) {
	      var metadata: InjectionMetadata = findInjectionSiteMetadata(beanType)
	      metadata.checkConfigMembers(beanDefinition)
	    }
	  }
	  def postProcessPropertyValues(pvs: PropertyValues, pds: Array[PropertyDescriptor], bean: AnyRef, beanName: String): PropertyValues = {
	    try {
	      logger.debug("about to attempt injection for class "+ bean.getClass )
	      val metadata = findInjectionSiteMetadata(bean.getClass)
	      metadata.inject(bean, beanName, pvs)
	    } catch {
	      case ex: Throwable =>
	        throw new BeanCreationException(beanName, "Injection of persistence dependencies failed", ex)
	    }
	    pvs
	  }
	  private def doWithMembers[T <: Member](clazz: Class[_], filter: T => Boolean, fieldsFactory: (Class[_]) => Array[T], doWith: T => Unit) = {
	    var targetClass: Class[_] = clazz
	    do {
	      fieldsFactory(targetClass).filter(filter).foreach(doWith)
	      targetClass = targetClass.getSuperclass
	    } while (targetClass != null && !targetClass.equals(classOf[AnyRef]))
	  }
	  private def findInjectionSiteMetadata(clazz: Class[_]): InjectionMetadata = {
	    val currElements = new LinkedList[InjectionMetadata.InjectedElement]
	    doWithMembers[Field](clazz, f => !Modifier.isStatic(f.getModifiers) && f.getAnnotation(annotation) != null, c => c.getDeclaredFields, f => {
	      val fieldAnnotation = f.getAnnotation(annotation)
	      val injectedElement = fieldMetadataCallback(fieldAnnotation, f)
	      currElements.add(injectedElement)
	    })
	    doWithMembers[Method](clazz, f => !Modifier.isStatic(f.getModifiers) && f.getAnnotation(annotation) != null, c => c.getDeclaredMethods, m => {
	      val methodAnnotation = m.getAnnotation(annotation)
	      val injectedElement = methodMetadataCallback(methodAnnotation, m)
	      currElements.add(injectedElement)
	    })
	    new InjectionMetadata(clazz, currElements)
	  }
	}
	

This class takes care of crawling the beans and performing the injection for you. All it needs from you is a subclass of InjectionMetadata.InjectedElement, which you provide. That object in turn is responsible for ultimately providing the reference to the object that you want to be injected based on the metadata available. To make this easy, this particular responsibility is factored out as callback functions which you provide in your concrete implementation. To do its work, you need to tell this class which annotation to match, and the type of object you'd like to inject (you could stipulate just a regular java.lang.Object, or, in Scala terms, an AnyRef.).

In this particular example, I want an annotation that can be used to lookup, and inject, a reference to an Akka actor, called a ActorRef in Akka parlance. Akka actors can live locally, or remotely, and when injected you can lookup an Actor by a logical name or by a path, which itself can be absolute, or relative, as Akka actors form supervisory hierarchies which map very much like a file system. For more information on the supported addressing scheme, check out ths document from the Akka documentation.

So, the example (in Java) might look like this:

@ActorReference("akka://my-system@serv.example.com:5678/app/service-b") private ActorRef actorRef ; 
or (in Scala)
@ActorReference("akka://my-system@serv.example.com:5678/app/service-b") private var actorRef: ActorRef = _  
Let's see how to implement this using the base class described above.

First, we need to extend the base class, and provide the callback hooks that tell the base class what to do with a field or property (a setter method). In our case, this is pretty simple, and straight forward:

class ActorReferenceAnnotatedSiteInjectPostProcessor(actorSystem: ActorSystem)
  extends AnnotatedSiteInjectionPostProcessor[ActorRef, ActorReference](
    classOf[akka.spring.ActorReference],
    (ar: ActorReference, f: Field) => new ActorReferenceInjectedElement(actorSystem, ar, f, null),
    (ar: ActorReference, m: Method) => new ActorReferenceInjectedElement(actorSystem, ar, m, BeanUtils.findPropertyForMethod(m)))

In this class, we extend the base class and call the constructor, passing in the class of the annotation to detect, a callback method which accepts an instance of the annotation and a particular instance of a field, and another callback method and a particular instance of a method. The contract is that, given the instance of the annotation and matching field or method, you will return an instance (or sublcass of) the InjectionMetadata.InjectedElement class. So, that's what we do, constructing an instance of ActorReferenceInjectedElement with the ActorSystem reference given in this subclass' constructor and the detected ActorReference annotation, and the detected class member to which the annotation was attached, a java.lang.reflect.Field or java.lang.reflect.Method as well as the annotation itself.

The ActorReferenceInjectedElement is where the important work happens. Let's take a look at that class.

class ActorReferenceInjectedElement(
  actorSystem: ActorSystem, 
  annotation:ActorReference, 
  member: Member, 
  propertyDescriptor: PropertyDescriptor) 
extends InjectionMetadata.InjectedElement(member, pd) {
			
  override def getResourceToInject(target: AnyRef, requestingBeanName: String) = actorSystem.actorFor(annotation.value() )
}
	

If you read Scala, then this is a pretty simple class. The constructor (the prototype of which is included inline in the class declaration, at the top) expects an ActorSystem, a reference to the ActorReference annotation, a java.lang.reflect.Member (the common base class of both Field and Method), and a PropertyDescriptor. We don't use the PropertyDescriptor in this code (except to invoke the super constructor), but it's nice to know that we have it available. It might be null, however, if we're looking at a java.lang.reflect.Field. The unspoken bit is that the constructor arguments implicitly become class variables, and so are reference-able from other methods.

In the class, we override the getResourceToInject method, providing the object to be injected. We simply need a reference to the actor, so call actorSystem.actorFor with the String that was provided as part of the annotation (the value() field). We could, for example, provide a wrapper object instead of the reference itself. This is what happens when you use the @PersistenceContext annotation. It injects an EntityManager proxy that in turn manages thread-local EntityManagers so that no matter which thread you call a method on, if it accesses the class EntityManager, it's guaranteed to be thread-safe. We don't do anything fancy like that in this case, but it's easy to see the potential there.

Now, all that's left to do is register the ActorReferenceAnnotatedSiteInjectPostProcessor in your Spring configuration. It's just a regular bean with constructor arguments. Spring detects the interfaces and calls the appropriate methods. Here's a code configuration based example (written in Scala):


@Configuration 
class MyAkkaConfiguration  {
  @Bean def actorSystem = ... // provide your own reference 
	
  @Bean def actorReferenceAnnotatedSiteInjectPostProcessor = 
    new ActorReferenceAnnotatedSiteInjectPostProcessor( this.actorSystem())	
	
  @Bean("sample") def someBeanThatDependsOnActors =
    new Object {  // anonymous inline object just to show you what a usage might look like 
      @ActorReference("myActor") 
      var someActor:ActorRef = _ 	
    }
} 					
object Main {
  val ac = new AnnotationConfigApplicationContext(classOf[MyAkkaConfiguration])
  val sample  = ac.getBean("sample")
  Assert.notNull( sample.actorSystem )	
}