Skip to content
 

JSR-330 mit Spring

Mit JSR-330 werden Annotationen, die für die Dependency Injection verwendet werden, standardisiert. Der Final Release ist am 14. Oktober 2009 erschienen. Den Sourcecode findet man auf der Projektseite atinject.

Spring 3.0.0.RC2 ist vollständig JSR-330 kompatibel und besteht auch die Tests des Technology Compatibility Kit (TCK) (siehe Spring Framework 3.0 RC2 released).

Weitere Libraries die JSR-330 unterstützen sind Weld (JSR-299) und zukünftige Versionen von Guice.

Für das folgende Beispiel werden diese Libraries benötigt.

<dependency org="org.springframework" name="spring-core" rev="3.0.0.RC2"/>
<dependency org="org.springframework" name="spring-beans" rev="3.0.0.RC2"/>
<dependency org="org.springframework" name="spring-context" rev="3.0.0.RC2"/>
<dependency org="org.springframework" name="spring-asm" rev="3.0.0.RC2"/>
<dependency org="org.springframework" name="spring-expression" rev="3.0.0.RC2"/>

Und natürlich das JSR-330 Jar. Dieses befindet sich im Maven Central Repository und kann mit folgenden Angaben heruntergeladen werden.

<dependency org="javax.inject" name="javax.inject" rev="1" />

Im applictaionContext.xml genügt es ein <context:component-scan …> einzufügen. JSR-330 wird automatisch aktiviert wenn sich das javax.inject Jar im Klassenpfad befindet.

<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:context="http://www.springframework.org/schema/context"
  xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-3.0.xsd

http://www.springframework.org/schema/context

                           http://www.springframework.org/schema/context/spring-context-3.0.xsd">

  <context:component-scan base-package="ch.ralscha.test" />
</beans>

Als Beispiel erstellen wir ein Interface RandomGenerator, welches eine Methode randomInt definiert, und dazu zwei Implementation (NormalRandomGenerator und SecureRandomGenerator).

package ch.ralscha.test;
public interface RandomGenerator {
  int randomInt();
}

Die beiden Implementationen werden mit @Named annotiert. In Spring kann @Named wie @Service oder @Component verwendet werden. Wenn @Named kein Parameter mitgegeben wird entsprechen die Namen der Beans den Namen der Klassen. Dabei wird der erste Buchstabe klein geschrieben. Die Klasse NormalRandomGenerator wird also mit dem Namen “normalRandomGenerator” im Context abgelegt.

package ch.ralscha.test;
import java.util.Random;
import javax.inject.Named;
@Named
public class NormalRandomGenerator implements RandomGenerator {

  private Random random;

  public NormalRandomGenerator() {
    random = new Random();
  }

  public int randomInt() {
    return random.nextInt(100);
  }
}

Die Klasse SecureRandomGenerator wird mit dem Bean Namen “secure” im Context abgelegt.

package ch.ralscha.test;
import java.security.SecureRandom;
import javax.inject.Named;
@Named("secure")
public class SecureRandomGenerator implements RandomGenerator {

  private SecureRandom random;  

  public SecureRandomGenerator() {
    random = new SecureRandom();
  }

  public int randomInt() {
    return random.nextInt(100);
  }
}

Als letztes erstellen wir eine Klasse Calculation in welcher der RandomGenerator injiziert wird, dazu wird @Inject verwendet. @Inject entspricht der Spring eigenen Annotation @Autowired.

Da zwei Implementationen des RandomGenerators existieren muss angegeben werden welche davon injiziert werden soll. Dies geschieht mit der Annotation @Named und als Parameter wird der Name des Beans übergeben. Dies entspricht der Spring eigenen Annotation @Qualifier.

package ch.ralscha.test;
import javax.inject.Inject;
import javax.inject.Named;

@Named
public class Calculation {

  @Inject
  @Named("secure")
  private RandomGenerator randomGenerator;

  //@Inject
  //@Named("normalRandomGenerator")
  //private RandomGenerator randomGenerator;

  public void doSomething() {
    System.out.println(randomGenerator.randomInt());
  }
}

Das Beispiel kann nun wie folgt gestartet werden:

ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
Calculation calc = ctx.getBean("calculation", Calculation.class);
calc.doSomething();

Das Beispiel funktioniert auch ohne die Datei applicationContext.xml indem man die neue Context Klasse AnnotationConfigApplicationContext verwendet.

ApplicationContext ctx = new AnnotationConfigApplicationContext("ch.ralscha.test");
Calculation calc = ctx.getBean("calculation", Calculation.class);
calc.doSomething();

Die obengenannte Methode, Beans mit @Inject @Named(“…”) zu injizieren, hat natürlich mehrere Nachteile. Man kann sich verschreiben. @Inject @Named(“secured”) würde nicht funktionieren und eine Exception zur Laufzeit wäre die Folge. Des weiteren ist diese Lösung nicht Refactoring-freundlich. Wenn zum Beispiel NormalRandomGenerator neu AVeryNormalRandomGenerator heisst, würde der Code auch nicht mehr funktionieren. Um all diese Nachteile zu vermeiden gibt es in JSR-330 die Möglichkeit sogenannte Qualifier Annotationen zu verwenden. Dazu wird die Annotation @javax.inject.Qualifier eingesetzt. Nicht zu verwechseln mit der Spring eigenen @Qualifier Annotation.

Da zwei Implementationen des Interface RandomGenerator existieren, werden zwei Qualifier Annotationen benötigt. Dies sind normale Annotationen die mit @javax.inject.Qualifier annotiert werden.

package ch.ralscha.test;
@javax.inject.Qualifier
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
public @interface Normal {}
package ch.ralscha.test;
@javax.inject.Qualifier
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
public @interface Secure {}

Nun werden die Implementationen mit der entsprechenden Annotationen annotiert.

@Named @Secure
public class SecureRandomGenerator implements RandomGenerator {
...
}
@Named @Normal
public class NormalRandomGenerator implements RandomGenerator {
...
}

In der Klasse Calculation wird die Qualifier Annotation für das injizieren des Objektes verwendet.

@Named
public class Calculation {

  //@Inject @Secure
  @Inject @Normal
  private RandomGenerator randomGenerator;

  public void doSomething() {
    System.out.println(randomGenerator.randomInt());
  }
}

Dies funktioniert auch wenn man Constructor Injection verwendet.

@Named
public class Calculation {

  private RandomGenerator randomGenerator;

  @Inject
  public Calculation(@Normal RandomGenerator randomGenerator) {
    this.randomGenerator = randomGenerator;
  }

  public void doSomething() {
    System.out.println(randomGenerator.randomInt());
  }
}

Auf diese Art kann nun die Implementation umbenannt werden ohne dass das Injizieren fehlschlägt. Es muss natürlich immer eine Implementation vorhanden sein die mit @Secure oder @Normal annotiert wird. Wenn diese fehlt wird zur Laufzeit eine Exception aufgeworfen.

One Comment

  1. [...] JSR 330 with Spring (in German) [...]

Leave a Reply