how to overcome ProvisionException due to circular dependency with assisted inject and factories?

553 views
Skip to first unread message

Oleg Estekhin

unread,
Mar 17, 2012, 3:06:36 AM3/17/12
to google...@googlegroups.com
Hi

The code below provides a working example of what I try to do.

I have a tree-like structure where the node implementation depends on the
content object it should contain. Each node constructor gets node content as
assisted value and a number of node factories of specific types that depend
on types of child nodes this node can have.

So I have the base Node<E> class and some concrete implementations. The real
guice module is configured with a lot of
"install(FactoryModuleBuilder).implement(Node<XXXXX>,
XXXXXNode.class).build(NodeFactory<XXXXX>)" for each XXXXX content type, and
everything works fine until I have a node that can contain child nodes of
the its own type.

The constructor of such a node (like IntegerNode in the example below) has
assisted parameter for the node content and factory parameter that basically
should create nodes of the same type as this node. The problem is if the
factory is used from the constructor it results in ProvisionException with
"Tried proxying TestGuiceModule$Node to support a circular dependency, but
it is not an interface." message (the second try at creating nodes in the
example code below), while using the same factory object after the node
constructor finishes works (the first try at creating nodes in the code
below).

If the node children have a types different from the node type then using
corresponding factories even from the node constructor works without any
errors.
And if some children have the same type as the node then the approach where
node constructor stores factories in the member variables and all children
nodes are created after the node constructor finishes provides a workaround.

But why does the Guice decides to detect that circular dependency? The test
code shows that the factory object is actually the same object instance so
it is presumably fully constructed and ready to create new node instances.
Why using the factory from the constructor of the type this factory creates
(actually, from the constructor of the instance this factory just have
created) results in exception?

=== test code ===
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.TypeLiteral;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.FactoryModuleBuilder;

public class TestGuiceModule {

public static void main( String[] args ) {
Injector injector = Guice.createInjector( new AbstractModule() {
@Override
protected void configure() {
install( new FactoryModuleBuilder()
.implement( new TypeLiteral<Node<Integer>>() {},
IntegerNode.class )
.build( new TypeLiteral<NodeFactory<Integer>>()
{} ) );
}
} );

NodeFactory<Integer> factory = injector.getInstance( Key.get( new
TypeLiteral<NodeFactory<Integer>>() {} ) );

System.out.println( "create a node first, create children later" );
Node<Integer> node1 = factory.createNode( 0 );
node1.createChildrenLater();
System.out.println();

System.out.println( "create a node and create some children in its
constructor" );
Node<Integer> node2 = factory.createNode( 1 );
node2.createChildrenLater();
}


public abstract static class Node<E> {

public E value;

public Node( E value ) {
this.value = value;
}

public abstract void createChildrenLater();
}

public static interface NodeFactory<E> {

Node<E> createNode( E value );

}

public static class IntegerNode extends Node<Integer> {

private final NodeFactory<Integer> factory;

@Inject
public IntegerNode( @Assisted Integer value, NodeFactory<Integer>
factory ) {
super( value );
System.out.println( "TestGuiceModule$IntegerNode.IntegerNode,
value=" + value + ", factory identity=" + System.identityHashCode(
factory ) );
this.factory = factory;
if ( value > 0 ) {
System.out.println( "creating children inside the node
constructor" );
Node<Integer> child = factory.createNode( value - 1 );
}
}

@Override
public void createChildrenLater() {
System.out.println(
"TestGuiceModule$IntegerNode.createChildrenLater" );
Node<Integer> child = factory.createNode( value - 1 );
}

}

}

Oleg Estekhin

unread,
Mar 17, 2012, 7:41:09 AM3/17/12
to google...@googlegroups.com
And a small follow-up with something like a probable bug in the Guice.

Consider the variant of code from the previous post - removed using the
factory from the constructor and added a "hack" postConstruct method
annotated with @inject to the Node class so the Guice will call it some time
after the node constructor finishes. This hack-postConstruct method creates
child nodes, so the overall result is that nodes are created by the factory
and their children are created automatically from the caller point of view
(the caller does not have to call "createChildrenLater()" after it gets a
instance node from the factory).

When run, this variant does not throw any ProvisionExceptions, but it prints
the "!!! the factory returned THIS node instead of the new node !!!"
message, which means that the instance returned from the call to factory is
equal to the "this" object, probably the previous object created by this
factory. This is a bug in the Guice - it should have thrown
ProvisionException as before if it does not like the way I use the factory.
Of course better yet, it should have returned a new instance, as expected.

If the "@inject postConstruct()" method is modified to "@inject
postConstruct( Injector injector )" (a parameter of Injector type is added,
but the method body does not use it), then the code will work as was
expected from the beginning - there will be no ProvisionExceptions and
factory will return unique instances on each call.

How the factory manages to return the same instance in the "@inject
method()" variant?
Why the difference between "@inject method()" and "@inject method( Injector
injector )" variants?

=== start of @inject postConstruct() variant ===


import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.TypeLiteral;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.FactoryModuleBuilder;

public class TestGuiceModule {

public static void main( String[] args ) {
Injector injector = Guice.createInjector( new AbstractModule() {
@Override
protected void configure() {
install( new FactoryModuleBuilder()
.implement( new TypeLiteral<Node<Integer>>() {},
IntegerNode.class )
.build( new TypeLiteral<NodeFactory<Integer>>()
{} ) );
}
} );

NodeFactory<Integer> factory = injector.getInstance( Key.get( new
TypeLiteral<NodeFactory<Integer>>() {} ) );

System.out.println( "create a node first, create children later" );
Node<Integer> node1 = factory.createNode( 0 );

System.out.println();

System.out.println( "create a node and create some children in its
constructor" );
Node<Integer> node2 = factory.createNode( 1 );
}

public abstract static class Node<E> {

public E value;

public Node( E value ) {
this.value = value;
}

@Inject
private void postConstruct() {
System.out.println( "TestGuiceModule$Node.postConstruct" );
createChildrenLater();
}

public abstract void createChildrenLater();
}

public static interface NodeFactory<E> {

Node<E> createNode( E value );

}

public static class IntegerNode extends Node<Integer> {

private final NodeFactory<Integer> factory;

@Inject
public IntegerNode( @Assisted Integer value, NodeFactory<Integer>
factory ) {
super( value );
System.out.println( "TestGuiceModule$IntegerNode.IntegerNode,

this identity=" + System.identityHashCode( this ) + ", value=" + value + ",

factory identity=" + System.identityHashCode( factory ) );
this.factory = factory;
}

@Override
public void createChildrenLater() {


if ( value > 0 ) {
System.out.println(

"TestGuiceModule$IntegerNode.createChildrenLater" );
Node<Integer> child = factory.createNode( value - 1 );

if ( this == child ) {
System.out.println( "\t!!! the factory returned THIS
node instead of the new node !!!" );
}
}
}

}

}
=== end of @inject postConstruct() variant ===

Reply all
Reply to author
Forward
0 new messages