• Die Mitarbeiter von open knowledge waren sowohl aus fachlicher als auch aus menschlicher Sicht eine Bereicherung für unser Migrations-Projekt. Insbesondere die hohe Qualifikation, die ausgepgrägte Lösungsorientierung sowie die partnerschaftliche Zusammenarbeit, die die Berater täglich im Projekt gezeigt haben, waren ein Schlüssel für die termingerechte und qualitätsgesicherte Fertigstellung der Migration. Dirk Feuerhelm, Projektleiter, Bertelsmann AG
  • … besonders beeindruckt hat mich die ganzheitliche Lösungskompetenz, ich meine damit die Fähigkeit von open knowledge, über das Verständnis für unsere komplexe Prozess- und IT-Welt und der Moderations- und Integrationsstärke in fachlichen und technischen Konzept-Workshops zu einer IT-technisch state-of-the-art-Lösung zu gelangen, die gleichzeitig ein Massanzug und offen und flexibel ist … Wolfgang Rosenow, Projektleiter T-Systems DDM GmbH
  • Die kompetente Betreuung durch open knowledge hat uns den Umstieg auf die JAVA-Plattform bei der Neuentwicklung von unternehmenskritischen Applikationen leicht gemacht. Gernot Weissensteiner, Entwicklungsleiter Österreichische Lotterien Ges.m.b.H.
  • open knowledge hat uns oft und kompetent im Bereich mobiler Technologien beraten. Lars Röwekamp und sein Team zeichnen sich aus durch tiefes Know-how, ein Gespür für pragmatische Lösungen und großes Commitment. Ein Gewinn für jedes Projekt! Eckhard Schneider, CTO, Interone GmbH
  • Die partnerschaftliche Zusammenarbeit mit open knowledge hat sich für uns in vielfältigen Themenstellungen bewährt. Hervorzuheben ist insbesondere die gesunde Mischung aus umfangreichem Know-How in den Bereichen Fachkompetenz sowie Methoden- und IT-Kompetenz der open knowledge-Mitarbeiter. Dies ermöglichte uns in allen Projektphasen, gemeinsam mit den Fachbereichen schnell und effizient Lösungen zu erarbeiten und stellt damit einen echten Mehrwert dar. Hermann Pollkläsener, Leitung IT, Deutsche Post Adress GmbH & Co. KG
  • Im Rahmen der langjährigen Zusammenarbeit haben wir die Mitarbeiter von open knowledge als kompetente und erfahrene Unterstützung für unsere internationalen Projekte schätzen gelernt. Besonders hervorzuheben ist auch die Flexibilität, wenn kurzfristig Unterstützung erforderlich gewesen ist. Dr. Ralf Wieting, Bereichsleiter Applikationsentwicklung & Online-Betrieb, CEWE COLOR AG & Co. OHG

Integration von Spring in CDI über eine CDI-Extension – Erster Teil

15.11.2010 // 10:00 Uhr // Kategorie: Java // Von: Arne Limburg

In meinem Beitrag zum Java-EE-Sonderheft des Java-Magazins in dem es um die Frage ging, ob man vor dem Hintergrund des Java EE 6 Standards bei Neuentwicklungen auf Spring oder auf Java EE setzen soll, habe ich auf ein bisher wenig beachtetes Feature des Standards hingewiesen: Java EE Extensions, genauer gesagt, CDI-Extensions. Während die letzten Jahre im Zeichen von Frameworks standen, die das Ziel hatten, Teile von Java EE (damals noch EJB 2.x) zu ersetzen (Hibernate, Spring, Seam), bieten CDI-Extensions nun die Möglichkeit, Frameworks zu schaffen, die Java EE ergänzen und erweitern. Ich habe sogar behauptet, dass die Integration von Spring in CDI in wenigen Hundert Zeilen Code möglich ist. In diesem Blog will ich zeigen, wie das geht.

Vorüberlegungen:

Sowohl eine CDI-Implementierung als auch Spring managen Beans. Es wird wohl nicht möglich sein, dieselbe Bean (also dasselbe Java-Objekt) sowohl von Spring als auch von der CDI-Implementierung managen zu lassen. Eine sinnvolle Integration muss dann aber sicherstellen, dass es möglich ist, sowohl Spring-Beans in CDI-Beans injecten zu lassen, als auch umgekehrt. Eine Integration von Spring in CDI wird also wohl zwei Teile umfassen:

  1. Man muss CDI die Spring-Beans „unterschieben“
  2. Man muss Spring die CDI-Beans „unterschieben“

Beides klingt wie eine interessante Herausforderung, da einige Kenntnis der Interna der beiden Lösungen vonnöten ist. Wenn CDI tatsächlich so leicht zu erweitern ist, sollte es möglich sein, den ersten Teil als CDI-Erweiterung umzusetzen. Was dazu zu tun ist, möchte ich im ersten Teil dieses Blogs zeigen:

Wie sieht eine CDI-Erweiterung aus? CDI-Erweiterungen werden über das Java Service-Provider-Interface angeboten und zwar unter dem Namen javax.enterprise.inject.spi.Extension, d.h. wir müssen im Ordner META-INF eine Datei namens javax.enterprise.inject.spi.Extension anlegen. Einziger Inhalt der Datei ist der voll qualifizierte Klassenname unserer Erweiterung, z.B. de.openknowledge.cdi.SpringCdiExtension. Über den java.util.ServiceLocator kann die CDI-Implementierung dann unsere Erweiterung zur Laufzeit instanziieren. Zu diesem Zweck muss unsere Klasse einen Default-Konstruktor haben. Eine weitere Bedingung, die die CDI-Spec an die Klasse stellt, ist, dass sie das Marker-Interface javax.enterprise.inject.spi.Extension implementiert.

public class SpringCdiExtension implements Extension {
}

Mit der Umsetzung dieser Bedingungen wäre die Extension zwar komplett, sie würde aber offensichtlich noch nichts tun. Wie also kann eine CDI-Extension mit dem CDI-Container in Interaktion treten? Die Antwort ist einfach: Sie kann auf Events des Containers hören. Damit eine CDI-Extension CDI-Events erhalten kann, muss sie eine Methode definieren, die bei Auftreten des Events aufgerufen werden kann. Wir wollen das CDI-Event AfterBeanDiscovery überwachen. Dieses tritt auf, wenn der Container die Erkennung seiner Beans abgeschlossen hat. Das Besondere an diesem Event ist, dass man über das Event weitere Beans beim CDI-Container registrieren kann und zwar über die Methode addBean(Bean bean). Die Metadaten für CDI stecken in Implementierungen des Interfaces javax.enterprise.inject.spi.Bean und Metadaten für Spring sind durch das Interface org.springframework.beans.factory.config.BeanDefinition repräsentiert.

Alles was wir also tun müssen, damit der CDI-Container die Spring-Beans verwenden kann, ist, das Bean-Interface von CDI zu implementieren für jede Spring-BeanDefinition addBean(…) auf dem AfterBeanDiscovery Event aufzurufen:

 
public void connectCdiAndSpring(@Observes AfterBeanDiscovery event,
BeanManager manager)
throws ClassNotFoundException {
ConfigurableApplicationContext springContext
= new ClassPathXmlApplicationContext("classpath:context.xml");
for (String beanName: applicationContext.getBeanDefinitionNames()) {
BeanDefinition beanDefinition
= applicationContext.getBeanFactory().getBeanDefinition(beanName);
event.addBean(createBean(beanDefinition));
}
}

Damit kennt der CDI-Container alle Spring-Beans und sie stehen in CDI zur Dependency-Injection zur Verfügung. Leider liefert der CDI-Standard keine Implementierung des javax.enterprise.inject.spi.Bean-Interfaces mit, so dass wir eine eigene Implementierung schreiben müssen:

public class SpringBean implements Bean<Object> {

private String beanName;
private Class<?> beanClass;
private Set<Type> beanTypes;
private Set<Annotation> qualifiers;
private Set<Class<? extends Annotation>> stereotypes;
private ConfigurableBeanFactory beanFactory;

public SpringBean(String beanName,
Class<?> beanClass,
Set<Type> beanTypes,
Set<Annotation> qualifiers,
Set<Class<? extends Annotation>> stereotypes,
ConfigurableBeanFactory beanFactory) {
this.beanName = beanName;
this.beanClass = beanClass;
this.beanTypes = Collections.unmodifiableSet(beanTypes);
this.qualifiers = Collections.unmodifiableSet(qualifiers);
this.stereotypes = Collections.unmodifiableSet(stereotypes);
this.beanFactory = beanFactory;
}

public String getName() {
return beanName;
}

public Class<?> getBeanClass() {
return beanClass;
}

public Set<Type> getTypes() {
return beanTypes;
}

public Set<Annotation> getQualifiers() {
return qualifiers;
}

public Set<Class<? extends Annotation>> getStereotypes() {
return stereotypes;
}

public Class<? extends Annotation> getScope() {
return Dependent.class;
}

public Set<InjectionPoint> getInjectionPoints() {
return Collections.emptySet();
}

public boolean isAlternative() {
return false;
}

public boolean isNullable() {
return true;
}

public Object create(CreationalContext<Object> creationalContext) {
return beanFactory.getBean(beanName);
}

public void destroy(Object beanInstance,
CreationalContext<Object> creationalContext) {
beanFactory.destroyBean(beanName, beanInstance);
}
}

Die Implementierung ist recht trivial. Dennoch ein paar Bemerkungen: Zunächst ist die Implementierung von create und destroy interessant. Hier wird die Bean-Instanz aus dem Spring-Context geholt, bzw. zerstört. Die Erläuterung des Dependent-Scopes und der Implementierung von isAlternative() (die sich auf das CDI-Konzept der Alternatives bezieht) würde hier den Rahmen sprengen und ist vielleicht Gegenstand eines nächsten Blogs. Bleibt nur die Frage, wie man den doch recht umfangreichen Konstruktor korrekt zu befüllen hat. Bei den Parametern handelt es sich hauptsächlich um Metadaten, die in CDI durch Annotations repräsentiert werden. Für deren Erkennung bietet CDI ein paar schöne Methoden: BeanManager.createAnnotatedType(Class type), BeanManager.isQualifier(…) und beanManager.isStereotype(…). Den AnnotatedType kann man dann nach den benötigten Annotations durchsuchen:

