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.

5 comments:

oakleyses said...

louis vuitton handbags, oakley sunglasses, louboutin, longchamp outlet, nike shoes, louis vuitton outlet stores, chanel handbags, burberry outlet, prada outlet, jordan shoes, tiffany and co, michael kors outlet, tory burch outlet, louis vuitton outlet, longchamp handbags, nike free, true religion jeans, michael kors outlet, kate spade outlet, polo ralph lauren outlet, tiffany and co, prada handbags, polo ralph lauren outlet, michael kors outlet, michael kors outlet, longchamp handbags, oakley sunglasses, ray ban sunglasses, kate spade handbags, burberry outlet, louis vuitton outlet, louboutin outlet, louboutin, coach factory outlet, air max, air max, coach outlet, gucci outlet, christian louboutin shoes, michael kors outlet, coach purses, ray ban sunglasses, michael kors outlet, louis vuitton, coach outlet store online, true religion jeans, oakley sunglasses cheap

oakleyses said...

louis vuitton handbags, oakley sunglasses, louboutin, longchamp outlet, nike shoes, louis vuitton outlet stores, chanel handbags, burberry outlet, prada outlet, jordan shoes, tiffany and co, michael kors outlet, tory burch outlet, louis vuitton outlet, longchamp handbags, nike free, true religion jeans, michael kors outlet, kate spade outlet, polo ralph lauren outlet, tiffany and co, prada handbags, polo ralph lauren outlet, michael kors outlet, michael kors outlet, longchamp handbags, oakley sunglasses, ray ban sunglasses, kate spade handbags, burberry outlet, louis vuitton outlet, louboutin outlet, louboutin, coach factory outlet, air max, air max, coach outlet, gucci outlet, christian louboutin shoes, michael kors outlet, coach purses, ray ban sunglasses, michael kors outlet, louis vuitton, coach outlet store online, true religion jeans, oakley sunglasses cheap

oakleyses said...

mac cosmetics, mont blanc, marc jacobs, canada goose outlet, nike huarache, vans shoes, soccer jerseys, hollister, giuseppe zanotti, beats by dre, abercrombie and fitch, longchamp, insanity workout, celine handbags, bottega veneta, ghd, nfl jerseys, north face outlet, chi flat iron, ugg boots, birkin bag, ugg australia, canada goose, herve leger, ugg pas cher, rolex watches, valentino shoes, canada goose uk, canada goose, ferragamo shoes, canada goose, ugg boots, uggs outlet, north face jackets, soccer shoes, asics running shoes, new balance shoes, p90x, lululemon outlet, canada goose jackets, mcm handbags, instyler, babyliss pro, ugg, wedding dresses, jimmy choo outlet, reebok outlet, nike roshe run

oakleyses said...

parajumpers, karen millen, air max, converse, pandora charms, moncler, louboutin, moncler, links of london, lancel, juicy couture outlet, oakley, hollister, pandora charms, supra shoes, thomas sabo, canada goose, gucci, wedding dresses, timberland boots, swarovski crystal, air max, coach outlet store online, moncler, ray ban, canada goose, moncler, ugg, louis vuitton, swarovski, hollister, montre homme, moncler, hollister clothing store, ralph lauren, rolex watches, moncler outlet, moncler, iphone 6 cases, baseball bats, juicy couture outlet, toms shoes, vans, pandora jewelry, ugg, converse shoes

Anna said...

Great and Useful Article.

Online Java Course

Java Online Training

Java Course Online

Best Recommended books for Spring framework

Java Interview Questions












Java Training Institutes in Chennai

Java Training in Chennai

J2EE Training in Chennai

java j2ee training institutes in chennai

Java Course in Chennai