Сервисный локатор Spring без автоподключения - PullRequest
1 голос
/ 16 июня 2020

Имея этот код:

public class ClassA {

    private InterfaceB interfaceB;

    private int a

    private int b;

    public ClassA(int a, int b) {
       this.a = a;
       this.b = b;
    }
}

В моем приложении может быть несколько объектов ClassA, созданных по запросу во время выполнения. Но все они должны использовать одну и ту же конкретную реализацию класса InterfaceB (существует несколько реализаций InterfaceB, но только одна используется во время выполнения в зависимости от используемой платформы). И в приложении должен быть только один объект InterfaceB (одноэлементный класс).

Я не могу автоматически подключать interfaceB, поскольку объекты ClassA создаются во время выполнения, когда известны параметры a и b конструктора.

Как я могу использовать Spring Framework для реализации шаблон локатора услуг здесь? Я планирую создать экземпляр локатора служб в ClassA и использовать его для получения объекта InterfaceB.

Ответы [ 2 ]

1 голос
/ 16 июня 2020

Вы можете создать дополнительный класс, который будет создавать ваш ClassA, и он будет содержать ссылку на interfaceB. Например:

@Component
public class ClassAFactory {

    @Autowired
    private InterfaceB interfaceB;

    public ClassA create(int a, int b) {
       return new ClassA(a, b, interfaceB);
    }
}

В этом случае вам нужно расширить ClassA, чтобы передать interfaceB. Тогда где-нибудь в вашем коде вы можете:

@Autowired
private ClassAFactory factory ;

...

ClassA classA = factory.create(1,1); 

0 голосов
/ 16 июня 2020

Я не думаю, что вам нужен шаблон локатора сервисов, в современных приложениях, управляемых Spring, он обычно больше не требуется.

Я постараюсь рассмотреть все ваши утверждения с точки зрения интеграции Spring Framework:

В моем приложении может быть несколько объектов ClassA, создаваемых по запросу во время выполнения.

Spring - это среда выполнения, поэтому все в любом случае создается во время выполнения. Если вам нужно много объектов, созданных по запросу, вы можете объявить ClassA как Spring bean с прототипом области видимости. Этот прототип может быть внедрен в другие бины. Другой возможный подход, если вы знаете, какие экземпляры будут созданы во время запуска приложения, - это определить множество bean-компонентов одного типа и использовать функцию квалификатора Spring, чтобы различать их во время инъекции.

Но все они должен использовать ту же конкретную реализацию класса InterfaceB (существует несколько реализаций InterfaceB, но только одна из них используется во время выполнения в зависимости от используемой платформы).

Это означает, что InterfaceB может быть обычным синглтоном однако, учитывая разные реализации, вы можете определить что-то вроде этого:

@Configuration 
public class MyConfiguration {

    @Bean
    @ConditionalOnProperty(name="myprop", havingValue="true")
    public InterfaceB interfaceBImpl1() {
        return new InterfaceBImpl1();
    }

    @Bean
    @ConditionalOnProperty(name="myprop", havingValue="false")
    public InterfaceB interfaceBImpl2() {
        return new InterfaceBImpl2();
    }
}

Я не могу автоматически подключить интерфейсB, поскольку объекты ClassA создаются во время выполнения, когда известны параметры конструктора a и b.

Вообще-то можно, с этим проблем нет. Определите bean-компонент classA как прототип.

Проверьте также @Lazy аннотацию spring на случай, если вы хотите инициализировать экземпляр этого classA только при первом вызове.


public class ClassA {
  /// fields ///

  public ClassA(InterfaceB intefaceB, int a, int b) {
    this.intefaceB = intefaceB;
    this.a = a;
    this.b = b;
  }
}
@Configuration 
class MyConfig {

   @Bean
   @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
   public ClassA classA(InterfaceB bImpl, int a, int b) {
      return new ClassA(bImpl, int a, int b);
   }
}

Обновление 1

На основе комментариев OP:

Вот рабочий пример:

Добавьте следующую зависимость в pom. xml:

<dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
    <version>1</version>
</dependency>

Он содержит только интерфейсы и не имеет транзитивных зависимостей

Затем, в зависимости от вашего варианта использования, объясненного в комментарии:


public class InterfaceB {
}

public class ClassA {
    private final InterfaceB interfaceB;

    public ClassA(InterfaceB interfaceB) {
        this.interfaceB = interfaceB;
    }

    public void doSomething() {
        System.out.println("Doing something on instance: [ " + this + " ]. The interface B instance is [ "+ interfaceB + " ]");
    }
}


public class ServiceA {

    private final List<ClassA> classAList;
    private Provider<ClassA> classAProvider;

    public ServiceA(Provider<ClassA> classAProvider) {
        this.classAProvider = classAProvider;
        this.classAList = new ArrayList<>();
    }

    public void addNewObject() {
        ClassA newObj = classAProvider.get();
        classAList.add(newObj);
    }

    public void doWithAllElementsInList() {
        classAList.forEach(ClassA::doSomething);
    }
}

Конфигурация Spring выглядит так:

public class SingletonWithPrototypesConfig {

    @Bean
    public ServiceA serviceA(Provider<ClassA> classAProvider) {
        return new ServiceA(classAProvider);
    }

    @Bean
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public ClassA classA(InterfaceB interfaceB) {
        return new ClassA(interfaceB);
    }

    @Bean
    public InterfaceB interfaceB() {
        return new InterfaceB();
    }
}

И основной класс, который получает службу A из контекста приложения (в вашем случае это должен быть, вероятно, контроллер или любой другой бизнес-поток):

public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SingletonWithPrototypesConfig.class);
        ServiceA serviceA = ctx.getBean(ServiceA.class);
        serviceA.doWithAllElementsInList(); // won't print anything, 0 elements in the list
        System.out.println("---------");
        serviceA.addNewObject();
        serviceA.addNewObject();
        serviceA.doWithAllElementsInList();
    }

В последней печати обратите внимание, что Экземпляры ClassA разные, но interfaceB - это тот же общий экземпляр.

Примечание: поставщик уже интегрирован с Spring, он находится в javax.inject.Provider этой новой банке.

...