java atd_java - 为什么Spring的ApplicationContext.getBean被认为是坏的?

java - 为什么Spring的ApplicationContext.getBean被认为是坏的?

我问了一个普通的Spring问题:自动转换Spring Beans并且有多个人回应,应该尽可能避免调用Spring的ApplicationContext。 这是为什么?

我还应该如何获得我配置Spring创建的bean的权限?

我在一个非Web应用程序中使用Spring,并计划访问LiorH所描述的共享ApplicationContext对象。

修订

我接受下面的答案,但这是Martin Fowler的另一个选择,他讨论了依赖注入与使用服务定位器的优点(这与调用包装的ApplicationContext.getBean()基本相同)。

在某种程度上,Fowler声称,“使用服务定位器,应用程序类通过消息向定位器显式请求它[服务]。注入时没有明确的请求,服务出现在应用程序类中 - 因此控制反转。控制反转是框架的一个共同特征,但它是有代价的。 当您尝试调试时,它往往很难理解并导致问题。 所以总的来说,我宁愿避免它[控制反转],除非我需要它。 这并不是说这是一件坏事,只是因为我认为它需要通过更简单的替代方案来证明自己的合理性。“

Vinnie asked 2019-02-26T20:06:28Z

13个解决方案

181 votes

我在对另一个问题的评论中提到了这一点,但是控制反转的整个想法是让你的班级都不知道或关心他们如何获得他们所依赖的对象。 这样可以轻松地随时更改您使用的给定依赖项的实现类型。 它还使类易于测试,因为您可以提供依赖项的模拟实现。 最后,它使课程更简单,更专注于他们的核心职责。

致电MyApplication不是反转控制! 虽然更改为给定bean名称配置的实现仍然很容易,但是现在该类直接依赖于Spring来提供该依赖性,并且无法以任何其他方式获取它。 您不能只在测试类中创建自己的模拟实现,并自己将其传递给它。 这基本上违背了Spring作为依赖注入容器的目的。

到处都想说:

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

例如,您应该声明一个方法:

public void setMyClass(MyClass myClass) {

this.myClass = myClass;

}

然后在你的配置中:

...

然后Spring会自动将MyApplication注入main。

以这种方式声明一切,并在它的根部都有类似的东西:

MyApplication是最集中的类,至少间接取决于程序中的其他所有服务。 引导时,在您的main方法中,您可以拨打applicationContext.getBean("myApplication"),但您不需要在其他任何地方拨打getBean()!

ColinD answered 2019-02-26T20:07:27Z

57 votes

更喜欢服务定位器而不是控制反转(IoC)的原因是:

服务定位器让其他人更容易在您的代码中跟踪。 IoC是“神奇的”,但维护程序员必须了解您的复杂Spring配置和所有无数的位置,以弄清楚如何连接对象。

IoC很难调试配置问题。 在某些类别的应用程序中,如果配置错误,应用程序将无法启动,您可能无法逐步完成调试器的操作。

IoC主要基于XML(Annotations改进了一些东西,但仍有很多XML)。 这意味着开发人员无法处理您的程序,除非他们知道Spring定义的所有魔术标记。 再也不懂Java了。 这阻碍了较少经验的程序员(即,当更简单的解决方案,例如服务定位器,将满足相同的要求时,使用更复杂的解决方案实际上是糟糕的设计)。 此外,对诊断XML问题的支持远远弱于对Java问题的支持。

依赖注入更适合更大的程序。 大多数情况下,额外的复杂性是不值得的。

通常使用Spring以防“以后可能想要更改实现”。 如果没有Spring IoC的复杂性,还有其他方法可以实现这一目标。

对于Web应用程序(Java EE WAR),Spring上下文在编译时实际上是绑定的(除非您希望运算符在爆炸的战争中围绕上下文进行grub)。 您可以使Spring使用属性文件,但是使用servlet属性文件需要位于预定位置,这意味着您无法在同一个盒子上同时部署多个servlet。 您可以使用Spring和JNDI在servlet启动时更改属性,但如果您使用JNDI作为管理员可修改的参数,则Spring本身的需求会减少(因为JNDI实际上是一个服务定位器)。

