• 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 – Zweiter Teil

16.11.2010 // 10:12 Uhr // Kategorie: Java // Von: Arne Limburg

In meinem letzten Blog-Eintrag habe ich gezeigt, wie man eine CDI-Extension schreibt, mit der man Spring-Beans in CDI-Beans injizieren kann. Ich möchte diese Extension in diesem Blog-Eintrag nun dahingehend erweitern, dass auch die CDI-Beans in Spring-Beans injiziert werden können.

Spring verwendet für Bean-Medadaten das Interface org.springframework.beans.factory.config.BeanDefinition. Für jede CDI-Bean müssen wir eine Instanz davon in Spring registrieren und dann das Erzeugen und Zerstören der Beans für diese Bean-Definitionen von Spring übernehmen. Für das Erzeugen und Zerstören von Beans ist in Spring der Scope zuständig. Wir werden also einen eigenen Scope für die CDI-Beans definieren. Hierzu implementieren wir das Interface org.springframework.beans.factory.config.Scope. Interessant sind hier die beiden Methoden get(…) und remove(…).

Die erste Methode muss zu einem Bean-Namen die aktuelle Instanz aus dem Scope liefern und die zweite muss die aktuelle Instanz aus dem Scope entfernen, wenn es eine Instanz gibt. Diese beiden Operationen müssen direkt auf dem CDI-Container durchgeführt werden. Das Interface zum CDI-Container ist der BeanManager und um von diesem aktuelle Instanzen zu erhalten, muss man die jeweilige Bean-Implementierung der Instanz parat haben. Unser Scope benötigt also den BeanManager und eine Map, die uns zu einem Spring-Bean-Namen die CDI-Bean liefert:

public class CdiScope implements Scope {

private BeanManager beanManager;
private Map<string,><object>> beans;

public CdiScope(BeanManager beanManager,
Map<string,><object>> beans) {
this.beanManager = beanManager;
this.beans = beans;
}

public Object get(String name, ObjectFactory objectFactory) {
Bean<object> bean = beans.get(name);
CreationalContext<object> context
= beanManager.createCreationalContext(bean);
return beanManager.getReference(bean, bean.getBeanClass(), context);
}

public Object remove(String name) {
Object beanInstance = get(name, null);
Bean<object> bean = beans.get(name);
CreationalContext<object> context
= beanManager.createCreationalContext(bean);
bean.destroy(beanInstance, context);
return beanInstance;
}



}

Das Interface „Scope“ hat noch die Methoden registerDestructionCallback, getConversationId und seit Spring 3 auch noch resolveContextualObject. RegisterDestructionCallback ist für uns irrelevant, da die Destruction der CDI-Beans vom CDI-Container übernommen wird. Dieser Call kann also ignoriert werden. GetConversationId und resolveContextualObject sind optionale Operationen. Hier können wir getrost immer null zurückliefern.

Putting it all together

Mit diesem Scope können wir also dem Spring-Container CDI-Beans unterschieben. Ein paar Fragen bleiben allerdings noch offen: Woher kriegen wir die CDI-Beans für den Scope-Konstruktor und wie initialisieren wir den Spring-Scope? Die Bean-Implementierungen aller CDI-Beans eines CDI-Containers zu erhalten ist tatsächlich nicht so einfach. Wer erwartet hätte, dass man wie bei Spring einfach den BeanManager nach getBeans() oder ähnlichem fragen kann, wird leider enttäuscht. Hier helfen uns aber wieder die Lifecycle-Events des Containers weiter. Wir haben bereits im vorherigen Blog-Eintrag gesehen, dass wir das AfterBeanDiscovery-Event empfangen können. Genauso gibt es im CDI-Container das ProcessBean-Event, dass vom Container immer dann erzeugt wird, wenn er eine neue Bean gefunden hat. Wir erweitern also die Extension aus dem vorherigen Blog-Eintrag um ein Attribut cdiBeans, in dem wir die CDI-Beans sammeln und um eine Methode, um das ProcessBean-Event zu empfangen:

public class SpringCdiExtension implements Extension {

private List<bean><object>> cdiBeans = new ArrayList<bean><object>>();

public void addBean(@Observes ProcessBean<object> bean) {
cdiBeans.add(bean.getBean());
}

public void connectCdiAndSpring(@Observes AfterBeanDiscovery event,
BeanManager manager)
throws ClassNotFoundException {

}

}

