CDI VooDoo – The Bean Manager and Circular Injection

CDI supports circular dependency injection when at least one of the beans has a normal scope*, this may be useful when sharing contextual information between beans of same or different scope (e.g. MVC pattern).

The CDI BeanManager is responsible for the creation of all CDI beans and managing contextual access to all normal scoped beans.

@RequestScoped
public class Foo {
   @Inject
   private Bar bar;
   ...
}

@RequestScoped
public class Bar {
   @Inject
   private Foo foo;
   ...
}

This works because the BeanManager injects a client proxy for any normal scoped bean. When a method is invoked on the client proxy, the call results in a contextual lookup via the BeanManager.

CDI normal scoped loockup

@Dependent* beans in CDI are treated as pseudo scoped, unlike normal scoped beans the bean-manager does not inject a client proxy. Instead it instantiates the @Dependent bean immediately, injecting a direct reference to the newly created bean. This direct relationship ensures that the @Dependent bean inherits the scope of enclosing bean.

Circular injects which only involve dependent beans are essentially the same as:

public class Foo {
  private Bar bar = new Bar();
  ...
}

public class Bar {
  private Foo foo = new Foo();
  ...
}

Which would lead to a StackOverflowError at runtime since Foo tries to instantiate a new Bar object, which in turn instanties a new Foo, and so on.

Weld discovers this issue during deployment, throwing a DeploymentException, preempting a StackOverflowError at runtime.

org.jboss.weld.exceptions.DeploymentException: WELD-001443: Pseudo scoped bean has circular dependencies. Dependency path: 
  - Managed Bean [class com.knitelius.circle.Foo] with qualifiers [@Any @Default],
  - [BackedAnnotatedField] @Inject private com.knitelius.jaxwsconversation.Foo.bar,
  - Managed Bean [class com.knitelius.circle.Bar] with qualifiers [@Any @Default],
  - [BackedAnnotatedField] @Inject private com.knitelius.jaxwsconversation.Bar.foo,
  - Managed Bean [class com.knitelius.circle.Foo] with qualifiers [@Any @Default]

It is suggested frequently that this can be resolved by lazy instantiation using CDI Instance or Provider.

@Dependent
public class Foo {
  @Inject
  private Instance<Bar> barInstance;
  ...
  public void doStuff() {
    Bar bar = barInstance.get();
    ...
  }
}

@Dependent
public class Bar {
  @Inject
  private Instance<Foo> fooInstance;
  ...
}

It will run, but it’s bad design and will most likely produce unexpected results.

CDI lazy instantiation with instance

Foo 1 holds a reference to Bar 1. Bar 1 on the other hand does not hold a circular reference back to Foo 1 but obtains its own dependent instance of Foo 2. Therefore no state can be shared between Foo or Bar.

Conclusion
The only logical use of circular injections is when state is to be shared between contextual beans, in all other scenarios you should definitely consider restructuring your code.

Acknowledgements

I would like to thank Björn Sonntag and Robert Meyer for reviewing my posts.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.