使用Spring,如果Spring调度到您的方法,您可能会丢失程序控制。 这很方便,适用于许多类型的应用程序,但不是全部。 当您需要在初始化期间创建任务(线程等)时,您可能需要控制程序流,或者需要Spring可能无法知道的内容何时绑定到WAR的可修改资源。

Spring非常适合事务管理并具有一些优势。 只是IoC在许多情况下可能过度工程化并为维护者带来无根据的复杂性。 不考虑先不使用IoC的方法,不要自动使用IoC。

Moa answered 2019-02-26T20:08:53Z

25 votes

确实,在application-context.xml中包含类可以避免使用getBean。 然而,即使这实际上也是不必要的。 如果您正在编写独立应用程序并且不想在application-context.xml中包含驱动程序类,则可以使用以下代码让Spring自动装配驱动程序的依赖项:

public class AutowireThisDriver {

private MySpringBean mySpringBean;

public static void main(String[] args) {

AutowireThisDriver atd = new AutowireThisDriver(); //get instance

ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(

"/WEB-INF/applicationContext.xml"); //get Spring context

//the magic: auto-wire the instance with all its dependencies:

ctx.getAutowireCapableBeanFactory().autowireBeanProperties(atd,

AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, true);

// code that uses mySpringBean ...

mySpringBean.doStuff() // no need to instantiate - thanks to Spring

}

public void setMySpringBean(MySpringBean bean) {

this.mySpringBean = bean;

}

}

当我有一些需要使用我的应用程序的某些方面(例如用于测试)的独立类时,我需要这样做几次,但我不想将它包含在应用程序上下文中,因为它不是 实际上是应用的一部分。 还要注意,这避免了使用String名称查找bean的需要,我一直认为这是丑陋的。

iftheshoefritz answered 2019-02-26T20:09:27Z

19 votes

使用像Spring这样的东西最酷的好处之一就是你不必将对象连接在一起。 Zeus的头部分开,你的类出现了,完全形成了所有的依赖关系,根据需要创建和接线。 这是神奇而奇妙的。

你越说ClassINeed classINeed = (ClassINeed)ApplicationContext.getBean("classINeed");,你得到的魔法越少。 更少的代码几乎总是更好。 如果你的班级真的需要一个ClassINeed bean,为什么不把它连接进来?

也就是说,显然需要创建第一个对象。 你的main方法通过getBean()获取一两个bean没有任何问题,但你应该避免它,因为每当你使用它时,你并没有真正使用Spring的所有魔法。

Brandon Yarbrough answered 2019-02-26T20:10:07Z

16 votes

动机是编写不依赖于Spring的代码。 这样,如果您选择切换容器,则不必重写任何代码。

把容器想象成你的代码看不见的东西,神奇地满足它的需要,而不被问到。

依赖注入是“服务定位器”模式的对应点。 如果您要按名称查找依赖项,您可能还要删除DI容器并使用类似JNDI的东西。

erickson answered 2019-02-26T20:10:48Z

10 votes

使用@Autowired或ApplicationContext.getBean()实在是一回事。 在这两种方式中,您都可以获得在上下文中配置的bean,并且在两种方式中,您的代码都依赖于spring。您唯一应该避免的是实例化ApplicationContext。 这只做一次! 换句话说,就像一条线

ApplicationContext context = new ClassPathXmlApplicationContext("AppContext.xml");

应该只在您的应用程序中使用一次。

easyplanner.cu.cc answered 2019-02-26T20:11:26Z

4 votes

这个想法是你依赖依赖注入(控制反转或IoC)。 也就是说,您的组件配置了他们需要的组件。 注入这些依赖项(通过构造函数或setter) - 你自己不会得到它们。

ApplicationContext.getBean()要求您在组件中明确命名bean。 相反,通过使用IoC,您的配置可以确定将使用哪个组件。

这允许您轻松地使用不同的组件实现重新连接应用程序,或者通过提供模拟变体(例如模拟DAO以便在测试期间不访问数据库)以直接方式配置对象以进行测试

Brian Agnew answered 2019-02-26T20:12:06Z

4 votes

