我可以在Spring Boot中使用代码来控制ApplicationContext做出的依赖关系解决方案决策吗?

时间:2019-10-27 07:16:17

标签: java spring spring-boot dependency-injection

我在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 个答案:

答案 0 :(得分:1)

功能Bean是Spring 5的新功能,它更适合于将功能注册为Bean提供程序。如果您需要的只是基于代码的配置,则无需执行此操作,而是可以使用标准的基于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也支持该类。

因此,在测试时,您可以使用自己的测试配置,然后自定义要加载的bean并提供其他bean。如果配置类是嵌套类,则甚至无需指定它:

@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)

替代:使用spring javaconfig手动创建的应用上下文

如果您不想使用javaconfig或组件扫描,而是想“自己”注册配置类,则可以像在主类中使用这种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 :(得分:1)

如果我正确理解了它,则只需要条件豆进行测试,建议您在主类上声明“生产” @Bean,然后为您的测试使用属性spring.main.allow-bean-definition-overriding=true @TestConfiguration覆盖了您需要的bean。

类似这样的东西:

@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();
       }
    }

}

答案 2 :(得分:0)

据我了解,您正在寻找可以根据特定需求在特定实现上运行的内容。 请调查此类:

  

org.springframework.beans.factory.config.ServiceLocatorFactoryBean

您可以配置它并定义实现并根据要求获取bean。

<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),并根据需要在运行时获取对象。