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表达式中使用属性占位符,以便正确读取值。因此,我们将初始化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
供应商
不可能直接从带有@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上找到。