关于 Java 配置的更多信息

工程 | Costin Leau | 2007年6月5日 | ...

正如你们大多数人现在已经知道的那样,Spring 不仅仅 关于 XML,因为最近,核心的一些“官方”扩展提供了配置容器的替代方法。

Spring Java 配置 1.0 M2 是在 发布 的产品之一 JavaOne 期间,虽然仍然标记为里程碑版本,但进行了大量更新和错误修复。

  • 根包已更改为 org.springframework.config.java
  • <li>scoped beans are fully supported</li>
    
    <li>the bean name generation can be customized</li>
    
    <li>the distribution contains a 'transformed' sample (petclinic) which uses XML, JavaConfig and Groovy.</li>
    

事实上,1.0 M2 完成的大部分工作都是整合了对初始公告 收到的反馈;非常感谢所有参与其中的人!

在本篇文章中,我想提供一些 Java 配置的示例,作为真正的基于注解的 IoC 配置。让我们从 Mark 的示例开始,在他的 文章 中使用,关于 Spring 2.1 注解驱动的依赖注入。

概括地说,以下是 Mark 使用的接口和类的图表。

diagram

连接通过 @Autowired 完成,而某些方法被标记为生命周期的一部分,通过 @PostConstruct 和 @PreDestroy。

将注解驱动的配置转换为 Java 配置非常简单。


@Configuration
public abstract class JavaCfg {

	@Bean (destroyMethodName = "tearDownDatabase")
	public JdbcMessageRepository messageRepo() {
		JdbcMessageRepository repo = new JdbcMessageRepository();
		repo.createTemplate(dataSource());
		// call custom init method
		repo.setUpDatabase();

		return repo;
	}
	
	@Bean
	public GreetingService greetService() {
		GreetingServiceImpl impl = new GreetingServiceImpl();
		impl.setMessageRepository(messageRepo());
		return impl;
	}


	@ExternalBean
	public abstract DataSource dataSource();
}

首先,使用标记有 @Configuration 的 Java 类创建配置。其中,声明了 2 个 Bean 并引用了一个外部 Bean。

声明的第一个 Bean 是 messageRepo(与方法名称相同),它还定义了一个销毁方法。请注意,自定义初始化方法是通过代码调用的,因此不需要任何注解或声明。您仍然可以使用 Spring InitializingBean 接口或 @Bean initMethodName 参数,但我建议不要这样做。上面的代码更加清晰简洁,更不用说您可以传入参数,而使用声明式初始化方法时无法做到这一点。

定义的第二个 Bean 是 greetService,它使用 messageRepo 作为依赖项。这就是 Java 配置的魔力发生的地方,因为每次创建 greetService 时,Spring 容器都会提供 messageRepo 后面的 Bean 实例。也就是说,如果 messageRepo 是单例的,则每次都会返回相同的实例。但是,如果指定了不同的作用域,那么当必须创建新实例时,您的代码将被调用。Rod 已经解释了这一点,因此请参阅他的博客 文章 以获取更多信息。

1.0 M2 的一个新增功能是 @ExternalBean 注解,它引用在当前配置之外声明的 Bean,同时仍然依赖于 Java 强类型,因此,您的 IDE 验证。@ExternalBean 在运行时使用 getBean() 查找覆盖它所声明的方法,如下所示。


   public DataSource dataSource() {
       return (DataSource) context.getBean("dataSource");
   }

当然,人们可以手动执行相同的操作,尤其是在使用 ConfigurationSupport 类时,但 @ExternalBean 使事情变得容易得多。请注意,在初始示例中,我使用了抽象方法来强调外部化,但是可以使用任何类型的非 final 方法。


  @ExternalBean
  public DataSource dataSource() {
      throw new UnsupportedOperationException("this line will NEVER execute since the method will be overridden");
  }

现在配置已创建,将其与 JavaConfiguration 后处理器一起声明为普通 Bean。



