領先一步
VMware 提供培訓和認證,以加速您的進度。
了解更多Spring 2.0 引入了 annotation 支援和 annotation 感知的組態選項,Java 5(或更高版本)開發人員可以使用這些選項。
@Transactional | 用於劃分和配置事務定義 |
---|---|
@Aspect (AspectJ) | 用於定義切面,以及 @Pointcut 定義和建議 (@Before, @After, @Around) |
@Repository | 用於指示作為 repository(又稱資料存取物件或 DAO)運作的類別 |
@Required | 用於強制要求已註解的 bean 屬性提供值 |
在 Spring 2.1 中,此 annotation 驅動的組態主題已顯著擴展,並將在我們朝向 RC1 版本邁進時繼續發展。實際上,現在可以通過 annotation 驅動 Spring 的依賴注入。此外,Spring 可以發現需要在應用程式內容中配置的 bean。
這篇部落格文章將作為一個教學風格的介紹,涵蓋 10 個易於理解的步驟中的基本功能。稍後在本週我將提供有關更多進階功能和自訂選項的資訊。如果您對替代的組態選項感興趣,您還應該查看 Spring Java 組態專案和 這篇部落格。
本教程至少需要 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);
}
現在,提供一個 stub 實作
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。在接下來的兩個步驟中,您將添加 annotation 以分別驅動依賴注入和初始化。
@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 annotation 也可以用於基於建構子的注入。如果您想嘗試該選項,請從 GreetingServiceImpl 中刪除 setter 方法,並添加此建構子(然後重新運行測試)
@Autowired
public GreetingServiceImpl(MessageRepository messageRepository) {
this.messageRepository = messageRepository;
}
如果需要,您甚至可以使用欄位注入。刪除建構子,直接將 annotation 添加到欄位,然後重新運行測試。程式碼應該如下所示
@Autowired
private MessageRepository messageRepository;
新增基於 JDBC 的 MessageRepository 實作
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 中,甚至可以發現 "candidate" bean,而不是像上面那樣在 XML 中明確提供。預設情況下,會識別某些 annotation。這包括 @Repository annotation 以及新的 @Component annotation。將這兩個 annotation 分別添加到 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 annotation)
@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 的情況下啟動,並且可以自訂(例如,偵測您自己的 annotation 和/或類型)。我將在下一篇文章中討論這些功能以及更多內容。
同時,我希望這篇文章能很好地達到其目的 - 提供使用這些新的 Spring 2.1 功能的實務經驗。與往常一樣,我們期待來自社群的回饋,因此請隨時發表評論!