Guice. Как внедрить разные объекты в поле родительского класса? - PullRequest
0 голосов
/ 25 февраля 2020

У меня проблема с инжекцией поля Guice. Рассмотрим следующую иерархию классов.

abstract class Base{
    @Inject
    protected MyService myService;
}

class A extends Base{
    @Inject
    private AnotherService anotherService;
 }

class B extends Base{
   ...
}

Я хочу иметь два разных экземпляра MyService во время выполнения - один для внедрения во все объекты класса A, а другой для объектов класса B. Я знаю, как достичь это поведение с помощью конструктора injecton:

  bind(MyService.class).annotatedWith(Names.named("forA")).to(MyServiceImpl.class).in(Singleton.class);
  bind(MyService.class).annotatedWith(Names.named("forB")).to(MyServiceImpl.class).in(Singleton.class);

  class A extends Base{
    @Inject @Named(value = "forA")
    public A(MyService service1, AnotherService service2) {
      this.myService = service1;
      this.anotherService = service2;
    }

Проблема в том, что переход к инжектору конструктора будет довольно сложным, поэтому я хотел бы придерживаться метода инжекции поля. Можно ли настроить поле ввода так, как я хочу?

1 Ответ

1 голос
/ 26 февраля 2020

Я бы использовал Пользовательские инъекции из Guice для достижения этого.

Во-первых, я создам аннотацию для аннотации MyService, которую я хочу внедрить, основываясь на типе суперкласса.

@BindingAnnotation
@interface MyServiceInject {
}

Тогда я бы пометил свое поле MyService аннотацией.

static abstract class Base {

    @MyServiceInject
    protected MyService myService;
}

Теперь нам нужен пользовательский TypeListener, который будет вызываться каждый раз, когда мы сталкиваемся с точкой инъекции.

Вот как мы его создадим.

static class ClassBasedMyServiceInjectionListener implements TypeListener {

    @Override
    public <I> void hear(TypeLiteral<I> typeLiteral, TypeEncounter<I> encounter) {
        Class<?> clazz = typeLiteral.getRawType();

        while (clazz != null) {
            for (Field field : clazz.getDeclaredFields()) {
                if (field.getType() == MyService.class && field.isAnnotationPresent(MyServiceInject.class)) { //if type of field is MyService and it has MyServiceInject annotation
                    encounter.register(new ClassBasedMyServiceInjector<>(field,
                                    typeLiteral.getType(),
                                    encounter.getProvider(MyServiceA.class),
                                    encounter.getProvider(MyServiceB.class)
                            )
                    ); //Now register a MemberInjector for this encounter.
                }
            }
            clazz = clazz.getSuperclass();
        }
    }
}

Теперь нам нужен MemberInjector, который будет выглядеть следующим образом.

static class ClassBasedMyServiceInjector<T> implements MembersInjector<T> {

    private final Field field;
    private final Type superClassType;
    private final Provider<MyServiceA> myServiceAProvider;
    private final Provider<MyServiceB> myServiceBProvider;

    ClassBasedMyServiceInjector(Field field, Type superClassType, Provider<MyServiceA> myServiceAProvider, Provider<MyServiceB> myServiceBProvider) {
        this.field = field;
        this.superClassType = superClassType;
        this.myServiceAProvider = myServiceAProvider;
        this.myServiceBProvider = myServiceBProvider;

        field.setAccessible(true);
    }

    public void injectMembers(T t) { //this will be called when guice wants to inject members
        try {
            if (superClassType == A.class) {//if super class is of type A
                field.set(t, myServiceAProvider.get()); //inject MyServiceA
            } else if (superClassType == B.class) { //if super class is of type B
                field.set(t, myServiceBProvider.get()); //inject MyServiceB
            }
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }
}

Наконец, я бы связал наш пользовательский TypeListener в методе конфигурирования нашего модуля вот так.

    @Override
    protected void configure() {
        bindListener(Matchers.any(), new ClassBasedMyServiceInjectionListener());
    }

Надеюсь, это поможет.

...