Da die BeanFactory in Spring nach Erstellung des ApplicationContexts geschlossen ist, können wir dann weder Bean-Definitionen noch einen eigenen Scope registrieren. Die Registrierungen müssen also während der Initialisierung der BeanFactory stattfinden. Hierzu definieren wir einen BeanFactoryPostProcessor. Neben der Registrierung des Scopes muss dieser auch Namen für die Bean-Definitionen erzeugen, weil jede Spring-Bean (im Gegensatz zu CDI-Beans) einen eindeutigen Namen benötigt.

public class DependencyRegisteringPostProcessor
implements BeanFactoryPostProcessor {

private BeanManager beanManager;
private List<bean><object>> cdiBeans;

public DependencyRegisteringPostProcessor(BeanManager beanManager, List<bean><object>> cdiBeans) {
this.beanManager = beanManager;
this.cdiBeans = cdiBeans;
}

public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
throws BeansException {
BeanDefinitionRegistry registry
= (BeanDefinitionRegistry)beanFactory;
Map<string,><object>> beans
= new HashMap<string,><object>>();

for (Bean<object> bean: cdiBeans) {
BeanDefinition beanDefinition = createBeanDefinition(bean);
String beanName = createBeanName(bean, beanDefinition, registry);
registry.registerBeanDefinition(beanName, beanDefinition);
beans.put(beanName, bean);
}

CdiScope cdiScope = new CdiScope(beanManager, beans);
beanFactory.registerScope(CdiScope.class.getName(), cdiScope);
}
}

Das Erzeugen der Bean-Definitionen ist in Spring recht einfach, da Spring eine generische Implementierung für das BeanDefinition-Interface mitliefert, das wir verwenden können. Die Implementierung der Methode wird dadurch recht einfach:

private BeanDefinition createBeanDefinition(Bean bean) {
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(bean.getBeanClass());
beanDefinition.setScope(CdiScope.class.getName());
beanDefinition.setLazyInit(true);
return beanDefinition;
}

Durch das Setzen unseres CDI-Scopes wird sichergestellt, dass Spring immer unsere Scope-Implementierung fragt, wenn eine Instanz benötigt wird, wodurch wir die Brücke zu CDI herstellen. Die Methode createBeanName(…) ist auch recht einfach implementiert, wenn man einmal weiß, wie Spring seine Bean-Namen generiert. Schlüssel ist hier die BeanDefinitionRegistry, mit der doppelte Bean-Namen vermieden werden können:

 bean, BeanDefinition beanDefinition, BeanDefinitionRegistry registry) {
String beanName = bean.getName();

if (beanName == null) {
beanName = BeanDefinitionReaderUtils.generateBeanName(beanDefinition, registry);
}

return beanName;
}

Jetzt müssen wir nur noch unseren Post-Processor an den Spring-Context hängen und nach einem Aufruf von context.refresh() haben wir unsere CDI-Beans im Spring-Container. Am sinnvollsten tun wir das in der Methode connectCdiAndSpring. Eine kleine Anpassung ist dort noch notwendig: Wir schieben ja in dem Post-Processor alle CDI-Beans Spring unter, um danach alle Spring-Beans CDI unterzuschieben. Dies führt allerdings dazu, dass nach dieser Aktion jede CDI-Bean doppelt vorhanden ist (einmal direkt und einmal über den Umweg als Spring-Bean). Wir müssen hier also alle Spring-Beans auslassen, die als CDI-Beans Spring-Beans geworden sind, also alle die in unserem CDI-Scope liegen:

public void connectCdiAndSpring(@Observes AfterBeanDiscovery event, BeanManager manager)
throws ClassNotFoundException {
ConfigurableApplicationContext springContext
= new ClassPathXmlApplicationContext(new String[] { "classpath:application-context.xml"}, false);

springContext.addBeanFactoryPostProcessor(new DependencyRegisteringPostProcessor(manager, cdiBeans));

springContext.refresh();

for (String beanName: springContext.getBeanDefinitionNames()) {
BeanDefinition beanDefinition
= springContext.getBeanFactory().getBeanDefinition(beanName);

if (!CdiScope.class.getName().equals(beanDefinition.getScope())) {
event.addBean(createBean(…));
}
}
}

Kleine Randnotiz: Wir benutzen hier den Konstruktor des ApplicationContexts, der nicht automatisch ein refresh() auf der Bean-Factory ausführt (Parameter „false“), weil wir das refresh() später manuell ausführen und so ein Doppel-Refresh() vermeiden.

Damit ist unsere Spring-Integration in CDI (und damit in JavaEE fertig). Das Ganze sind nur etwas mehr als 200 Zeilen Code und beinhaltet einiges Wissen über Interna der beiden Technologien. Ich hoffe ich konnte neben der Umsetzung der Extension auch einen kleinen Einblick in diese Interna bieten.

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