Using Grails with CDI/Weld
Two of the libraries I’m most interested in lately are Grails and CDI/JSR299 (Weld being the implementation). The problem is that the two are really not compatible directly with each other. First, CDI is a JavaEE6 technology requiring a compliant container (ie: Glassfishv3) whereas Grails can run in any servlet container (plain ol’ WAR). Second, Grails uses Spring for its dependency injection system which does not really support CDI. However, in an ideal world, it would be nice to mix them as CDI makes a great backend framework while Grails makes a great frontend framework. So, what do we do to make these co-exist?
UPDATE 2: Finally got it to work by just doing: def books = library.books rather than library.getBooks(). Still not sure why the latter way does not work, but I’m still looking at it.
UPDATE 1: For some reason, the code appears to work but when deploying in Glassfishv3, it fails when invoking a method on the bean due to ClassFormatError. I’m still investigating, but for now, consider this a place to start until I can resolve the underlying issue.
First, let’s start with a simple case of what we want to do via a standard Grails controller and injecting a standard CDI bean.
class BookController { @Inject Library library; def list = { def books = library.getBooks(); [ books : books ]; } }
In this example, Grails/Spring has no idea how to resolve the @Inject annotation. Only CDI does. Further, even CDI does not since BookController is managed by Spring, not CDI itself. So, what do we do? How can we mash the two together to get the resolution to occur?
BeanPostProcessor
BeanPostProcessors is a Spring injection point that can be used to post process beans that Spring controls (ie: Grails controllers). As a result, you can read the bean class file and inject fields as necessary (this is exactly how the @Required annotation in Spring works via the RequiredBeanPostProcessor). In our example, we can create a custom BeanPostProcessor that searches for any @Inject annotations and uses the CDI BeanManager to perform the injection. It’s really that simple. Since CDI requires containers to expose the BeanManager via JNDI, it’s just a matter of lookup, finding the associated beans, and injecting. The following is the code I used to create this scenario.
First, let create the post processor structure:
package com.znet.inject; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; public class InjectBeanPostProcessor implements BeanPostProcessor { public InjectBeanPostProcessor() { System.out.println("InjectBeanPostProcessor"); } @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return bean; } }
To register that with Spring and Grails so that every Grails spring-managed bean (services, controllers, etc) gets invoked into the processor, simply add the bean definition to the Spring DSL in grails-app/conf/spring/resources.groovy
beans = { injectBeanPostProcessor(com.znet.inject.InjectBeanPostProcessor) { } }
You should now be able to start your Grails app and the processor will get invoked and every controller (as they are needed [request scoped]) will be passed into the processor. Now that we have that, we can add the logic to inject the CDI beans.
First, we need to eventually get access to the underlying CDI BeanManager. The BeanManager is responsible for registering, managing, instantiating, looking up, etc the beans in the CDI system. To do so we simply use JNDI to lookup the BeanManager in the known JNDI space:
protected BeanManager getBeanManager() { if (this.beanManager == null) { try { InitialContext initialContext = new InitialContext(); this.manager = (BeanManager) initialContext.lookup("java:comp/BeanManager"); } catch (NamingException e) { throw new IllegalStateException("unable to lookup bean manager", e); } } return this.manager; }
It’s now time to search the Spring beans for fields with @Inject annotations. The best time to do this is in the post-process phase so that all over processors update the bean first. We start by searching for all declared fields on the instance and every superclass of the instance searching for the @Inject annotation:
@Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { Class<?> clazz = bean.getClass(); processFields(bean, clazz); } protected void processFields(Object bean, Class<?> clazz) throws Exception { // search fields for (Field field : clazz.getDeclaredFields()) { // check for Inject annotation if (field.getAnnotation(Inject.class) != null) { injectField(bean, field); } } // process parent Class<?> parent = clazz.getSuperclass(); if (parent != null) { processFields(bean, parent); } } protected void injectField(Object bean, Field field) throws Exception { // inject via CDI BeanManager }
With that we now recursively find all associated injection fields. The injectField method is the complex part of the process, but even still pretty simplistic. The BeanManager interface provides us with a method to retrieve all beans for a given class type and annotations. Nicely enough, both of those properties are exposed on the field. Once we have the properties, we ask the bean manager for a set of all matching beans. From that we can inject the bean(s) into the field instance. My example code below tries to be smart by checking whether the injected property wants a list of beans or just a single bean. To get the actual instance, we have to create a context for the associated bean. Each bean has a specific context (application, request, session, etc). CDI has to ensure the bean is properly context’d to manage it accordingly. This only involves two other methods on BeanManager.
protected void injectField(Object instance, Field field) throws Exception { // get the CDI bean manager BeanManager mgr = getBeanManager(); // get the associated type (generic type) and annotations Class<?> clazz = field.getType(); Type type = field.getGenericType(); Annotation[] annotations = field.getAnnotations(); // remove the @Inject annotation (otherwise, CDI complains it is an invalid annotation) List<Annotation> anns = new ArrayList<Annotation>(annotations.length - 1); for (Annotation annotation : annotations) { if (annotation.annotationType().equals(Inject.class)) { continue; } anns.add(annotation); } annotations = anns.toArray(new Annotation[anns.size()]); // lookup the set of matching beans Set<Bean<?>> beans = mgr.getBeans(type, annotations); // throw exception if no bean available if (beans.size() == 0) { throw new IllegalStateException("no bean available"); } // if multiple beans save as a list if possible (otherwise, throw exception) else if (beans.size() > 1 || Collection.class.isAssignableFrom(clazz)) { // ensure field type expects a collection if (!Collection.class.isAssignableFrom(clazz)) { throw new IllegalStateException("multiple w/o list"); } // check if user already init'd the collection Collection collection = (Collection) field.get(instance); // otherwise, attempt to instantiate the type with the proper class if (collection == null) { if ((clazz.getModifiers() & Modifier.ABSTRACT) != 0 || clazz.isInterface()) { if (List.class.isAssignableFrom(clazz)) { collection = new ArrayList(beans.size()); } else if (SortedSet.class.isAssignableFrom(clazz)) { collection = new TreeSet(); } else if (Set.class.isAssignableFrom(clazz)) { collection = new HashSet(beans.size()); } else { throw new IllegalStateException("unsupported class"); } } else { collection = (Collection) clazz.newInstance(); } } // ensure list is clear collection.clear(); // create reference to each bean and add to the collection for (Bean<?> bean : beans) { // create the context and get the associated reference CreationalContext<?> ctx = mgr.createCreationalContext(bean); Object reference = mgr.getReference(bean, type, ctx); collection.add(reference); } // set the instance in the field (NOTE: this prolly should use bean utils or something to support setters as well) field.setAccessible(true); field.set(instance, collection); } // if non-collection based, just set the single instance else { // get the single bean and instantiate in the associated context Bean<?> bean = beans.iterator().next(); CreationalContext<?> ctx = mgr.createCreationalContext(bean); Object reference = mgr.getReference(bean, type, ctx); // set the instance in the field field.setAccessible(true); field.set(instance, reference); } }
That’s it. Our BeanPostProcessor can now lookup and inject CDI beans into any Spring-managed bean (such as Grails controllers). Going back to the original example, we could extend it to any type of CDI-capability such as:
class BookController { @Inject @Asynchronous @RequestScoped @Named("library") Library library; def list = { [ books : library.getBooks() ] } }
Thanks for listening and enjoy. Hopefully in the next few years, Grails and CDI will be interoperate better as I think the two create a nice system together.
Nick

Leave a Reply