private Bean<?> createBean(String beanName,
BeanDefinition beanDefinition,
ConfigurableBeanFactory beanFactory,
BeanManager beanManager)
throws ClassNotFoundException {
Class<?> beanClass = Class.forName(beanDefinition.getBeanClassName());
AnnotatedType<?> annotatedType
= beanManager.createAnnotatedType(beanClass);
Set<Type> beanTypes = annotatedType.getTypeClosure();
Set<Annotation> qualifiers = new HashSet<Annotation>();
qualifiers.add(new AnnotationLiteral<Any>() {});
qualifiers.add(new AnnotationLiteral<Default>() {});
Set> stereotypes
= new HashSet<Class<? extends Annotation>> stereotypes
= new HashSet>();
for (Annotation annotation: annotatedType.getAnnotations()) {
if (beanManager.isQualifier(annotation.annotationType())) {
qualifiers.add(annotation);
}
if (beanManager.isStereotype(annotation.annotationType())) {
stereotypes.add(annotation.annotationType());
}
}
return new SpringBean(beanName, beanClass, beanTypes,
qualifiers, stereotypes, beanFactory);
}
<Class<? extends Annotation>>();
for (Annotation annotation: annotatedType.getAnnotations()) {
if (beanManager.isQualifier(annotation.annotationType())) {
qualifiers.add(annotation);
}
if (beanManager.isStereotype(annotation.annotationType())) {
stereotypes.add(annotation.annotationType());
}
}
return new SpringBean(beanName, beanClass, beanTypes,
qualifiers, stereotypes, beanFactory);
}

Über zwei Zeilen muss ich noch ein paar Worte verlieren:

qualifiers.add(new AnnotationLiteral<Any>() {});
qualifiers.add(new AnnotationLiteral<Default>() {});
  1. Laut CDI-Spec hat jede Bean implizit die Qualifier @Any und @Default. Das wird von dem CDI-Container Weld (den ich zum Testen verwendet habe) so interpretiert, dass diese Qualifier immer enthalten sein müssen. Keine Ahnung ob z.B. OpenWebBeans (ein weiterer CDI-Container) diese beiden Annotations auch in der Liste benötigt… Da sie immer implizit da sind (also wahrscheinlich nie als echte Annotation an einer Bean stehen), könnte man sie meiner Meinung nach auch aus dieser Liste weglassen. Wie gesagt, Weld braucht sie.
  2. Was soll das für ein Konstrukt sein, was CDI da mitliefert AnnotationLiteral<Any>() {} ???

Nun ja, wir alle kennen Annotations und zwar als @Any z.B. Diejenigen, die schon mal eine eigene Annotation geschrieben haben, wissen auch, dass das sowas ähnliches wie ein Interface ist, d.h. es gibt auch ein zugehöriges class-Objekt. Wenn wir also (wie bei den Stereotypes) eine Annotation-Klasse benötigen, können wir einfach Any.class verwenden. Bei den Qualifiers benötigen wir aber Annotation-Instanzen, weil auch die Attribute relevant sein können. Normalerweise instantiiert die JVM die Annotation-Instanzen automatisch nach Bedarf.

Was kann man allerdings machen, wenn man Annotations von Hand instanziieren muss? Hierfür bietet CDI die Klasse AnnotationLiteral. Annotation-Instanzen implementieren immer das Interface java.lang.annotation.Annotation. Dies tut AnnotationLiteral auch und verhält sich auch sonst so wie eine Annotation-Instanz. Wenn man also eine Instanz einer konkreten Annotation simulieren will, kann man zu einer anonymen Unterklasse von AnnotationLiteral greifen, die man mit der entsprechenden Annotation parametrisiert:

Wem das zu kompliziert war, der merkt sich einfach, dass man auf diese Weise Annotations instanziieren kann.

Damit ist der erste Teil geschafft. Mit nur ein paar Zeilen Code habe ich eine Extension geschrieben, mit der Spring-Beans im CDI-Context verwendet (und auch automatisch injiziert) werden können. In meinem nächsten Blog werde ich die Extension dahingehend erweitern, dass auch in Spring die CDI-Beans verwendet und injiziert werden können.

Keine Einträge

Schreiben Sie den ersten Kommentar!

Kommentar hinterlassen:

Ihr Kommentar wird nach Prüfung freigeschaltet.

News

Java EE Summit - powered by open knowledge

Intensives und kompaktes Java EE Know-how in drei Tagen? Möglich wird das auf dem Java EE Summit im Juni in München - powered by open knowledge!
mehr ...

oben
Blog RSS-Feed