Monday, November 25, 2013

Using CDI from Spring

Spring and Java EE are converging in many areas, mainly due to CDI. In fact, by confining yourself to a suitable subset of annotations and other configuration mechanisms, you can design your persistence and service layers to run either on Java EE 6 or on Spring 3, simply by selecting the appropriate set of libraries.

The web layer is a different story: if your web framework is tightly coupled to either Java EE 6 or Spring, it will be hard or even impossible to simply change the container, but even in the web layer, there are solutions like Apache Wicket which work well both with Spring and Java EE, all you need is some glue code to access the dependency injection container.

I'm currently migrating a web application from Tomcat/Spring to Glassfish, and since this application is based on Spring MVC, I cannot completely replace Spring in this step. The goal of the migration is to drop all Spring dependencies from all components except the web frontend and to somehow make the service beans (stateless session beans and CDI managed beans) visible to the Spring web application context.


Looking for a solution, I found Rick Hightower's post on CDI and Spring living in harmony and the related CDISource project. The basic idea is to use a Spring BeanFactoryPostProcessor to populate Spring's application context with a factory bean for each CDI bean obtained from the CDI BeanManager, thus making all CDI beans accessible as Spring beans.

It was very easy to integrate the CDISource Spring Bridge into my project, I just cloned their Git repository, ran the Maven build, added

  1. <dependency>  
  2.   <groupId>org.cdisource.springbridge</groupId>  
  3.   <artifactId>springbridge</artifactId>  
  4.   <version>1.0-SNAPSHOT</version>  
  5. </dependency>  

to my own POM and included

in my Spring configuration.

This was enough to get going, but I soon found out that some standard but not-so-basic use cases are not yet supported, e.g.
  • @Stateless EJBs @Injected into CDI beans.
  • Multiple beans of the same type with @Named or other qualifiers.
  • Producer methods
So I started hacking the CdiBeanFactoryPostProcessor to make it work for my application. Here is the solution:

  1. package org.cdisource.springintegration;  
  2.   
  3. import java.lang.reflect.Type;  
  4. import java.util.Set;  
  5.   
  6. import javax.enterprise.inject.spi.Bean;  
  7.   
  8. import org.slf4j.Logger;  
  9. import org.slf4j.LoggerFactory;  
  10. import org.springframework.beans.BeansException;  
  11. import org.springframework.beans.factory.config.BeanFactoryPostProcessor;  
  12. import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;  
  13. import org.springframework.beans.factory.support.BeanDefinitionBuilder;  
  14. import org.springframework.beans.factory.support.DefaultListableBeanFactory;  
  15.   
  16. public class CdiBeanFactoryPostProcessor implements BeanFactoryPostProcessor {  
  17.   
  18.     private static Logger logger = LoggerFactory.getLogger(CdiBeanFactoryPostProcessor.class);  
  19.   
  20.     private boolean useLongName;  
  21.   
  22.     private BeanManagerLocationUtil beanManagerLocationUtil = new BeanManagerLocationUtil();  
  23.   
  24.     @Override  
  25.     public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)  
  26.             throws BeansException {  
  27.   
  28.         DefaultListableBeanFactory factory = (DefaultListableBeanFactory) beanFactory;  
  29.   
  30.         Set> beans = beanManagerLocationUtil.beanManager().getBeans(Object.class);  
  31.         for (Bean bean : beans) {  
  32.             if (bean instanceof SpringIntegrationExtention.SpringBean) {  
  33.                 continue;  
  34.             }  
  35.   
  36.             if (bean.getName() != null && bean.getName().equals("Spring Injection")) {  
  37.                 continue;  
  38.             }  
  39.             logger.debug("bean types = {}", bean.getTypes());  
  40.             Class beanClass = getBeanClass(bean);  
  41.             BeanDefinitionBuilder definition = BeanDefinitionBuilder  
  42.                     .rootBeanDefinition(CdiFactoryBean.class)  
  43.                     .addPropertyValue("beanClass", beanClass)  
  44.                     .addPropertyValue("beanManager", beanManagerLocationUtil.beanManager())  
  45.                     .addPropertyValue("qualifiers", bean.getQualifiers()).setLazyInit(true);  
  46.             String name = generateName(bean);  
  47.             factory.registerBeanDefinition(name, definition.getBeanDefinition());  
  48.             logger.debug("bean name = {}, bean class = {}", bean.getName(), beanClass.getName());  
  49.         }  
  50.     }  
  51.   
  52.     private Class getBeanClass(Bean bean) {  
  53.         Class klass = Object.class;  
  54.         for (Type type : bean.getTypes()) {  
  55.             if (type instanceof Class) {  
  56.                 Class currentClass = (Class) type;  
  57.                 if (klass.isAssignableFrom(currentClass)) {  
  58.                     klass = currentClass;  
  59.                 }  
  60.             }  
  61.         }  
  62.         return klass;  
  63.     }  
  64.   
  65.     private String generateName(Bean bean) {  
  66.         String name = bean.getName() != null ? bean.getName() : generateNameBasedOnClassName(bean);  
  67.         return name;  
  68.     }  
  69.   
  70.     private String generateNameBasedOnClassName(Bean bean) {  
  71.         Class beanClass = getBeanClass(bean);  
  72.         return !useLongName ? beanClass.getSimpleName() + "FactoryBean" : beanClass.getName()  
  73.                 .replace(".""_") + "FactoryBean";  
  74.     }  
  75.   
  76.     public void setUseLongName(boolean useLongName) {  
  77.         this.useLongName = useLongName;  
  78.     }  
  79.   
  80. }  

