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 还实现了一个方便的应用程序上下文,它使用通配符从类路径中加载类,如下所示:

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 开车从大沙丘前往丹佛的途中,在 Jaguar 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 社区所有即将举行的活动。

查看所有