《Spring Boot 2.0 極簡(jiǎn)教程》附錄 I : Spring 5.0 新特性

《Spring Boot 2.0 極簡(jiǎn)教程》附錄 I : Spring 5.0 新特性

因?yàn)镾pring Boot 2.0 是基于Spring Framework 5.0而開發(fā)的,所以我們這里對(duì) Spring 5.0的新功能特性做一個(gè)簡(jiǎn)單的介紹。
.
Spring Framework 項(xiàng)目的領(lǐng)導(dǎo)人 Juergen Hoeller 于 2016 年 7 月 28 日宣布了第一個(gè) Spring Framework 5.0 里程碑版本(5.0 M1)。2017 年 9 月 正式發(fā)行了Spring Framework 5.0 GA 版本。

Spring Framework 5.0 的亮點(diǎn)絕對(duì)是響應(yīng)式編程,這是一個(gè)重要的范式轉(zhuǎn)變。Spring Framework 5新引入的反應(yīng)式(Reactor)編程框架WebFlux將會(huì)取代傳統(tǒng)的基于Servlet API的阻塞模型。

雖然響應(yīng)式編程是 Spring Framework 5.0 中的閃光點(diǎn),但它不會(huì)在任何地方得到支持。 下游技術(shù)需要提供響應(yīng)式支持。你會(huì)看到未來將要發(fā)布的 Spring Data、Spring Security、Spring Integration 等版本所提供的響應(yīng)式編程功能。Spring Data 團(tuán)隊(duì)已經(jīng)為 MongoDB 和 Redis 實(shí)現(xiàn)了響應(yīng)式支持。使用 JDBC 獲取響應(yīng)式支持還為時(shí)過早。JDBC 規(guī)范本身就是阻塞的,在傳統(tǒng)的 JDBC 數(shù)據(jù)庫中看到響應(yīng)式編程的還需要一段時(shí)間。

隨著響應(yīng)式編程越來越受歡迎,我們可以期待越來越多的技術(shù)將實(shí)現(xiàn)響應(yīng)式解決方案。 Reactive Stack 在整個(gè) Spring 5.0生態(tài)中的位置如下圖所示

Spring 5 于 2017 年 9 月發(fā)布了通用版本 (GA),它標(biāo)志著自 2013 年 12 月以來第一個(gè)主要 Spring Framework 版本。Spring 5 兼容 Java?8 和 JDK 9,它集成了反應(yīng)式流 ( Reactive Stream ),提供一種顛覆性方法來實(shí)現(xiàn)Web 應(yīng)用程序開發(fā)。

Spring WebFlux 是 Spring 5 的反應(yīng)式編程的核心,它為開發(fā)人員提供了兩種Spring Web編程模型:

? 一種基于注解的模型
? Functional Web Framework (WebFlux.fn)

基于注解的模型是 Spring WebMVC 的現(xiàn)代替代方案,該模型基于反應(yīng)式構(gòu)建,而 Functional Web Framework 是傳統(tǒng)的基于 @Controller 注解的編程模型的替代方案。

Spring Framework 5.0 的新特性如下:

基于JDK 8+ 和Java EE 7+
使用泛型類型推斷、Lambda 表達(dá)式等提升代碼可讀性。
基于Java EE 7 API 支持:Servlet 3.1, Bean Validation 1.1, JPA 2.1, JMS 2.0
基于Web 服務(wù)器版本:Tomcat 8.5+, Jetty 9.4+, WildFly 10+
完全兼容JDK 9。
運(yùn)行時(shí)兼容Java EE 8 API,Servlet 4.0, Bean Validation 2.0, JPA 2.2, JSON Binding API 1.0。
針對(duì)Tomcat 9.0, Hibernate Validator 6.0, Apache Johnzon 1.1進(jìn)行測(cè)試。
在這里,我們舉一個(gè) Spring 5.0中使用 Java 8中Lambda 表達(dá)式的例子。在Java 8里,任何函數(shù)式接口作為方法的參數(shù)傳入或者作為方法返回值的場(chǎng)合,都可以用Lambda表達(dá)式代替。例如,Spring的JdbcTemplate類里有一個(gè)方法定義如下:

public <T> List<T> query(String sql, RowMapper<T> rowMapper) throws DataAccessException {
    return result(query(sql, new RowMapperResultSetExtractor<>(rowMapper)));
}

