Java 我可以使用代码控制ApplicationContext在Spring Boot中做出的依赖项解析决策吗?
我在SpringBoot中使用自动连接将接口的实现注入到标记为组件的类中。 有时我需要使用某些接口的特定实现来运行应用程序(和或测试)。 我知道这可以通过注释组合(Java 我可以使用代码控制ApplicationContext在Spring Boot中做出的依赖项解析决策吗?,java,spring,spring-boot,dependency-injection,Java,Spring,Spring Boot,Dependency Injection,我在SpringBoot中使用自动连接将接口的实现注入到标记为组件的类中。 有时我需要使用某些接口的特定实现来运行应用程序(和或测试)。 我知道这可以通过注释组合(@Qualifier、@Primary等)来实现,但这些注释并不适合我的需要。 我希望能够(可选)编写在ApplicationContext确定将创建哪些接口实现之前运行的代码,并在该代码中覆盖一个或多个决策 我曾尝试使用如下代码: context.registerBean(MyService.class, () -> new
@Qualifier
、@Primary
等)来实现,但这些注释并不适合我的需要。
我希望能够(可选)编写在ApplicationContext
确定将创建哪些接口实现之前运行的代码,并在该代码中覆盖一个或多个决策
我曾尝试使用如下代码:
context.registerBean(MyService.class, () -> new MyService());
如下所述:
但是,我在代码中找不到足够早地插入它的位置,这样它就会影响应用程序中的所有自动连接字段。
特别是,这是测试中的一个问题(标记为@SpringBootTest
)
我希望能够使用类似于C语言的代码:
在一个测试中,我可能会使用以下代码,然后运行测试:
container.Register<IDataLayer, MockDataLayer>();
container.Register<IPersistenceLayer, FilePersistenceLayer>();
container.Register<IDataLayer, SQLDataLayer>();
container.Register<IPersistenceLayer, MockPersistenceLayer>();
container.Register();
container.Register();
在另一个测试中,我可能会使用以下代码,然后运行测试:
container.Register<IDataLayer, MockDataLayer>();
container.Register<IPersistenceLayer, FilePersistenceLayer>();
container.Register<IDataLayer, SQLDataLayer>();
container.Register<IPersistenceLayer, MockPersistenceLayer>();
container.Register();
container.Register();
在生产中,我可能会运行这个
container.Register<IDataLayer, SQLDataLayer>();
container.Register<IPersistenceLayer, FilePersistenceLayer>();
container.Register();
container.Register();
或者仅仅依靠文件配置
是否有可能创建对
ApplicationContext
所做选择的这种级别的控制,或者我必须依赖注释和xml配置文件的脆弱选择,以使我的每个测试完全按照我的需要运行?据我所知,您正在寻找在特定需求的特定实现上运行的东西。
请关注本课程:
org.springframework.beans.factory.config.ServiceLocatoryFactoryBean
您可以配置它,定义实现,并根据需求获取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)并根据需要在运行时获取对象。函数bean是Spring 5的一个新特性,它更适合将函数注册为bean提供者。如果您只需要基于代码的配置,则不需要进行配置,但可以使用标准的基于Spring注释的配置
<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" />
常规的、标准的SpringJavaConfig
简单示例,配置类:
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);
}
}
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(ApplicationConfiguration.class);
HelloWorld helloWorld = ctx.getBean(HelloWorld.class);
helloWorld.setMessage("Hello World!");
helloWorld.getMessage();
}
这将使用组件扫描来查找配置类,然后调用其方法来获取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
将添加到您的配置中-如果您不想添加配置,而是完全替换配置,请使用@springbootest(classes=YourCustomConfiguration.class)
备选方案:使用SpringJavaConfig手动创建应用程序上下文
如果您不想使用javaconfig或组件扫描,而是想“自己”注册配置类,您可以这样做,例如在main类中使用这种main方法:
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);
}
}
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注册
如果确实希望避免使用configuration类,也可以这样做:
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());
}
}
@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();
}
}
}
与另一种选择一样,这不是Spring的常见方法,但也没有错。如果我没有弄错,您的测试只需要条件bean,我建议您声明“生产”
@Bean
s在您的主类上,然后对于您的测试,您可以使用属性spring.main.allow Bean definition overriding=true
,使用@TestConfiguration
覆盖您需要的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());
}
}
@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();
}
}
}
为此使用基于代码的配置文件是否有问题?(xml配置通常不再是首选)我宁愿不必使用配置文件。我想用代码控制它。这给了我更好的控制,并且在开发环境中更易于维护。我不熟悉“基于代码”和“配置文件”的组合。你能提供这种解决方案的链接让我看一下吗?基于java代码的配置是代码,是执行Spring配置的标准方式。通常,配置文件本身由Spring扫描,但如果您也希望手动扫描,请给出一个示例。如果我理解,您需要动态注释来选择实现类。@eis感谢您提供此指针。如果这是可行的,它看起来确实像我所需要的,但是我看到,虽然它是为我的一些自动连接字段调用的,但它没有为其他字段调用。例如,我的一个迄今为止注释为Primary的实现似乎仍然被创建。我有一个标准的spring启动应用程序,我的大多数类在main/java中,一些测试和测试类在test/java中。我应该如何使用此配置模式来确保它将控制所有分辨率选择?为了实现这一点,删除所有主要注释就足够了吗?我认为“常规的、标准的SpringJavaConfig”对我来说是一个很好的解决方案,但我有一个问题。当我使用此方法时,是否应该从类中删除组件注释,以便通过配置类的解析是明确的?@DavidSackstein是的,对于使用javaconfig配置配置的bean。你可以混合使用这些方法,但不要在同一个类中使用,我会