領先一步
VMware 提供培訓和認證,助您加速進步。
瞭解更多正如大家現在所知,Spring 不僅僅是關於 XML,因為最近,一些“官方”核心擴充套件提供了配置容器的替代方式。
Spring Java Configuration 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 Configuration 的例子,將其作為一個真正的基於註解的 IoC 配置。讓我們從 Mark 的例子開始,他在 關於 Spring 2.1 註解驅動依賴注入的文章 中使用了它。
簡而言之,下面是 Mark 使用的介面和類的圖示
依賴注入是透過 @Autowired 完成的,而一些方法則透過 @PostConstruct 和 @PreDestroy 標記為生命週期的一部分。
將註解驅動的配置轉換為 Java Configuration 非常簡單
@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 Configuration 的魔力所在,因為每次建立 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");
}
現在配置已經建立,將其作為一個普通 bean 與 JavaConfiguration 後處理器一起宣告
<?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 Configuration XML 檔案)。
由於一張圖片勝過千言萬語,請看下圖,透過 SpringIDE 實現的相同設定: 
我使用了最新的 SpringIDE 快照,它提供了 Java Configuration 註解的視覺化、導航和驗證(例如,外掛會檢查 destroyMethodName 是否指向 bean 建立方法返回型別上的一個有效方法)。
Java Configuration 支援 XML 宣告的大部分功能,即您可以指定作用域、自動裝配策略、懶載入、depends-on 以及 bean 級別的自定義元資料(透過 @Bean)和預設值(透過 @Configuration)。在 1.0 M2 中,您甚至可以使用 @ScopedProxy 註解,這是 <aop:scoped-proxy/> 的直接替代品。
然而,Java Configuration 相對於傳統的 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 Configuration 將使用方法可見性來確定某個 bean 是公共的(即它可以在其宣告配置外部使用)還是私有的(非公共)。因此,任何標記了 @Bean 註解但非公共的方法都會建立一個隱藏的 bean。這允許您提供 bean 定義封裝,禁止意外或故意的訪問。非常重要的是要注意,隱藏的 bean 不會被轉換為 巢狀 bean——它們是功能齊全的頂級 bean:它們有自己的生命週期並支援自定義作用域,而內部 bean 則依賴於父 bean。
為了證明這一點,我將 hiddenBean 標記為 singleton,將 secretBean 標記為 prototype。
讓我們用以下測試來驗證行為
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 Configuration “推入”父容器中,但由於它們與隱藏 bean 在同一配置中宣告,因此它們可以在例項化期間引用“秘密” bean。
對於那些想完全擺脫 XML 的人,Spring Java Configuration 提供了 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 Configuration?我的回答是:“取決於”。
Java Configuration 忠實於 IoC 原則,因為配置位於程式碼之外,這意味著您擁有真正的 **P**OJO(即,程式碼中沒有配置註解)。
之前在本部落格上介紹的註解驅動注入,允許物件對它們的配置有更多的瞭解。它們可以請求依賴項、自動裝配,甚至可以指定它們的作用域。注入仍然發生(即物件仍然由容器管理),但您的一些配置現在包含在您的物件中。
使用 JavaConfig,您可以無限制地配置您的物件,因為您使用的是純 Java。您可以使用任意數量的、任意型別的引數,並且可以呼叫任意數量的方法。由於它是 Java,因此您的配置是重構友好的,您可以利用 IDE 的自動完成功能。這非常靈活和強大!
另一方面,使用註解驅動注入,您可以對您的物件進行細粒度的控制(類、方法甚至欄位級別),並獲得更多上下文資訊。
考慮 @Autowire 方法
@Autowired
public void createTemplate(DataSource dataSource) {
this.jdbcTemplate = new SimpleJdbcTemplate(dataSource);
}
Spring 使用該註解不僅是為了確定自動裝配將發生的方法,還確定了所需的型別。此外,可以使用多引數方法,這是“傳統”自動裝配不支援的功能,它使用 JavaBeans 約定,因此使用 setter。
最終,這兩種方法服務於一個目的:配置 Spring 容器。您可以單獨使用其中一種,也可以同時使用它們,再加上一些 XML 和 屬性。事實上,Java Configuration 分發版用 XML、註解和 Groovy 的配置替換了 Petclinic 的“傳統” XML 配置。考慮到這篇部落格 文章,不久 JRuby 也將被包含進來。
底線是,您可以選擇最適合您開發風格的方法。
附註:如果您對這個話題感興趣,您可能想參加以下 SpringOne 會議 進行深入討論 :)
祝好,Costin