Spring Framework (2018)
Spring
History
22 years ago, it started to use java for enterprise application.
2003, Rod Johnson created Spring for enterprise java tech, and EJB.
In 2017, Spring started to support Kotlin.
Spring 5
- 画期的機能
- リアクティブ・プログラミング・モデル
- Spring 5 フレームワークはリアクティブな基盤の上に構築されていて、完全に非同期、ノンブロッキング型らしい。
- 新しいイベント・ループ形式の実行モデルは、わずかな数のスレッドだけを使用して垂直スケーリングすることができるとか。
- リアクティブ・プログラミング・モデル
アノテーションベースのコントローラでもリアクティブ基盤の上で作動する。
‘kotlin-spring’ plugin
自動的にアノテーションのつけられたまたはメタアノテーションをつけられたクラスやメソッドを open
にする。
Kotlin では、 何も修飾子(modifier)をつけなければ Java でいう final
になるため、
これまで @Configuration
, @Tramsactonal
には open
modifier が必要だった。
apply plugin: 'kotlin-spring'
org.springframework.lang pakage
標準では、 Java の API で適宜された型は Kotlin ではプラットフォームタイプとして、 null check が緩和された状態の型になる。
-Xjsr305
を strict
に設定することで有効になる。
Spring Initializrで生成したプロジェクトでは、デフォルトでstrictモード。
compileKotlin {
kotlinOptions {
freeCompilerArgs = ["-Xjsr305=strict"]
jvmTarget = "1.8"
}
}
compileTestKotlin {
kotlinOptions {
freeCompilerArgs = ["-Xjsr305=strict"]
jvmTarget = "1.8"
}
}
kotlin-reflect
Kotlin reflection library. Spring Framework 5 で必要。
jackson-module-kotlin
Kotlin のクラスをシリアライズ・デシリアライズする。
単一のコンストラクタのクラスを自動的に使用する。 セカンダリコンストラクタとstaticファクトリーもサポートされている。(要調査)
JUnit 5
JUnit 4 から 5 への移行
./gradlew wrapper --gradle-version 4.7
下の行を build.gradle
に追加する。
dependencies {
testCompile('org.springframework.boot:spring-boot-starter-test') {
exclude module: 'junit'
}
testImplementation('org.junit.jupiter:junit-jupiter-api')
testRuntimeOnly('org.junit.jupiter:junit-jupiter-engine')
}
test {
useJUnitPlatform()
}
@RunWith(SpringRunner::class)
=> @ExtendWith(SpringExtension::class)
Kotlin & JUnit 5 でのテスト
最近の傾向もあわせて・・・
- Camel case のテストメソッド名ではなく、 センテンスとして書いて
`
で囲む。- Example
fun `Assert task page title, content and status code`() { ... }
- Example
- JUnit 5 では、コンストラクタとメソッドパラメータのインジェクションができ、 Kotlin のイミュータブルで non-null なプロパティとうまく融合する。
- Example
class SomethingTest(@Autowired val bean: BeanClass)
- Example
getForObject
,getForEntity
Kotlin extensions (インポートが必要)
@BeforeAll
, @AfterAll
annotations.
Kotlin JPA Plugin
buildscript {
dependencies {
classpath("org.jetbrains.kotlin:kotlin-noarg:${kotlinVersion}")
}
}
apply plugin: 'kotlin-jpa'
Controller
org.springframework.ui.set extension
model["title"] = "Blog"
instead of model.addAttribute("title", "Blog")
Spring Boot で Application を作るには
Spring Boot は Kotlin 1.2.x をサポートする。 このサポートを有効にするには、次の2つをクラスパスに入れる。
- org.jetbrains.kotlin:kotlin-stdlib
- org.jetbrains.kotlin:kotlin-reflect
JSON データ を Kotlin にシリアライズ・デシリアライズするには、 “Jackson’s Kotlin module” が必要。
org.jetbrains.kotlin:kotlin-allopen:${kotlinVersion}
- 必要なファイルを open
にする。 これで、 Spring boot のアプリケーションクラスもわざわざ open
にしなくて済む。
入れないバージョン
// Gradle 4.9
plugins {
id 'org.jetbrains.kotlin.jvm' version '1.2.51'
id 'org.springframework.boot' version '2.0.3.RELEASE'
}
// 依存関係を解決するプラグイン
apply plugin: 'io.spring.dependency-management'
group 'com.example'
version '1.0-SNAPSHOT'
repositories {
jcenter()
mavenCentral()
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
compile 'org.springframework.boot:spring-boot-starter-web'
//testCompile("org.springframework.boot:spring-boot-starter-test")
}
compileKotlin {
kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
kotlinOptions.jvmTarget = "1.8"
}
プロジェクトの基本をダウンロード
start.spring.io からダウンロードできる。
Null-safety
org.springframework.lang package で定義されている。
com.google.code.findbugs:jsr305:3.0.2
を dependencies に追加する。
@NonNull
@Nullable
@NonNullApi
: パッケージレベルのアノテーション。 パッケージ内のパラメータ、返り値は non-null として扱われる。@NonNullFields
: パッケージレベルのアノテーション。 基本的にフィールドの値は non-null として扱われる。
アプリケーションクラス
companion object
の中のメソッドとして作成するか、 トップレベルファンクションとして作成するか。
// with all-open
@SpringBootApplication
class DemoApplication
fun main(args: Array<String>) {
runApplication<DemoApplication>(*args)
}
// without all-open
@SpringBootApplication
open class Application {
companion object {
@JvmStatic
fun main(args: Array<String>) {
SpringApplication.run(Application::class.java, *args)
}
}
}
一番目の例では、 トップレベルアプリケーションにするだけでなく、 Kotlin の DSL も使用している。
runApplication
というメソッドが SpringApplication.run(Application::class.java, *args)
の置き換えになる。
runApplication
は Spring boot のドキュメントにもあるように、次のような記述が可能である。
runApplication<MyApplication>(*args) {
setBannerMode(OFF)
}
これを SprngApplication
でやろうとすると次のようになる。
SpringApplication app = new SpringApplication(SpringBootConsoleApplication.class);
app.setBannerMode(Banner.Mode.OFF);
app.run(args);
Kotlin で SpringApplication
を使ってやるなら次のようにできる。
SpringApplication(SpringBootConsoleApplication.class).also {
it.setBannerMode(Banner.Mode.OFF)
it.run(args)
}
Kotlin 専用 メソッド
Kotlin のために、 DSL や拡張関数が追加されている。
Example
BeanDefinitionDSL
RouterFunctionDsl
router
@Configuration class ApplicationRoutes(val userHandler: UserHandler) { @Bean fun mainRouter() = router { accept(TEXT_HTML).nest { (GET("/user/") or GET("/users/")).invoke(userHandler::findAllView) GET("/users/{login}", userHandler::findViewById) } accept(APPLICATION_JSON).nest { (GET("/api/user/") or GET("/api/users/")).invoke(userHandler::findAll) POST("/api/users/", userHandler::create) } } }
KotlinBodySpec
Controller Parameter が Null-safety に対応
コントローラのメソッドパラメータ
@RequestParam name: String?
: 任意。@RequestParam name: String
: 必須。
Spring Messaging の @Header
アノテーションでも同様。
@Autowired
, @Bean
, @Inject
も Nullable か否かを Bean の必要・不必要の判定に使っている。
@Autowired lateinit var foo: Foo
=>Foo
がビーンとして登録されていることが必要。-
@Autowired lateinit var foo: Foo?
=>Foo
がビーンとして登録されていなくてもエラーが出ない。 @Bean fun baz(foo: Foo, bar: Bar?) = Baz(foo, bar)
Foo
のビーンは登録されている必要がある。Bar
のビーンは登録されてなくてもいい。
autowired のコンストラクタでも同様。
Annotation
プロパティまたはプライマリコンストラクタのあるクラスではアノテーションに use-site targets を付ける必要がある。
@Entity data class User(
@Id
@GeneratedValue(strategy = javax.persistence.GenerationType.AUTO)
var id: Long? = null,
@get:Size(min=5, max=15) // added annotation use-site target here
val name: String
)
All-Open
all-open が open にするアノテーションは次のアノテーションである。
@Component
@Configuration
,@Controller
,@RestController
,@Service
,@Repository
@Async
@Transactional
@Cacheable
新しい RequestMapping
@RequestMapping(value = "/foo", method = RequestMethod.GET)
@RequestMapping(path = "/foo", method = RequestMethod.GET)
@RequestMapping("/foo", method = [RequestMethod.GET])
@RequestMapping(path = ["/foo"], method = [RequestMethod.GET])