How can I make a non-assisted dependency assisted?

202 views Asked by At

Suppose I have a third party class as follows:

public class MyObject {
    @Inject
    public MyObject(Foo foo, Bar bar) { ... }
}

Now suppose that I have a factory interface like so:

public interface MyObjectFactory {
    public MyObject build(Bar bar);
}

The idea is that I wish to have a MyObjectFactory that builds a MyObject for a fixed Foo - that is, essentially adding in the @Assisted annotation on the Bar constructor parameter from the outside. Of course, manually implementing MyObjectFactory is always possible:

public class MyObjectFactoryImpl implements MyObjectFactory {
    @Inject private Provider<Foo> foo;

    @Override
    public MyObject build(Bar bar) { return new MyObject(foo.get(), bar); }
}

But let's say that there are conditions that require me to have Guice build MyObject instances - for example, method interceptors. This seems like a job for "injecting the injector":

public class MyObjectFactoryImpl implements MyObjectFactory {
    @Inject private Injector injector;

    @Override
    public MyObject build(Bar bar) {
        Injector child = injector.createChildInjector(new AbstractModule() {
            @Override
            protected void configure() {
                bind(Bar.class).toInstance(bar);
                // Set up method interceptors for MyObject here...
            }
        });
        return child.getInstance(MyObject.class);
    }
}

This sounds evil and boiler-plate-y, so I'm wondering if there are any alternate implementations and/or a way to have Guice generate the factory impl.

2

There are 2 answers

0
durron597 On BEST ANSWER

First of all, it is rare that you want to be passing instances of MyObject around in your class for exactly the reasons you describe. You have no control over them, so you can't add @Assisted annotations, you can't add method interceptors, etc. etc. Plus, what happens when you want to swap out the third party library for a different implementation?

Therefore, you should be wrapping MyObject into another object.

// **Please** choose better names than this in your real code.
public class MyWrapperBackedByMyObject implements MyWrapperInterface {
    private final MyObject delegate;

    @Inject
    MyWrapperObject(Foo foo, @Assisted Bar bar) {
        delegate = new MyObject(foo, bar);
    }

    @NotOnWeekends  // Example of how you might do method interception
    public void orderPizza() {
       delegate.orderPizza();
    }
}

Then, remove all references to MyObject throughout your code, using the naming convention I describe above, there should only be references to MyWrapperInterface.

2
Milan Baran On

actually it is. Have a look Assisted Inject

Include

        <dependency>
            <groupId>com.google.inject.extensions</groupId>
            <artifactId>guice-assistedinject</artifactId>
            <version>${guice.version}</version>
        </dependency>

Update injection with assisted

public class MyInjectedObject extends MyObject implements MyIntf {
@Inject
public MyObject(Foo foo, @Assisted Bar bar) { super(foo,bar); }
}

You have to add one more interface:

public interface MyIntf {}

In your module bind generic factory to your interface

    install(new FactoryModuleBuilder()
            .implement(MyIntf.class, MyInjectedObject.class)
            .build(MyObjectFactory.class)
);

Now you can inject MyObjectFactory anywhere you want.

MyObject obj = myObjectFactory.build(bar);