领先一步
VMware 提供培训和认证,以加速您的进步。
了解更多正如你们大多数人现在已经知道的那样,Spring 不仅仅 关于 XML,因为最近,核心的一些“官方”扩展提供了配置容器的替代方法。
Spring Java 配置 1.0 M2 是在 发布 的产品之一 JavaOne 期间,虽然仍然标记为里程碑版本,但进行了大量更新和错误修复。
<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 使用的接口和类的图表。
连接通过 @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 快照,它提供了可视化、导航以及对 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 的人,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