Version 5.0.7.RELEASE
1.Kotlin
Kotlin是一門運(yùn)行于JVM(或其他平臺(tái))之上的靜態(tài)語(yǔ)言,它可以在提供和現(xiàn)有Java編寫的庫(kù)的良好交互性的同時(shí),寫出簡(jiǎn)明優(yōu)雅的代碼。
Spring框架為Kotlin提供第一手的支持從而使得開發(fā)者在開發(fā)Kotlin應(yīng)用時(shí)會(huì)感覺(jué)Spring框架就像是用原生Kotlin編寫的框架一樣。
學(xué)習(xí)Spring + Kotlin的方式就是看這篇全面的教程。您可以順便加入Kotlin Slack的#spring頻道,或在需要支持的時(shí)候在Stackoverflow上使用spring或kotlin標(biāo)簽來(lái)提問(wèn)。
1.1.系統(tǒng)需求
Spring框架支持Kotlin 1.1+,并且需要在classpath里提供kotlin-stdlib(或者其他對(duì)應(yīng)的某個(gè),例如kotlin-stdlib-jre8對(duì)應(yīng)Kotlin 1.1,kotlin-stdlib-jdk8對(duì)應(yīng) Kotlin 1.2)和kotlin-reflect。如果是在start.spring.io上啟動(dòng)的Kotlin項(xiàng)目的話,這些依賴默認(rèn)就會(huì)被提供。
1.2.擴(kuò)展
Kotlin的擴(kuò)展提供了給現(xiàn)有存在的類添加額外功能的能力。Spring框架的Kotlin API充分運(yùn)用了擴(kuò)展來(lái)給現(xiàn)有的Spring API提供新的Kotlin獨(dú)有的便利。
Spring框架 KDoc API上列舉并給出了所有可用的Kotlin擴(kuò)展和DSL的文檔。
要記住Kotlin的擴(kuò)展在使用之前要先導(dǎo)入。舉例來(lái)說(shuō),
GenericApplicationContext.registerBean這個(gè)Kotlin擴(kuò)展只有在org.springframework.context.support.registerBean被導(dǎo)入了才會(huì)起作用。也就是說(shuō),和靜態(tài)導(dǎo)入類似,IDE應(yīng)該在大多數(shù)情境下對(duì)這種導(dǎo)入進(jìn)行自動(dòng)提示。
例如,Kotin 具體類型參數(shù)提供了針對(duì)JVM泛型類型擦除的一種變通方案,并且Spring框架提供了很多運(yùn)用了此功能的擴(kuò)展。這使得Kotlin版的API,例如Spring WebFlux上的新的WebClient——RestTemplate,還有其他很多API,都變得更好了。
像Reactor和Spring Data等庫(kù)的API也提供了Kotlin擴(kuò)展,因此從整體上給予了Kotlin更好的開發(fā)體驗(yàn)。
想要在Java中獲取一系列Foo對(duì)象,我們會(huì)自然地寫成這樣:
Flux<User> users = client.get().retrieve().bodyToFlux(User.class)
但在使用了Spring的Kotlin擴(kuò)展后,我們可能會(huì)這樣寫:
val users = client.get().retrieve().bodyToFlux<User>()
// 或者(兩者是等價(jià)的)
val users : Flux<User> = client.get().retrieve().bodyToFlux()
和在Java里一樣,Kotlin中的users是強(qiáng)類型的,但Kotlin更聰明的類型推斷可以簡(jiǎn)化語(yǔ)法。
1.3.空值安全
Kotlin的關(guān)鍵特征之一就是空值安全——在編譯期間干凈地解決null值問(wèn)題,而不是在運(yùn)行期間撞到著名的NullPointerException異常。使用可空聲明和“值或非值”表達(dá)式可以讓應(yīng)用更加安全,而不用花費(fèi)精力在Optional這種包裝器上。(Kotlin允許在空值上使用函數(shù);參考這篇Kotlin空值安全全面教程。)
盡管Java不能在其類型系統(tǒng)中表達(dá)空值安全,但Spring框架現(xiàn)在通過(guò)位于org.springframework.lang包下的工具友好注解提供了一套全SPring框架適用的空值安全API。默認(rèn)情況下,Kotlin中使用的來(lái)自Java API的類型會(huì)被認(rèn)為是平臺(tái)類型的,這種類型不會(huì)執(zhí)行嚴(yán)格的控制檢查。Kotlin對(duì)JSR 305注解的支持 + Spring可空性注解通過(guò)在編譯期間解決null相關(guān)問(wèn)題可以給Kotlin開發(fā)者提供整個(gè)Spring框架的空值安全。
Reactor和Spring Data等庫(kù)借助這一功能提供了空值安全API。
JSR 305檢查可以通過(guò)添加 -Xjsr305編譯器標(biāo)識(shí)來(lái)配置,其選項(xiàng)為:-Xjsr305{strict|warn|ignore}。
在Kotlin 1.1+中,默認(rèn)的行為和-Xjsr305=warn是一致的??紤]到Kotlin從Spring API中推斷的類型,想要啟用Spring框架API的空值安全需要使用strict。但是心里需要明白,Spring API的可空性聲明即使是小版本發(fā)布時(shí)也可能會(huì)更新,而且在未來(lái)有更多的檢查會(huì)被添加進(jìn)去。
目前尚不支持泛型類型參數(shù)、可變參數(shù)和數(shù)組元素的可空性,但會(huì)在即將發(fā)布的新版本里提供。訪問(wèn)相關(guān)討論來(lái)獲取最新消息。
1.4.接口和類
Spring框架支持多種Kotlin構(gòu)造器,如通過(guò)主要構(gòu)造器、不可變類數(shù)據(jù)綁定以及函數(shù)可選參數(shù)默認(rèn)值等來(lái)實(shí)例化Kotlin類。
Kotlin的參數(shù)名稱是使用一個(gè)專用的KotlinReflectionParameterNameDiscoverer來(lái)識(shí)別的,它可以在不啟用Java 8 -parameters編譯選項(xiàng)進(jìn)行編譯的情況下找到接口方法參數(shù)的名字。
在序列化/反序列化JSON數(shù)據(jù)時(shí)要用到的Jackson Kotlin模塊,如果在classpath中能找到它就會(huì)自動(dòng)啟用,否則在Jackson和Kotlin都能檢查到卻找不到這個(gè)模塊的時(shí)候,會(huì)通過(guò)日志記錄一條警告。
1.5.注解
Spring框架同樣也使用了Kotlin空值安全確認(rèn)某個(gè)HTTP參數(shù)是否是必填的,而不需要定義required屬性。這也意味著@RequestParam name: String?會(huì)被當(dāng)做非必填項(xiàng),反之@RequestParam name: String會(huì)被當(dāng)做必填項(xiàng)。該功能同樣支持Spring信息@Header注解。
在更簡(jiǎn)潔的風(fēng)格中,使用@Autowired、@Bean、@Inject的Spring bean注入會(huì)使用該信息來(lái)確定某個(gè)bean是不是必須注入的。
例如,@Autowired lateinit var foo: Foo意味著Foo類型的bean必須注冊(cè)到應(yīng)用上下文里,而@Autowired lateinit var foo: Foo?在這個(gè)bean不存在時(shí)則不會(huì)拋出異常。
同理,@Bean fun baz(foo: Foo, bar: Bar?) = Baz(foo, bar)意味著Foo類型的bean必須注冊(cè)到應(yīng)用上下文里,但Bar類型的bean則可存在可不存在。同樣的行為也存在于自動(dòng)注入構(gòu)造器參數(shù)里。
如果你正在使用具有屬性的類或主要構(gòu)造器參數(shù)中使用bean驗(yàn)證,那么你可能需要使用用方靶向注解(annotation use-site target),譬如
@field:NotNull或@get:Size(min=5, max=5),就像這篇Stack Overflow回復(fù)所討論的。
1.6.定義bean的DSL
Spring 5框架提供了基于lambda的函數(shù)式的一種注冊(cè)bean的方法作為XML或Java代碼式配置(@Configuration和@Bean)的替代方案。簡(jiǎn)單地說(shuō),它可以像FactoryBean那樣借助lambda來(lái)注冊(cè)bean。這種機(jī)制不需要任何反射或者CGLIB代理,故效率非常好。
我們先使用Java來(lái)實(shí)現(xiàn)一個(gè)例子:
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean(Foo.class);
context.registerBean(Bar.class, () -> new Bar(context.getBean(Foo.class))
但是在使用了Kotlin的具體類型參數(shù)和GenericApplicationContext后,可以簡(jiǎn)單地?fù)Q成這種寫法:
val context = GenericApplicationContext().apply {
registerBean<Foo>()
registerBean { Bar(it.getBean<Foo>()) }
}
為了更加接近聲明式的風(fēng)格和更加簡(jiǎn)明的語(yǔ)法,Spring提供了Kotlin 定義bean 的DSL。它通過(guò)簡(jiǎn)潔的聲明式API聲明了一個(gè)ApplicationContextInitializer,用來(lái)處理自定義bean注冊(cè)的profile和Enviroment。
fun beans() = beans {
bean<UserHandler>()
bean<Routes>()
bean<WebHandler>("webHandler") {
RouterFunctions.toWebHandler(
ref<Routes>().router(),
HandlerStrategies.builder().viewResolver(ref()).build()
)
}
bean("messageSource") {
ReloadableResourceBundleMessageSource().appy {
setBasename("messages")
setDefaultEncoding("UTF-8)
}
}
bean {
val prefix = "classpath:/templates/"
val suffix = ".mustache"
val loader = MustacheResourceTemplateLoader(prefix, suffix)
MustacheViewResolver(Mustache.compiler().withLoader(loader)).apply {
setPrefix(prefix)
setSuffix(suffix)
}
}
profile("foo") {
bean<Foo>()
}
}
這這段代碼中,bean<Routes>()可以使用構(gòu)造器實(shí)現(xiàn)自動(dòng)注入,ref<Routes>()是applicationContext.getBean(Routes::class.java)的簡(jiǎn)寫。
beans()方法用來(lái)給應(yīng)用上下文注冊(cè)bean。
val context = GenericApplicationCotnext().apply {
beans().initialize(this)
refresh()
}
DSL是可編程的,也就是說(shuō)可以使用使用
if、for循環(huán)和其他任何Kotlin功能來(lái)自定義bean的注冊(cè)邏輯。
參考Spring的Kotlin函數(shù)式bean聲明獲取更多示例。
Spring Boot是基于Java代碼式配置的,并且目前不特別提供函數(shù)式bean聲明的支持。但可以通過(guò)Spring Boot的
ApplicationContextInitializer來(lái)實(shí)驗(yàn)性地開啟函數(shù)式bean聲明的使用。參考這個(gè)Stack Overflow的回答來(lái)了解更多細(xì)節(jié)和最新信息。
1.7.Web
1.7.1.WebFlux函數(shù)式DSL
Spring框架現(xiàn)在提供了一種Kotlin路由DSL,使開發(fā)者可以將WebFlux函數(shù)式API替換為更加簡(jiǎn)潔并符合習(xí)慣的Kotlin代碼:
router {
accept(TEXT_HTML).nest {
GET("/") { ok().render("index") }
GET("/sse") { ok().render("sse") }
GET("/users", userHandler::findAllView)
}
"/api".nest {
accept(APPLICATION_JSON).nest {
GET("/users", userHandler::findAll)
}
accept(TEXT_EVENT_STREAM).nest {
GET("/users", userHandler::stream)
}
}
resources("/**", ClassPathResource("static/"))
}
這里的DSL是可編程的,也就是說(shuō)可以使用使用
if、for循環(huán)和其他任何Kotlin功能來(lái)自定義注冊(cè)邏輯。當(dāng)路由的注冊(cè)依賴于動(dòng)態(tài)數(shù)據(jù)(例如來(lái)自數(shù)據(jù)庫(kù)的數(shù)據(jù))時(shí),這種方式會(huì)非常有用。
參考MiXiT項(xiàng)目的路由獲取具體示例。
1.7.2.Kotlin腳本模板
自4.3起,Spring框架提供了一種[ScriptTemplateView](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/servlet/view/script/ScriptTemplateView.html),它可以使用支持JSR-223的腳本引擎來(lái)渲染模板。Spring 5在WebFlux中進(jìn)一步擴(kuò)展了這項(xiàng)功能,并使之支持了i18n和模板嵌套。
Kotlin提供了類似的支持并且支持渲染基于Kotlin的模板,閱讀這個(gè)提交獲取詳情。
于是我們有了許多有趣的應(yīng)用場(chǎng)景——例如使用kotlinx.htmlDSL來(lái)編寫類型安全的模板,或簡(jiǎn)單地以插值的形式使用Kotlin的多行String。
這也使得在支持Kotlin的IDE中編寫Kotlin模板時(shí),可以獲取全面的自動(dòng)補(bǔ)全和重構(gòu)支持:
import io.spring.demo.*
"""
${include("header")}
<h1>${i18n("title)}</h1>
<ul>
${users.joinToLine{ "<li>${i18n("user")} ${it.firstname} ${it.lastname}</li>" }}
</ul>
${include("footer")}
"""
參考Kotlin腳本模板示例項(xiàng)目獲取詳細(xì)信息。
1.8.Spring 項(xiàng)目中的Kotlin
本節(jié)的重點(diǎn)是在使用Kotlin開發(fā)Spring項(xiàng)目時(shí)需要特別注意的地方和值得了解的建議。
1.8.1.默認(rèn)是final的
默認(rèn)情況下,Kotlin的所有類都是final的。用在類上的open標(biāo)識(shí)符和Java里的final是正好相反的:加上它意味著其他類可以繼承這個(gè)類。open同樣可以用于成員函數(shù),想要重寫成員函數(shù),就得給它們打上open標(biāo)識(shí)符。
盡管Kotlin的JVM友好設(shè)計(jì)整體上對(duì)Spring是沒(méi)有反作用的,但如果沒(méi)有考慮到Kotlin的這項(xiàng)特性的話,那么在項(xiàng)目開始之初就可能就會(huì)遇到阻礙。這是因?yàn)镾pring的bean通常是使用CGLIB來(lái)做代理的。例如@Configuration類,由于技術(shù)原因會(huì)在運(yùn)行時(shí)被繼承。變通的方案就是給每個(gè)通過(guò)CGLIB代理的類和成員方法都加上open關(guān)鍵字,但很快這種做法就會(huì)變得讓人難以忍受,同事這也違背了Kotlin想要使代碼保持簡(jiǎn)潔和可控的初衷。
幸運(yùn)的是,Kotlin如今推出了一個(gè)叫做[kotlin-spring](https://kotlinlang.org/docs/reference/compiler-plugins.html#kotlin-spring-compiler-plugin)的插件,它是kotlin-allopen插件的一個(gè)預(yù)配置版本,會(huì)給添加了如下注解的類及它們的成員方法自動(dòng)加上open標(biāo)識(shí)符:
@Component@Async@Transactional@Cacheable
元注解支持意味著使用@Configuration、@Controller、@RestController、@Service和@Repository注解的類型同樣會(huì)自動(dòng)添加open,因?yàn)檫@些注解都被@Component注解了。
start.spring.io默認(rèn)開啟這一功能,所以在實(shí)踐中您可以像寫Java代碼一樣編寫Kotlin bean,而不用添加額外的open關(guān)鍵字。
1.8.2.在持久化時(shí)使用不可變類
在Kotlin的主構(gòu)造器中使用只讀屬性是非常方便的,并且應(yīng)該考慮作為一項(xiàng)最佳實(shí)踐:
class Person(val name: String, val age: Int)
你也可以選擇加上data關(guān)鍵字來(lái)告訴編譯器自動(dòng)使用使用主構(gòu)造器中的所有成員生產(chǎn)出:
- equals()/hashCode()這對(duì)函數(shù)
- 形如“User(name=John, age=42)”的toString()函數(shù)
- 根據(jù)屬性聲明的位置而產(chǎn)生的一批componentN()函數(shù)
- copy()函數(shù)
即使當(dāng)Person的屬性是只讀的情況下,要改變某個(gè)單獨(dú)的屬性也是很容易的:
data class Person(val name: String, val age: Int)
val jack = Person(name = "Jack", age = 1)
val olderJack = jack.copy(age = 2)
通常的持久化技術(shù),像JPA,需要一個(gè)默認(rèn)構(gòu)造器來(lái)避免這種類型的設(shè)計(jì)。幸運(yùn)的是,現(xiàn)在Kotlin提供了插件kotlin-jpa,用于解決這種“默認(rèn)構(gòu)造器地獄”。它可以給使用了JPA注解的類生成無(wú)參構(gòu)造器。
如果想在其他持久化框架中使用這種機(jī)制,您可以配置kotlin-noarg插件。
對(duì)于使用了Spring Data對(duì)象映射(如MongoDB、Redis、Cassandra等)的模塊,在無(wú)需
kotlin-noarg插件的情況下也可以使用Kotlin不可變類。
1.8.3.依賴注入
我們推薦嘗試并喜歡上使用val只讀(同時(shí)也可以加上不可空性)屬性進(jìn)行構(gòu)造器注入的方式。
@Component
class YourBean(
private val mongoTemplate: MongoTemplate,
private val solrClient: SolrClient
)
自Spring 4.3起,只有一個(gè)構(gòu)造器的類,構(gòu)造器的參數(shù)會(huì)自動(dòng)注入。這也是為什么上述例子不需要聲明為
@Autowired constructor。
如果真的需要使用字段注入,那就使用lateinit var結(jié)構(gòu)。
@Component
class YourBean {
@Autowired
lateinit var mongoTemplate: MongoTemplate
@Autowired
lateinit var solrClient: SolrClient
}
1.8.4.配置屬性注入
在Java中,可以使用@Value("${property}")之類的注解注入配置屬性。然而在Kotlin中,$被用作字符串嵌入的保留字。
因此,如果想在Kotlin中使用@Value注解,$字符就需要轉(zhuǎn)義為@Value("\${property}")。
作為替代,可以聲明如下bean自定義屬性替換前綴:
@Bean
fun propertyConfigurer() = PropertySourcesPlaceholderConfigurer().apply {
setPlaceholderPrefix("%{")
}
現(xiàn)有仍在使用${...}的代碼(如Spring Boot actuator或者@LocalServerPort),可以使用配置bean來(lái)自定義,代碼如下:
@Bean
fun kotlinPropertyConfigurer() = PropertySourcesPlaceholderConfigurer().apply {
setPlaceholderPrefix("%{")
setIgnoreUnresolvablePlaceholders(true)
}
@Bean
fun defaultPropertyConfigurer() = PropertySourcesPlaceholderConfigurer()
如果使用了Spring Boot,那么可以使用
@ConfigurationProperties來(lái)代替@Value注解。因?yàn)榛跇?gòu)造器初始化的不可變類目前尚不支持,所以目前它只能用在lateinit或可空的var屬性(推薦使用這種形式)上。參考@ConfigurationProperties針對(duì)不可變POJO的綁定和@ConfigurationProperties針對(duì)接口的綁定這些問(wèn)題獲取詳細(xì)信息。
1.8.5.注解上的數(shù)組屬性
Kotlin注解和Java注解十分相像,但廣泛運(yùn)用于Spring的數(shù)組屬性的行為卻是不同的。如Kotlin文檔所述,不同于其他屬性,value屬性名可以省略并指定為vararg參數(shù)。
要理解這一點(diǎn),我們來(lái)看一下使用最廣泛的Spring注解之一的@RequestMapping。這個(gè)Java注解的定義是這樣的:
public @interface RequestMapping {
@AliasFor("path")
String[] value() default {};
@AliasFor("value")
String[] path() default {};
RequestMethod[] method() default {};
// ...
}
典型的@RequestMapping的使用場(chǎng)景是映射特定路徑和請(qǐng)求方式的HTTP請(qǐng)求到對(duì)應(yīng)的處理方法上。在Java中,給數(shù)組屬性傳入單個(gè)值是可以的,并且它會(huì)自動(dòng)轉(zhuǎn)化為一個(gè)數(shù)組。
這就是為什么我們可以直接寫@RequestMapping(value = "/foo", method= RequestMethod.GET)或@RequestMapping(path = "/foo", method = RequestMethod.GET)。
然而,在Kotlin 1.2+,我們得寫成@RequestMapping("/foo", method = [RequestMethod.GET])或者@RequestMapping(path = ["/foo"], method = [RequestMethod.GET])(需要給數(shù)組屬性加上方括號(hào))。
要換掉這個(gè)特殊的method屬性(大多數(shù)情況下只有一個(gè)值)的方式,可以使用像@GetMapping或者@PostMapping這樣的快捷注解。
提示:如果
@RequestMapping的method注解沒(méi)有指定,所有的HTTP請(qǐng)求方法都將被匹配,而不單單是GET方法。
1.8.6.測(cè)試
逐類生命周期
Kotlin允許使用反引號(hào)給測(cè)試方法指定更有意義的名字。并且自JUnit 5起,Kotlin測(cè)試類可以使用@TestInstance(TestInstance.Lifecycle.PER_CLASS)注解使測(cè)試類只實(shí)例化一次,所以我們可以在非靜態(tài)方法上使用@BeforeAll和@AfterAll注解。這些都很適合Kotlin。
借助junit-platform.properties文件的junit.jupiter.testinstance.lifecycle.default = per_class配置項(xiàng),我們現(xiàn)在可以將默認(rèn)行為改為PER_CLASS。
class IntegrationTests {
val application = Application(8181)
val client = WebClient.create("http://localhost:8181")
@BeforeAll
fun beforeAll() {
application.start()
}
@Test
fun `Find all users on HTML page`() {
client.get().uri("/users")
.accept(TEXT_HTML)
.retrieve()
.bodyToMono<String>()
.test()
.expectNextMatches { it.contains("Foo") }
.verifyComplete()
}
@AfterAll
fun afterAll() {
application.stop()
}
}
描述式測(cè)試
可以使用Kotlin和JUnit 5創(chuàng)建描述式測(cè)試。
class SpecificationLikeTests {
@Tested
@DisplayName("a calculator")
inner class Calculator {
val calculator = SampleCalculator()
@Test
fun `should return the result of adding the first number to the second number`() {
val sum = calculator.sum(2, 4)
assertEquals(6, sum)
}
@Test
fun `should return the result of subtracting the second number from the first number` () {
val subtract = calculator.subtract(4, 2)
assertEquals(2, subtract)
}
}
}
WebTestClient類型推斷在Kotlin中的問(wèn)題
由于一個(gè)類型推斷問(wèn)題,所以確保使用Kotlin的expectBody擴(kuò)展(像.expectBody<String>().isEqualTo("foo"))作為Kotlin在遇到Java API協(xié)作問(wèn)題的變通。
同時(shí)請(qǐng)參考SPR-16057相關(guān)問(wèn)題。
1.9.起步
1.9.1.start.spring.io
開發(fā)新的使用Kotlin的Spring 5項(xiàng)目的最簡(jiǎn)單的方式就是在start.spring.io上創(chuàng)建一個(gè)新的Spring Boot 2項(xiàng)目。
如這篇博客所述,創(chuàng)建一個(gè)獨(dú)立的WebFlux項(xiàng)目也是可以的。
1.9.2.選擇web風(fēng)格
Spring框架現(xiàn)在提供兩套不同的web技術(shù)棧:Spring MVC和Spring WebFlux。
如果開發(fā)者想創(chuàng)建處理延遲、長(zhǎng)連接、流等場(chǎng)景,或僅僅是想要使用web函數(shù)式KotlinDSL,那么推薦使用Spring WebFlux。
對(duì)于其他使用場(chǎng)景,特別是使用了JPA這樣的阻塞技術(shù),那么Spring MVC和它的基于注解的編程模型是一個(gè)完美有效且充分支持的選擇。
1.10.資源
- Kotlin語(yǔ)言參考手冊(cè)
- Kotlin Slack(有專用的#spring頻道)
- 具有
spring和kotlin標(biāo)簽的Stackoverflow - 在瀏覽器中嘗試Kotlin
- Kotlin 博客
- 了不起的Kotlin
1.10.1.教程
1.10.2.博客文章
- 使用Kotlin開發(fā)Spring Boot應(yīng)用
- 基于PostgreSQL、Spring Boot和Kotlin的空間信息系統(tǒng)
- Spring框架 5.0中的Kotlin支持介紹
- 函數(shù)式Spring框架 5 Kotlin API
1.10.3.示例
- spring-boot-kotlin-demo:常規(guī)Spring Boot + Spring Data JPA項(xiàng)目
- mixit:Spring Boot 2 + WebFlux + Reactive Spring Data MongoDB
- spring-kotlinfunctional:獨(dú)立WebFlux + 函數(shù)式bean定義DSL
- spring-kotlin-fullstack:使用Kotlin2js替換JavaScrip或TypeScript的WebFlux Kotlin 全棧示例
- spring-petclinic-kotlin:Spring PetClinic 示例項(xiàng)目的Kotlin版
- spring-kotlin-deepdive:一步步從Boot 1.0 + Java 遷移到Boot 2.0 + Kotlin
1.10.4.問(wèn)題
這里是一系列關(guān)于Spring + Kotlin支持的現(xiàn)有問(wèn)題。
Spring框架
Spring Boot
@ConfigurationProperties針對(duì)不可變POJO的綁定@ConfigurationProperties針對(duì)接口的綁定- 通過(guò)
SpringApplication暴露函數(shù)式bean注冊(cè)API - 在Spring Boot API上添加空安全注解
- 使用Kotlin的bom給Kotlin提供依賴管理
Kotlin
- Spring框架支持的父問(wèn)題
- Kotlin需要類型推斷而Java不需要
- 更好的泛型空安全支持
- 開放類的智能回退轉(zhuǎn)換
- 如果不是所有參數(shù)都是SAM參數(shù),就無(wú)法將參數(shù)作為函數(shù)傳入
- 給泛型類型參數(shù)添加JSR 305元注解
- 給庫(kù)添加一個(gè)方式避免Kotlin 1.0和1.1依賴混在一起
- 通過(guò)腳本變量直接支持JSR 223綁定
- 在Kotlin Eclipse插件中支持 all-open and no-arg 編譯器插件
2.Apache Groovy
Groovy是一門強(qiáng)大、可選參數(shù)類型的動(dòng)態(tài)語(yǔ)言,并具有靜態(tài)類型和靜態(tài)編譯的能力。它提供了簡(jiǎn)潔的語(yǔ)法,并且可以順利地整合進(jìn)任何現(xiàn)有的Java項(xiàng)目。
Spring框架提供了一個(gè)專用的ApplicationContext用以支持基于Groovy的bean定義DSL。更多信息請(qǐng)參考《GroovyBean定義DSL》。
關(guān)于Groovy的更多支持,如基于Groovy編寫的bean、可刷新腳本bean等在下節(jié)《動(dòng)態(tài)語(yǔ)言支持》中會(huì)介紹。
3.動(dòng)態(tài)預(yù)言支持
3.1.簡(jiǎn)介
Spring 2.0引入了在Spring中使用那些用動(dòng)態(tài)語(yǔ)言(如JRuby)定義的類和對(duì)象的功能的全面支持。這使得您可以使用某種可支持的動(dòng)態(tài)語(yǔ)言編寫任意數(shù)量的類,并讓Spring容器透明地給結(jié)果對(duì)象實(shí)例化、配置、注入。
目前支持的動(dòng)態(tài)語(yǔ)言有:
- JRuby 1.5+
- Groovy 1.8+
- BeanShell 2.0
為什么只支持這些語(yǔ)言?
選擇這些語(yǔ)言的原因:1.這些語(yǔ)言在Java企業(yè)社區(qū)有很大的吸引力;2.在添加這些語(yǔ)言的支持時(shí)不需要求助于其他語(yǔ)言;3.Spring的開發(fā)者最熟悉這些語(yǔ)言。
有關(guān)這些動(dòng)態(tài)語(yǔ)言支持可以立即發(fā)揮作用的完整可用的示例將在《場(chǎng)景》這一節(jié)討論。
3.2.第一個(gè)實(shí)例
本章節(jié)的大部分篇幅都集中在詳細(xì)討論動(dòng)態(tài)語(yǔ)言的支持上。在深入研究這些動(dòng)態(tài)語(yǔ)言支持的輸入和輸出之前,我們先來(lái)速覽一個(gè)使用動(dòng)態(tài)語(yǔ)言定義bean的實(shí)例。定義這個(gè)bean的動(dòng)態(tài)語(yǔ)言是Groovy(這個(gè)實(shí)例從Spring測(cè)試用例中抽出來(lái)的,如果你想看一下其他語(yǔ)言的等價(jià)實(shí)現(xiàn),請(qǐng)參考其源碼)。
下文是Groovy bean將要實(shí)現(xiàn)的Messenger接口,注意它是使用純Java來(lái)定義的。其他需要注入Messenger實(shí)例的對(duì)象并不知道其接口實(shí)現(xiàn)是基于Groovy的。
package org.springframework.scripting;
public interface Messenger {
String getMessage();
}
這里定義一個(gè)依賴Messenger接口的類:
package org.springframework.scripting;
public class DefaultBookingService implements BookingService {
private Messenger messenger;
public void setMessenger(Messenger messenger) {
this.messenger = messenger;
}
public void processBooking() {
// 注入進(jìn)來(lái)的Messenger對(duì)象的使用…
}
}
這里是使用Groovy對(duì)Messenger接口的實(shí)現(xiàn):
// 來(lái)自“Messenger.groovy”文件
package org.springframework.scripting.groovy;
// 導(dǎo)入將要被實(shí)現(xiàn)的Messenger(使用Java編寫)接口
import org.springframework.scripting.Messenger
// 使用Groovy實(shí)現(xiàn)接口
class GroovyMessenger implements Messenger {
String message
}
最后給出的是會(huì)使Groovy所定義的Messenger實(shí)現(xiàn)注入到DefaultBookingService類起作用的bean配置代碼。
要使用自定義動(dòng)態(tài)語(yǔ)言標(biāo)簽來(lái)定義bean,開發(fā)者需要在Spring XML配置文件的頂部注明對(duì)應(yīng)的XML Schema。此外,還得使用Spring
ApplicationContext作為控制反轉(zhuǎn)容器的實(shí)現(xiàn)。雖然使用簡(jiǎn)單的BeanFactory實(shí)現(xiàn)也是支持的,但開發(fā)者需要管理Spring內(nèi)部的管道。
更多基于schema的配置信息,請(qǐng)參閱基于XML Schema的配置方式。
<?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:lang="http://www.springframework.org/schema/lang"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang.xsd">
<!-- 定義Groovy的Messenger bean -->
<lang:groovy id="messenger" script-source="classpath:Messenger.groovy">
<lang:property name="message" value="I Can Do The Frug" />
</lang:groovy>
<!-- 被注入了Groovy實(shí)現(xiàn)的一個(gè)正常的bean -->
<bean id="bookingService" class="x.y.DefaultBookingService">
<property name="messenger" ref="messenger" />
</bean>
</beans>
因?yàn)楸蛔⑷氲?code>Messenger實(shí)例的確是實(shí)例化了,所以bookingService bean(DefaultBookingService的實(shí)現(xiàn))現(xiàn)在可以像平常一樣使用這個(gè)私有成員變量。在這里,只有普通的Java和普通的Groovy,沒(méi)有任何特別的地方。
希望上面的XML代碼段是不言自明的,如果不是的話也不要過(guò)分擔(dān)心。請(qǐng)繼續(xù)閱讀有關(guān)上述配置的原因和內(nèi)容的詳細(xì)信息。
3.3.定義動(dòng)態(tài)語(yǔ)言實(shí)現(xiàn)的bean
本節(jié)會(huì)討論如何使用任意被支持的動(dòng)態(tài)語(yǔ)言來(lái)定義Spring bean。
請(qǐng)記住本章并不試圖闡述被支持動(dòng)態(tài)語(yǔ)言的語(yǔ)法和方言。比如說(shuō),如果你想在應(yīng)用上使用Groovy編寫某些類,那么前提就是你已經(jīng)學(xué)會(huì)使用Groovy了。如果需要更多關(guān)于動(dòng)態(tài)語(yǔ)言本身的信息,請(qǐng)參考本章末尾的《更多資源》部分。
3.3.1.通用概念
使用動(dòng)態(tài)語(yǔ)言bean需要如下步驟:
- 編寫動(dòng)態(tài)語(yǔ)言源碼的測(cè)試(當(dāng)然了)
- 編寫動(dòng)態(tài)語(yǔ)言源碼本身
- 在XML配置文件中使用適當(dāng)?shù)?code><lang:language/>定義這些動(dòng)態(tài)語(yǔ)言bean(當(dāng)然也可以使用Spring API編程式地定義這些bean,不過(guò)你得自行查閱源碼尋找方法,因?yàn)楸菊虏簧婕斑@種進(jìn)階配置的介紹)。記住這是一個(gè)不斷迭代的步驟。每個(gè)動(dòng)態(tài)語(yǔ)言源碼文件至少包含一個(gè)bean的定義(不過(guò)一個(gè)動(dòng)態(tài)語(yǔ)言源碼文件可以被多個(gè)bean的定義引用)。
前兩個(gè)步驟(測(cè)試和編寫動(dòng)態(tài)語(yǔ)言源碼)不在本章的范圍之內(nèi),請(qǐng)自行參閱相關(guān)語(yǔ)言說(shuō)明或參考手冊(cè)進(jìn)行開發(fā)。不過(guò)你仍然可以先閱讀本章剩余部分,因?yàn)镾pring的動(dòng)態(tài)語(yǔ)言支持確實(shí)對(duì)動(dòng)態(tài)語(yǔ)言的源碼文件做了一些(小的)假設(shè)。
<lang:language/> 標(biāo)簽
最后一步是定義每個(gè)你想要配置的動(dòng)態(tài)語(yǔ)言bean的配置(和通常JavaBean的定義沒(méi)有什么不同)。然而,和以往通過(guò)指定想要由容器來(lái)實(shí)例化和配置的bean的完整類名的方式不同,我們得使用<lang:language/>標(biāo)簽來(lái)定義這些bean。
每種被支持的語(yǔ)言都有對(duì)應(yīng)的<lang:language/>標(biāo)簽:
-
<lang:groovy/>(Groovy) -
<lang:bsh/>(BeanShell) -
<lang:std/>(JSR-223)
配置時(shí)對(duì)應(yīng)的屬性和子標(biāo)簽取決于定義這個(gè)bean所使用的語(yǔ)言(針對(duì)具體語(yǔ)言特性的全部?jī)?nèi)幕將會(huì)在本節(jié)稍后提供)。
可刷新bean
Spring的動(dòng)態(tài)語(yǔ)言支持中最吸引人的附加價(jià)值就是“可刷新bean”功能。
可刷新bean是帶有少量配置的動(dòng)態(tài)語(yǔ)言bean。動(dòng)態(tài)語(yǔ)言bean可以監(jiān)視其所屬源代碼資源的改動(dòng),并在它們發(fā)生改變的時(shí)候重新加載自己(例如在開發(fā)者在文件系統(tǒng)上編輯并保存了源文件)。
這使得開發(fā)者可以部署任意數(shù)量的動(dòng)態(tài)語(yǔ)言源碼作為應(yīng)用的一部分,配置Spring容器創(chuàng)建基于這些動(dòng)態(tài)語(yǔ)言源碼的bean(使用本章提到的機(jī)制),并在之后當(dāng)需求發(fā)生了變動(dòng)或一些附加的要素添加進(jìn)來(lái)的時(shí)候,簡(jiǎn)單地編輯動(dòng)態(tài)語(yǔ)言源碼并讓所有的改動(dòng)反映這些被修改的源碼文件對(duì)應(yīng)的bean上即可。不需要停止正在運(yùn)行的應(yīng)用(如果是web應(yīng)用,也不需要重部署)。如此修改的動(dòng)態(tài)語(yǔ)言bean將從更改的動(dòng)態(tài)語(yǔ)言源文件中獲取新的狀態(tài)和邏輯。
請(qǐng)記住該功能默認(rèn)是關(guān)閉的。
讓我們通過(guò)一個(gè)示例來(lái)看一下開始使用可刷新bean是多么的簡(jiǎn)單吧。想要開啟可刷新bean的功能,只需要給對(duì)應(yīng)bean的`<lang:language/>標(biāo)簽附加一個(gè)屬性即可。假設(shè)繼續(xù)使用之前的例子,這里給出使可刷新bean起作用的修改后的Spring XML 配置文件:
<beans>
<!-- 由于存在“refresh-check-delay”屬性,這個(gè)bean現(xiàn)在是可刷新的 -->
<lang:groovy id="messenger"
refresh-check-delay="5000" <!-- 如果有改動(dòng),會(huì)在五秒內(nèi)觸發(fā)刷新 -->
script-source="classpath:Messenger.groovy">
<lang:property name="message" value="I Can Do The Frug" />
</lang:groovy>
<bean id="bookingService" class="x.y.DefaultBookingService">
<property name="messenger" ref="messenger" />
</bean>
</beans>
這就是所有你需要做的了。在“messenger”上定義的“refresh-check-delay”屬性是檢測(cè)到對(duì)應(yīng)源碼修改后觸發(fā)刷新的毫秒數(shù)。給它傳入一個(gè)復(fù)制就可以關(guān)閉刷新行為。請(qǐng)記住,默認(rèn)情況下,刷新行為是關(guān)閉的。如果不需要刷新行為,簡(jiǎn)單地不要定義該屬性就好。
現(xiàn)在運(yùn)行這個(gè)應(yīng)用,我們就可以演練一下可刷新bean的功能了。請(qǐng)理解下面代碼中“跳轉(zhuǎn)到等待輸入階段以暫停執(zhí)行”的場(chǎng)景。調(diào)用System.in.read()僅僅是為了讓項(xiàng)目暫停,從而使我們可以離開去修改對(duì)應(yīng)源碼文件從而在程序恢復(fù)執(zhí)行時(shí)觸發(fā)對(duì)應(yīng)bean的重載。
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.scripting.Messenger;
public final class Boot {
public static void main(final String[] args) throws Exception {
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
Messenger messenger = (Messenger) ctx.getBean("messenger");
System.out.println(messenger.getMessage());
// 暫停執(zhí)行從而可以去對(duì)源碼文件進(jìn)行修改…
System.in.read();
System.out.println(messenger.getMessage());
}
}
接下來(lái),假設(shè)在本示例中,Message的getMessage()方法需要變?yōu)檩敵鰡我?hào)包裹的消息。下文是我們?cè)诔绦驎和?zhí)行時(shí)對(duì)Messenger.groovy源文件所做的改動(dòng)。
package org.springframework.scripting
class GroovyMessenger implements Messenger {
private String message = "Bingo"
public String getMessage() {
// 修改方法實(shí)現(xiàn),將消息包裹在單引號(hào)里面
return "'" + this.message + "'"
}
public void setMessage(String message) {
this.message = message
}
}
程序在執(zhí)行的時(shí)候,在輸入暫停之前會(huì)輸出“I Can Do The Frug”。在修改并保存了源碼文件之后,再回復(fù)程序執(zhí)行,getMessage()方法的調(diào)用結(jié)果就變成了“'I Can Do The Frug'”(注意多出了單引號(hào))。
理解在'refresh-check-delay'的時(shí)間之內(nèi),腳本更改將不會(huì)觸發(fā)刷新是很重要的。和這一點(diǎn)同樣重要的是理解在動(dòng)態(tài)語(yǔ)言bean的方法被調(diào)用之前,腳本的改動(dòng)實(shí)際上不會(huì)被“拾取”。 只有動(dòng)態(tài)語(yǔ)言bean上的方法被調(diào)用的時(shí)候,系統(tǒng)才會(huì)檢查對(duì)應(yīng)的腳本源碼是否更改過(guò)了。任何和刷新腳本相關(guān)的異常(如遇到編譯異常,或?qū)?yīng)文件不存在)都會(huì)產(chǎn)生傳播到調(diào)用代碼時(shí)的致命異常。
上文提到的可刷新bean行為不適用于使用<lang:inline-script/>標(biāo)簽(參考《內(nèi)聯(lián)動(dòng)態(tài)語(yǔ)言源文件》)定義的腳本文件。它僅僅適用于那些源文件的更改可以清楚地被檢查到的bean。例如,使用代碼檢查存放于文件系統(tǒng)的動(dòng)態(tài)語(yǔ)言源文件的最后編輯時(shí)間。
內(nèi)聯(lián)動(dòng)態(tài)語(yǔ)言源文件
Spring同樣支持將動(dòng)態(tài)語(yǔ)言文件直接嵌入到Spring bean的定義中去。特別地,<lang:inline-script />標(biāo)簽可以直接在Spring的配置文件中編寫動(dòng)態(tài)語(yǔ)言源碼。為了更清楚地理解,還是舉個(gè)例子吧:
<lang:groovy id="messenger">
<lang:inline-script>
package org.springframework.scripting.groovy;
import org.springframework.scripting.Messenger
class GroovyMessenger implements Messenger {
String message
}
</lang:inline-script>
<lang:property name="message" value="I Can Do The Frug" />
</lang:groovy>
如果我們先不考慮將動(dòng)態(tài)語(yǔ)言源碼放到Spring的配置文件中是不是一種好的實(shí)踐的問(wèn)題,那么<lang:inline-script />標(biāo)簽在一些場(chǎng)景下就可以展現(xiàn)身手了。比如,我們可能會(huì)希望快速添加一個(gè)Spring Validator 的實(shí)現(xiàn)給Spring MVC 的Controller。這不過(guò)是使用內(nèi)聯(lián)源碼的一種情景。(參考《腳本式驗(yàn)證器》上的示例。)
理解動(dòng)態(tài)語(yǔ)言bean的上下文構(gòu)造器注入
關(guān)于Spring的動(dòng)態(tài)語(yǔ)言支持,有一點(diǎn)非常重要。也就是說(shuō),(當(dāng)前)不能夠給動(dòng)態(tài)語(yǔ)言bean的構(gòu)造器傳遞參數(shù)(因此構(gòu)造器注入在動(dòng)態(tài)語(yǔ)言bean上是不可用的)。為了完全搞懂這種針對(duì)構(gòu)造器和屬性的特殊處理,以下代碼和配置將不起作用。
// 來(lái)自文件 Messenger.groovy
package org.springframework.scripting.groovy;
import org.springframework.scripting.Messenger
class GroovyMessenger implements Messenger {
GroovyMessenger() {}
// this constructor is not available for Constructor Injection
GroovyMessenger(String message) {
this.message = message;
}
String message
String anotherMessage
}
<lang:groovy id="badMessenger"
script-source="classpath:Messenger.groovy">
<!-- 下面這個(gè)構(gòu)造器參數(shù)并不能注入到GroovyMessenger中 -->
<!-- 事實(shí)上,根據(jù)schema的定義,是不允許這樣寫的 -->
<constructor-arg value="不起作用" />
<!-- 只有屬性值可以注入到動(dòng)態(tài)語(yǔ)言對(duì)象中 -->
<lang:property name="anotherMessage" value="直接傳給動(dòng)態(tài)語(yǔ)言對(duì)象" />
</lang>
在實(shí)踐中這條限制并不如它起初看上去那么重要,因?yàn)閟etter注入模式被壓倒性數(shù)量的開發(fā)者所喜愛(ài)(讓我們改天再來(lái)討論這是不是一件好事)。
3.3.2.Groovy bean
待翻。
3.3.3.BeanShell bean
BeanShell庫(kù)依賴
Spring的BeanShell腳本支持需要在項(xiàng)目的classpath中引入如下庫(kù):
- bsh-2.0b4.jar
來(lái)自BeanShell的主頁(yè):
BeanShell是一個(gè)使用Java編寫的小型、免費(fèi)、可嵌入的具有動(dòng)態(tài)語(yǔ)言功能的Java源碼解釋器。BeanShell動(dòng)態(tài)地執(zhí)行標(biāo)準(zhǔn)Java語(yǔ)法,并給它擴(kuò)展了腳本語(yǔ)言共通的方便特征,如弱類型、指令和方法閉包,就像Perl和JavaScript里的那樣。
和Groovy不同,BeanShell bean需要(少許)額外配置。Spring的BeanShell動(dòng)態(tài)語(yǔ)言支持實(shí)現(xiàn)很有趣的一點(diǎn)在于:Spring創(chuàng)建了一個(gè)JDK動(dòng)態(tài)代理來(lái)實(shí)現(xiàn)所有指定了'script-interfaces'屬性的<lang:bsh>標(biāo)簽所對(duì)應(yīng)的接口(這也是為什么你必須至少給這個(gè)屬性提供一個(gè)接口,并且在使用BeanShell bean時(shí)(相應(yīng)地)編寫接口)。這意味著BeanShell對(duì)象上的所有方法的調(diào)用都會(huì)經(jīng)過(guò)JDK動(dòng)態(tài)代理調(diào)用機(jī)制。
我們來(lái)看一個(gè)使用BeanShell bean來(lái)實(shí)現(xiàn)之前章節(jié)定義的Messenger接口(為了方便閱讀在下文給出接口代碼)的一個(gè)完全可用的例子。
package org.springframework.scripting;
public interface Messenger {
String getMessage();
}
這里是BeanShell對(duì)Messenger接口的“實(shí)現(xiàn)”(不是嚴(yán)格意義的術(shù)語(yǔ))。
String message;
String getMessage() {
return message;
}
void setMessage(String aMessage) {
message = aMessage;
}
接下來(lái)是在Spring XML中定義上述“類”的“實(shí)例”(同樣,不是嚴(yán)格意義的術(shù)語(yǔ))。
<lang:bsh id="messageService" script-source="classpath:BshMessenger.bsh"
script-interfaces="org.springframework.scripting.Messenger">
<lang:property name="message" value="Hello World!" />
</lang:bsh>
參考《場(chǎng)景》,那里有些你可能會(huì)希望使用BeanShell bean的場(chǎng)景。
3.4.場(chǎng)景
當(dāng)然了,使用腳本語(yǔ)言定義Spring bean的可能場(chǎng)合多且有效。本節(jié)討論兩個(gè)可能的使用場(chǎng)景。
3.4.1.腳本式Spring MVC 控制器
在使用動(dòng)態(tài)語(yǔ)言bean時(shí),有一組類可能會(huì)從中受益,那就是Spring MVC 控制器。在純Spring MVC應(yīng)用中,web應(yīng)用的導(dǎo)航流很大程度上是封裝在Spring MVC控制器中的。由于需要修復(fù)問(wèn)題或更改業(yè)務(wù)需求,所以導(dǎo)航流和其他web應(yīng)用展示層邏輯常常需要修改。通過(guò)修改一個(gè)或多個(gè)動(dòng)態(tài)語(yǔ)言源文件就能看到它們?cè)谝粋€(gè)正在運(yùn)行的項(xiàng)目中立即生效,可能更容易完成此類修改需求。
記住,在如Spring這種輕量級(jí)架構(gòu)模型項(xiàng)目中,開發(fā)者的通常的目標(biāo)是使用真的非常輕量的展示層,將應(yīng)用中所有豐富的業(yè)務(wù)邏輯放置到領(lǐng)域?qū)雍蜆I(yè)務(wù)層的類中。使用動(dòng)態(tài)語(yǔ)言bean開發(fā)Spring MVC 控制器使開發(fā)者只需要編輯并保存文本文件就可以切換視圖層邏輯;在動(dòng)態(tài)語(yǔ)言源文件中的任何修改將(取決于配置)自動(dòng)映射到對(duì)應(yīng)的bean中去。
為了能夠自動(dòng)“拾取”到動(dòng)態(tài)語(yǔ)言bean上的任何更改,開發(fā)者需要啟動(dòng)“可刷新bean”功能。參考《可刷新bean》獲得關(guān)于該功能的詳細(xì)信息。
下文是使用Groovy動(dòng)態(tài)語(yǔ)言來(lái)實(shí)現(xiàn)org.springframework.web.servlet.mvc.Controller的例子。
// 來(lái)自文件 '/WEB-INF/groovy/FortuneController.groovy'
package org.springframework.showcase.fortune.web
import org.springframework.showcase.fortune.service.FortuneService
import org.springframework.showcase.fortune.domain.Fortune
import org.springframework.web.servlet.ModelAndView
import org.springframework.web.servlet.mvc.Controller
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
class FortuneController implements Controller {
@Property FortuneService fortuneService
ModelAndView handleRequest(HttpServletRequest request,
HttpServletResponse httpServletResponse) {
return new ModelAndView("tell", "fortune", this.fortuneService.tellFortune())
}
}
<lang:groovy id="fortune"
refresh-check-delay="3000"
script-source="/WEB-INF/groovy/FortuneController.groovy">
<lang:property name="fortuneService" ref="fortuneService"/>
</lang:groovy>
3.4.2.腳本式校驗(yàn)器
另一個(gè)使用動(dòng)態(tài)語(yǔ)言bean可以提高Spring項(xiàng)目開發(fā)的靈活性的領(lǐng)域是校驗(yàn)。也許使用弱類型的動(dòng)態(tài)語(yǔ)言(也許還支持內(nèi)聯(lián)正則表達(dá)式)來(lái)提供復(fù)雜驗(yàn)證邏輯相較于傳統(tǒng)的Java會(huì)更加方便。
再說(shuō)一遍,使用動(dòng)態(tài)語(yǔ)言bean是你可以僅僅通過(guò)編輯和保存文本文件就可以更改校驗(yàn)邏輯;任何更改都將(取決于配置)自動(dòng)映射到已經(jīng)啟動(dòng)的應(yīng)用上,而無(wú)需重啟項(xiàng)目。
請(qǐng)記住,為了能夠自動(dòng)“拾取”到動(dòng)態(tài)語(yǔ)言bean上的任何更改,開發(fā)者需要啟動(dòng)“可刷新bean”功能。參考《可刷新bean》獲得關(guān)于該功能的詳細(xì)信息。
下文是使用Groovy動(dòng)態(tài)語(yǔ)言實(shí)現(xiàn)一個(gè)Spring org.springframework.validation.Validator校驗(yàn)器的例子。(參考《使用Spring Validator接口進(jìn)行校驗(yàn)》獲取關(guān)于Validator接口的更多信息。)
import org.springframework.validation.Validator
import org.springframework.validation.Errors
import org.springframework.beans.TestBean
class TestBeanValidator implements Validator {
boolean supports(Class clazz) {
return TestBean.class.isAssignableFrom(clazz)
}
void validate(Object bean, Errors errors) {
if(bean.name?.trim()?.size() > 0) {
return
}
errors.reject("whitespace", "Cannot be composed wholly of whitespace.")
}
}
3.5.雜七雜八
本節(jié)包含于動(dòng)態(tài)語(yǔ)言支持相關(guān)的零碎的事情。
3.5.1.AOP - 切入腳本式bean
使用Spring AOP 切入腳本式bean是可行的。Spring AOP框架事實(shí)上并不知道將要切入的某個(gè)bean是不是腳本式bean。所以在你的開發(fā)中,所有可能會(huì)用到或打算用的AOP的使用場(chǎng)景和功能都能工作在腳本式bean上。但也有一件(?。┦虑樵谇腥肽_本式bean時(shí)是要注意的,那就是不能使用基于類的代理,必須得使用(基于接口的代理)[https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#aop-proxying]。
當(dāng)然不僅切入腳本式bean時(shí)沒(méi)有限制的,使用某種被支持的動(dòng)態(tài)語(yǔ)言來(lái)編寫切面處理其本身從而切入其他Spring bean也是可以的。盡管這真得算得上是動(dòng)態(tài)語(yǔ)言支持的高階用法了。
3.5.2.Scope
如果不是很明顯,腳本式bean當(dāng)然可以像任何其他bean一樣設(shè)置scope。各種<lang:language />標(biāo)簽上的scope屬性使你可以控制對(duì)應(yīng)的腳本式bean的scope,就像傳統(tǒng)的bean一樣。(默認(rèn)的scope是(單例的)[https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#beans-factory-scopes-singleton],和“傳統(tǒng)的”bean一樣。)
下文是使用scope屬性定義一個(gè)Groovy bean的scope為(prototype)[https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#beans-factory-scopes-prototype]。
<?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:lang="http://www.springframework.org/schema/lang"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang.xsd">
<lang:groovy id="messenger" script-source="classpath:Messenger.groovy" scope="prototype">
<lang:property name="message" value="I Can Do The RoboCop" />
</lang:groovy>
<bean id="bookingService" class="x.y.DefaultBookingService">
<property name="messenger" ref="messenger" />
</bean>
</beans>
參考《(控制反轉(zhuǎn)容器)[https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#beans]》里的《(Bean scope)[https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#beans]》獲取更多關(guān)于Spring框架中scope支持的信息。
3.5.3.lang XML schema
Spring XML 配置文件中的lang標(biāo)簽是用來(lái)處理使用如JRuby或Groovy這種動(dòng)態(tài)語(yǔ)言編寫的對(duì)象作為Spring容器的bean的。
這些標(biāo)簽(和動(dòng)態(tài)語(yǔ)言支持)在《動(dòng)態(tài)語(yǔ)言支持》這一章進(jìn)行了全面講解。請(qǐng)閱讀這一章來(lái)學(xué)習(xí)動(dòng)態(tài)語(yǔ)言支持和這些lang標(biāo)簽本身的知識(shí)。
為了完整性,要想使用lang schema,需要在XML 配置文件的頂部加入如下序文;下文的代碼段中引用了正確的schema,從而使您可以使用lang命名空間下的標(biāo)簽。
<?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:lang="http://www.springframework.org/schema/lang" xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang.xsd"> <!-- 此處開始bean的配置 -->
</beans>
3.6.更多資源
通過(guò)如下鏈接來(lái)獲取關(guān)于本章討論的對(duì)應(yīng)腳本語(yǔ)言的更多資源。