Spring and annotation-driven injection - No Fluff Just Stuff

Spring and annotation-driven injection

Posted by: Craig Walls on December 6, 2005

It's been awhile since I've blogged, I know. The last couple of months have kept me quite busy...

  • Preparing for the LoneStar Software Symposium in Dallas
  • My wife having surgery (which prevented me from attending LSSS in Dallas)
  • Preparing for Christmas
  • Preparing for The Spring Experience
  • Preparing for my daughter's first birthday
  • Looking for a new job

It's been quite hectic, to say the least.

But before I board a plane for Fort Lauderdale to speak at The Spring Experience, I wanted to throw a little something out here to make up for my silence.

Norman and I spend a lot of time on e-mail and chat discussing the ins and outs of Spring vs. EJB 3. Norm, being a JBoss'er, comes at this from an EJB 3/JBoss perspective, while I bring an Spring view to the table.

Recently, the topic turned to why Spring must be configured via XML and why dependency injection can't be done using annotations. Actually, Spring doesn't have to be configured with XML. XML just happens to be the weapon of choice for Spring'ers. In fact, I've been thinking of writing a YAML if for no other reason than to prove that it can be done.

But since the topic came up, why not try a Java 5 annotation approach?

Regardless of your philosophical views on whether injection should or should not be done using annotations, I thought it would at least be an interesting exercise to add annotation-injection to Spring. (Truthfully, I'm not sure that I'm in favor of using annotation-injection, but with enough people talking about it, I thought it couldn't hurt to try it out and see what comes of it.)

What follows is a work in progress annotation-injection Spring container. Let me stress the phrase "work in progress", as there's plenty of room for improvement.

Defining the annotations

In my simple annotation-driven Spring container, I'm going to use three annotations:

  • @Bean : Marks a class as being a Spring-managed bean. Roughly equivalent to the <bean> XML tag.
  • @Ref : Tags a property to be injected with another bean. Roughly equivalent to the <ref> XML tag.
  • @Value : Tags a property to be injected with a specific value. Roughly equivalent to the <value> XML tag.

The code for the annotations are as follows:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Bean {
  String name();
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Ref {
  String name();
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Ref {
  String name();
}

The annotations themselves are fairly straight-forward. Where they get interesting is in how they're interpreted by a Spring container.

Defining an annotation container

Processing the Spring annotations should follow a simple pattern:

  • For each class that has a @Bean annotation do:
    • Create a BeanDefinition
    • For each property that has a @Ref annotation do:
      • Add a RuntimeBeanReference to the BeanDefinition's MutablePropertyValues.
    • For each property that has a @Ref annotation do:
      • Set the specific value to the BeanDefinition's MutablePropertyValues.
    • Register the BeanDefinition to the container.
  • Refresh the container (to resolve any unresolved references).

My own implementation of AnnotationApplicationContext is listed here:

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.support.GenericApplicationContext;

public class AnnotationApplicationContext extends GenericApplicationContext {  
  public AnnotationApplicationContext(String[] beanNames) {
    registerBeansByName(beanNames);
    refresh();
  }
  
  private void registerBeansByName(String[] beanNames) {
    for(int i=0; i < beanNames.length; i++) {       registerBeanByName(beanNames[i]);     }   }      private void registerBeanByName(String beanName) {     try {       registerBean(Class.forName(beanName));     } catch (ClassNotFoundException e) {       throw new BeanDefinitionStoreException("Error registering bean with class '"+beanName+"'");     }   }      private void registerBean(Class clazz) {       if(clazz.isAnnotationPresent(Bean.class)) {         Field[] fields = clazz.getDeclaredFields();         Map refMap = new HashMap();         for(int i=0; i < fields.length; i++) {           Field field = fields[i];           if(field.isAnnotationPresent(Ref.class)) {             Ref ref = (Ref) field.getAnnotation(Ref.class);             RuntimeBeanReference beanRef = new RuntimeBeanReference(ref.name());             refMap.put(field.getName(), beanRef);           } else if (field.isAnnotationPresent(Value.class)) {             Value value = (Value) field.getAnnotation(Value.class);             refMap.put(field.getName(), value.value());                                 }         }                        Bean annotation = (Bean) clazz.getAnnotation(Bean.class);         MutablePropertyValues mpvs = new MutablePropertyValues(refMap);          BeanDefinition beanDef = new RootBeanDefinition(clazz, mpvs, true);         registerBeanDefinition(annotation.name(), beanDef);       }   } }

The most interesting part of AnnotationApplicationContext is in the registerBean() method. Here, I inspect a given class for the @Bean annotation and, if found, process any @Ref or @Value annotations on its properties. In the end, I call the registerBeanDefinition() method to register the bean with the container.

The unfortunate part of AnnotationApplicationContext is that I must specify all annotated beans with their FQCN at constructor time. I'd rather provide a list of packages to inspect for @Bean-annotated classes, but from what I can tell, it's really hard to get a list of classes given a specific package name. (If someone knows a trick for this, let me know.) So I must explicitly tell the container which classes might have Spring annotations in them.

Spring annotations in action

Now let's look at a few Spring-annotated beans. First, here's a DvdPlayerImpl class:

@Bean(name="player")
public class DvdPlayerImpl implements DvdPlayer {
  public DvdPlayerImpl() {}
  
  public void play() {
      if(dvd != null) {
        System.out.println("NOW PLAYING:  " + dvd.getTitle());
        dvd.play();
      } else {
        System.err.println("No DVD loaded");
      }
  }
  
  @Ref(name="movie")
  private Dvd dvd;
  public void setDvd(Dvd dvd) { this.dvd = dvd; }
}

Here the bean has been named "player" and thus will be registered into the Spring application context with that name. It also has a "dvd" property that I use the @Ref annotation to inject it with a bean named "movie".

As for the "movie" bean, here's the code for SuddenImpact:

@Bean(name="movie")
public class SuddenImpact implements Dvd {
  public SuddenImpact() {}
  
  public void play() {
      System.out.println("Go ahead...make my day.");
  }

  @Value("Sudden Impact")
  private String title;
  public void setTitle(String title) { this.title = title; }
  public String getTitle() { return title; }
}

As before, the @Bean annotation is used to declare this as a Spring-managed bean. But instead of using @Ref to inject a bean reference, I'm using @Value to inject a hard-coded value into the "title" property.

To try these out, you simply instantiate AnnotationApplicationContext, giving it the names of the annotated classes, then use the beans contained therein:

  public static void main(String[] args) throws Exception {
    String[] annotatedClasses = new String[] {
            "com.habuma.movies.DvdPlayerImpl",
            "com.habuma.movies.SuddenImpact"
    };
      
    ApplicationContext ctx = new AnnotationApplicationContext(annotatedClasses);
    DvdPlayer player = (DvdPlayer) ctx.getBean("player");
    player.play();
  }

When this main() method is executed, the output is:

NOW PLAYING: Sudden Impact
Go ahead...make my day.

As I said, AnnotationApplicationContext is a work in progress. If you have any suggestions or would like to build more functionality into it, then please feel free. Keep me informed of your progress.

Why I think this is a bad idea

As I stated before, this was a fun exercise, but I'm not so sure if it's a good idea to use annotations for dependency injection.

I think annotations should be used to provide metadata that describes objects at the class-level, not at the instance-level. Consider EJB 3/Hibernate-persistence annotations. These annotations describe persistence at a class-level. An Employee class may be annotated with instructions on how all employees should be persisted, but the annotations don't describe how any particular instance of Employee should be persisted. This, in my opinion, is a proper use of annotations.

Dependency injection, on the other hand, is an instance-level activity. Using annotations to describe how a bean and its properties should be registered in a dependency-injection container (such as Spring) makes it hard to have more than one instance of that class (each configured differently) in the same container. The annotations effectively say that all instances of DvdPlayerImpl should be registered in the container with the name "player" and their "dvd" property should always be injected with a bean whose name is "movie". (Of course, with all instances being registered with the same name, only one instance will be registered.) This, again in my opinion, is a misuse of annotations.

So, even though I've shown you how to add annotation-driven dependency-injection to Spring, I'm convinced that you should probably never use it. But I wanted to throw this out here anyway to spark some discussion.

It's your turn: Tell me why annotation-driven injection is a good idea. Or tell me why it's a bad idea. I'm eager to hear your thoughts. Or maybe you have some ideas on how to improve AnnotationApplicationContext. Regardless, leave a comment here and let's talk about it.

For those of you going to The Spring Experience, look for me and say "hi". I look forward to meeting you.

Craig Walls

About Craig Walls

Craig Walls is a Principal Engineer, Java Champion, Alexa Champion, and the author of Spring AI in Action, Spring in Action, and Build Talking Apps. He's a zealous promoter of the Spring Framework, speaking frequently at local user groups and conferences and writing about Spring. When he's not slinging code, Craig is planning his next trip to Disney World or Disneyland and spending as much time as he can with his wife, two daughters, 1 bird and 2 dogs.

Why Attend the NFJS Tour?

  • » Cutting-Edge Technologies
  • » Agile Practices
  • » Peer Exchange

Current Topics:

  • Languages on the JVM: Scala, Groovy, Clojure
  • Enterprise Java
  • Core Java, Java 8
  • Agility
  • Testing: Geb, Spock, Easyb
  • REST
  • NoSQL: MongoDB, Cassandra
  • Hadoop
  • Spring 4
  • Cloud
  • Automation Tools: Gradle, Git, Jenkins, Sonar
  • HTML5, CSS3, AngularJS, jQuery, Usability
  • Mobile Apps - iPhone and Android
  • More...
Learn More »