Spring 容器实例化并配置应用环境中定义的 Bean。也可以要求 Bean 工厂配置一个预先存在的对象,给定一个包含要应用的配置的 Bean 定义的名称。Spring-aspects.jar 包含一个注解驱动的 aspect,利用这种能力允许对任何对象进行依赖注入。该支持旨在用于在任何容器控制之外创建的对象。领域对象通常属于这一类,因为它们通常是用 new 操作符以编程方式创建的,或者由 ORM 工具作为数据库查询的结果创建的。
@Configurable
注解标志着一个类有资格进行 Spring 驱动的配置。在最简单的情况下,你可以纯粹使用它作为一个标记注解,正如下面的例子所示:
package com.xyz.myapp.domain;
import org.springframework.beans.factory.annotation.Configurable;
@Configurable
public class Account {
// ...
}
当以这种方式用作标记接口时,Spring 通过使用与全限定类型名称(com.xyz.myapp.domain.Account)相同名称的 Bean 定义(通常是 prototype 作用域,也就是多例)来配置被注解类型(本例中为 Account)的新实例。由于 Bean 的默认名称是其类型的全称,所以声明 prototype 定义的一个方便方法是省略 id 属性,如下例所示:
<bean class="com.xyz.myapp.domain.Account" scope="prototype">
<property name="fundsTransferService" ref="fundsTransferService"/>
</bean>
如果你想明确地指定要使用的 prototype Bean 定义的名称,你可以直接在注解中这样做,如下例所示:
package com.xyz.myapp.domain;
import org.springframework.beans.factory.annotation.Configurable;
@Configurable("account")
public class Account {
// ...
}
Spring 现在寻找一个名为 account 的 bean 定义,并将其作为配置新 Account 实例的定义。
你也可以使用自动布线来避免指定一个专门的 Bean 定义。要让 Spring 应用自动布线,请使用 @Configurable
注解的 autowire
属性。你可以指定 @Configurable(autowire=Autowire.BY_TYPE)
或 @Configurable(autowire=Autowire.BY_NAME)
来分别按类型或按名称进行自动布线。作为一种选择,最好是通过 @Autowired
或 @Inject
在字段或方法级别为你的 @Configurable Bean
指定显式的、z注解驱动的依赖注入(更多细节请参见基于注解的容器配置)。
最后,你可以通过使用 dependencyCheck 属性(例如,@Configurable(autowire=Autowire.BY_NAME,dependencyCheck=true)
)来启用 Spring 对新创建和配置的对象中的对象引用的依赖性检查。如果这个属性被设置为 true,Spring 会在配置后验证所有的属性(不是基元或集合)是否已经被设置。
请注意,单独使用注解是没有用的。Spring-aspects.jar 中的 AnnotationBeanConfigurerAspect 对注解的存在起作用。本质上,这个 aspect 说:「在从一个用 @Configurable
注解的类型的新对象的初始化返回后,根据注解的属性使用 Spring 配置新创建的对象」。在这里,「初始化」指的是新实例化的对象(例如,用 new 操作符实例化的对象),以及正在进行反序列化的可序列化对象(例如,通过 readResolve()
)。
上面这段话中的一个关键短语是 「本质上」。对于大多数情况,「从新对象的初始化中返回后」的确切语义是没有问题的。在这种情况下,「初始化之后」意味着依赖关系是在对象被构造之后注入的。这意味着依赖关系不能在类的构造函数体中使用。如果你想让依赖关系在构造函数体运行之前被注入,从而可以在构造函数体中使用,你需要在 @Configurable
声明中定义这一点,如下所示:
@Configurable(preConstruction = true)
你可以在《AspectJ 编程指南》的这个附录中找到更多关于 AspectJ 中各种 pointcut 类型的语言语义的信息。
要做到这一点,必须用 AspectJ 织网器编织注解的类型。你可以使用构建时的 Ant 或 Maven 任务来做到这一点(例如,见 AspectJ 开发环境指南),也可以使用加载时编织(见 Spring 框架中 AspectJ 的加载时编织)。AnnotationBeanConfigurerAspect 本身需要由 Spring 进行配置(以便获得对用于配置新对象的 Bean 工厂的引用)。如果你使用基于 Java 的配置,你可以在任何 @Configuration
类中添加@EnableSpringConfigured
,如下所示:
@Configuration
@EnableSpringConfigured
public class AppConfig {
}
如果你喜欢基于 XML 的配置,Spring 上下文命名空间定义了一个方便的 context:spring-configured
元素,你可以按以下方式使用:
<context:spring-configured/>
在切面被配置之前创建的 @Configurable 对象的实例会导致向调试日志发出一条消息,并且不会对该对象进行配置。一个例子可能是 Spring 配置中的一个 Bean,它在被 Spring 初始化时创建了域对象。在这种情况下,你可以使用 depends-on bean 属性来手动指定 bean 依赖于配置切面。下面的例子展示了如何使用 depends-on 属性:
<bean id="myService"
class="com.xzy.myapp.service.MyService"
depends-on="org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect">
<!-- ... -->
</bean>
:::info 不要通过 bean configurer 切面激活 @Configurable 处理,除非你真的想在运行时依赖它的语义。特别是,请确保不要在容器中作为普通Spring Bean 注册的 bean 类上使用 @Configurable。这样做会导致双重初始化,一次通过容器,一次通过切面。 :::
单元测试 @Configurable 对象
支持 @Configurable 的目标之一是实现领域对象的独立单元测试,而不存在硬编码查找的困难。如果 @Configurable 类型没有被 AspectJ 编织,该注释在单元测试中没有影响。你可以在被测对象中设置模拟或存根属性引用,并正常进行。如果 AspectJ 编织了 @Configurable 类型,你仍然可以在容器外正常进行单元测试,但每次构建 @Configurable 对象时,你都会看到一条警告信息,表明它没有被 Spring 配置。
在多个应用环境(Application Contexts)中工作
用于实现 @Configurable 支持的 AnnotationBeanConfigurerAspect 是一个 AspectJ 单例切面。单例切面的作用域与静态成员的作用域相同。每个定义该类型的 classloader 有一个 aspect 实例。这意味着,如果你在同一个 classloader 层次结构中定义了多个应用上下文,你需要考虑在哪里定义 @EnableSpringConfigured Bean,以及在 classpath 上放置 spring-aspects.jar。
考虑一个典型的 Spring Web 应用程序配置,它有一个共享的父应用程序上下文,它定义了通用的业务服务、支持这些服务所需的一切,以及每个 Servlet 的一个子应用程序上下文(它包含该 Servlet 的特定定义)。所有这些上下文都在同一个 classloader 层次结构中共存,因此 AnnotationBeanConfigurerAspect 只能持有对其中一个的引用。在这种情况下,我们建议在共享(父)应用程序上下文中定义@EnableSpringConfigured bean。这定义了你可能想要注入到域对象中的服务。一个后果是,你不能通过使用 @Configurable 机制(这可能不是你想做的事情)来配置域对象,并引用定义在子(特定服务)上下文中的 bean。
当在同一个容器中部署多个 Web 应用程序时,确保每个 Web 应用程序通过使用自己的类加载器加载 spring-aspects.jar 中的类型(例如,将 spring-aspects.jar 放在 ‘WEB-INF/lib’ 中)。如果 spring-aspects.jar 只被添加到容器范围内的 classpath 中(因此被共享的父类加载器加载),所有的 Web 应用程序就会共享同一个切面的实例(这可能不是你想要的)。