其他人指出了一般问题(并且是有效的答案),但我只是提供一个额外的评论:不是你不应该这样做,而是尽可能少地做。

通常这意味着它只完成一次:在引导过程中。 然后它只是访问“root”bean,通过它可以解析其他依赖项。 这可以是可重用的代码,如基本servlet(如果开发Web应用程序)。

StaxMan answered 2019-02-26T20:12:41Z

3 votes

我只发现了两种需要getBean()的情况:

其他人提到在main()中使用getBean()来获取独立程序的“main”bean。

我对getBean()的另一个用途是在交互式用户配置确定特定情况的bean组成的情况下。 因此,例如,部分引导系统使用带有scope ='prototype'bean定义的getBean()循环遍历数据库表,然后设置其他属性。 据推测,有一个UI可以调整数据库表,这比尝试(重新)编写应用程序上下文XML更友好。

nsayer answered 2019-02-26T20:13:22Z

3 votes

Spring之一是避免耦合。 定义并使用Interfaces,DI,AOP并避免使用ApplicationContext.getBean():-)

sourcerebels answered 2019-02-26T20:13:48Z

3 votes

还有一次使用getBean是有意义的。 如果您正在重新配置已存在的系统,那么在Spring上下文文件中未明确调用依赖项。 您可以通过调用getBean来启动该过程,这样您就不必立即将其连接起来。 通过这种方式,您可以慢慢构建弹簧配置,将每个部件随着时间的推移放置到位,并使钻头正确排列。 对getBean的调用最终将被替换,但是当您了解代码的结构或缺少代码的结构时,您可以开始连接越来越多的bean并使用越来越少的getBean调用的过程。

Tony Giaccone answered 2019-02-26T20:14:16Z

3 votes

其中一个原因是可测试性。 说你有这个班:

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"));

}

}

你怎么测试这个bean? 例如。 像这样:

class MyBeanTest {

public void creatingMyBean_writesStackoverflowPageToOutput() {

// setup

String stackOverflowHtml = "dummy";

StringBuilder result = new StringBuilder();

// execution

new MyBean(Collections.singletonMap("https://stackoverflow.com", stackOverflowHtml)::get, result::append);

// evaluation

assertEquals(result.toString(), stackOverflowHtml);

}

}

容易,对吗?

虽然你仍然依赖Spring(由于注释),你可以删除你对spring的依赖而不改变任何代码(只有注释定义),并且测试开发人员不需要知道关于spring如何工作的任何事情(也许他应该反正,但是 它允许分别从弹簧的位置检查和测试代码。

使用ApplicationContext时仍然可以这样做。 然而,你需要模拟ApplicationContext这是一个巨大的界面。 您需要一个虚拟实现,或者您可以使用模拟框架,如Mockito:

@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"));

}

}

class MyBeanTest {

public void creatingMyBean_writesStackoverflowPageToOutput() {

// setup

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);

// execution

new MyBean(context);

// evaluation

assertEquals(result.toString(), stackOverflowHtml);

}

}

这是一种可能性,但我认为大多数人会同意第一种选择更优雅,使测试更简单。

真正存在问题的唯一选择是:

@Component

class MyBean {

@Autowired

MyBean(StringOutput out) {

out.print(new HttpLoader().load("http://stackoverflow.com"));

}

}

测试这需要付出巨大努力,否则您的bean将尝试在每次测试时连接到stackoverflow。 一旦您遇到网络故障(或者由于访问率过高而导致stackoverflow上的管理员阻止您),您将会有随机失败的测试。

因此,作为结论,我不会说直接使用ApplicationContext是自动错误的,应该不惜一切代价避免。 但是,如果有更好的选择(并且在大多数情况下),那么使用更好的选项。

yankee answered 2019-02-26T20:15:37Z

2 votes

但是,仍然存在需要服务定位器模式的情况。例如,我有一个控制器bean,这个控制器可能有一些默认的服务bean,可以通过配置注入依赖。此控制器现在或以后可以调用许多额外的或新的服务,然后需要服务定位器来检索服务bean。

lwpro2 answered 2019-02-26T20:16:04Z

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值