把 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