保持领先
VMware 提供培训和认证,助你加速发展。
了解更多Spring 的依赖注入(DI)机制允许配置在应用上下文中定义的 bean。如果你想将相同的想法扩展到非 bean 对象呢?Spring 对领域对象 DI 的支持利用 AspectJ 织入技术将 DI 扩展到任何对象,即使它是由 Web 或 ORM 框架创建的。这使得创建具有丰富领域行为的对象成为可能,因为领域对象现在可以与注入的对象协作。在这篇博客中,我将讨论 Spring Framework 在这一领域的最新改进。
领域对象 DI 背后的核心思想非常简单:通过 AspectJ 织入的切面选择与符合特定规范的任何对象的创建或反序列化对应的连接点。这些连接点的通知将依赖项注入到正在创建或反序列化的对象中。当然,细节决定成败。例如,如何选择与反序列化对应的连接点?或者如何确保每个对象只注入一次依赖项?通过提供一些预先编写好的切面,Spring 使开发人员免受这些细节的影响。
目前,大多数 Spring 用户使用 @Configurable 注解来指定可配置的类。随着即将发布的 Spring 2.5.2 中的最新改进(自每夜构建 379 开始可用),你有了更多选项,使此功能更加强大。新改进遵循“让简单的事情更简单,让复杂的事情成为可能”的原则。根据你对 AspectJ 的熟悉程度和预期的设计复杂性,其中一种选项会非常适用。图 1 显示了新的切面层次结构,它使得简单性与灵活性的结合成为可能。
图 1:领域对象依赖注入切面的继承层次结构。
那么,这些切面分别提供什么?我们从下往上看。
@Configurable
public class Order {
private transient MailSender mailSender;
public void setMailSender(MailSender mailSender) {
this.mailSender = mailSender;
}
public void process() {
...
mailSender.send(...);
...
}
}
接下来,你指示 Spring 如何配置 Order 类型的对象。配置说明遵循原型 bean 的标准 bean 定义,如下所示
<context:spring-configured/>
<bean class="example.Order" scope="prototype">
<property name="mailSender" ref="externalMailSender"/>
</bean>
<bean id="externalMailSender" ...>
...
</bean>
现在,对于任何 Order 对象的创建或反序列化,Spring 都将使用 externalMailSender bean 设置创建对象的 mailSender 属性。
Spring 2.5 引入了一种新的基于注解的配置选项,可以消除或减少伴随的 XML 配置。基于 @Configurable 注解的 DI 也因此受益。例如,你可以如下所示将 mailSender 属性标记为 @Autowired
@Configurable
public class Order {
private transient MailSender mailSender;
@Autowired
public void setMailSender(MailSender mailSender) {
this.mailSender = mailSender;
}
public void process() {
...
mailSender.send(...);
...
}
}
你甚至可以通过直接标注字段本身来去掉 setter 方法,将上面的代码简化为
@Configurable
public class Order {
@Autowired private transient MailSender mailSender;
public void process() {
...
mailSender.send(...);
...
}
}
无论哪种情况,伴随的 XML 配置都可以简化为如下所示(注意使用了 <context:annotation-config/>)
<context:spring-configured/>
<context:annotation-config/>
<bean id="externalMailSender" ...>
...
</bean>
有关此领域对象 DI 选项的更多详细信息,请参阅使用 AspectJ 为 Spring 领域对象注入依赖项。
在设计层面,此切面配置任何类型实现了 ConfigurableObject 接口的领域对象。虽然让类型直接实现 ConfigurableObject 接口无疑是有效的选择,但一个更优雅的替代方案是在另一个切面(AbstractInterfaceDrivenDependencyInjectionAspect 的子切面将是合乎逻辑的选择)中使用 declare parents 语句。该语句将声明一个可配置的类实现了 ConfigurableObject 接口。这使得你的领域类不受 Spring 特定构件的影响,同时又能从 DI 机制中受益。让我们来看一个这种用法的例子。
考虑前面章节中的 Order 类。与其使用 @Configurable,不如让它实现一个领域特定的 MailSenderClient 接口,这表明它使用了 MailSender。
public class Order implements MailSenderClient {
private transient MailSender mailSender;
public void setMailSender(MailSender mailSender) {
this.mailSender = mailSender;
}
public void process() {
...
mailSender.send(...);
...
}
}
接下来,你编写 AbstractInterfaceDrivenDependencyInjectionAspect 的一个子切面,将依赖项注入到任何 MailSenderClient 对象中。
public aspect MailClientDependencyInjectionAspect extends
AbstractInterfaceDrivenDependencyInjectionAspect {
private MailSender mailSender;
declare parents: MailSenderClient implements ConfigurableObject;
public pointcut inConfigurableBean() : within(MailSenderClient+);
public void configureBean(Object bean) {
((MailSenderClient)bean).setMailSender(this.mailSender);
}
public void setMailSender(MailSender mailSender) {
this.mailSender = mailSender;
}
}
切面中使用了两个 AspectJ 构造
The configureBean() 方法通过直接调用适当的 setter 方法来执行向 bean 的注入。当然,任何其他适合 bean 配置的逻辑,例如调用多参数方法或调用任何初始化方法,也都完全可行。请注意,以这种方式使用的直接调用避免了反射,并且如果领域对象创建速率很高,可以带来显著的性能提升。
你需要配置 MailClientDependencyInjectionAspect 切面实例本身,以注入其依赖项——mailSender 属性。Spring 的做法是为该切面创建一个 bean 并在应用上下文中配置它
<bean class="example.MailClientDependencyInjectionAspect"
factory-method="aspectOf">
<property name="mailSender" ref="externalMailSender"/>
</bean>
<bean id="externalMailSender" ...>
...
</bean>
围绕这个切面还有一些额外的模式
然而,这些想法就留待另一篇博客文章来讨论吧。
该切面声明了子切面可以定义的六个切入点
此切面还定义了一个抽象方法 configureBean(Object bean),其实现应指定与依赖注入对应的逻辑。
所以,你现在拥有了在应用中启用领域对象 DI 的所有选项。如果你正在进行 DDD 或需要将 DI 扩展到你的领域对象,你应该关注这组新的切面。根据你的具体需求和 AspectJ 知识,你会发现其中一种有助于创建一个优雅的解决方案。