<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

	<bean id="config" class="blog.javaconfig.JavaCfg" />

	<bean id="processor"
		class="org.springframework.config.java.process.ConfigurationPostProcessor" />

	<bean id="dataSource"
		class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<property name="driverClassName" value="${jdbc.driver}" />
		<property name="url" value="${jdbc.url}" />
		<property name="username" value="${jdbc.username}" />
		<property name="password" value="${jdbc.password}" />
	</bean>

</beans>

您就可以开始了(如果您正在运行 Mark 的测试,请确保使用 Java 配置 XML 文件)。

因为一图胜千言,请参见下面通过 SpringIDE 的相同设置:springIDE view

我使用了最新的 SpringIDE 快照,它提供了可视化、导航以及对 Java 配置注解的验证(例如,插件检查 destroyMethodName 指向 Bean 创建方法返回类型上的正确方法)。

隐藏与寻找

Java 配置支持大多数 XML 声明功能,也就是说,您可以指定作用域、自动装配策略、延迟加载、depends-on 以及 Bean 级别(通过 @Bean)和默认值(通过 @Configuration)的自定义元数据。在 1.0 M2 中,您甚至可以获得 @ScopedProxy 注解,它是 <aop:scoped-proxy/> 的直接替代。

但是,Java 配置相对于传统的 XML 容器提供的一项新功能是“Bean 可见性”——定义无法在配置外部使用的 Bean 的能力。再次,让我们看看一些代码。


@Configuration
public class VisibilityConfiguration {

	@Bean(scope = DefaultScopes.PROTOTYPE)
	public Object publicBean() {
		List list = new ArrayList();
		list.add(hiddenBean());
		list.add(secretBean());

		System.out.println("creating public bean");
		return list;
	}

	@Bean(scope = DefaultScopes.SINGLETON)
	protected Object hiddenBean() {
		System.out.println("creating hidden bean");
		return new String("hidden bean");
	}

	@Bean(scope = DefaultScopes.PROTOTYPE)
	private Object secretBean() {
		List list = new ArrayList();
		// hidden beans can access beans defined in the 'owning' context
		list.add(hiddenBean());
		System.out.println("creating secret bean");
		return list;
	}
}

Java 配置将使用方法可见性来确定某个 Bean 是公开的(即它是否可以在其声明配置之外使用)还是私有的(非公开的)。因此,任何标记有 @Bean 的非公开方法都将创建一个隐藏的 Bean。这允许您提供 Bean 定义封装,禁止意外或非意外的访问。非常重要的是要注意,隐藏的 Bean 不会转换为 嵌套 Bean——它们是功能齐全的顶级 Bean:它们拥有自己的生命周期并支持自定义作用域,这与依赖于父 Bean 的内部 Bean 形成对比。

为了证明这一点,我将 hiddenBean 标记为单例,并将 secretBean 标记为原型。

让我们使用以下测试来测试行为。


public class VisibilityTest extends TestCase {
	private ConfigurableApplicationContext context;

	@Override
	protected void setUp() throws Exception {
		context = new AnnotationApplicationContext("**/VisibilityConfiguration.class");
	}

	@Override
	protected void tearDown() throws Exception {
		context.close();
	}

	public void testApplicationContext() {
		assertNotNull(context);
		System.out.println(Arrays.toString(context.getBeanDefinitionNames()));
		// I don't belive you container! I know you are hidding something 
		context.getBean("hiddenBean");
	}
}

测试应该打印。

[blog.javaconfig.VisibilityConfiguration, publicBean]
creating hidden bean
creating secret bean
creating public bean
creating secret bean
creating public bean

之后应该失败,类似于以下内容。


org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'hiddenBean' is defined
...

控制台中的第一行显示 secretBean 和 hiddenBean 在我们持有的上下文中未定义。但是,以下几行显示隐藏的 Bean 创建了一次(因为它是一个单例),而 secretBean 创建了两次,对于每个 publicBean,因为它是一个原型。

