1. 概述

在这个教程中,我们将学习如何在Spring上下文中设置为null的bean。这在某些情况下可能会很有用,例如在测试时,当我们不想提供模拟对象时。此外,在使用一些可选功能时,我们可能希望避免创建实现,而是传递null

此外,通过这种方式,我们可以创建占位符,如果需要在bean生命周期之外决定选择所需实现,这是一种延迟决策的方法。最后,这种方法可能是移除特定bean过程中的一部分,即从上下文中删除它们。

2. 组件设置

根据上下文的配置方式,有几种方法可以将bean设置为null。我们将考虑XML配置注解配置,以及基于Java的配置。我们将使用一个简单的例子,包含两个类:

@Component
public class MainComponent {
    private SubComponent subComponent;
    public MainComponent(final SubComponent subComponent) {
        this.subComponent = subComponent;
    }
    public SubComponent getSubComponent() {
        return subComponent;
    }
    public void setSubComponent(final SubComponent subComponent) {
        this.subComponent = subComponent;
    }
}

接下来,我们将展示如何在基于Java的Spring上下文中将SubComponent设置为null

@Component
public class SubComponent {}

3. 使用占位符的XML配置

在XML配置中,我们可以使用特殊占位符来标识null值:

<beans>
    <bean class="com.baeldung.nullablebean.MainComponent" name="mainComponent">
        <constructor-arg>
            <null/>
        </constructor-arg>
    </bean>
</beans>

这个配置会得到以下结果:

@Test
void givenNullableXMLContextWhenCreatingMainComponentThenSubComponentIsNull() {
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
      "nullable-application-context.xml");
    MainComponent bean = context.getBean(MainComponent.class);
    assertNull(bean.getSubComponent());
}

4. 使用SpEL的XML配置

我们也可以使用SpEL在XML中实现类似的效果,与之前的配置有所不同:

<beans>
    <bean class="com.baeldung.nullablebean.MainComponent" name="mainComponent">
        <constructor-arg value="#{null}"/>
    </bean>
</beans>

同样地,我们可以确认SubComponent被设置为null

@Test
void givenNullableSpELXMLContextWhenCreatingMainComponentThenSubComponentIsNull() {
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
      "nullable-spel-application-context.xml");
    MainComponent bean = context.getBean(MainComponent.class);
    assertNull(bean.getSubComponent());
}

5. 使用SpEL和属性文件的XML配置

改进上述解决方案的一种方法是将bean名称存储在属性文件中。这样,我们可以在需要时随时传递null,而无需更改配置:

nullableBean = null

XML配置将使用PropertyPlaceholderConfigurer来读取属性:

<beans>
    <bean id="propertyConfigurer"
      class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="location" value="classpath:nullable.properties"/>
    </bean>
    <bean class="com.baeldung.nullablebean.MainComponent" name="mainComponent">
        <constructor-arg value="#{ ${nullableBean} }"/>
    </bean>
    <bean class="com.baeldung.nullablebean.SubComponent" name="subComponent"/>
</beans>

然而,我们需要在SpEL表达式中使用属性占位符,以便正确读取值。因此,我们将初始化SubComponentnull

@Test
void givenNullableSpELXMLContextWithNullablePropertiesWhenCreatingMainComponentThenSubComponentIsNull() {
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
      "nullable-configurable-spel-application-context.xml");
    MainComponent bean = context.getBean(MainComponent.class);
    assertNull(bean.getSubComponent());
}

要提供实现,只需更改属性文件即可:

nullableBean = subComponent

6. Java配置中的null供应商

不可能直接从带有@Bean注解的方法中返回null。因此,我们需要以某种方式包装它。我们可以使用Supplier来实现这一点:

@Bean
public Supplier<SubComponent> subComponentSupplier() {
    return () -> null;
}

技术上,我们可以使用任何类来包装null值,但使用Supplier更具语义性。对于null,我们不关心Supplier是否会被多次调用。然而,如果我们想为通常的bean实现类似解决方案,必须确保单例时Supplier提供相同的实例。

这个解决方案也会提供正确的行为:

@Test
void givenNullableSupplierContextWhenCreatingMainComponentThenSubComponentIsNull() {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
      NullableSupplierConfiguration.class);
    MainComponent bean = context.getBean(MainComponent.class);
    assertNull(bean.getSubComponent());
}

请注意,从@Bean方法简单地返回null可能会引发问题:

@Bean
public SubComponent subComponent() {
    return null;
}

在这种情况下,上下文会因UnsatisfiedDependencyException而失败:

@Test
void givenNullableContextWhenCreatingMainComponentThenSubComponentIsNull() {
    assertThrows(UnsatisfiedDependencyException.class, () ->  new AnnotationConfigApplicationContext(
      NullableConfiguration.class));
}

7. 使用Optional

当使用Optional时,Spring会自动识别bean可能在上下文中不存在,并在没有额外配置的情况下传递null

@Bean
public MainComponent mainComponent(Optional<SubComponent> optionalSubComponent) {
    return new MainComponent(optionalSubComponent.orElse(null));
}

如果Spring在上下文中找不到SubComponent,它将传递一个空的Optional:

@Test
void givenOptionableContextWhenCreatingMainComponentThenSubComponentIsNull() {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
      OptionableConfiguration.class);
    MainComponent bean = context.getBean(MainComponent.class);
    assertNull(bean.getSubComponent());
}

8. 非必需的自动注入

另一种将bean值设置为null的方式是将其声明为非必需的。但是,这种方法只适用于构造函数以外的注入:

@Component
public class NonRequiredMainComponent {
    @Autowired(required = false)
    private NonRequiredSubComponent subComponent;
    public NonRequiredSubComponent getSubComponent() {
        return subComponent;
    }
    public void setSubComponent(final NonRequiredSubComponent subComponent) {
        this.subComponent = subComponent;
    }
}

这个依赖对组件的正常运行不是必需的:

@Test
void givenNonRequiredContextWhenCreatingMainComponentThenSubComponentIsNull() {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
      NonRequiredConfiguration.class);
    NonRequiredMainComponent bean = context.getBean(NonRequiredMainComponent.class);
    assertNull(bean.getSubComponent());
}

9. 使用@Nullable

此外,我们还可以使用@Nullable注解来标识我们期望bean可能为null。Spring和Jakarta的注解(如@Nullable)都可以用于此:

@Component
public class NullableMainComponent {
    private NullableSubComponent subComponent;
    public NullableMainComponent(final @Nullable NullableSubComponent subComponent) {
        this.subComponent = subComponent;
    }
    public NullableSubComponent getSubComponent() {
        return subComponent;
    }
    public void setSubComponent(final NullableSubComponent subComponent) {
        this.subComponent = subComponent;
    }
}

我们不需要将NullableSubComponent识别为Spring组件:

public class NullableSubComponent {}

Spring上下文将根据@Nullable注解将其设置为null

@Test
void givenContextWhenCreatingNullableMainComponentThenSubComponentIsNull() {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
      NullableJavaConfiguration.class);
    NullableMainComponent bean = context.getBean(NullableMainComponent.class);
    assertNull(bean.getSubComponent());
}

10. 总结

在Spring上下文中使用null并不常见,但在某些情况下是有道理的。然而,将bean设置为null的过程可能不太直观。

在这篇文章中,我们了解了如何以多种方式处理这个问题。

如往常一样,文章中的代码可在GitHub上找到。