The main change is in getBeanClass(): It is important to note that the CDI API Bean.getBeanClass() does not return the class of the given bean: the result is the class in which the bean is defined. In particular, for producer methods, the result is the class containing the method and not the method return type.

Bean.getTypes() returns all classes and interfaces of potential injection points that the bean will match. This set of types does not always include the bean's implementation class, e.g. for a stateless session bean with a local business interface, the bean types will only be java.lang.Object and the business interfaces (and any superinterfaces of the business interface).

getBeanClass() currently selects the most specialized CDI bean type and uses this type to create the corresponding Spring factory bean.

The next point to note are qualifiers: I added a qualifiers property to the CdiFactoryBean and changed its getObject()method to use the qualifiers for looking up the bean in the CDI bean container:

  1. package org.cdisource.springintegration;  
  2.   
  3. import java.lang.annotation.Annotation;  
  4. import java.util.Set;  
  5.   
  6. import javax.enterprise.inject.spi.BeanManager;  
  7.   
  8. import org.cdisource.beancontainer.BeanContainer;  
  9. import org.cdisource.beancontainer.BeanContainerImpl;  
  10. import org.springframework.beans.factory.FactoryBean;  
  11. import org.springframework.beans.factory.InitializingBean;  
  12.   
  13. public class CdiFactoryBean implements FactoryBean, InitializingBean {  
  14.   
  15.     private Class beanClass;  
  16.     private boolean singleton = true;  
  17.     private BeanContainer beanContainer;  
  18.     private BeanManager beanManager;  
  19.     private Set qualifiers;  
  20.   
  21.     @Override  
  22.     public void afterPropertiesSet() throws Exception {  
  23.         if (beanManager == null)  
  24.             throw new IllegalStateException("BeanManager must be set");  
  25.         beanContainer = new BeanContainerImpl(beanManager);  
  26.     }  
  27.   
  28.     public void setBeanClass(Class beanClass) {  
  29.         this.beanClass = beanClass;  
  30.     }  
  31.   
  32.     @Override  
  33.     public Object getObject() throws Exception {  
  34.         return beanContainer.getBeanByType(beanClass, qualifiers.toArray(new Annotation[] {}));  
  35.     }  
  36.   
  37.     @Override  
  38.     public Class getObjectType() {  
  39.         return beanClass;  
  40.     }  
  41.   
  42.     @Override  
  43.     public boolean isSingleton() {  
  44.         return singleton;  
  45.     }  
  46.   
  47.     public void setSingleton(boolean singleton) {  
  48.         this.singleton = singleton;  
  49.     }  
  50.   
  51.     public void setBeanManager(BeanManager beanManager) {  
  52.         this.beanManager = beanManager;  
  53.     }  
  54.   
  55.     public void setQualifiers(Set qualifiers) {  
  56.         this.qualifiers = qualifiers;  
  57.     }  
  58. }  

I'm sure this is not the end of the story, both Spring and CDI are complex enough to allow use cases where this bridge is likely to break, but it now works perfectly for me in my real-life application.

1 comment:

jeje said...

asics gel runmiles avis Ils pourraient être utilisés asics gel nimbus homme decathlon pour traiter les maladies vénériennes lorsqu'ils sont administrés à l'état frais, tandis adidas zx flux rose noir pas cher qu'une teinture fabriquée à partir d'eux serait utilisée comme sédatif, anesthésique adidas zx flux adv x et vulnéraire. En ce qui concerne les chaussures de travail traditionnelles. Les nike air flight huarache noir baskets des femmes Nike Dunk fleur de prune il asics femme running arrive dans la couleur rose pâle, la Nike Dunk Metropolis crampon nike pas cher adulte Femme, l'attrayant tout noir avec des informations grises, les revenus Nike Dunk noir massif.