Могу ли я использовать код для управления решениями по разрешению зависимостей, принимаемыми ApplicationContext в Spring Boot? - PullRequest
1 голос
/ 27 октября 2019

Я использую автоматическую разводку в Spring Boot для внедрения реализаций интерфейсов в классы, помеченные как компоненты. Иногда мне нужно запустить приложение (и / или тесты) с конкретными реализациями некоторых интерфейсов. Я знаю, что это можно сделать с помощью комбинаций аннотаций (@Qualifier, @Primary и более), но они не подходят для моих нужд. Я хотел бы иметь возможность (опционально) писать код, который выполняется до того, как ApplicationContext определит, какие реализации моих интерфейсов будут созданы, и в этом коде переопределит одно или несколько из этих решений.

Я пыталсяиспользовать код, подобный этому:

context.registerBean(MyService.class, () -> new MyService());

, как описано здесь: https://www.baeldung.com/spring-5-functional-beans.

Но я не смог найти место в коде, чтобы вставить это достаточно рано, чтобы это повлияло на все поля с автопроводкойв приложении. В частности, это проблема в тестах (помечена @SpringBootTest).

Я хотел бы иметь возможность использовать код, который выглядит примерно так, как это делается в C #:

В одном тесте я мог бы использовать этот код, а затем запустить тест:

container.Register<IDataLayer, MockDataLayer>();
container.Register<IPersistenceLayer, FilePersistenceLayer>();

В другом тесте я мог бы использовать этот код, а затем запустить тест:

container.Register<IDataLayer, SQLDataLayer>();
container.Register<IPersistenceLayer, MockPersistenceLayer>();

И в производстве я мог бы запустить этот

container.Register<IDataLayer, SQLDataLayer>();
container.Register<IPersistenceLayer, FilePersistenceLayer>();

или просто полагаться на конфигурацию файла.

Можно ли создать этот уровень контроля над выбором, сделанным ApplicationContext, или я должен полагаться на хрупкий выбор аннотаций и файлов конфигурации xml, чтобы каждый мой тест выполнялся именно так, как мне нужно?

Ответы [ 3 ]

1 голос
/ 27 октября 2019

Функциональные бины - это новая функция Spring 5, которая больше ориентирована на регистрацию функций в качестве поставщиков бинов. Если вам нужна только конфигурация на основе кода, вам не нужно идти туда, но вы можете использовать стандартную конфигурацию на основе аннотаций Spring.

Обычная, стандартная Spring javaconfig

Тривиальный пример, класс конфигурации:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ApplicationConfiguration {

    @Bean
    public DemoManager helloWorld()
    {
        return new DemoManagerImpl();
    }
}

Основной класс:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

Это будет использовать компонентное сканирование, чтобы найти ваш класс конфигурации, а затем вызвать его методы для получения bean-компонентов. Вы можете предоставить классы конфигурации, которые вы хотите в качестве аргументов, и SpringBootTest, который вы упомянули, также поддерживает это.

Таким образом, при тестировании вы можете использовать свою собственную тестовую конфигурацию, чтобы настроить загружаемые компоненты и предоставить дополнительные компоненты. Если класс конфигурации является вложенным классом, вам даже не нужно указывать его:

@SpringBootTest
@RunWith(SpringRunner.class)
public class SpringBootDemoApplicationTests
{  
    @Test
    public void testSomething() {
       // ...
    }

    @TestConfiguration
    static class MyTestConfiguration {

        //tests specific beans
        @Bean
        DataSource createDataSource(){
            //
        }
    }
}

Использование @TestConfiguration добавит в вашу конфигурацию - если вы не хотите добавлять, а вместо этого заменять конфигурациюв общем, используйте @SpringBootTest(classes = YourCustomConfiguration.class).

Альтернатива: вручную созданный контекст приложения, с пружиной javaconfig

Если вы не хотите использовать javaconfig или компонентное сканирование, но вместо этого хотите также зарегистрировать свои классы конфигурации«Вы сами», вы можете сделать это, например, иметь такой метод main в main классе:

public static void main(String[] args) {
   ApplicationContext ctx = new AnnotationConfigApplicationContext(ApplicationConfiguration.class);

   HelloWorld helloWorld = ctx.getBean(HelloWorld.class);
   helloWorld.setMessage("Hello World!");
   helloWorld.getMessage();
}

он обычно не используется, но и не ошибается.

Альтернатива 2: ручной контекст приложения, ручная регистрация bean-компонентов

Если вы действительно хотите избежать и класса конфигурации, вы также можете сделать это следующим образом:

import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class SomeClass {
  public static void main(String args[]) {

    // first, we create empty context ourselves
    ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext();

    // then we get its bean factory to be able to register stuff
    ConfigurableListableBeanFactory beanFactory = ctx.getBeanFactory();

    // register our bean
    YourBean beanToRegister = new YourBean();
    beanFactory.registerSingleton(beanToRegister.getClass().getCanonicalName(), beanToRegister);

    ctx.refresh(); // context refresh actually updates the status

    // here we can test a bean was actually created and working
    YourBean helloWorld = ctx.getBean(YourBean.class);
    helloWorld.setAuthor("Hello World!");
    System.out.println(helloWorld.getAuthor());
  }
}

как и в случае другой альтернативы, это не такобщий подход с Spring, но это не так.

1 голос
/ 27 октября 2019

Если я понял правильно, вам нужны только условные бины для ваших тестов, я предлагаю вам объявить ваши "производственные" @Bean s в ваших основных классах, а затем для ваших тестов вы можете использовать свойство spring.main.allow-bean-definition-overriding=true с @TestConfiguration переопределяя нужные вам бобы.

Примерно так:

@SpringBootTest(properties={"spring.main.allow-bean-definition-overriding=true"})
public class MyConditionalTest {

    @Test
    public void testMyStuff() { 
    // do your test here
    }

    @TestConfiguration
    public OverrideSpringBean {

       @Bean
       public IDataLayer dataLayer() {
           return new MockPersistenceLayer();
       }
    }

}
0 голосов
/ 27 октября 2019

Как я понимаю, вы ищете что-то, что работает на конкретной реализации по конкретному требованию. Пожалуйста, посмотрите в этот класс:

org.springframework.beans.factory.config.ServiceLocatorFactoryBean

Вы можете настроить его, определить реализации и получить бин согласнотребование.

<beans:bean id="dataStrategyFactory" class="org.springframework.beans.factory.config.ServiceLocatorFactoryBean">
        <beans:property name="serviceLocatorInterface" value="com.abc.DataStrategyFactory" />
    </beans:bean>
    <beans:alias name="FileImpl" alias="FILE" />
    <beans:alias name="DBImpl" alias="DB" />
    <beans:alias name="WSImpl" alias="WS" />
    <beans:alias name="NativeImpl" alias="DEFAULT" />

Укажите реализацию интерфейса (здесь, DataStrategyFactory) и получите объект во время выполнения по требованию.

...