为何 Spring 的 ApplicationContext.getBean 被认为不佳?

为何 Spring 的 ApplicationContext.getBean 被认为不佳?

技术背景

在 Spring 框架中,ApplicationContext.getBean() 是用于从 Spring 应用上下文中获取 Bean 的方法。然而,许多开发者建议尽量避免使用该方法。Spring 的核心特性之一是依赖注入(Dependency Injection,DI),也称为控制反转(Inversion of Control,IoC),其目的是让类无需关心如何获取依赖对象,从而提高代码的可维护性、可测试性和可扩展性。

避免使用 ApplicationContext.getBean() 的原因

违背控制反转原则

控制反转的理念是让类不了解也不关心如何获取其依赖的对象。而调用 ApplicationContext.getBean() 并非控制反转,因为类直接依赖于 Spring 来提供依赖,无法通过其他方式获取。例如:

1
MyClass myClass = applicationContext.getBean("myClass");

在这种情况下,类与 Spring 框架紧密耦合,无法在测试时轻松提供模拟实现。

降低代码可测试性

使用 ApplicationContext.getBean() 会使测试变得复杂。为了测试使用该方法的类,需要模拟 ApplicationContext,这是一个庞大的接口,增加了测试的难度。以下是一个示例:

1
2
3
4
5
6
7
8
9
@Component
class MyBean {
@Autowired
MyBean(ApplicationContext context) {
HttpLoader loader = context.getBean(HttpLoader.class);
StringOutput out = context.getBean(StringOutput.class);
out.print(loader.load("http://stackoverflow.com"));
}
}

测试这个类时,需要使用 Mockito 等框架来模拟 ApplicationContext

1
2
3
4
5
6
7
8
9
10
11
12
class MyBeanTest {
public void creatingMyBean_writesStackoverflowPageToOutput() {
String stackOverflowHtml = "dummy";
StringBuilder result = new StringBuilder();
ApplicationContext context = Mockito.mock(ApplicationContext.class);
Mockito.when(context.getBean(HttpLoader.class))
.thenReturn(Collections.singletonMap("https://stackoverflow.com", stackOverflowHtml)::get);
Mockito.when(context.getBean(StringOutput.class)).thenReturn(result::append);
new MyBean(context);
assertEquals(result.toString(), stackOverflowHtml);
}
}

相比之下,使用依赖注入的方式进行测试会更简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
interface HttpLoader {
String load(String url);
}
interface StringOutput {
void print(String txt);
}
@Component
class MyBean {
@Autowired
MyBean(HttpLoader loader, StringOutput out) {
out.print(loader.load("http://stackoverflow.com"));
}
}
class MyBeanTest {
public void creatingMyBean_writesStackoverflowPageToOutput() {
String stackOverflowHtml = "dummy";
StringBuilder result = new StringBuilder();
new MyBean(Collections.singletonMap("https://stackoverflow.com", stackOverflowHtml)::get, result::append);
assertEquals(result.toString(), stackOverflowHtml);
}
}

增加代码复杂度

使用 ApplicationContext.getBean() 会使代码变得复杂,因为需要显式地从应用上下文中获取 Bean。而且,如果 Bean 的名称发生变化,需要在多个地方修改代码。

不利于代码维护

对于维护人员来说,理解使用 ApplicationContext.getBean() 的代码会更加困难。他们需要了解 Spring 配置和所有相关的位置,才能弄清楚对象是如何连接的。

替代方案

依赖注入

通过构造函数或 setter 方法进行依赖注入是更好的选择。例如:

1
2
3
4
5
6
7
public class MyOtherClass {
private MyClass myClass;

public void setMyClass(MyClass myClass) {
this.myClass = myClass;
}
}

在 Spring 配置文件中进行配置:

1
2
3
4
<bean id="myClass" class="MyClass">...</bean>
<bean id="myOtherClass" class="MyOtherClass">
<property name="myClass" ref="myClass"/>
</bean>

Spring 会自动将 myClass 注入到 myOtherClass 中。

使用注解

使用 @Autowired 注解可以更方便地实现依赖注入:

1
2
3
4
5
@Component
public class MyOtherClass {
@Autowired
private MyClass myClass;
}

最佳实践

  • 尽量使用依赖注入来管理对象之间的依赖关系,避免直接使用 ApplicationContext.getBean()
  • 只有在必要时,如在应用启动时获取根 Bean,才使用 ApplicationContext.getBean()
  • 在测试时,优先选择使用依赖注入的方式,这样可以更轻松地提供模拟对象。

常见问题

是否绝对不能使用 ApplicationContext.getBean()

不是的。在某些情况下,如应用启动时获取根 Bean,或者在需要根据用户交互动态创建 Bean 实例时,可以使用 ApplicationContext.getBean()。但应该尽量减少其使用频率。

使用 @AutowiredApplicationContext.getBean() 有什么区别?

@Autowired 是 Spring 提供的依赖注入注解,通过它可以让 Spring 自动将依赖的 Bean 注入到类中,代码与 Spring 的耦合度相对较低。而 ApplicationContext.getBean() 是直接从应用上下文中获取 Bean,代码与 Spring 框架紧密耦合。

如何在不使用 ApplicationContext.getBean() 的情况下测试 Spring 管理的类?

可以使用 Spring 的测试框架,如 @SpringBootTest,结合 @MockBean 注解来创建模拟对象,进行单元测试或集成测试。也可以使用依赖注入的方式,在测试时手动提供模拟对象。


为何 Spring 的 ApplicationContext.getBean 被认为不佳?
https://119291.xyz/posts/2025-04-28.why-spring-applicationcontext-getbean-is-considered-bad/
作者
ww
发布于
2025年4月28日
许可协议