抢占先机
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 Framework 本身中看到大量 setter 注入的原因之一。Spring 本身使用了 setter 注入的事实,以及我们主要倡导它,也导致许多第三方软件开始使用 setter 注入,以及博客和文章开始提及 setter 注入。
(顺便问一下,大家还记得控制反转的 1 类、2 类和 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 个依赖项的类是否承担了过多的职责...
有一件事是肯定的——在业务方法中检查必需的依赖项来强制执行,这是我绝对不会做的事情。