领先一步
VMware 提供培训和认证,助您加速进步。
了解更多几个月前,在我还没有博客的日子里,Cedric 和 Bob 曾讨论过“Getter 注入”。
基本概念是,IoC 容器可以在部署时覆盖托管对象上的抽象或具体方法。容器正在注入一个方法,例如 getter 方法,而不是像 Setter 注入中那样注入引用或原始类型。碰巧的是,我当时已经在为 Spring 1.1 开发容器方法覆盖机制,该机制现已在 Spring 1.1 RC1 中发布。这是一个有趣的概念,并且绝对是完整 IoC 容器的一部分。但是,我认为这个概念更普遍,需要一个更通用的名称。此外,它应该只在相当窄的场景中使用。
你为什么要这样做?Cedric 的动机是 setter 方法“无用”,并且“在一个 Java 对象中拥有你永远不会调用的方法是一种设计异味”。在他看来,对象中最重要的实际上是 getter 方法,它们通常返回保存在 setter 中的对象引用。因此,他建议让容器实现 getter 方法,并取消 setter。实际上,这意味着容器将实际覆盖作为应用程序代码一部分定义的 getter 方法,否则无法使用它们。因此,容器最终将使用类似于 CMP 2.x 的机制来实现它(尽管希望任何相似之处到此为止)。
我并不真正认同“无用方法”的论点,因为 setter 将被使用依赖注入的 IoC 容器调用,并且 将在没有任何容器的单元测试中被调用。如果对象在容器外部使用,它们将被应用程序代码调用。此外,getter/setter 组合是一种很好的建立默认值的方式,以防你选择不配置一个或多个 setter 被调用:如果你需要它,setter 就在那里。虽然我能理解 Cedric 的动机,但这里有一个权衡:如果我们摆脱了所谓的无用 setter,我们就会剩下不完整的类。如果 getter 是抽象的,我们就会回到需要测试抽象对象的 CMP 2.x 测试场景。如果 getter 是具体的,我们就会例行地编写将在运行时被覆盖的方法。在我看来,这才是真正的无用代码。(一般来说,我不喜欢覆盖具体方法,并尽可能避免它。我想我第一次读到这个建议是在《UML 参考手册》中,它很有道理。)“setter 注入”中也包含一些魔法元素。如果我能有一个简单的 POJO,没有花哨的容器子类化,我更喜欢它。正如 Cedric 自己在去年 5 月 TSSS 的一次小组讨论中说得非常好,“只有当科学失败时才使用魔法。”
我认为这个概念应该改名为 方法注入,并且它对某些其他——不那么常见——的场景具有更大的价值。
我不会在典型的使用依赖注入配置对象时将其用作 Setter 或构造函数注入的替代方案。Setter 方法和构造函数是普通的 Java 构造,在容器中工作得很好,但并不依赖于容器。这很好。IoC 容器提供的魔法方法会增加对容器的依赖,尽管当然仍然可以在容器外部对对象进行子类化,并且它们仍然只是 Java。
本质上,我将方法注入视为在某些极端情况下对子类化的一种替代方案,在这种情况下,超类应与容器依赖项隔离,并且容器比常规子类更容易实现必要的行为。所讨论的方法不需要是 getter 方法(如 Setter 注入的 getter),尽管通常它会是返回某些东西的方法。
我看到了容器实现方法的三个主要用例
它们可以将容器依赖项从应用程序代码中移除。它们可以依赖于部署之前未知的基础设施。它们可以为运行时环境定制遗留代码的行为。然而,普通的旧子类化在这里也很有意义。容器子类化也比常规子类化更具动态性。我们可以潜在地采用一个基类并以不同的方式部署它,而无需管理多个类的源代码。然而,由于它的魔法系数高于常规子类化、策略接口或各种替代方案,我觉得方法注入不应该过于急切地使用。
对我来说,方法注入的主要吸引力在于它是一种摆脱我有时在使用 Spring 1.0 时不得不产生的容器依赖的方式,并且它将适用于任何支持“非单例”或“原型”对象概念的容器。(也就是说,一个容器,它根据配置,让你可以选择在请求时获取 IoC 管理对象的共享实例或新实例。)我喜欢使用 Spring,但我讨厌为了配置而导入 Spring API。
导致我实现此功能的特定用例是,当通过 Spring 配置的一个“单例”对象需要创建非单例对象的实例时——例如,一个单线程、一次性处理对象——但又希望该对象通过依赖注入配置,而不是仅仅使用 new。例如,假设 ThreadSafeService 需要创建 SingleShotHelper 的实例,而 SingleShotHelper 本身是通过依赖注入配置的。在 Spring 1.0.x 中,ThreadSafeService 必须实现 BeanFactoryAware 生命周期接口,保存 BeanFactory 引用并调用
(SingleShotHelper) beanFactory.getBean("singleShotHelper")
每次需要创建一个助手时。这工作正常,测试起来也不难(BeanFactory 是一个简单的接口,所以很容易模拟),但它是一个 Spring 依赖,如果能更接近一个完全非侵入性的框架,那就太理想了。类型转换也有些不雅,但无伤大雅。
我通常在 10 个类中会遇到一个这样的情况。我有时会重构它以提取一个方法,像这样
protected SingleShotHelper createSingleShotHelper() { return (SingleShotHelper) context.getBean("singleShotHelper"); } 我现在可以子类化来实现这一点,并将 Spring 依赖项保留在超类之外,但这似乎有点过度。
这种方法是容器而非应用程序开发人员实现的理想选择。它返回一个容器知道的对象;整个事情实际上可以通过配置比代码更简洁地表达(当你考虑到保存 BeanFactory 引用所需的一点点代码时)。
随着 Spring 1.1 中引入的新方法注入功能,可以使用抽象(或具体)方法,例如
protected abstract SingleShotHelper createSingleShotHelper();
并告诉容器在部署时覆盖该方法以返回来自相同或父工厂的特定 bean,如下所示
<lookup-method name="createSingleShotHelper" bean="singleShotHelper" >
方法可以是受保护的或公共的。任意数量的方法都可以被覆盖。<lookup-method> 元素可以像 property 或 constructor-arg 元素一样在 bean 定义元素中使用。
我认为方法注入最引人注目的用例是返回查找容器管理的命名对象的结果。(当然,这并非 Spring 特有:任何容器都可以实现这一点。)查找通常是针对非单例 bean(用 Spring 术语来说)。
这样,应用程序代码中就没有对 Spring 或任何其他 IoC 容器的依赖。一个极端情况在无需导入 Spring API 的情况下解决了。正如我所说,这个功能直接受到我正在处理的客户端项目需求的驱动,并已在实践中证明有用。
查找方法可以与 Setter 注入或构造函数注入结合使用。它们不带参数,因此方法重载不是问题。
实现使用 CGLIB 对类进行子类化。(它仅在 CGLIB 在类路径上时可用,以避免使 Spring 核心容器依赖于 CGLIB。)
Spring 更进一步,允许您为被覆盖的方法定义任意行为——而不仅仅是 bean 查找。例如,您可能希望这样做,以便基于运行时基础设施使用通用行为——例如使用 Spring TransactionInterceptor 类进行事务回滚。(当然,通常应该使用回滚规则来避免这种情况。)或者可能存在通用覆盖行为的引人注目的用例——例如“如果存在活动事务,则返回事务性数据源 DS1,否则返回非事务性数据源 DS2”。同样,如果我们可以将这种逻辑从应用程序代码中隐藏起来,那将是一个胜利。在这里,我们超出了纯粹“getter”的范围:例如,我们可以覆盖方法来发布事件。
通常有任意容器覆盖的替代方案,例如通过子类化类并以正常方式覆盖方法(科学,而不是魔法),或者使用 AOP。在像示例中那样的 bean 查找情况下,容器进行覆盖有明显的好处,因为它消除了对 Spring API 的依赖。用 XML 描述也简单得多。对于更一般的情况,需要有一种解决重载方法的方法。
这已经比我计划的要长了——而且花了一些时间!——所以如果有人感兴趣的话,我将在以后的帖子中讨论 Spring 1.1 的任意覆盖机制(包括它如何解决重载方法)。我对所有那些不知疲倦的博主,比如 Dion 和 Matt Raible,他们似乎每天发 3 次博客,又有了新的敬佩。