Injecting a Spring bean using CDI @Inject

Christian Seifert picture Christian Seifert · Nov 10, 2010 · Viewed 15.6k times · Source

I'm trying to inject a bean defined in a Spring context into a CDI managed component but I'm not successful. The bean is not injected, instead a new instance gets created each time the injection should be performed. My environment is Tomcat 7 with JBoss Weld.

The Spring ApplicationContext is straighforward:

<beans>
  ...
  <bean id="testFromSpring" class="test.Test" />
  ...
</bean>

The CDI managed bean looks like this:

@javax.inject.Named("testA")
public class TestA {

  @javax.inject.Inject
  private Test myTest = null;

  ...

  public Test getTest() {
    return this.myTest;
  }

}

This is my faces-config.xml

<faces-config xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd" version="2.0">
  <application>
    <el-resolver>org.springframework.web.jsf.el.SpringBeanFacesELResolver</el-resolver>
  </application>
</faces-config>

However, when I access the test property from within a JSF page, a new Test instance is being created each time the access occurs. This is a simple example:

<html>
  ...
  <p>1: <h:outputText value="#{testFromSpring}" /></p>
  <p>2: <h:outputText value="#{testA.test}" /></p>
  ...

I get the following output:

1: test.Test@44d79c75
2: test.Test@53f336eb

After a refresh:

1: test.Test@44d79c75
2: test.Test@89f2ac63

I can see that the first output is correct. No matter how often I refresh the page, the testFromSpring returns the value from the bean defined in the Spring context. However the second output clearly shows that each time the getTest method on the test components is invoked, a new Test instance is created and injected instead of using the instance from the Spring context as I would expect.

So, what's the reason for this behaviour?

How can I inject the bean from the Spring context into the CDI managed bean?

I also tried using a qualifier using the name defined in the Spring context, but now an exception is thrown indicating, that the bean cannot be found:

org.jboss.weld.exceptions.DeploymentException: WELD-001408 Injection point has unsatisfied dependencies.  Injection point:  field test.TestA.myTest;  Qualifiers:  [@javax.inject.Named(value=testFromSpring)]

for the code

@javax.inject.Named("testA")
public class TestA {

  @javax.inject.Inject
  @javax.inject.Named("testFromSpring")
  private Test myTest = null;

Answer

Bozho picture Bozho · Nov 10, 2010

Pascal is right that you can't inject something managed by spring into a weld bean (or vice-versa).

But you can define a producer that gets spring beans and gives them to Weld. This sounds like an extreme hack, btw, and I don't think you are supposed to use both frameworks in one project. Choose one and remove the other. Otherwise you'll get in multiple problems.

Here's how it would look like.

@Qualifier
@Retention(Runtime)
public @interface SpringBean {
     @NonBinding String name();
}


public class SpringBeanProducer {

    @Produces @SpringBean
    public Object create(InjectionPoint ip) {
         // get the name() from the annotation on the injection point
         String springBeanName = ip.getAnnotations()....

         //get the ServletContext from the FacesContext
         ServletContext ctx = FacesContext.getCurrentInstance()... 

         return WebApplicationContextUtils
              .getRequiredWebApplication(ctx).getBean(springBeanName);
    }
}

Then you can have:

@Inject @SpringBean("fooBean")
private Foo yourObject;

P.S. You can make the above more type-safe. Instead of getting the bean by name, you can get, through reflection, the generic type of the injection point, and look it up in the spring context.