领先一步
VMware 提供培训和认证,助您加速进步。
了解更多几个月前,我们在 www.springframework.org 上开始发布问卷调查,征求大家对 Spring、其功能以及使用方式的反馈。我提出的第一个问题是,大家是否会检查必需的依赖项,如果会,他们使用了什么机制。我很快就跟进了这个问题,询问社区他们使用何种事务管理策略。
令我高兴的是,当我第一次查看结果时(那是在三月份),在第一个问卷中,很多人通过投票告诉我们他们正在使用 @Required 注解。第二个关于事务管理的问卷调查很快就显示,很多人正在使用 @Transactional 注解。下面你可以看到关于检查必需依赖项的问卷调查结果。结合关于事务管理的问卷调查(约 30% 的受访者使用 @Transactional 注解来界定事务边界),这些结果一致表明大家正在大量使用 Spring 2.0,这对我们来说是个非常好的消息。由于将使用 Spring 1.x 的应用程序升级到 Spring 2.0 应该没有任何问题,我们真心希望大家不要固守 Spring 1.x,事实上,大家也大规模地进行了升级。
| 8% | 我在业务方法中检查它们。 |
| 9% | 使用 init-method 和断言机制(参见 Assert)。 |
| 9% | 在 XML 中使用 dependency-check 属性。 |
| 13% | 我无需检查,我使用构造函数注入。 |
| 15% | 使用 InitializingBean 和断言机制。 |
| 17% | 使用 Spring 2.0 的 @Required 注解。 |
| 29% | 我不检查必需的依赖项。 |
但有趣的是,有 29% 的人根本不检查必需的依赖项。在伴随讨论的论坛帖子中,出现了一些有趣的建议,解释了为什么有些人不做这件事以及其他人是如何解决的。让我们来回顾一下其中的一些。
换句话说,我们可以**强制**我们的类的用户(同样,这可能是 Spring,但也可能是一个直接实例化你的类的单元测试)在传递参数的同时实例化它。
public class Service {
public Collaborator collaborator;
// constructor with arguments, you *have* to
// satisfy the argument to instantiate this class
public Service(Collaborator collaborator) {
this.collaborator = collaborator;
}
}
在需要检查必需依赖项时,我们可以利用这一点。如果我们修改上面的代码示例以包含断言,那么我们就能 100% 确定该类在没有注入其协作对象的情况下永远不会被实例化。
public Service(Collaborator collaborator) {
if (collaborator == null) {
throw new IllegalArgumentException("Collaborator cannot be null");
}
this.collaborator = collaborator;
}
换句话说,如果我们使用构造函数注入并结合我上面展示的断言机制,那么我们就不需要一个专门的依赖项检查机制。
这是你在 Spring 框架本身中看到大量 Setter 注入的原因之一。Setter 注入在 Spring 本身中的使用,以及我们大力提倡它,也导致了许多第三方软件开始使用 Setter 注入,以及博客和文章开始提及 Setter 注入。
(顺便问一下,大家还记得第一代、第二代和 M 型控制反转吗? ;-))
出于这两个原因,我认为构造函数注入在应用程序代码中的可用性远高于在框架代码中。在应用程序代码中,你本身对需要配置的可选值的需求就比较少(你的应用程序代码不太可能在许多需要可配置属性的情况下被使用)。其次,应用程序代码比框架代码更少使用类继承。例如,应用程序中的特化不像框架代码中那样频繁发生——同样,应用程序代码的使用场景要少得多。
不使用构造函数注入的其他论点之一是构造函数中缺乏参数名,以及这些参数名不会出现在 XML 中。我认为在**大多数**应用程序中,这并没有太大影响。首先考虑使用 Setter 注入的变体:
<bean id="authenticator" class="com.mycompany.service.AuthenticatorImpl"/>
<bean id="accountService" class="com.mycompany.service.AccountService">
<property name="authenticator" ref="authenticator"/>
</bean>
这个版本提到了 authenticator 作为属性名和 bean 名。这是我经常遇到的模式。我认为,在使用构造函数注入时,缺乏构造函数参数名(以及它们不在 XML 中显示)并不会真正让我们感到困惑。
<bean id="authenticator" class="com.mycompany.service.AuthenticatorImpl"/>
<bean id="accountService" class="com.mycompany.service.AccountService">
<constructor-arg ref="authenticator"/>
</bean>
public class Service {
private Collaborator collaborator;
@Required
public void setCollaborator(Collaborator c) {
this.collaborator = c;
}
}
<bean class="org.sfw.beans.factory.annotation.RequiredAnnotationBeanFactoryPostProcessor"/>
public class Service implements InitializingBean {
private Collaborator collaborator;
public void setCollaborator(Collaborator c) {
this.collaborator = c;
}
// from the InitializingBean interface
public void afterPropertiesSet() {
if (collaborator == null) {
throw new IllegalStateException("Collaborator must be set in order for service to work");
}
}
}
另一种机制,类似于 Java 中的 @Required,是在 XML 中的 dependency-check 属性,奇怪的是,它的使用率并不高。通过调整此属性(默认关闭)启用依赖项检查,将告诉 Spring 开始检查 bean 上的某些依赖项。有关此功能的更多信息,请参阅参考文档。
在某些情况下,我不会使用构造函数注入。例如,一个具有**大量**依赖项或其他可配置值的类。我个人不认为一个带有 20 个参数的构造函数是一个好代码的例子。当然,问题在于,一个具有 20 个依赖项的类是否不承担太多的职责……
有一件事是肯定的——通过在业务方法中检查必需依赖项来强制它们,我肯定不会这样做。