這個(gè)查詢方法的第二個(gè)參數(shù)需要RowMapper接口的一個(gè)實(shí)例。在Java 8中我們可以寫一個(gè)lambda表達(dá)式作為第二個(gè)參數(shù)的值傳進(jìn)去。
沒有 Lambda 表達(dá)式之前,我們通常會(huì)把代碼寫成這樣:

jdbcTemplate.query("select * from product", new RowMapper<Product>(){

  @Override
  public Product mapRow(ResultSet rs, int rowNum) throws SQLException {
    Integer id = rs.getInt("id");
    String description = rs.getString("description");
    Integer quantity = rs.getInt("quantity");
    BigDecimal price = rs.getBigDecimal("price");

    Product product = new Product();
    product.setId(id);
    product.setDescription(description);
    product.setQuantity(quantity);
    product.setPrice(price);

    return product;
  }});

而在 Java 8中,我們可以這么寫:

jdbcTemplate.query("select * from product", (rs, rowNum) -> {
    Integer id = rs.getInt("id");
    String description = rs.getString("description");
    Integer quantity = rs.getInt("quantity");
    BigDecimal price = rs.getBigDecimal("price");

    Product product = new Product();
    product.setId(id);
    product.setDescription(description);
    product.setQuantity(quantity);
    product.setPrice(price);

    return product;
});

我們注意到Java 8中這段代碼使用了lambda表達(dá)式,這比之前的版本中使用匿名內(nèi)部類的方式緊湊、簡(jiǎn)潔得多。

移除的Packages, Classes 和Methods
移除包beans.factory.access (BeanFactoryLocator 機(jī)制).
移除包jdbc.support.nativejdbc (NativeJdbcExtractor 機(jī)制).
移除包mock.staticmock 從 spring-aspects 模塊中移除。
移除包web.view.tiles2 和orm.hibernate3/hibernate4 .現(xiàn)在最低版本要求: Tiles 3 和Hibernate 5.
不再支持 AnnotationDrivenStaticEntityMockingControl

不再支持: Portlet, Velocity, JasperReports, XMLBeans, JDO, Guava.
建議: 如果上面的功能您仍需要使用,建議使用Spring Framework 4.3.x 。
通用基礎(chǔ)包
基于JDK 8+ 的功能增強(qiáng)
兼容JDK 9
在包級(jí)別聲明Non-null API:
可以顯式使用 @Nullable 注解標(biāo)注可空參數(shù), 成員變量和返回值。
主要使用 IntelliJ IDEA 和Kotlin開發(fā), 同時(shí)還有 Eclipse 與FindBugs.
一些Spring APIs 直接摒棄使用 null 值(e.g. StringUtils類)。
Resource 接口中readableChannel 的資源訪問類基于 NIO實(shí)現(xiàn) 。
文件系統(tǒng)的訪問類不再使用FileInput/OutputStream ,而是采用NIO.2 流。
Spring Framework 5.0 提供了自己的spring-jcl日志模塊替代標(biāo)準(zhǔn) Commons Logging,它還可以自動(dòng)檢測(cè)Log4j 2.x, SLF4J, JUL (java.util.logging),自帶開箱即用的 Commons Logging 橋接器。Spring Logging 還提升了性能。
通過資源抽象支持防御性編程,為 getFile 訪問提供了 isFile 指示器。
spring-core 模塊中的修改字節(jié)碼的功能包基于ASM 6.0。接下來會(huì)基于CGLIB 3.2.5 和Objenesis 2.6。

核心容器

