javascript
Spring2.5的新特性
第一部分
簡介
從誕生之初,Spring框架就堅守它的宗旨:簡化企業級應用開發,同時給復雜問題提供強大的、非侵入性解決方案。一年前發布的Spring2.0就把這些主題推到了一個新的高度。XML Schema的支持和自定義命名空間的使用大大減少了基于XML的配置。使用Java5及更新版本java的開發人員如今可以利用植入了像泛型(generic)和注解等新語言特性的Spring庫。最近,和AspectJ表達式語言的緊密集成,使得以非侵入方式添加跨越定義良好的Spring管理對象分組的行為成為可能。
Spring支持JSR-250注解
Java EE5中引入了“Java平臺的公共注解(Common Annotations for the Java Platform)”,而且該公共注解從Java SE 6一開始就被包含其中。 2006年5月,BEA系統宣布了他們在一個名為Pitchfork的項目上與Interface21的合作,該項目提供了基于Spring的Java EE 5編程模型的實現,包括支持用于注入(injection)、攔截( interception)和事務處理(transactions)的JSR-250注解和EJB 3注解(JSR-220)。 在2.5版本中,Spring框架的核心(core)現在支持以下JSR-250注解:
- @Resource
- @PostConstruct
- @PreDestroy
結合Spring,這些注解在任何開發環境下都可以使用——無論是否有應用程序服務器——甚至是集成測試環境都可以。激活這樣的支持僅僅是注冊一個單獨的Spring post-processor的事情:
<bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor"/>@Resource注解
@Resource 注解被用來激活一個命名資源(named resource)的依賴注入,在JavaEE應用程序中,該注解被典型地轉換為綁定于JNDI context中的一個對象。 Spring確實支持使用@Resource通過JNDI lookup來解析對象,默認地,擁有與@Resource注解所提供名字相匹配的“bean name(bean名字)”的Spring管理對象會被注入。 在下面的例子中,Spring會向加了注解的setter方法傳遞bean名為“dataSource”的Spring管理對象的引用。
@Resource(name="dataSource")public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
直接使用@Resource注解一個域(field)同樣是可能的。通過不暴露setter方法,代碼愈發緊湊并且還提供了域不可修改的額外益處。正如下面將要證明的,@Resource注解甚至不需要一個顯式的字符串值,在沒有提供任何值的情況下,域名將被當作默認值。
@Resourceprivate DataSource dataSource; // inject the bean named 'dataSource'
該方式被應用到setter方法的時候,默認名是從相應的屬性衍生出來,換句話說,命名為'setDataSource'的方法被用來處理名為'dataSource'的屬性。
private DataSource dataSource;@Resource
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
當@Resource沒有顯式提供名字的時候,如果根據默認名字找不到對應的Spring管理對象,注入機制會回滾至類型匹配(type-match)。如果剛好只有一個Spring管理對象符合該依賴的類型,那么它會被注入。通過設置CommonAnnotationBeanPostProcessor 的‘fallbackToDefaultTypeMatch’屬性為“false”(默認值是“true”)可以禁用這一特性。
<bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor"><property name="fallbackToDefaultTypeMatch" value="false"/>
</bean>
正如上文所提到的,在解析標有@Resource注解的依賴時,Spring支持JNDI-lookup。如若要強制對所有使用@Resource注解的依賴進行JNDI lookup,那也只要將CommonAnnotationBeanPostProcessor的'alwaysUseJndiLookup' 標識設置為true就可以了(默認值是false)。
<bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor"><property name="alwaysUseJndiLookup" value="true"/>
</bean>
另一個選擇是,激活指定為‘resource-ref-mappings’的依據全局JNDI名的查找,在@Resource注解內提供‘mappedName’屬性。即使目標對象實際上是一個JNDI資源,仍然推薦引入一個Spring管理對象,這樣可以提供一個間接層并且因此降低耦合程度。自Spring2.0開始添加命名空間以來,定義一個委托Spring處理JNDI lookup的bean也變得愈發簡練:
<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/petclinic"/>這個方法的優點在于間接層帶來了巨大的部署彈性。比如說,一個單獨的系統測試環境應該不再需要JNDI注冊。在這種情況下,在系統測試配置中可以提供如下的bean定義:
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"p:driverClassName="${jdbc.driverClassName}"
p:url="${jdbc.url}"
p:username="${jdbc.username}"
p:password="${jdbc.password}"/>
順便提一下,上面的例子中,實際的JDBC連接屬性從一個屬性文件(properties file)解析而來,在這個屬性文件里,關鍵字與提供的${占位符}互相對應,這需要注冊一個名為PropertyPlaceholderConfigurer的BeanFactoryPostProcessor實現來完成。這是具體化那些屬性(通常是針對特定環境的屬性)常用的技術,這些屬性可能比其他配置修改得更為頻繁。
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"><property name="location" value="classpath:jdbc.properties"/>
</bean>
Srping2.5中新加入了‘context’命名空間,這個命名空間讓我們能夠得到更為簡潔的方式來實現屬性占位符(property placeholder)的配置:
<context:property-placeholder location="classpath:jdbc.properties"/>生命周期注解:@PostConstruct和@PreDestroy
@PostConstruct 和@PreDestroy注解分別用來觸發Spring的初始化和銷毀回調。這個特性在原有基礎上得到了擴展,但并沒有替代在Spring2.5之前版本中提供的同樣的回調的另兩個選項。第一個選項是實現Spring的InitializingBean 和DisposableBean 接口中的一個或兩個。這兩個接口都需要一個回調方法的實現(分別是afterPropertiesSet()和destroy() )。這種基于接口的方法利用了Spring自動識別任何實現這些接口的Spring管理對象的能力,因而不再需要另外的配置。另一方面,Spring的一個關鍵目標是盡可能的非侵入。因此,許多Spring用戶并不采用實現這些Spring特定接口的方法,而利用第二個選項,那就是提供他們自己的初始化和銷毀方法。盡管入侵性小,但缺點在于使用這個方式的話就必須顯式聲明bean元素的init-method或destroy-method屬性。顯式配置有時候是必須的,例如當回調需要在開發人員控制能力之外的代碼上被調用的時候。PetClinic應用程序很好地說明了這個場景。當它和JDBC配置一起運行的時候,會用到一個第三方DataSource,并且它顯式聲明了一個destroy-method。另外要注意到的是,單獨的連接池數據源是dataSource的另一個部署選項,并且不需要修改任何代碼。
<bean id="dataSource"class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
p:driverClassName="${jdbc.driverClassName}"
p:url="${jdbc.url}"
p:username="${jdbc.username}"
p:password="${jdbc.password}"/>
在使用Spring2.5的過程中,如果一個對象需要調用一個初始化的回調方法的話,這個回調方法可以采用@PostConstruct來注解。例如一個假想的例子,一個后臺任務需要在啟動的時候就開始對一個文件目錄進行輪詢:
public class FilePoller {@PostConstruct
public void startPolling() {
...
}
...
}
類似地,一個在Spring管理對象上用@PreDestroy注解的方法會在這個對象寄宿的應用程序上下文(application context)關閉的時候被調用。
public class FilePoller {@PreDestroy
public void stopPolling() {
...
}
...
}
在添加了對JSR-250注解的支持以后,現在的Spring2.5結合前面提到的兩種生命周期方法的長處。將@PostConstruct和@PreDestroy作為方法層注解加入,足可以實現在受Spring管理的上下文(context)中觸發回調。換句話說,不需要另外基于XML的配置。同時,這兩個注解是Java語言本身的一部分(甚至被包括在Java SE 版本6中),所以無需引入特定Spring包。這兩個注解擁有在其他環境中也能理解的標識語義的優點,隨著時間的推移,Java開發人員可能會發現這些注解在第三方開發庫中被越來越多的運用到。最后,基于注解生命周期回調的其中一個有趣的結果是,不止一個方法可以帶有這兩個注解中的任何一個,并且所有注解了的方法會被調用。
激活剛剛描述的關于@Resource 、@PostConstruct和@PreDestroy注解的所有行為,正如上文提到的,需要為Spring的CommonAnnotationBeanPostProcessor提供一個bean定義。但另一個更簡練的方法則可能是使用2.5中的新的context命名空間:
<context:annotation-config/>引入這個單個元素將不單單注冊一個CommonAnnotationBeanPostProcessor,也會像下文將敘述的那樣激活自動裝配(autowire)行為。CommonAnnotationBeanPostProcessor也為@WebServiceRef 和@EJB注解提供支持。這些將在本文系列的第三篇中和Spring2.5為企業集成提供的其他新特性一起討論。
利用注解來優化細粒度自動裝配
涵蓋Spring對自動裝配支持的文檔中常常會提到由于自動裝配機制的粗粒度而伴隨有很多限制性。Spring2.5之前,自動裝配可以通過很多不同的方式來配置:構造器,類型setter,名字setter,或者自動偵測(在該方式中Spring選擇自動裝配一個構造器或者類型setter)。這些不同的選擇確實提供了很大程度的靈活性,但它們中沒有一個方法能夠提供細粒度控制。換句話說,Spring2.5之前還不可能自動裝配某個對象setter方法的特定子集,或者通過類型或名字來自動裝配它的一些屬性。結果,許多Spring用戶意識到將自動裝配應用到構建原型和測試中的好處,但當提到在產品中維護和支持系統時,大部分人認為,加入冗長的顯式配置對于澄清它所擔負的職責是非常值得的。
然而,Spring2.5大幅度地改變了布局。如上文所述,自動配置選項現在已經被擴展,支持JSR-250 @Resource注解來激活在每個方法或域基礎上被命名資源的自動裝配。然而,@Resource注解若單獨使用的話有很多限制。因此,Sring2.5引進了一個名為@Autowired的注解進一步提高控制級別。為激活這里所講的行為需要注冊一個單獨的bean定義:
<bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"/>另外如上文提到的,context命名空間提供了一個更簡明的方法。它將激活本文所討論的兩個post-processor(AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor)和我們在Spring2.0中引入的基于注解的post-processor:RequiredAnnotationBeanPostProcessor和PersistenceAnnotationBeanPostProcessor。
<context:annotation-config/>利用@Autowired 注解可以對相應類型注入依賴。域、構造器和方法都可以激活此行為。實際上,aotowired方法并不一定要是setter方法,且可以接受多個參數。下面這個例子是完整的可接受的用法:
@Autowiredpublic void setup(DataSource dataSource, AnotherObject o) { ... }
默認地,標有@Autowired注解的依賴被認為是必須的。然而,也可以將required屬性值設置為false來聲明它們中的任何一個。在下面這個例子中,DefaultStrategy只有在context命名空間中沒有SomeStrategy類型的Spring管理對象時才能被使用。
@Autowired(required=false)private SomeStrategy strategy = new DefaultStrategy();
通過類型進行的自動裝配明顯地在Spring context包含多于一個期望類型的對象的時候造成歧義。默認地,如果一個必須的依賴沒不是恰好一個bean與之對應的話,自動裝配機制就會失敗。同樣的,對于任何一個可選屬性,如果它擁有一個以上的候選,也都會失敗(如果屬性可選且沒有任何候選可用的話,該屬性則會被簡單地跳過)。有很多不同的配置選項可以避免這些沖突。
若Context中擁有一個指定類型的一個主關鍵實例,對這個類型定義的bean定義應該包含‘primary’屬性。當Context中含有其他可用實例的時候這個方法就很適用,但那些非主關鍵實例總是顯式配置的。
<bean id="dataSource" primary="true" ... />在需要更多控制的時候,任何autowired的域、構造參數、或者方法參數可以進一步加注@Qualifier注解。qualifier可以包含一個字符串值,在這種情況下,Spring會試圖通過名字來找到對應的對象。
@Autowired@Qualifier("primaryDataSource")
private DataSource dataSource;
@Qualifier作為一個獨立注解存在的主要原因是它可以被應用在構造器參數或方法參數上,但上文提到的@Autowired注解只能運用在構造器或方法本身。
@Autowiredpublic void setup(@Qualifier("primaryDataSource") DataSource dataSource, AnotherObject o) { ... }
事實上,@Qualifier作為一個單獨的注解在定制化方面提供了更多的好處。用戶自定義的注解在自動裝配過程中也可以起到qualifier的作用,最簡單的實現方式是在運用自定義注解的同時將@Qualifier作為它的元注解。
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface VetSpecialty { ... }
自定義注解可以選擇包含一個值來提供通過名字匹配的功能,但更普遍的用法是將它作為“標記”注解或定義一個對qualifier過程提供一些更多含義的值。例如,下面這個摘錄則描繪了一個域,它應該和通過名字匹配得到的結果中合格的對象進行自動裝配。
@Autowired@VetSpecialty("dentistry")
private Clinic dentistryClinic;
在使用XML配置來達到依賴解析的目標時,'qualifier' 子元素可以被加注到bean定義中。在下文的組件掃描部分,我們將呈現一個可供選擇的非XML方法。
<bean id="dentistryClinic" class="samples.DentistryClinic"><qualifier type="example.VetSpecialty" value="dentistry"/>
</bean>
為了避免對@Qualifier注解的任何依賴性,可以在Spring context中提供一個CustomAutowireConfigurer的bean定義并直接注冊所有自定義注解類型:
<bean class="org.springframework.beans.factory.annotation.CustomAutowireConfigurer"><property name="customQualifierTypes">
<set>
<value>example.VetSpecialty</value>
</set>
</property>
</bean>
現在,自定義修飾符被顯式聲明了,就不再需要@Qualifier這個元注解符了。
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})@Retention(RetentionPolicy.RUNTIME)
public @interface VetSpecialty { ... }
其實,在配置AutowiredAnnotationBeanPostProcessor的時候,取代@Autowired注解都是有可能的。
<bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"><property name="autowiredAnnotationType" value="example.Injected"/>
</bean>
大部分情況下,定義自定義‘標記’注解的能力結合通過名字或其他文法值進行匹配選項,足以完成自動裝配過程的細粒度控制。但Spring還支持在qualifier注解上任意數目的任意屬性。比如,下面是一個極為細粒度修飾的例子。
@SpecializedClinic(species="dog", breed="poodle")private Clinic poodleClinic;
自定義修飾符的實現應該定義這些屬性:
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface SpecializedClinic {
String species();
String breed();
}
自定義修飾符屬性可以匹配那些XML中bean定義的qualifier注解的屬性子元素。這些元素通常以鍵/值對方式提供。
<bean id="poodleClinic" class="example.PoodleClinic"><qualifier type="example.SpecializedClinic">
<attribute key="species" value="dog"/>
<attribute key="breed" value="poodle"/>
</qualifier>
</bean>
目前為止,關于autowire的描述都只是針對單獨的實例,其實也支持集合。在任何需要得到所有context中某種特定類型的Spring管理對象的時候,只需要簡單地在一個強類型(strongly-typed)集合上加注@Autowired 注解。
@Autowiredprivate List<Clinic> allClinics;
本章節最后一個值得指出的特性是自動裝配的使用替代了Spring的Aware接口。在Spring2.5之前,如果某個對象需要一個Spring context的ResourceLoader的引用,它可以通過實現ResourceLoaderAware的方式使得Spring通過setResourceLoader(ResourceLoader resourceLoader)方法來提供該依賴。借助同樣的方法可以得到Spring管理的MessageSource的引用,甚至可以得到ApplicationContext本身。對于Spring2.5用戶而言,這個行為現在通過autowiring得到全面支持(需要指出的是包含這些Spring特定依賴的時候應該考慮周到,特別是它們只能用于從業務邏輯清楚地分割出來的基礎構架代碼中)。
@Autowiredprivate MessageSource messageSource;
@Autowired
private ResourceLoader resourceLoader;
@Autowired
private ApplicationContext applicationContext;
自動偵測Spring組件
從2.0版本開始,Spring引入了構造型(stereotype)注解的概念以及將@Repository注解作為數據訪問代碼的標記的方法。在此基礎上,Spring2.5又加入了兩個新的注解 —— @Service和@Controller 來完成為通常的三層架構(數據訪問對象、服務、web控制器)角色委任。Spring2.5也引入了泛型@Component注解,其他構造型可從邏輯上對其進行擴展。通過清晰地指明應用程序的角色,這些構造型方便了Spring AOP和post-processor的使用,這些post-processor給基于這些角色的加了注解的對象提供了附加行為。比如,Spring2.0引入了PersistenceExceptionTranslationPostProcessor對任何帶有@Repository 注解的對象自動激活其數據訪問異常轉換。
這些注解同樣可以結合Spring2.5其他一些新性能來使用:自動偵測classpath上的組件。盡管XML已經成為最常見的Spring元數據的格式,但它決不是唯一選擇。實際上,Spring容器內的元數據是由純Java來表示的,當XML被用來定義Spring管理對象時,在實例化過程之前,那些定義會被解析并轉化成Java對象。Spring2.5的一個巨大的新功能是支持從源碼層注解讀取元數據。因而,上文描述的自動裝配機制使用注解的元數據來注入依賴,但它仍然需要注冊至少一個bean定義以便提供每個Spring管理對象的實現類。組件掃描功能則使得這個XML中最起碼的bean定義都不再存在需求性。
正如上面所示,Spring注解驅動的自動裝配可以在不犧牲細粒度控制的前提下極大程度地減少XML的使用。組件偵測機制將這個優點更發揚光大。全面替代XML中的配置不再必要,組件掃描反而可以處理XML元數據來簡化整體配置。結合XML和注解驅動技術可以得到一個平衡優化的方法,這在2.5版本的PetClinic范例中有詳細闡述。在該范例中,基礎構架組件(數據源、事務管理等)結合上文提到的外化屬性在XML中定義。數據訪問層對象也有部分在XML中定義,它們的配置也都利用了@Autowired注解來簡化依賴注入。最后,web層控制器完全不在XML中顯式定義,相反,下面提供的這段配置被用來觸發所有web控制器的自動偵測:
<context:component-scan base-package="org.springframework.samples.petclinic.web"/>需要注意到的是這段示例中使用到了base-package屬性。組件掃描的默認匹配規則會遞歸偵測該包(多個包可以以逗號分隔的list方式提供)內的所有類的所有Spring構造型注解。正因為如此,PetClinic應用程序范例中的各類控制器的實現都采用了@Controller注解(Spring的內置構造型之一)。請看下面這個例子:
@Controllerpublic class ClinicController {
private final Clinic clinic;
@Autowired
public ClinicController(Clinic clinic) {
this.clinic = clinic;
}
...
自動偵測組件在Spring容器中注冊,就像它們在XML中被定義一樣。如上所示,那些對象可以輪流利用注解驅動的自動裝配。
組件掃描的匹配規則可以通過過濾器(filter)來自定義,以根據類型、AspectJ表達式、或針對命名模式的正則表達式來決定包含或不包含哪些組件。默認的構造型也可以被禁用。比如這里有一個配置的例子,這個配置會忽略默認的構造型,但會自動偵測名字以Stub打頭或者包含@Mock注解的所有類:
<context:component-scan base-package="example" use-default-filters="false"><context:include-filter type="aspectj" expression="example..Stub*"/>
<context:include-filter type="annotation" expression="example.Mock"/>
</context:component-scan>
類型匹配的限制性也可以用排他的過濾器控制。例如,除了@Repository注解外其他都依賴于默認過濾器,那么就需要加入一個排他過濾器(exclude-filter)。
<context:component-scan base-package="example"><context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
</context:component-scan>
很明顯,有很多方法可以擴展組件掃描來注冊自定義的類型。構造型注解是最簡單的選擇,所以構造型概念本身也是可擴展的。像先前提到的,@Component是泛型模型,@Repository、@Service,和@Controller注解都從該構造型邏輯擴展而得。正因為如此,@Component可被用來作為元注解(也就是說,在另外的注解上聲明的注解),所有具有@Component元注解的自定義注解都會被默認掃描匹配規則自動偵測到。一個例子就有希望讓你領會到其實它根本沒有聽起來那么難。
讓我們回想一下在講@PostConstruct和@PreDestroy生命周期注解的時候的假想的后臺任務。也許一個應用程序有很多很多這樣的后臺任務,這些任務實例需要XML bean定義以便在Spring context里注冊并使它們自己的生命周期方法在正確時候被調用。利用組件掃描就不再需要這些顯式的XML bean定義。如果這些后臺任務都實現一個相同的接口或者都沿用同樣的命名慣例,那么可以用include-filters。然而,更簡單的方法是為這些任務對象創建一個注解并提供@Component元注解。
@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface BackgroundTask {
String value() default "";
}
然后在所有后臺任務的類定義中提供自定義構造型注解。
@BackgroundTaskpublic class FilePoller {
@PostConstruct
public void startPolling() {
...
}
@PreDestroy
public void stopPolling() {
...
}
...
}
泛型@Component注解可以像例子中提供的那樣簡單使用,自定義注解技術則提供了一個使用更具涵義的、領域特定的名字的機會。這些領域特定注解提供更深入的機會,比如使用AspectJ切點表達式來識別所有后臺任務,以便增加advice來監控這些任務的活動性。
默認的,組件被偵測到的時候,Spring會自動生成一個沒有修飾符的類名作為bean名字。上一個例子中,生成的bean名字會是filePoller。但是,任何加注了Spring構造型注解(@Component、@Repository、@Service或 @Controller)或是加注了其他的以@Component作為元注解的注解(比如上面例子中的@BackgroundTask )的類,構造型注解的value屬性可以被顯式指定,實例將該值作為它的bean名字注冊到context中。接下來的例子里,實例名應該是petClinic而不是默認生成的名字simpleJdbcClinic。
@Service("petClinic")public class SimpleJdbcClinic {
...
}
同樣的,在下面修正版的FilePoller例子里,生成的bean名字應該是poller而不是filePoller。
@BackgroundTask("poller")public class FilePoller {
...
}
雖然所有Spring管理對象都被默認地當作單例實例來處理,但有些時候還是有必要為某個對象指明一個備用的范圍(scope)。舉個例子來說,在web層,一個Spring管理對象可能捆綁到request或session的范圍。對于2.0版本,Spring的scope機制更具延展性,這樣一來,自定義scope可以被注冊到應用程序上下文(application context)。在XML配置中,僅僅是簡單地包含進scope屬性及該scope的名字就可以了。
<bean id="shoppingCart" class="example.ShoppingCart" scope="session">...
</bean>
Spring2.5中,為被掃描的組件提供@Scope注解可以起到同樣的作用。
@Component@Scope("session")
public class ShoppingCart {
...
}
這里要指出的最后一點是使用組件掃描時qualifier注解應用是多么的簡單。在上一節,下面這個對象曾被作為使用自定義qualifier注解進行自動裝配的例子:
@VetSpecialty("dentistry")private Clinic dentistryClinic;
同樣的例子接著展現了在XML內使用‘qualifier’元素為依賴提供指定目標bean定義。在使用組件掃描時,XML元數據不是必須的。但自定義修飾符也許在目標類定義中被作為類型層注解而引入。另一個將被掃描的@Repository實例作為依賴的例子如下:
@Repository@VetSpecialty("dentistry")
public class DentistryClinic implements Clinic {
...
}
最終,因為前面的例子展現了自定義注解及其屬性的例子,相等同的非XML表示依賴目標的方法如下:
@Repository@SpecializedClinic(species="dog", breed="poodle")
public class PoodleClinic implements Clinic {
...
}
小結
Spring2.5在很多方面都提供了很有意義的新功能。本文主要關注于怎樣通過掌控Java注解的力量將配置簡化。就如在JSR-250中定義的那樣,Spring支持公共注解(Common Annotations),同時為自動裝配過程的更細粒度的控制提供了額外注解。Spring2.5也擴展了從Spring2.0的@Repository就開始的構造型(stereotype)注解,并且所有這些構造型注解都可以和新的組件掃描功能結合使用。Spring2.5仍然全面支持基于XML的配置,同時它又引進了一個新的context命名空間對常見配置場景提供更精要的文法。實際上,支持XML和基于注解配置的無縫結合最終產生一個更為平衡的全面的方法。基本構架的復雜配置可以在模塊XML文件中定義,而應用程序棧日益增多地更高層配置可以更多的從基于注解的技術中獲益——前提是都在同一個Spring2.5應用程序context內。
?
?
?
?
?
?
?
?
?
?
?
?
第二部分
?
?
Spring 框架從創建伊始就致力于為復雜問題提供強大的、非侵入性的解決方案。Spring 2.0當中為縮減XML配置文件數量引入定制命名空間功能,從此它便深深植根于核心Spring框架(aop、context、jee、jms、 lang、tx和util命名空間)、Spring Portfolio項目(例如Spring Security)和非Spring項目中(例如CXF)。
Spring 2.5推出了一整套注解,作為基于XML的配置的替換方案。注解可用于Spring管理對象的自動發現、依賴注入、生命周期方法、Web層配置和單元/集成測試。
探索Spring 2.5中引入的注解技術系列文章由三部分組成,本文是其中的第二篇,它主要講述了Web層中的注解支持。最后一篇文章將著重介紹可用于集成和測試的其它特性。
這個系列文章的第一部分論述了Java注解(annotation)是如何代替XML來配置Spring管理對象和依賴注入的。我們再用一個例子回顧一下:
@Controller表明ClinicController是Web層組件,@Autowired請求一個被依賴注入的Clinic實例。這個例子只需要少量的XML語句就能使容器識別兩個注解,并限定組件的掃描范圍:
這對Web層可謂是個福音,因為在這層Spring的XML配置文件已日益臃腫,甚至可能還不如層下的配置來得有用。控制器掌握著許多屬性,例如視圖名稱、表單對象名稱和驗證器類型,這些多是關乎配置的,甚少關于依賴注入的。通過bean定義繼承,或者避免配置變化不是很頻繁的屬性,也可以有效的管理類似的配置。不過以我的經驗,很多開發人員都不會這樣做,結果就是XML文件總比實際需要的要龐大。不過 @Controller和@Autowired對Web層的配置會產生積極的作用。
在系列文章的第二部分我們將繼續討論這個問題,并瀏覽Spring 2.5在Web層的注解技術。這些注解被非正式的稱為@MVC,它涉及到了Spring MVC和Spring Porlet MVC,實際上本文討論的大部分功能都可以應用在這兩個框架上。
從Controller到@Controller
與第一部分討論的注解相比,@MVC已不只是作為配置的一種替換方案這樣簡單了,考慮下面這個著名的Spring MVC控制器簽名:
所有的Spring MVC控制器要么直接實現Controller接口,要么就得擴展類似AbstractController、 SimpleFormController、 MultiActionController或AbstractWizardFormController這樣的基類實現。正是Controller接口允許Spring MVC的DispatcherServlet把所有上述對象都看作是“處理器(handlers)”,并在一個名為 SimpleControllerHandlerAdapter的適配器的幫助下調用它們。
@MVC從三個重要的方面改變了這個程序設計模型:
?? 1. 不需要任何接口或者基類。
?? 2. 允許有任意數量的請求處理方法。
?? 3. 在方法簽名上具有高度的靈活性。
考慮到以上三個要點,就可以說很公平的說@MVC不僅僅是個替換方案了,它將會是Spring MVC的控制器技術演變過程中下一個重要步驟。
DispatcherServlet在名為AnnotationMethodHandlerAdapter的適配器幫助下調用被注解的控制器。正是這個適配器做了大量工作支持我們此后將會討論的注解,同時也是它有效的取代了對于控制器基類的需求。
@RequestMapping簡介
我們還是從一個類似于傳統的Spring MVC Controller控制器開始:
}
此處與以往的不同在于,這個控制器并沒有擴展Controller接口,并且它用@RequestMapping注解指明show()是映射到 URI路徑 “/accounts/show”的請求處理方法。除此以外,其余代碼都是一個典型的Spring MVC控制器應有的內容。
在將上述的方法完全轉化到@MVC后,我們會再回過頭來看@RequestMapping,但是在此之前還有一點需要提請注意,上面的請求映射URI也可匹配帶有任意擴展名的URI路徑,例如:
靈活的請求處理方法簽名
我們曾經承諾過要提供靈活的方法簽名,現在來看一下成果。輸入的參數中移除了響應對象,增加了一個代表模型的Map;返回的不再是ModelAndView,而是一個字符串,指明呈現響應時要用的視圖名字:
Map輸入參數是一個“隱式的”模型,對于我們來說在調用方法前創建它很方便,其中添加的鍵—值對數據便于在視圖中解析應用。本例視圖為show.jsp頁面。
@MVC可以接受多種類型的輸入參數,例如 HttpServletRequest/HttpServletResponse、HttpSession、Locale、InputStream、 OutputStream、File[]等等,它們的順序不受任何限制;同樣它也允許多種返回類型,例如ModelAndView、Map、 String,或者什么都不返回。你可以查看@RequestMapping的JavaDoc以了解它支持的所有輸入和返回參數類型。
有種令人感興趣的情形是當方法沒有指定視圖時(例如返回類型為void)會有什么事情發生,按照慣例DispatcherServlet要再使用請求URI的路徑信息,不過要移去前面的斜杠和擴展名。讓我們把返回類型改為void:
對于給定的請求處理方法和“/accounts/show”的請求映射,我們可以期望DispatcherServlet能夠獲得“accounts/show”的默認視圖名稱,當它與如下適當的視圖解析器結合共同作用時,會產生與前面指明返回視圖名同樣的結果:
強烈推薦視圖名稱依賴慣例的方式,因為這樣可以從控制器代碼中消除硬編碼的視圖名稱。如果你想定制 DispatcherServlet獲取默認視圖名的方式,就在servlet上下文環境中配置一個你自己的 RequestToViewNameTranslator實現,并為其bean id賦名為“viewNameTranslator”。
用@RequestParam提取和解析參數
@MVC另外一個特性是其提取和解析請求參數的能力。讓我們繼續重構上面的方法,并在其中添加@RequestParam注解:
這里@RequestParam注解可以用來提取名為“number”的String類型的參數,并將之作為輸入參數傳入。 @RequestParam支持類型轉換,還有必需和可選參數。類型轉換目前支持所有的基本Java類型,你可通過定制的PropertyEditors 來擴展它的范圍。下面是一些例子,其中包括了必需和可選參數:
注意,最后一個例子沒有提供清晰的參數名。當且僅當代碼帶調試符號編譯時,結果會提取名為“amount ”的參數,否則,將拋出IllegalStateException異常,因為當前的信息不足以從請求中提取參數。由于這個原因,在編碼時最好顯式的指定參數名。
繼續@RequestMapping的討論
把@RequestMapping放在類級別上是合法的,這可令它與方法級別上的@RequestMapping注解協同工作,取得縮小選擇范圍的效果,下面是一些例子。
類級別:
第一個方法級的請求映射和類級別的映射結合,當HTTP方法是POST時與路徑“/accounts/delete”匹配;第二個添加了一個要求,就是名為“type”的請求參數和其值“checking”都需要在請求中出現;第三個根本就沒有指定路徑,這個方法匹配所有的 HTTP方法,如果有必要的話可以用它的方法名。下面改寫我們的方法,使它可以依靠方法名進行匹配,程序如下:
方法匹配的請求是“/accounts/show”,依據的是類級別的@RequestMapping指定的匹配路徑“/accounts/*”和方法名“show”。
消除類級別的請求映射
Web層注解頻遭詬病是有事實依據的,那就是嵌入源代碼的URI路徑。這個問題很好矯正,URI路徑和控制器類之間的匹配關系用XML配置文件去管理,只在方法級的映射中使用@RequestMapping注解。
我們將配置一個ControllerClassNameHandlerMapping,它使用依賴控制器類名字的慣例,將URI映射到控制器:
現在“/accounts/*”這樣的請求都被匹配到AccountsController上,它與方法級別上的@RequestMapping注解協作的很好,只要添加上方法名就能夠完成上述映射。此外,既然我們的方法并不會返回視圖名稱,我們現在就可以依據慣例匹配類名、方法名、URI路徑和視圖名。
當@Controller被完全轉換為@MVC后,程序的寫法如下:
對應的XML配置文件如下:
你可以看出這是一個最精減的XML。程序里注解中沒有嵌入URI路徑,也沒有顯式指定視圖名,請求處理方法也只有很簡單的一行,方法簽名與我們的需求精準匹配,其它的請求處理方法也很容易添加。不需要基類,也不需要XML(至少也是沒有直接配置控制器),我們就能獲得上述所有優勢。
也許接下來你就可以看到,這種程序設計模型是多么有效了。
@MVC表單處理
一個典型的表單處理場景包括:獲得可編輯對象,在編輯模式下顯示它持有的數據、允許用戶提交并最終進行驗證和保存變化數據。Spring MVC提供下列幾個特性輔助進行上述所有活動:數據綁定機制,完全用從請求參數中獲得的數據填充一個對象;支持錯誤處理和驗證;JSP表單標記庫;基類控制器。使用@MVC,除了由于@ModelAttribute、@InitBinder和@SessionAttributes這些注解的存在而不再需要基類控制器外,其它一切都不需要改變。
@ModelAttribute注解
看一下這些請求處理方法簽名:
它們是非常有效的請求處理方法簽名。第一個方法處理初始的HTTP GET請求,準備被編輯的數據,返回一個Account對象供Spring MVC表單標簽使用。第二個方法在用戶提交更改時處理隨后的HTTP POST請求,并接收一個Account對象作為輸入參數,它是Spring MVC的數據綁定機制用請求中的參數自動填充的。這是一個非常簡單的程序模型。
Account對象中含有要被編輯的數據。在Spring MVC的術語當中,Account被稱作是表單模型對象。這個對象必須通過某個名稱讓表單標簽(還有數據綁定機制)知道它的存在。下面是從JSP頁面中截取的部分代碼,引用了一個名為“account”的表單模型對象:
即使我們沒有在任何地方指定“account”的名稱,這段JSP程序也會和上面所講的方法簽名協作的很好。這是因為@MVC用返回對象的類型名稱作為默認值,因此一個Account類型的對象默認的就對應一個名為“account”的表單模型對象。如果默認的不合適,我們就可以用 @ModelAttribute來改變它的名稱,如下所示:
此處setupModelAttribute()不是一個請求處理方法,而是任何請求處理方法被調用之前,用來準備表單項模型對象的一個方法。對那些熟悉 Spring MVC的老用戶來說,這和SimpleFormController的formBackingObject()方法是非常相似的。
最初的GET方法中我們得到一次表單模型對象,在隨后的POST方法中當我們依靠數據綁定機制用用戶所做的改變覆蓋已有的Account對象時,我們會第二次得到它,在這種表單處理場景中把@ModelAttribute放在方法上是很有用的。當然,作為一種兩次獲得對象的替換方案,我們也可以在兩次請求過程中將它保存進HTTP的會話(session),這就是我們下面將要分析的情況。
用@SessionAttributes存儲屬性
@SessionAttributes注解可以用來指定請求過程中要放進session中的表單模型對象的名稱或類型,下面是一些例子:
根據上面的注解,AccountFormController會在初始的GET方法和隨后的POST方法之間,把名為 “account”的表單模型對象(或者象第二個例子中的那樣,把所有Account類型的表單模型對象)存入HTTP會話(session)中。不過,當有改變連續發生的時候,就應當把屬性對象從會話中移除了。我們可以借助SessionStatus實例來做這件事,如果把它添加進onSubmit的方法簽名中,@MVC會完成這個任務:
定制數據綁定
有時數據綁定需要定制,例如我們也許需要指定必需填寫的域,或者需要為日期、貨幣金額等類似事情注冊定制的PropertyEditors。用@MVC實現這些功能是非常容易的:
@InitBinder注解的方法可以訪問@MVC用來綁定請求參數的DataBinder實例,它允許我們為每個控制器定制必須項。
數據綁定結果和驗證
數據綁定也許會導致類似于類型轉換或域缺失的錯誤。不管發生什么錯誤,我們都希望能返回到編輯的表單,讓用戶自行更正。要想實現這個目的,我們可直接在方法簽名的表單模型對象后面追加一個BindingResult對象,例程如下:
發生錯誤時我們返回到出現問題的視圖,并把從BindingResult得到的屬性增加到模型上,這樣特定域的錯誤就能夠反饋給用戶。要注意的是,我們并沒有指定一個顯式的視圖名,而是允許DispatcherServlet依靠與入口URI路徑信息匹配的默認視圖名。
調用Validator對象并把BindingResult傳給它,僅這一行代碼就可實現驗證操作。這允許我們在一個地方收集綁定和驗證錯誤:
現在是時候結束我們的Spring 2.5 Web層注解(非正式稱法為@MVC)之旅了。
總結
Web層的注解已經證明是相當有用的,不僅是因為它能夠大大減少XML配置文件的數量,而且還在于它能成就一個可自由訪問 Spring MVC控制器技術的精致、靈活和簡潔的程序設計模型。我們強烈推薦使用“慣例優先原則(convention-over-configuration)” 特性,以及以處理器映射為中心的策略給控制器派發請求,避免在源碼中嵌入URI路徑或是定義顯式的視圖名引用。
最后是本文沒有討論,但值得關注的一些非常重要的Spring MVC擴展。最新發布的Spring Web Flow版本2添加了一些特性,例如基于JSF視圖的Spring MVC、Spring的JavaScript庫,還有支持更先進編輯場景的高級狀態和導航管理。
總結
以上是生活随笔為你收集整理的Spring2.5的新特性的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: jbpm已办任务
- 下一篇: hibernate QBC检索方式查询