把 Spring Bean 设置为 Null

1、概览

本文将带你了解如何把 Spring Context 中的 Bean 设置为 null。在某些情况下,这可能很有用。例如,在测试时不想提供 Mock 对象。以及,在使用一些可选功能时,可能希望避免创建实现,并直接传递 null。

2、组件设置

有几种方法可以将 Bean 设置为 null,具体取决于 Context 的配置方式,本文主要考虑 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;
    }
}

本例将演示如何在 Spring Context 中将 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、在 XML 配置中使用 SpEL

可以在 XML 配置中使用 SpEL 实现类似的结果。与之前的配置会有一些不同之处:

<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. 在 XML 配置中使用 SpEL 和 Properties

改进前一种解决方案的方法之一是在 property 文件中存储 Bean 名称。这样,就可以在需要时传递一个 null 值,而无需更改配置:

nullableBean = null

XML 配置使用 PropertyPlaceholderConfigurer 来读取 properties:

<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 表达式中使用属性占位符,以便正确读取值。如上,把 SubComponent 初始化为 null

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

使用 @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;
}

在这种情况下,Context 会抛出 UnsatisfiedDependencyException 异常:

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

7、使用 Optional

当使用 Optional 时,Spring 会自动识别到该 Bean 可能不存在于 Context 中,并在没有任何额外配置的情况下传递 null

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

如果 Spring 无法在 Context 中找到 SubComponent,它将传递一个空(empty)的 Optional

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

8、使用 @Autowired(required = false)

另一种使用 null 作为 Bean 值的方法是声明它为非必填值。不过,这种方法只适用于非构造函数注入:

@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 的注解都适用于此:

@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 Context 将根据 @Nullable 注解将其设置为 null:

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

10、总结

在 Spring Context 中使用 null 并不是最常见的做法,但有时可能是合理的。不过,将 Bean 设置为 null 的过程可能不太直观。


Ref:https://www.baeldung.com/spring-setting-bean-null