支持 @Nullable 注解標(biāo)注的optional injection points.
支持GenericApplicationContext和AnnotationConfigApplicationContext類的函數(shù)式風(fēng)格編程。
在使用 CGLIB 代理下,對(duì)接口方法的事務(wù)、緩存和異步注釋進(jìn)行一致性檢測(cè)。
將 XML 配置命名空間簡(jiǎn)化為無版本化的模式。
始終使用最新的 xsd 文件;不再支持已棄用的功能。
仍然支持聲明特定版本, 但針對(duì)最新的schema進(jìn)行了驗(yàn)證。
支持候選組件的索引 (作為類路徑掃描的替代解決方案)。Spring Framework 5 改進(jìn)了掃描和識(shí)別組件的方法,使大型項(xiàng)目的性能得到提升。目前,掃描是在編譯時(shí)執(zhí)行的,而且向 META-INF/spring.components 文件中的索引文件添加了組件坐標(biāo)。該索引是通過一個(gè)為項(xiàng)目定義的特定平臺(tái)應(yīng)用的構(gòu)建任務(wù)來生成的。標(biāo)有來自 javax 包的注解的組件會(huì)添加到索引中,任何帶 @Index 注解的類或接口都會(huì)添加到索引中。
從索引讀取實(shí)體而不是全量掃描類路徑, 在性能上, 對(duì)于小于 200 個(gè)類的小型項(xiàng)目可能沒有明顯差異,但對(duì)大型項(xiàng)目影響較大: 加載組件索引開銷更低。加載組件索引的耗費(fèi)是廉價(jià)的。因此當(dāng)類的數(shù)量不斷增長(zhǎng),加上構(gòu)建索引的啟動(dòng)時(shí)間仍然可以維持一個(gè)常數(shù); 而對(duì)于組件掃描而言,啟動(dòng)時(shí)間則會(huì)有明顯的增長(zhǎng)。這個(gè)對(duì)于我們處于大型 Spring 項(xiàng)目的開發(fā)者所意味著的,是應(yīng)用程序的啟動(dòng)時(shí)間將被大大縮減。雖然 20 或者 30 秒鐘看似沒什么,但如果每天要這樣登上好幾百次,加起來就夠你受的了。使用了組件索引能幫助您更加快速地啟動(dòng) Spring 應(yīng)用,節(jié)省了寶貴的時(shí)間。
Spring 的傳統(tǒng)類路徑掃描方式?jīng)]有刪除,而是保留為一種后備選擇。

Spring Web MVC

在 Spring 提供的過濾器接口 Filter實(shí)現(xiàn)中完整支持Servlet 3.1。
支持 Spring MVC 控制器方法中的 Servlet 4.0 PushBuilder 參數(shù)。
MaxUploadSizeExceededException 共用服務(wù)器上的 Servlet 3.0 多部分解析。
通過 MediaTypeFactory 委派統(tǒng)一支持公共媒體類型。
取代使用 Java Activation框架。
與不可變對(duì)象的數(shù)據(jù)綁定 (Kotlin / Lombok / @ConstructorProperties)
支持 JSON 綁定 API (使用 Eclipse Yasson 或 Apache Johnzon 作為Jackson和 GSON 的替代品)。
支持Jackson 2.9。
支持 Protobuf 3。
支持Reactor 3.1 Flux 和 Mono 以及 RxJava 1.3/2.1 作為spring mvc 控制器的方法返回值, 目標(biāo)是在Spring MVC controllers中使用新的reactive WebClient 或Spring Data Reactive Repositories。
新的 ParsingPathMatcher 替代 AntPathMatcher, 具有更高效的解析和擴(kuò)展語法。
@ExceptionHandler 方法允許傳入 RedirectAttributes 參數(shù)。
支持 ResponseStatusException 作為 @ResponseStatus 的可選替代方案。
不需要再去實(shí)現(xiàn)Invocable 接口來創(chuàng)建腳本執(zhí)行引擎, 可以直接使用 ScriptEngine#eval(String, Bindings) 來執(zhí)行腳本, 并通過再在 ScriptTemplateView中傳遞 RenderingContext參數(shù)來實(shí)現(xiàn) i18n 國際化和嵌套模板。
·Spring's FreeMarker宏(macros)spring.ftl 現(xiàn)在使用 HTML 輸出格式 (需要 FreeMarker 2.3. 24 + 版本支持)。

Spring WebFlux

Spring 5.0的一個(gè)激動(dòng)人心的特性就是新的響應(yīng)式 Web框架 WebFlux, 它是完全響應(yīng)式且非阻塞的。Reactive Streams 是來自于 Netflix, Pivotal, Typesafe, Red Hat, Oracle, Twitter 以及 Spray.io 的工程師們特地開發(fā)的API框架。它為響應(yīng)式編程實(shí)現(xiàn)的實(shí)現(xiàn)提供了一個(gè)公共的 API。就好比是實(shí)現(xiàn)JPA接口的Hibernate 框架。這里 JPA 就是這個(gè)Reactive Streams API, 而 Hibernate 就是其具體實(shí)現(xiàn)。Reactive Streams API 是 Java 9 的官方版本的一部分。在 Java 8 中, 需要專門引入依賴來使用 Reactive Streams API。

Spring Framework 5.0 中擁有一個(gè)新的 spring-webflux 模塊,支持響應(yīng)式 HTTP 和 WebSocket 客戶端。Spring Framework 5.0 還提供了對(duì)于運(yùn)行于服務(wù)器之上,包含了 REST, HTML, 以及 WebSocket 風(fēng)格交互的響應(yīng)式 Web應(yīng)用程序的支持。

在 spring-webflux 中包含了兩種獨(dú)立的服務(wù)端編程模型:

(1)基于注解:使用到了@Controller 以及 Spring MVC 的其它一些注解;
(2)使用 Java 8 lambda 表達(dá)式的函數(shù)式風(fēng)格的路由和處理。
有了 Spring Webflux, 現(xiàn)在可以創(chuàng)建出響應(yīng)式且非阻塞的WebClient作為 RestTemplate 的一個(gè)替代方案。

下面是一個(gè)使用 Spring 5.0 的 REST 端點(diǎn)的 WebClient 實(shí)現(xiàn)示例:

WebClient webClient = WebClient.create();
Mono person = webClient.get()
.uri("http://localhost:8080/movie/42")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.then(response -> response.bodyToMono(Movie.class));
WebClient webClient = WebClient.create();
Mono person = webClient.get()
.uri("http://localhost:8080/movie/42")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.then(response -> response.bodyToMono(Movie.class));

盡管新的 WebFlux 模塊給我么帶來了激動(dòng)人心的新能力,傳統(tǒng)的 Spring MVC 在 Spring Framework 5.0 仍然得到了完整的支持。

使用 Kotlin 進(jìn)行函數(shù)式編程

Spring Framework 5.0 引入了對(duì) JetBrains Kotlin 語言的支持。同時(shí),Spring 5.0中也在使用 Kotlin 語言進(jìn)行開發(fā) API。我們可以到spring-framework源碼工程目錄下面去看一下 Kotlin 的代碼列表:

spring-framework$ find . -name *.kt
./spring-beans/src/main/kotlin/org/springframework/beans/factory/BeanFactoryExtensions.kt
./spring-beans/src/main/kotlin/org/springframework/beans/factory/ListableBeanFactoryExtensions.kt
./spring-beans/src/test/kotlin/org/springframework/beans/BeanUtilsKotlinTests.kt
./spring-beans/src/test/kotlin/org/springframework/beans/factory/annotation/KotlinAutowiredTests.kt
./spring-beans/src/test/kotlin/org/springframework/beans/factory/BeanFactoryExtensionsTests.kt
./spring-beans/src/test/kotlin/org/springframework/beans/factory/ListableBeanFactoryExtensionsTests.kt
./spring-context/src/main/kotlin/org/springframework/context/annotation/AnnotationConfigApplicationContextExtensions.kt
./spring-context/src/main/kotlin/org/springframework/context/support/BeanDefinitionDsl.kt
./spring-context/src/main/kotlin/org/springframework/context/support/GenericApplicationContextExtensions.kt
./spring-context/src/main/kotlin/org/springframework/ui/ModelExtensions.kt
./spring-context/src/main/kotlin/org/springframework/ui/ModelMapExtensions.kt
./spring-context/src/test/kotlin/org/springframework/context/annotation/AnnotationConfigApplicationContextExtensionsTests.kt
./spring-context/src/test/kotlin/org/springframework/context/annotation/Spr16022Tests.kt
./spring-context/src/test/kotlin/org/springframework/context/support/BeanDefinitionDslTests.kt
./spring-context/src/test/kotlin/org/springframework/context/support/GenericApplicationContextExtensionsTests.kt
./spring-context/src/test/kotlin/org/springframework/ui/ModelExtensionsTests.kt
./spring-context/src/test/kotlin/org/springframework/ui/ModelMapExtensionsTests.kt
./spring-core/src/main/kotlin/PropertyResolverExtensions.kt
./spring-core/src/test/kotlin/org/springframework/core/env/PropertyResolverExtensionsTests.kt
./spring-core/src/test/kotlin/org/springframework/core/KotlinMethodParameterTests.kt
./spring-core/src/test/kotlin/org/springframework/core/KotlinReflectionParameterNameDiscovererTests.kt
./spring-jdbc/src/main/kotlin/org/springframework/jdbc/core/JdbcOperationsExtensions.kt
./spring-jdbc/src/main/kotlin/org/springframework/jdbc/core/namedparam/MapSqlParameterSourceExtensions.kt
./spring-jdbc/src/test/kotlin/org/springframework/jdbc/core/JdbcOperationsExtensionsTests.kt
./spring-jdbc/src/test/kotlin/org/springframework/jdbc/core/namedparam/MapSqlParameterSourceExtensionsTests.kt
./spring-messaging/src/test/kotlin/org/springframework/messaging/simp/annotation/support/SimpAnnotationMethodMessageHandlerKotlinTests.kt
./spring-test/src/main/kotlin/org/springframework/test/web/reactive/server/WebTestClientExtensions.kt
./spring-test/src/test/kotlin/org/springframework/test/web/reactive/server/WebTestClientExtensionsTests.kt
./spring-web/src/main/kotlin/org/springframework/web/client/RestOperationsExtensions.kt
./spring-web/src/test/kotlin/org/springframework/web/client/RestOperationsExtensionsTests.kt
./spring-web/src/test/kotlin/org/springframework/web/method/annotation/RequestParamMethodArgumentResolverKotlinTests.kt
./spring-webflux/src/main/kotlin/org/springframework/web/reactive/function/client/ClientResponseExtensions.kt
./spring-webflux/src/main/kotlin/org/springframework/web/reactive/function/client/WebClientExtensions.kt
./spring-webflux/src/main/kotlin/org/springframework/web/reactive/function/server/RouterFunctionDsl.kt
./spring-webflux/src/main/kotlin/org/springframework/web/reactive/function/server/ServerRequestExtensions.kt
./spring-webflux/src/main/kotlin/org/springframework/web/reactive/function/server/ServerResponseExtensions.kt
./spring-webflux/src/test/kotlin/org/springframework/web/reactive/function/client/ClientResponseExtensionsTests.kt
./spring-webflux/src/test/kotlin/org/springframework/web/reactive/function/client/WebClientExtensionsTests.kt
./spring-webflux/src/test/kotlin/org/springframework/web/reactive/function/server/RouterFunctionDslTests.kt
./spring-webflux/src/test/kotlin/org/springframework/web/reactive/function/server/ServerRequestExtensionsTests.kt
./spring-webflux/src/test/kotlin/org/springframework/web/reactive/function/server/ServerResponseExtensionsTests.kt
./spring-webflux/src/test/kotlin/org/springframework/web/reactive/result/method/annotation/RequestParamMethodArgumentResolverKotlinTests.kt
./spring-webflux/src/test/kotlin/org/springframework/web/reactive/result/view/script/ScriptTemplateWithBindingsExtensions.kt
./spring-webmvc/src/test/kotlin/org/springframework/web/servlet/mvc/method/annotation/ServletAnnotationControllerHandlerMethodKotlinTests.kt
./spring-webmvc/src/test/kotlin/org/springframework/web/servlet/view/script/ScriptTemplateWithBindingsExtensions.kt

我們可以來閱讀以下下面這個(gè)類的代碼,來看看 Spring 框架是怎樣使用 Kotlin 中的 DSL 實(shí)現(xiàn)極簡(jiǎn)化的編程風(fēng)格的。

spring-context/src/main/kotlin/org/springframework/context/support/BeanDefinitionDsl.kt

這個(gè)類的代碼如下:

package org.springframework.context.support

import org.springframework.beans.factory.config.BeanDefinitionCustomizer
import org.springframework.beans.factory.support.AbstractBeanDefinition
import org.springframework.context.ApplicationContextInitializer
import org.springframework.core.env.ConfigurableEnvironment
import java.util.function.Supplier

fun beans(init: BeanDefinitionDsl.() -> Unit): BeanDefinitionDsl {
    val beans = BeanDefinitionDsl()
    beans.init()
    return beans
}

