简体   繁体   中英

How do I bind the Interface with Implementation for Generic Classes?

[Guice 4.0]

I would like to have an interface for the generic class and utilize it in the dependency injection with the use of Guice. For the code listed below I get the following error:

Exception in thread "main" com.google.inject.CreationException: Unable to create injector, see the following errors:

1) Could not find a suitable constructor in com.ulmon.fsqtransit.guicetest.Class1. Classes must have either one (and only one) constructor annotated with @Inject or a zero-argument constructor that is not private.
  at com.ulmon.fsqtransit.guicetest.Class1.class(Class1.java:14)
  at com.ulmon.fsqtransit.guicetest.Module.configure(Module.java:14)

--

public class Class1<T1 extends Number, T2 extends Number>
    implements InterfClass1<T1, T2> {
    public static final String ANNOT1 = "ANNOT1";
    public static final String ANNOT2 = "ANNOT2";
    private T1 t1;
    private T2 t2;
    // for the factory
    @AssistedInject
    public Class1(
            @Assisted(Class1.ANNOT1) T1 t1,
            @Assisted(Class1.ANNOT2) T2 t2
            ) {
        this.t1 = t1;
        this.t2 = t2;
    }
    public T1 getT1() {
        return t1;
    }
    public T2 getT2() {
        return t2;
    }
}


public class Module extends AbstractModule {
    @Override
    protected void configure() {

        bind(new TypeLiteral<InterfClass1<Integer, Integer>>(){})
            .to(new TypeLiteral<Class1<Integer, Integer>>(){});
    }

    public static void main(String[] args) {
        Injector inj = Guice.createInjector(new Module());
    }
}

What causes this error?

After editing

Let's add the factory and modify the module (remove the InterfClass1 interface from the module):

public interface Class1Factory<T1 extends Number, T2 extends Number> {
    public Class1<T1, T2> createClass1(
            @Assisted(Class1.ANNOT1) T1 t1,
            @Assisted(Class1.ANNOT2) T2 t2
            );
}

public class Module extends AbstractModule {
    @Override
    protected void configure() {
        install(new FactoryModuleBuilder()
            .build(new TypeLiteral<Class1Factory<Integer, Double>>(){}));
    }
    public static void main(String[] args) {
        Injector inj = Guice.createInjector(new Module());
        Class1Factory f = inj.getInstance(Key.get(new TypeLiteral<Class1Factory<Integer, Double>>(){}));
        f.createClass1(10, 11.0);
    }
}

It works just fine!

Let's now incorporate the interface:

public class Module extends AbstractModule {
    @Override
    protected void configure() {
        bind(new TypeLiteral<InterfClass1<Integer, Double>>(){})
            .to(new TypeLiteral<Class1<Integer, Double>>(){});
        install(new FactoryModuleBuilder()
            .build(new TypeLiteral<Class1Factory<Integer, Double>>(){}));
    }
    public static void main(String[] args) {
        Injector inj = Guice.createInjector(new Module());
        Class1Factory f = inj.getInstance(Key.get(new TypeLiteral<Class1Factory<Integer, Double>>(){}));
        f.createClass1(10, 11.0);
    }
}

And we get:

1) Could not find a suitable constructor in com.ulmon.fsqtransit.guicetest.Class1. Classes must have either one (and only one) constructor annotated with @Inject or a zero-argument constructor that is not private.
  at com.ulmon.fsqtransit.guicetest.Class1.class(Class1.java:15)
  at com.ulmon.fsqtransit.guicetest.Module.configure(Module.java:14)

Ok, let's, regardless of the error, also change the factory:

public interface Class1Factory<T1 extends Number, T2 extends Number> {
    public InterfClass1<T1, T2> createClass1(
            @Assisted(Class1.ANNOT1) T1 t1,
            @Assisted(Class1.ANNOT2) T2 t2
            );
}

And we get:

1) Could not find a suitable constructor in com.ulmon.fsqtransit.guicetest.Class1. Classes must have either one (and only one) constructor annotated with @Inject or a zero-argument constructor that is not private.
  at com.ulmon.fsqtransit.guicetest.Class1.class(Class1.java:15)
  at com.ulmon.fsqtransit.guicetest.Module.configure(Module.java:14)

2) com.ulmon.fsqtransit.guicetest.InterfClass1<java.lang.Integer, java.lang.Double> is an interface, not a concrete class.  Unable to create AssistedInject factory.
  while locating com.ulmon.fsqtransit.guicetest.InterfClass1<java.lang.Integer, java.lang.Double>
  at com.ulmon.fsqtransit.guicetest.Class1Factory.createClass1(Class1Factory.java:1)

However, if I remove the relation between the interface and class in the module and add this relation into the specification of the factory, I get no error:

public class Module extends AbstractModule {
    @Override
    protected void configure() {
        install(new FactoryModuleBuilder()
            .implement(new TypeLiteral<InterfClass1<Integer, Double>>(){}
                , new TypeLiteral<Class1<Integer, Double>>(){})
            .build(new TypeLiteral<Class1Factory<Integer, Double>>(){}));
    }
    public static void main(String[] args) {
        Injector inj = Guice.createInjector(new Module());
        Class1Factory f = inj.getInstance(Key.get(new TypeLiteral<Class1Factory<Integer, Double>>(){}));
        f.createClass1(10, 11.0);
    }
}

It works fine!

Why Guice has this strange behaviour? Why I cannot relate the interface with the implementation class regardless of the factory?

I can't figure out exactly what you're trying to do, but it looks to me like you aren't actually trying to create an AssistedInject factory, because you haven't created the interface for the factory. In this case, you don't need to use the @Assisted parameters at all. Your constructor should look like this (with no other changes to the class):

@Inject
public Class1(
        @Named(Class1.ANNOT1) T1 t1,
        @Named(Class1.ANNOT2) T2 t2
        ) {

Then, specify the bindings in your module. You have to explicitly tell Guice what to inject for the arguments, and the annotation you've specified :

@Override
protected void configure() {
    bind(new TypeLiteral<InterfClass1<Integer, Integer>>(){})
        .to(new TypeLiteral<Class1<Integer, Integer>>(){});
    bind(Integer.class)
        .annotatedWith(Names.named(Class1.ANNOT1))
        .toInstance(5);
    bind(Integer.class)
        .annotatedWith(Names.named(Class1.ANNOT2))
        .toInstance(15);
}

Then, we can run it like this:

public static void main(String[] args) {
    Injector inj = Guice.createInjector(new Module());
    InterfClass1<Integer, Integer> interf =
        inj.getInstance(Key.get(new TypeLiteral<InterfClass1<Integer, Integer>>(){}));
    System.out.println(interf.getClass());
    System.out.println("T1: " + interf.getT1() + " T2: " + interf.getT2());
}

As you can see when running it, it will inject a Class implementation, with the two arguments as specified:

class guice.Class1
T1: 5 T2: 15

If you are trying to build a factory of Class1 objects, using AssistedInject, this is significantly more difficult because of the way you've specified the generit type parameters. Unfortunately, you cannot inject a generic factory using AssistedInject in the normal way , and this is made doubly more difficult because your Class1 implementation requires T1 extends Number , but your interface can be any T1 you want. This means that your factory must restrict the type of the input arguments it receives to be subclasses of Number ; in other words, your factory can't create just any InterfClass1 it wants, it can only create instances with the proper input arguments.

Hopefully the first half of the answer is what you're trying to do. If it's not, we need more information about your use case to be able to answer this question.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM