领先一步
VMware 提供培训和认证来加速您的进步。
了解更多Spring 的依赖注入 (DI) 机制允许配置在应用程序上下文中定义的 Bean。如果您想将相同的理念扩展到非 Bean,该怎么办?Spring 对领域对象 DI 的支持利用 AspectJ 编织将 DI 扩展到任何对象,即使它是通过 Web 或 ORM 框架创建的。这使得能够创建行为丰富的领域对象,因为领域对象现在可以与注入的对象协作。在本篇博文中,我将讨论 Spring 框架在该领域的最新改进。
领域对象 DI 背后的核心思想非常简单:一个 AspectJ 编织的方面选择与匹配特定规范的任何对象的创建或反序列化相对应的连接点。对这些连接点的建议将依赖项注入到正在创建或反序列化的对象中。当然,魔鬼在于细节。例如,您如何选择与反序列化相对应的连接点,或者您如何仅对每个对象注入一次依赖项?通过提供一些预先编写的方面,Spring 使开发人员免于所有这些细节。
目前,大多数 Spring 用户使用@Configurable注释来指定可配置的类。随着即将推出的 Spring 2.5.2 的最新改进,从 nightly build 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接口当然是一个有效的选择,但一种优雅的替代方案是在另一个方面使用declare parents语句(AbstractInterfaceDrivenDependencyInjectionAspect 的子方面将是一个逻辑选择)。该语句将声明一个可配置的类作为实现ConfigurableObject接口。这使您的领域类免受 Spring 特定工件的影响,同时受益于 DI 机制。让我们看看这种用法的示例。
考虑前面部分中的Order类。您可以让它实现一个特定于领域的MailSenderClient接口,而不是使用@Configurable,这表示它使用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 结构
configureBean()方法通过直接调用相应的 setter 来执行注入。当然,任何其他适合 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 知识,您会发现其中一个方面有助于创建优雅的解决方案。