open class BeanDefinitionDsl(private val condition: (ConfigurableEnvironment) -> Boolean = { true })
    : ApplicationContextInitializer<GenericApplicationContext> {
    @PublishedApi
    internal val registrations = arrayListOf<(GenericApplicationContext) -> Unit>()
    @PublishedApi
    internal val children = arrayListOf<BeanDefinitionDsl>()

    enum class Scope {
        SINGLETON,
        PROTOTYPE
    }

    enum class Autowire {
        NO,
        BY_NAME,
        BY_TYPE,
        CONSTRUCTOR
    }

    inner class BeanDefinitionContext(@PublishedApi internal val context: GenericApplicationContext) {
        inline fun <reified T : Any> ref(name: String? = null): T = when (name) {
            null -> context.getBean(T::class.java)
            else -> context.getBean(name, T::class.java)
        }

        val env: ConfigurableEnvironment
            get() = context.environment
    }

    inline fun <reified T : Any> bean(name: String? = null,
                                      scope: Scope? = null,
                                      isLazyInit: Boolean? = null,
                                      isPrimary: Boolean? = null,
                                      autowireMode: Autowire = Autowire.CONSTRUCTOR,
                                      isAutowireCandidate: Boolean? = null) {
        registrations.add {
            val customizer = BeanDefinitionCustomizer { bd ->
                scope?.let { bd.scope = scope.name.toLowerCase() }
                isLazyInit?.let { bd.isLazyInit = isLazyInit }
                isPrimary?.let { bd.isPrimary = isPrimary }
                isAutowireCandidate?.let { bd.isAutowireCandidate = isAutowireCandidate }
                if (bd is AbstractBeanDefinition) {
                    bd.autowireMode = autowireMode.ordinal
                }
            }
            when (name) {
                null -> it.registerBean(T::class.java, customizer)
                else -> it.registerBean(name, T::class.java, customizer)
            }
        }
    }

    inline fun <reified T : Any> bean(name: String? = null,
                                      scope: Scope? = null,
                                      isLazyInit: Boolean? = null,
                                      isPrimary: Boolean? = null,
                                      autowireMode: Autowire = Autowire.NO,
                                      isAutowireCandidate: Boolean? = null,
                                      crossinline function: BeanDefinitionContext.() -> T) {
        val customizer = BeanDefinitionCustomizer { bd ->
            scope?.let { bd.scope = scope.name.toLowerCase() }
            isLazyInit?.let { bd.isLazyInit = isLazyInit }
            isPrimary?.let { bd.isPrimary = isPrimary }
            isAutowireCandidate?.let { bd.isAutowireCandidate = isAutowireCandidate }
            if (bd is AbstractBeanDefinition) {
                bd.autowireMode = autowireMode.ordinal
            }
        }
        registrations.add {
            val beanContext = BeanDefinitionContext(it)
            when (name) {
                null -> it.registerBean(T::class.java,
                        Supplier { function.invoke(beanContext) }, customizer)
                else -> it.registerBean(name, T::class.java,
                        Supplier { function.invoke(beanContext) }, customizer)
            }
        }
    }

    fun profile(profile: String, init: BeanDefinitionDsl.() -> Unit): BeanDefinitionDsl {
        val beans = BeanDefinitionDsl({ it.activeProfiles.contains(profile) })
        beans.init()
        children.add(beans)
        return beans
    }

    fun environment(condition: ConfigurableEnvironment.() -> Boolean,
                    init: BeanDefinitionDsl.() -> Unit): BeanDefinitionDsl {
        val beans = BeanDefinitionDsl(condition::invoke)
        beans.init()
        children.add(beans)
        return beans
    }

    override fun initialize(context: GenericApplicationContext) {
        for (registration in registrations) {
            if (condition.invoke(context.environment)) {
                registration.invoke(context)
            }
        }
        for (child in children) {
            child.initialize(context)
        }
    }
}

其中的

fun beans(init: BeanDefinitionDsl.() -> Unit): BeanDefinitionDsl {
    val beans = BeanDefinitionDsl()
    beans.init()
    return beans
}

這段代碼就是 Kotlin 中實(shí)現(xiàn)DSL的典型例子。關(guān)于 Kotlin DSL 的相關(guān)內(nèi)容可以參考《Kotlin 極簡(jiǎn)教程》一書。
有了這個(gè)擴(kuò)展類,我們就可以使用Kotlin DSL 風(fēng)格的函數(shù)式Bean 定義


beans {
    bean<UserHandler>()
    bean<Routes>()
    bean<WebHandler>("webHandler") {
    RouterFunctions.toWebHandler(
        ref<Routes>().router(),
        HandlerStrategies.builder().viewResolver(ref()).build())
    }
    bean("messageSource") {
        ReloadableResourceBundleMessageSource().apply {
            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>()
    }
 }

Kotlin 是一種支持函數(shù)式編程編程風(fēng)格的面向?qū)ο笳Z言。Kotlin 運(yùn)行在 JVM 之上,但運(yùn)行環(huán)境并不限于 JVM。有了對(duì) Kotlin 的支持,開發(fā)者可以進(jìn)行深度的函數(shù)式 Spring 編程,特別是在函數(shù)式 Web 端點(diǎn)以及 Bean 注冊(cè)這些方面。

