Spring 的一个 Java 配置选项

工程 | Rod Johnson | 2006 年 11 月 28 日 | ...

得益于我们的可插拔理念以及实现中的大量辛勤工作,Spring IoC 容器(与 Spring 的其余大部分组件一样)极其灵活。

常常被忽视的一点是,Spring 配置不必局限于 XML,尽管 XML 格式是最常用的。Spring 拥有自己的内部元数据格式,即 BeanDefinition 接口及其子接口。代表 IoC 容器实例的 BeanFactory 和 ApplicationContext 实现由这种 Java 元数据驱动,并且与元数据解析(通常由 BeanDefinitionReader 实现执行)完全分离。

BeanDefinition 元数据最初并非为终端开发者设计。在 Spring 2.0 中,NamespaceHandlers(处理 XML 扩展命名空间的类)生成 BeanDefinition 元数据,并且我们引入了 BeanDefinitionBuilder,带有流畅的 API 使其更容易。但生成 BeanDefinition 元数据仍然属于基础设施编码的范畴,而不是你在编写业务逻辑和定义常规 Spring Bean 时每天要做的事情。

今天,我想描述一种在 Java 代码中定义 Bean 的新选项,它*是*面向终端用户(开发者)而不是基础设施提供者。这目前是 Spring 核心的里程碑版本附加组件,但可能会进入 Spring 主体。

我们先来看一个例子

@Configuration
public class MyConfig {
   @Bean
   public Person rod() {
      return new Person("Rod Johnson");
   }

   @Bean(scope = Scope.PROTOTYPE)
   public Book book() {
      Book book = new Book("Expert One-on-One J2EE Design and Development");
      book.setAuthor(rod());  // rod() method is actually a bean reference !
      return book;
   }
}

@Configuration 注解将此对象标识为一个特殊的配置类。每个 @Bean 方法都定义一个 Bean。Bean 的名称就是方法的名称。可以使用注解定义额外的别名,但最好从方法而不是注解中选取名称,因为这意味着编译器可以确保消除歧义。

Bean 在 Java 代码中通过构造函数、属性或任意方法调用进行配置。请注意,对另一个 Bean 方法的调用建立了从“book”Bean 到“rod”Bean 的依赖关系。但这比在没有框架支持的情况下在 Java 中实例化对象具有关键优势:例如

  • 每个 @Bean 都是一个 Spring 组件,可以利用所有 Spring 服务,例如声明式事务管理
  • 每个公共的 @Bean 方法都被添加到 Spring 容器中,因此可以注入到其他对象中,进行 JMX 导出以及其他好处。
  • 它可以顺利地融入现有的 Spring 环境。

通过将其与实现相同结果的 XML 定义进行比较,可能会更容易理解正在发生的事情,XML 定义如下

<bean id="rod" class="Person" scope="singleton">
   <constructor-arg>Rod Johnson</constructor-arg>
</bean>

<bean id="book" class="Book" scope="prototype">
   <constructor-arg>Expert One-on-One J2EE Design and Development</constructor-arg>
   <property name="author" ref="rod"/>
</bean>

尽管它基于注解,但这种 Java 配置机制在使用注解方面是独特的,我见过注解不包含在核心业务逻辑中,而是包含在单独的配置类中。实际上,它是一种配置的 DSL。因此,它保留了 Spring 的非侵入性承诺:你无需更改 Java 代码即可使用它。

配置类类似于 XML Bean 定义文件,因此 @Configuration 注解包含一些与 <beans> 元素相似的选项,例如默认的自动装配或延迟初始化。例如


@Configuration(defaultAutowire = Autowire.BY_TYPE, defaultLazy = Lazy.FALSE)
public class DataSourceConfiguration  extends ConfigurationSupport {
}

@Bean 注解允许设置作用域和延迟初始化等选项,就像使用 <bean> 元素一样。默认作用域是 Singleton,与 XML 相同。

这种 Java 配置风格具有一些有趣的特点。例如

  • 引用(例如示例中对“rod”Bean 的引用)在重构后仍然有效;任何好的 IDE 都提供出色的工具支持。
  • <li>Because configurations are Java classes, they can participate in inheritance relationships. For example, you could define a superclass that demands some abstract @Beans to be implemented in subclasses.</li>
    <li>It creates a new visibility option. An @Bean method can be protected, it which case it benefits from the usual characteristics of the Spring component, but is not visible externally--that is it not injectable and cannot be obtained by calling getBean() on the IoC context.</li>
    

