1. 概述
本教程將教你如何在 Spring 上下文中將 Bean 設置為 null。 這在某些情況下可能很有用,例如在測試中,我們不想提供 Mock 對象。 此外,在使用一些可選功能時,我們可能希望避免創建實現並傳遞 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;
}
}
我們演示如何將 SubComponent 設置為 null 在 Spring 上下文中:
@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 = nullXML 配置將使用 > 來讀取屬性:
<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 = subComponent6. Java 配置中的空供應商 (Null Supplier)
無法直接從帶有 @Bean</em/> 註解的類的方法中返回 null</em/>。因此,我們需要以某種方式將其包裝起來。我們可以使用 Supplier</em/> 來實現:
@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. 使用 可選
當使用 可選 時,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. 非必需的自動注入
另一種使用 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 上下文會將其設置為 null,基於@Nullable註解:
@Test
void givenContextWhenCreatingNullableMainComponentThenSubComponentIsNull() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
NullableJavaConfiguration.class);
NullableMainComponent bean = context.getBean(NullableMainComponent.class);
assertNull(bean.getSubComponent());
}10. 結論
在 Spring 上下文中,使用 nulls 並非最常見的做法,但在某些情況下,這可能是合理的。然而,將 Bean 設置為 null 的過程可能並不直觀。
在本文中,我們學習了通過多種方式解決此問題。