在 Spring Framework 5.0 中, 可以為 WEB 的函數(shù)式 API 編寫干凈極簡(jiǎn)而且相當(dāng)“地道”的 Kotlin 代碼,就像下面這樣:

{
("/movie" and accept(TEXT_HTML)).nest {
GET("/", movieHandler::findAllView)
GET("/{card}", movieHandler::findOneView)
}
("/api/movie" and accept(APPLICATION_JSON)).nest {
GET("/", movieApiHandler::findAll)
GET("/{id}", movieApiHandler::findOne)
}
}

對(duì)于 Bean 的注冊(cè),作為 XML 或者 @Configuration 以及 @Bean 的替代辦法, 現(xiàn)在你可以使用 Kotlin 來注冊(cè) Spring Bean了,就像下面這樣:

val context = GenericApplicationContext {
registerBean()
registerBean { Cinema(it.getBean()) }
}

測(cè)試方面的提升

Spring Framework 5.0 完全支持 JUnit 5 Jupiter,所以可以使用 JUnit 5 來編寫測(cè)試以及擴(kuò)展。此外還提供了一個(gè)編程以及擴(kuò)展模型,Jupiter 子項(xiàng)目提供了一個(gè)測(cè)試引擎來在 Spring 上運(yùn)行基于 Jupiter 的測(cè)試。另外,Spring Framework 5 還提供了在 Spring TestContext Framework 中進(jìn)行并行測(cè)試的擴(kuò)展。
現(xiàn)在可以在您的單元測(cè)試中利用 Java 8 中提供的函數(shù)式編程特性。下面的代碼清單演示了這一支持:
代碼清單 JUnit 5 全面使用了 Java 8 流和 Lambda 表達(dá)式

@Test
void givenStreamOfInts_SumShouldBeMoreThan_100() {
    assertTrue(Stream.of(10, 50, 50)
      .stream()
      .mapToInt(i -> i)
      .sum() > 110, () -> "序列之和大于 100");
}

Spring 5 繼承了 JUnit 5,在 Spring TestContext Framework 內(nèi)實(shí)現(xiàn)了多個(gè)擴(kuò)展的 API。針對(duì)響應(yīng)式編程模型, spring-test 現(xiàn)在還引入了支持 Spring WebFlux 的 WebTestClient 集成測(cè)試的支持,類似于 MockMvc,并不需要一個(gè)運(yùn)行著的服務(wù)端。使用一個(gè)模擬的請(qǐng)求或者響應(yīng), WebTestClient 就可以直接綁定到 WebFlux 服務(wù)端設(shè)施。
WebTestClient 可綁定到真實(shí)的服務(wù)器,或者使用控制器或函數(shù)。在下面的代碼清單中,WebTestClient 被綁定到 localhost:

代碼清單. 綁定到 localhost 的 WebTestClient

WebTestClient testClient = WebTestClient
  .bindToServer()
  .baseUrl("http://localhost:8080")
  .build();

在下面的代碼清單中,測(cè)試了 RouterFunction:

代碼清單. 將 WebTestClient 綁定到 RouterFunction

RouterFunction bookRouter = RouterFunctions.route(
  RequestPredicates.GET("/books"),
  request -> ServerResponse.ok().build()
);
  
WebTestClient
  .bindToRouterFunction(bookRouter)
  .build().get().uri("/books")
  .exchange()
  .expectStatus().isOk()
  .expectBody().isEmpty();

當(dāng)然, Spring Framework 5.0 仍然支持我們的老朋友 Junit。JUnit 5 現(xiàn)在是GA 版本。對(duì)于 JUnit4, Spring Framework 在未來還是要支持一段時(shí)間的。

庫支持
Spring Framework 5.0目前支持以下升級(jí)庫的版本 :

Jackson 2.6+
EhCache 2.10+ / 3.0 GA
Hibernate 5.0+
JDBC 4.0+
XmlUnit 2.x+
OkHttp 3.x+
Netty 4.1+

Spring 5.0 詳細(xì)的新特性可參考Github wiki:https://github.com/spring-projects/spring-framework/wiki/What's-New-in-Spring-Framework-5.x

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容