领先一步
VMware 提供培训和认证,助您加速进步。
了解更多Spring 2.0 引入了注解支持和注解感知配置选项,可供使用 Java 5(或更高版本)的 Spring 用户利用
| @Transactional | 用于标记和配置事务定义 |
|---|---|
| @Aspect (AspectJ) | 用于定义切面,以及 @Pointcut 定义和通知(@Before、@After、@Around) |
| @Repository | 用于指示充当仓库(也称为数据访问对象或 DAO)的类 |
| @Required | 用于强制为带注解的 bean 属性提供值 |
通过 Spring 2.1,这种注解驱动配置的主题得到了显著扩展,并将在我们迈向 RC1 版本时继续发展。事实上,现在可以通过注解驱动 Spring 的依赖注入。此外,Spring 可以*发现*需要在应用程序上下文中配置的 bean。
这篇博文将作为分 10 个易于遵循的步骤的教程式介绍,介绍基本功能。我将在本周晚些时候提供有关一些更高级功能和自定义选项的信息。如果您对其他配置选项感兴趣,还应该查看 Spring Java Configuration 项目和这篇博客。
本教程至少需要 Java 5,建议使用 Java 6(否则在步骤 1 结束时有一个单一要求)。
获取 spring-framework-2.1-m1-with-dependencies.zip。解压缩存档后,您将在“dist”目录中找到 spring.jar 和 spring-mock.jar。将它们添加到您的 CLASSPATH 中,以及以下内容(所示路径相对于解压缩的 2.1-m1 存档的“lib”目录)
public interface GreetingService {
String greet(String name);
}
然后,一个简单的实现
public class GreetingServiceImpl implements GreetingService {
private MessageRepository messageRepository;
public void setMessageRepository(MessageRepository messageRepository) {
this.messageRepository = messageRepository;
}
public String greet(String name) {
Locale locale = Locale.getDefault();
String message = messageRepository.getMessage(locale.getDisplayLanguage());
return message + " " + name;
}
}
由于服务依赖于一个MessageRepository,请先定义该接口
public interface MessageRepository {
String getMessage(String language);
}
目前,先提供一个存根实现
public class StubMessageRepository implements MessageRepository {
Map<String,String> messages = new HashMap<String,String>();
public void initialize() {
messages.put("English", "Welcome");
messages.put("Deutsch", "Willkommen");
}
public String getMessage(String language) {
return messages.get(language);
}
}
为 Spring 应用程序上下文定义 bean。请注意,我包含了新的 'context' 命名空间(注意:'aop' 命名空间也包含在此处,将在最后一步中使用)
<?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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.1.xsd">
<bean class="blog.GreetingServiceImpl"/>
<bean class="blog.StubMessageRepository"/>
</beans>
显然,此配置看起来有点稀疏。正如你可能猜测的那样,'context' 命名空间很快就会发挥作用。
public class GreetingServiceImplTests extends AbstractDependencyInjectionSpringContextTests {
private GreetingService greetingService;
public void setGreetingService(GreetingService greetingService) {
this.greetingService = greetingService;
}
@Override
protected String[] getConfigLocations() {
return new String[] { "/blog/applicationContext.xml" };
}
public void testEnglishWelcome() {
Locale.setDefault(Locale.ENGLISH);
String name = "Spring Community";
String greeting = greetingService.greet(name);
assertEquals("Welcome " + name, greeting);
}
public void testGermanWelcome() {
Locale.setDefault(Locale.GERMAN);
String name = "Spring Community";
String greeting = greetingService.greet(name);
assertEquals("Willkommen " + name, greeting);
}
}
尝试运行测试,并注意它们会因 NullPointerException 而失败。这是预料之中的,因为 GreetingServiceImpl 还没有提供 MessageRepository。在接下来的两步中,你将添加注解来驱动依赖注入和初始化。
@Autowired
public void setMessageRepository(MessageRepository messageRepository) {
this.messageRepository = messageRepository;
}
然后,将 'annotation-config' 元素(来自新的 'context' 命名空间)添加到你的配置中
<beans ... >
<context:annotation-config/>
<bean class="blog.GreetingServiceImpl"/>
<bean class="blog.StubMessageRepository"/>
</beans>
重新运行测试。它们仍然会失败,但如果你仔细观察,会发现是一个新问题。断言失败,因为返回的消息是 null。这意味着 'messageRepository' 属性 *已* 设置在 greeting 服务上!现在,只需要初始化 StubMessageRepository。
@PostConstruct
public void initialize() {
messages.put("English", "Welcome");
messages.put("Deutsch", "Willkommen");
}
重新运行测试。这次它们应该会通过!
@Autowired 注解也可以用于基于构造函数的注入。如果你想尝试此选项,请从 GreetingServiceImpl 中删除 setter 方法,并添加此构造函数(然后重新运行测试)
@Autowired
public GreetingServiceImpl(MessageRepository messageRepository) {
this.messageRepository = messageRepository;
}
如果愿意,你甚至可以使用字段注入。删除构造函数,将注解直接添加到字段,然后重新运行测试。代码应如下所示
@Autowired
private MessageRepository messageRepository;
添加 MessageRepository 的基于 JDBC 的存储库实现
public class JdbcMessageRepository implements MessageRepository {
private SimpleJdbcTemplate jdbcTemplate;
@PostConstruct
public void setUpDatabase() {
jdbcTemplate.update("create table messages (language varchar(20), message varchar(100))");
jdbcTemplate.update("insert into messages (language, message) values ('English', 'Welcome')");
jdbcTemplate.update("insert into messages (language, message) values ('Deutsch', 'Willkommen')");
}
@PreDestroy
public void tearDownDatabase() {
jdbcTemplate.update("drop table messages");
}
public String getMessage(String language) {
return jdbcTemplate.queryForObject("select message from messages where language = ?", String.class, language);
}
}
请注意,除了用于初始化的 @PostConstruct,它还使用 @PreDestroy 来标记一个将在销毁时调用的方法。此实现有一个不清楚的地方:SimpleJdbcTemplate 将如何提供?一个选项是为模板提供一个 bean 定义。另一个选项是某种方式将 DataSource 实现提供给模板的构造函数。添加以下(已注解的)方法
@Autowired
public void createTemplate(DataSource dataSource) {
this.jdbcTemplate = new SimpleJdbcTemplate(dataSource);
}
这演示了依赖注入与任意方法(非传统 'setter')的集成。这将在下一步的测试过程中进行。
在 Spring 2.1 中,“候选” bean 甚至可以被发现,而不是像上面那样在 XML 中显式提供。某些注解默认会被识别。这包括 @Repository 注解以及新的 @Component 注解。将这两个注解分别添加到 JdbcMessageRepository 和 GreetingServiceImpl
@Repository
public class JdbcMessageRepository implements MessageRepository { ... }
@Component
public class GreetingServiceImpl implements GreetingService { ... }
然后通过删除现有的显式 bean 定义并简单地添加一个 component-scan 标签来修改 XML 文件
<beans ... >
<context:component-scan base-package="blog"/>
</beans>
然后,只添加 DataSource bean 定义和用于配置属性占位符的新标签
<beans ... >
<context:component-scan base-package="blog"/>
<context:property-placeholder location="classpath:blog/jdbc.properties"/>
<bean 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>
... 以及 jdbc.properties 文件本身
jdbc.driver=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:mem:blog
jdbc.username=sa
jdbc.password=
重新运行测试,你应该能看到绿色条,即使 XML 中只定义了数据源。
最后,添加一个切面(@Aspect 注解也默认自动检测)
@Aspect
public class ServiceInvocationLogger {
private int invocationCount;
@Pointcut("execution(* blog.*Service+.*(..))")
public void serviceInvocation() {}
@Before("serviceInvocation()")
public void log() {
invocationCount++;
System.out.println("service invocation #" + invocationCount);
}
}
要激活自动代理生成,只需将以下标签添加到 xml 中
<aop:aspectj-autoproxy/>
重新运行测试,你应该能看到日志消息!
注意:扫描和配置过程可以在没有任何 XML 的情况下启动,并且可以进行自定义(例如,检测你自己的注解和/或类型)。我将在下一篇文章中讨论这些功能以及更多内容。
在此期间,我希望这篇帖子能很好地起到作用——提供这些新 Spring 2.1 功能的实践经验。一如既往,我们期待社区的反馈,所以请随时发表评论!