我在向人们展示这一点(现在已经一年多了)的经验是,他们有时需要一些时间来理解它,但通常最终都会非常热情。

这*不是*为了取代 Spring 的 XML 格式。就像 Spring 2.0 扩展命名空间以及自 Spring 1.0 以来就可能使用的属性文件一样,它是一个额外的选项。复杂的应用程序需要多种类型的配置,Spring 旨在为配置提供最佳的整体解决方案。我们将继续探索其他形式的配置。

你通常会混合使用 Java 和 XML 配置。在同一个应用程序上下文中,你可以使用任意数量的 Java 配置类。

以下示例使用 XML Bean 定义来定义一个 MyConfig Bean(如上所示),它可以像任何普通 Bean 一样被注入。ConfigurationPostProcessor 处理所有带有 @Configuration 注解的 Bean,生成必要的 Bean 定义。

<beans>

 <bean class="..MyConfig"/>


 <bean class="org.springframework.beans.factory.java.ConfigurationPostProcessor"/>
 
 <bean class="SomeRandomBean">
 	<property...
 </bean>
</beans>

当然,你可以在同一个 XML 中包含普通的旧式 Bean 定义,就像示例中的“SomeRandomBean”。你也可以使用 Java 配置和现有的 XML 配置构建上下文。

Costin 还实现了一个方便的应用程序上下文,它使用通配符从 classpath 中加载类,如下所示

ApplicationContext oneConfig = new  AnnotationApplicationContext(SimpleConfiguration.class.getName());
ApplicationContext aBunchOfConfigs = new AnnotationApplicationContext("**/configuration/*Configuration.class");

类使用 ASM 进行检查,而无需加载它们。在未来的版本中,我们可能会提供额外的自动检测场景。

该版本在此处。Costin Leau 现在是项目负责人。代码应被视为 Alpha 质量。我们包含了一个修改后的 Spring Pet Store 示例,但毫无疑问,将在实际项目的使用中吸取教训。

Costin 和我很乐意收到您对该功能的反馈意见。

这段代码将何去何从?好吧,这取决于你。它确实需要反馈(欢迎提出建议),所有可能的范围(和实现改进)将通过实际使用而显现出来,任何技术都是如此。目前它不在路线图上,但如果它能激起足够的兴趣,可能会进入未来版本的 Spring 核心。

此外,它还需要一个响亮的名字。欢迎提出建议!


尽管今天是第一次(alpha)发布,但这项功能有着令人惊讶的悠久历史——超过一年。2005 年 8 月,我在科罗拉多州克雷斯特德巴特参加一个软件峰会时,与 Spring.NET 项目的 Mark Pollack 和 Aleks Seovic 一起进行了一次疯狂的编码。我记得在 Aleks 驾车从大沙丘前往丹佛时,我在捷豹 XJ8 的后座上写了很多代码。我可能需要写代码来分散我对危险的注意力。我认为这个想法的萌芽可能可以追溯到 2005 年 JavaOne 上与 Howard Lewis Ship 的一次谈话...

遗憾的是,从那时起我只断断续续地处理过这个问题,没有时间做得更多,所以还没有把它达到可以发布的程度。幸运的是,Spring Modules 的负责人兼 Spring 大师 Costin Leau 自今年年初加入 Interface21 以来,有更多时间进行 Spring 编码,他已经挺身而出,推进了这项工作。

该实现不需要对 Spring 核心进行任何修改。正如我所说,IoC 容器具有高度的灵活性。如果你有兴趣,它将配置对象视为一个工厂 Bean,每个 Bean 定义都由该对象上的一个实例工厂方法支持:这是一个自 Spring 1.1 以来就可用的机制。它对配置实例进行了一些字节码操作,目前使用 CGLIB,以确保对单例范围 @Bean 方法的重复调用始终返回同一个对象。

获取 Spring 通讯

保持与 Spring 通讯的连接

订阅

先行一步

VMware 提供培训和认证,助你加速进步。

了解更多

获取支持

Tanzu Spring 在一项简单的订阅中提供对 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件。

了解更多

即将举行的活动

查看 Spring 社区即将举行的所有活动。

查看全部