那么隐藏的 Bean 在哪里呢?在子容器中。

父容器(在本例中为 context)完全不知道它,因此也不知道在其中声明的任何 Bean。尽管如此,在子上下文内声明的 Bean 可以访问在父上下文内声明的任何 Bean,但反之则不行。另一方面,公开的 Bean(例如 publicBean)由 Java 配置“推送到”父容器中,但由于它们与隐藏的 Bean 声明在同一配置中,因此它们可以在实例化期间引用“秘密”Bean。

看,没有 XML!

对于那些希望完全放弃 XML 的人,Spring Java 配置提供了 AnnotationApplicationContext,它使用类而不是 XML 文件,正如您从上面的测试用例中看到的那样。虽然我的示例有效,但它并不理想,因为如果没有缓存,应用程序上下文将为每个测试创建和销毁。另一种方法是重用现有的 AbstractDependencyInjectionSpringContextTests 并适当地覆盖上下文创建。


public class NoXMLTest extends AbstractDependencyInjectionSpringContextTests {

	@Override
	protected ConfigurableApplicationContext createApplicationContext(String[] locations) {
		GenericApplicationContext context = new GenericApplicationContext();
		customizeBeanFactory(context.getDefaultListableBeanFactory());
                // use Java Configuration annotation-based bean definition reader
		new ConfigurationClassScanningBeanDefinitionReader(context).loadBeanDefinitions(locations);
		context.refresh();
		return context;
	}

	@Override
	protected String[] getConfigLocations() {
		return new String[] { "**/*.class" };
	}

	public void testAppCtx() {
		assertNotNull(applicationContext);
	}
}

(这可以通过 SPR-3550 进一步简化)。

哪种方法更好?

你们中的一些人可能想知道哪种注解配置方法最好:注解驱动注入或 Java 配置?我的答案是:“视情况而定”。

Java 配置忠于 IoC 原则,因为配置位于代码之外,这意味着您拥有真正的 POJOs(即代码中没有配置注解)。

之前在本博客中介绍的注解驱动注入允许对象对其配置更加了解。它们可以请求依赖项、自动装配,甚至可以指定其作用域。注入仍然发生(也就是说,对象仍然由容器管理),但您配置的一些部分现在包含在您的对象中。

使用 JavaConfig,您可以不受任何限制地配置您的对象,因为您正在使用纯 Java。您可以使用任意数量的任意类型的参数,并且可以调用任意数量的方法。由于它是 Java,因此您的配置易于重构,并且您可以利用 IDE 自动完成功能。这非常灵活和强大!

另一方面,使用注解驱动注入,您可以对对象进行细粒度(类、方法甚至字段级别)控制,以及更多上下文信息。

考虑 @Autowire 方法。


        @Autowired
        public void createTemplate(DataSource dataSource) {
                this.jdbcTemplate = new SimpleJdbcTemplate(dataSource);
        }

Spring 不仅使用注解来确定将发生自动装配的方法,还使用它来确定所需类型。此外,可以使用多参数方法,这是“传统”自动装配不支持的功能,“传统”自动装配使用 JavaBeans 约定,因此使用 setter。

归根结底,这两种方法都服务于一个目的:配置 Spring 容器。您可以使用其中一种,或者两种都使用,以及一些 XML 和 属性,如果您愿意的话。实际上,Java 配置发行版用 XML、注解和 Groovy 基于配置替换了 Petclinic 的“传统”XML 配置。考虑到这篇博客 文章,很快 JRuby 就会被包含进来。

底线是您可以选择最适合您开发风格的方法。

附注:如果您对这个主题感兴趣,您可能想参加以下 SpringOne 会议,进行深入讨论 :)

致辞,Costin

获取 Spring 时事通讯

与 Spring 时事通讯保持联系

订阅

领先一步

VMware 提供培训和认证,以加速您的进步。

了解更多

获取支持

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

了解更多

即将举行的活动

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

查看全部