概要
以前に[Kotlin with Spring Boot 1.5で簡単なRest APIを実装する] (https://qiita.com/rubytomato@github/items/7d4bb10ca3779ab3277c)というタイトルで書いた記事のSpring Boot 2.0版です。
現在(2018/2)の最新バージョンであるRC2を使用しています。
- 2018/3/1にSpring Boot 2.0.0 GAがリリースされました。([Spring Boot 2.0 goes GA] (https://spring.io/blog/2018/03/01/spring-boot-2-0-goes-ga))
- 2018/5/9にSpring Boot 2.0.2がリリースされましたのでソースコードおよび記事を修正しました。([Spring Boot 2.0.2] (https://spring.io/blog/2018/05/09/spring-boot-2-0-2))
ソースコードは[rubytomato/demo-kotlin-spring2] (https://github.com/rubytomato/demo-kotlin-spring2)にあります。
環境
- Windows 10 Professional
- Java 1.8.0_172
- Kotlin 1.2.31
- Spring Boot 2.0.2
- Spring Framework 5.0.5
- MySQL 5.7.19
- Gradle 4.5.1
- IntelliJ IDEA 2017.3
参考
- [Spring Boot 2.0 Release Notes] (https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Release-Notes)
- [Spring Boot with Java 9 and above] (https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-with-Java-9-and-above)
1.5から移行する場合
[Spring Boot 2.0 Migration Guide] (https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Migration-Guide)が用意されています。
アプリケーション
アプリケーションのひな型はSpring Initializrで生成しました。
生成したbuild.gradleのkotlinのバージョンには1.2.20が指定されていますが、Spring Boot
2.0.0は1.2.21までサポートしているので、この記事では1.2.21を使用しています。
build.gradle
buildscript {
ext {
kotlinVersion = "1.2.31"
springBootVersion = "2.0.2.RELEASE"
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}")
classpath("org.jetbrains.kotlin:kotlin-allopen:${kotlinVersion}")
classpath("org.jetbrains.kotlin:kotlin-noarg:${kotlinVersion}")
}
}
apply plugin: "kotlin"
apply plugin: "kotlin-spring"
apply plugin: "kotlin-jpa"
apply plugin: "org.springframework.boot"
apply plugin: "io.spring.dependency-management"
apply plugin: "kotlin-kapt"
group = "com.example"
version = "0.0.3-SNAPSHOT"
sourceCompatibility = 1.8
targetCompatibility = 1.8
compileKotlin {
kotlinOptions {
freeCompilerArgs = ["-Xjsr305=strict"]
jvmTarget = "1.8"
}
}
compileTestKotlin {
kotlinOptions {
freeCompilerArgs = ["-Xjsr305=strict"]
jvmTarget = "1.8"
}
}
compileJava {
options.compilerArgs << "-Xlint:unchecked,deprecation"
}
compileTestJava {
options.compilerArgs << "-Xlint:unchecked,deprecation"
}
repositories {
mavenCentral()
}
dependencies {
// for web
compile("org.springframework.boot:spring-boot-starter-web")
compile("com.fasterxml.jackson.module:jackson-module-kotlin")
// for db
compile("org.springframework.boot:spring-boot-starter-data-jpa")
runtime("mysql:mysql-connector-java")
// compile("javax.xml.bind:jaxb-api:2.1")
// kotlin
compile("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
compile("org.jetbrains.kotlin:kotlin-reflect")
// for dev
kapt("org.springframework.boot:spring-boot-configuration-processor")
runtime("org.springframework.boot:spring-boot-devtools")
compileOnly("org.springframework.boot:spring-boot-configuration-processor")
// for test
testCompile("org.springframework.boot:spring-boot-starter-test")
testCompile("com.h2database:h2")
}
-Xjsr305 compiler flag
Spring Initializrで生成したプロジェクトでは、デフォルトでstrictモードになっています。
strictモードでは、Java側のオブジェクトのフィールドにNullableアノテーションが付いている場合、Kotlin側でNullableとして扱わないとコンパイルエラーになります。
例えば、次のコードはresult.body.id
の箇所がコンパイルエラーになります。
(strictモードでなければコンパイルエラーにはなりませんが警告はでます。)
@Test
fun `test get`() {
val result: ResponseEntity<Memo> = testRestTemplate.getForEntity("/memo/1", Memo::class.java)
assertThat(result.body.id).isEqualTo(1L)
// Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type Memo? :compileTestKotlin FAILED
}
これはbodyフィールドにNullableアノテーションが付いてるためです。
public class HttpEntity<T> {
@Nullable
private final T body;
}
コンパイルを通すには次のようにNullableとして扱います。
assertThat(result.body?.id).isEqualTo(1L)
// もしくはresult.body!!.id
依存性管理
依存性管理にプラグインio.spring.dependency-management
が必要になりました。
javax.xml.bind
コメントアウトしてあるjavax.xml.bind:jaxb-apiは、Java9のときに必要になります。(原因はhttps://github.com/spring-projects/spring-boot/issues/11205)
json
2.0からspring-boot-starter-jsonというjson用のstarterが用意されています。
spring-boot-starter-webの依存関係に含まれているので明示的に指定する必要はありませんが、kotlinでは別途com.fasterxml.jackson.module:jackson-module-kotlinが必要になります。
application.yml
spring:
application:
name: kotlin with spring-boot 2.0
output:
ansi:
enabled: detect
profiles:
active: dev
datasource:
url: jdbc:mysql://localhost:3306/demo_db?useSSL=false
username: demo_user
password: demo_pass
hikari:
connection-test-query: select 1
connection-timeout: 10000
maximum-pool-size: 2
minimum-idle: 2
jpa:
open-in-view: true
properties:
hibernate:
show_sql: true
format_sql: true
use_sql_comments: true
generate_statistics: true
jackson:
serialization:
write-dates-as-timestamps: false
write-durations-as-timestamps: true
server:
servlet:
context-path: /app
port: 9000
logging.file: demo.log
logging:
file:
max-size: 50MB
max-history: 10
level:
root: info
org.springframework: info
org.hibernate: info
org.hibernate.SQL: debug
com.example.demo: debug
2.0からデフォルトのデータソースがHikariCPになっています。
HikariCPのプロパティを設定するにはspring.datasource.hikari.*
に指定します。
また、application.properties(.yml)ファイルは名前空間の変更などが行われているので、1.5からの移行をサポートするモジュールが用意されています。
使い方は以下のモジュールを組み込むだけです。組み込むと書き換えが必要なプロパティがあればコンソールに警告を表示し、一時的に新しいプロパティへ変換して起動してくれます。
Maven
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-properties-migrator</artifactId>
</dependency>
Gradle
runtime("org.springframework.boot:spring-boot-properties-migrator")
アプリケーションの起動クラス
2.0からrunApplicationという関数が用意されています。
package com.example.demokotlin2
@SpringBootApplication
class DemoKotlin2Application
fun main(args: Array<String>) {
runApplication<DemoKotlin2Application>(*args)
}
エンティティとリポジトリ
リポジトリはSpring Data Jpaのバージョンが上がったため若干変更点がありました。
エンティティ
1.5から変更はありませんでした。
package com.example.demokotlin2.entity
import java.io.Serializable
import java.time.LocalDateTime
import javax.persistence.*
@Entity
@Table(name = "memo")
data class Memo(
@Column(name = "title", nullable = false)
var title: String,
@Column(name = "description", nullable = false)
var description: String,
@Column(name = "done", nullable = false)
var done: Boolean = false,
@Column(name = "updated", nullable = false)
var updated: LocalDateTime = LocalDateTime.now(),
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long = 0
) : Serializable
リポジトリ
この例ではJpaRepositoryインタフェースを実装していますが、このインタフェースで定義されているメソッドのシグネチャがいくつか変わっているので、1.5から移行する場合はコードの修正が必要になるかもしれません。
package com.example.demokotlin2.repository
import com.example.demokotlin2.entity.Memo
import org.springframework.data.jpa.repository.JpaRepository
interface MemoRepository : JpaRepository<Memo, Long>
- シグネチャの変更点は補足欄にまとめてあります。
サービス
1.5と比べて特筆すべき変更点はありませんでした。
package com.example.demokotlin2.service
import com.example.demokotlin2.entity.Memo
import org.springframework.data.domain.Pageable
interface MemoService {
fun findById(id: Long): Memo?
fun findAll(page: Pageable): List<Memo>
fun store(memo: Memo)
fun removeById(id: Long)
}
package com.example.demokotlin2.service.impl
import com.example.demokotlin2.entity.Memo
import com.example.demokotlin2.repository.MemoRepository
import com.example.demokotlin2.service.MemoService
import org.springframework.data.domain.Pageable
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
@Service
class MemoServiceImpl(
private val repository: MemoRepository) : MemoService {
@Transactional(readOnly = true)
override fun findById(id: Long): Memo? {
return repository.findById(id).orElseGet(null)
}
@Transactional(readOnly = true)
override fun findAll(page: Pageable): List<Memo> {
return repository.findAll(page).content
}
@Transactional(timeout = 10)
override fun store(memo: Memo) {
repository.save(memo)
}
@Transactional(timeout = 10)
override fun removeById(id: Long) {
repository.deleteById(id)
}
}
コントローラ
1.5と比べて特筆すべき変更点はありませんでした。
package com.example.demokotlin2.controller
import com.example.demokotlin2.entity.Memo
import com.example.demokotlin2.service.MemoService
import org.springframework.data.domain.Pageable
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*
@RestController
@RequestMapping(path = ["memo"])
class MemoController(
private val service: MemoService) {
@GetMapping(path = ["{id}"], produces = [MediaType.APPLICATION_JSON_UTF8_VALUE])
fun id(@PathVariable(value = "id") id: Long): ResponseEntity<Memo> {
val memo = service.findById(id)
return if (memo != null) {
ResponseEntity.ok(memo)
} else {
ResponseEntity(HttpStatus.NOT_FOUND)
}
}
@GetMapping(path = ["list"], produces = [MediaType.APPLICATION_JSON_UTF8_VALUE])
fun list(page: Pageable): ResponseEntity<List<Memo>> {
return ResponseEntity.ok(service.findAll(page))
}
@PostMapping(produces = [MediaType.TEXT_PLAIN_VALUE], consumes = [MediaType.APPLICATION_JSON_UTF8_VALUE])
fun store(@RequestBody memo: Memo): ResponseEntity<String> {
service.store(memo)
return ResponseEntity.ok("success")
}
@DeleteMapping(path = ["{id}"], produces = [MediaType.TEXT_PLAIN_VALUE])
fun remove(@PathVariable(value = "id") id: Long): ResponseEntity<String> {
service.removeById(id)
return ResponseEntity.ok("success")
}
}
- ページング、ソートの設定をカスタマイズできるプロパティが追加されています。
spring.data.web.pageable.*
spring.data.web.sort.*
たとえば、1ページに表示する最大件数は次のプロパティで設定できます。
spring.data.web.pageable.default-page-size= 10
APIの動作確認
検索
> curl -v http://localhost:9000/app/memo/1
> curl -v http://localhost:9000/app/memo/list
ページング
> curl -v http://localhost:9000/app/memo/list?page=0&size=10
新規登録
> curl -v -H "Content-Type:application/json" -d @memo.json -X POST http://localhost:9000/app/memo
削除
> curl -v -X DELETE http://localhost:9000/app/memo/1
テストコード
リポジトリの単体テスト
package com.example.demokotlin2.repository
import com.example.demokotlin2.entity.Memo
import org.junit.Test
import org.junit.runner.RunWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager
import org.springframework.test.context.junit4.SpringRunner
import org.assertj.core.api.Assertions.*
import org.springframework.test.context.jdbc.Sql
import java.time.LocalDateTime
@RunWith(SpringRunner::class)
@DataJpaTest
class MemoRepositoryTests {
@Autowired
lateinit var entityManager: TestEntityManager
@Autowired
lateinit var sut: MemoRepository
@Test
@Sql(statements = ["INSERT INTO memo (id, title, description, done, updated) VALUES (1, 'memo test', 'memo description', FALSE, CURRENT_TIMESTAMP)"])
fun findById() {
val expected = entityManager.find(Memo::class.java, 1L)
val actual = sut.findById(expected.id).orElseGet { null }
assertThat(actual).isNotNull()
assertThat(actual).isEqualTo(expected)
}
@Test
fun save() {
val updated = LocalDateTime.of(2018, 2, 19, 18, 12, 0)
val expected = Memo(title = "test title", description = "test description", done = true, updated = updated)
sut.saveAndFlush(expected)
val actual = entityManager.find(Memo::class.java, expected.id)
assertThat(actual).isEqualTo(expected)
}
@Test
@Sql(statements = ["INSERT INTO memo (id, title, description, done, updated) VALUES (1, 'memo test', 'memo description', FALSE, CURRENT_TIMESTAMP)"])
fun delete() {
val expected = entityManager.find(Memo::class.java, 1L)
sut.deleteById(expected.id)
sut.flush()
val actual = entityManager.find(Memo::class.java, expected.id)
assertThat(actual).isNull()
}
}
サービスの単体テスト
package com.example.demokotlin2.service
import com.example.demokotlin2.entity.Memo
import com.example.demokotlin2.repository.MemoRepository
import com.example.demokotlin2.service.impl.MemoServiceImpl
import org.junit.*
import org.mockito.*
import org.mockito.Mockito.*
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule
import org.mockito.quality.Strictness
import org.assertj.core.api.Assertions.*
import org.springframework.data.domain.PageImpl
import org.springframework.data.domain.PageRequest
import java.util.*
class MemoServiceImplTests {
@Rule
@JvmField
val rule: MockitoRule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS)
@Mock
lateinit var repository: MemoRepository
@InjectMocks
lateinit var sut: MemoServiceImpl
@Test
fun findById() {
val expected = Optional.ofNullable(Memo(id = 11, title = "title", description = "description", done = true))
Mockito.`when`(repository.findById(anyLong())).thenReturn(expected)
val actual = sut.findById(11)
assertThat(actual).isNotNull()
assertThat(actual).isEqualTo(expected.get())
verify(repository, times(1)).findById(anyLong())
}
@Test
fun findAllWithEmptyList() {
val pageable: PageRequest = PageRequest.of(0, 5)
val expected = emptyList<Memo>()
val page = PageImpl<Memo>(expected, pageable, 0)
Mockito.`when`(repository.findAll(eq(pageable))).thenReturn(page)
val actual = sut.findAll(pageable)
assertThat(actual).hasSize(0)
verify(repository, times(1)).findAll(eq(pageable))
}
@Test
fun findAll() {
val pageable: PageRequest = PageRequest.of(0, 5)
val expected = listOf(
Memo(id = 1, title = "title A", description = "desc A", done = true),
Memo(id = 2, title = "title B", description = "desc B", done = false),
Memo(id = 3, title = "title C", description = "desc C", done = true)
)
val page = PageImpl<Memo>(expected, pageable, 3)
Mockito.`when`(repository.findAll(eq(pageable))).thenReturn(page)
val actual = sut.findAll(pageable)
assertThat(actual).hasSize(3)
assertThat(actual).containsSequence(expected)
verify(repository, times(1)).findAll(eq(pageable))
}
}
コントローラーのテスト
単体
package com.example.demokotlin2.controller
import com.example.demokotlin2.entity.Memo
import com.example.demokotlin2.service.MemoService
import com.fasterxml.jackson.databind.ObjectMapper
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyLong
import org.mockito.Mockito
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
import org.springframework.boot.test.mock.mockito.MockBean
import org.springframework.http.MediaType
import org.springframework.test.context.junit4.SpringRunner
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.*
import org.springframework.test.web.servlet.result.MockMvcResultHandlers.print
import java.nio.charset.Charset
@RunWith(SpringRunner::class)
@WebMvcTest(MemoController::class)
class MemoControllerTests {
@Autowired
lateinit var mvc: MockMvc
@Autowired
lateinit var objectMapper: ObjectMapper
@MockBean
lateinit var memoService: MemoService
private val contentType = MediaType(MediaType.APPLICATION_JSON.type,
MediaType.APPLICATION_JSON.subtype, Charset.forName("utf8"))
@Test
fun getOne() {
val expected = Memo(id=1L, title="test title", description="test description", done=true)
val expectedJson = objectMapper.writeValueAsString(expected)
Mockito.`when`(memoService.findById(anyLong())).thenReturn(expected)
val builder = MockMvcRequestBuilders.get("/memo/{id}", 1)
.accept(MediaType.APPLICATION_JSON_UTF8)
val result = mvc.perform(builder)
.andExpect(status().isOk)
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$").isNotEmpty)
.andExpect(jsonPath("$.title").value(expected.title))
.andExpect(jsonPath("$.description").value(expected.description))
.andExpect(jsonPath("$.done").value(expected.done))
.andDo(`print`())
.andReturn()
assertThat(result.response.contentAsString).isEqualTo(expectedJson)
}
}
結合
package com.example.demokotlin2.controller
import com.example.demokotlin2.DemoKotlin2Application
import com.example.demokotlin2.entity.Memo
import org.junit.Test
import org.junit.runner.RunWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.web.client.TestRestTemplate
import org.springframework.test.context.junit4.SpringRunner
import org.assertj.core.api.Assertions.*
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
@RunWith(SpringRunner::class)
@SpringBootTest(classes = [DemoKotlin2Application::class],
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class MemoControllerTests {
class MemoList : MutableList<Memo> by ArrayList()
@Autowired
lateinit var testRestTemplate: TestRestTemplate
@Test
fun getOne() {
val result = testRestTemplate.getForEntity("/memo/1", Memo::class.java)
assertThat(result).isNotNull
assertThat(result.statusCode).isEqualTo(HttpStatus.OK)
assertThat(result.headers.contentType).isEqualTo(MediaType.APPLICATION_JSON_UTF8)
assertThat(result.body?.id).isEqualTo(1L)
}
@Test
fun pagination() {
val result = testRestTemplate.getForEntity("/memo/list?page={page}&size={size}", MemoList::class.java, 0, 3)
assertThat(result).isNotNull
assertThat(result.statusCode).isEqualTo(HttpStatus.OK)
assertThat(result.headers.contentType).isEqualTo(MediaType.APPLICATION_JSON_UTF8)
assertThat(result.body).hasSize(3)
}
}
補足
M1~M7、RC1,RC2のリリースノート
M1からRC2までのリリースノートの内容を大まかにまとめました。(2018/2/19)
ただ、すべてを確認、検証するには時間的に難しかったので、個人的に興味を引いたもの、影響の大きそうなものをピックアップしています。
[Spring-Boot-2.0.0-M1-Release-Notes] (https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0.0-M1-Release-Notes)
- [Spring Boot 2.0.0 M1 Configuration Changelog] (https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0.0-M1-Configuration-Changelog)
★baselineの変更
- Java8 or laterが必要です。
- Java6,7はサポートされません。
★主要な依存関係のバージョンの変更
component | Spring Boot 1.5.10 | Spring Boot 2.0.0 |
---|---|---|
Spring Framework | 4.3.14 | 5.0.4 |
Spring Security | 4.2.4 | 5.0.3 |
Spring Data Commons | 1.13.0 | 2.0.5 |
Spring Data Jpa | 1.11.10 | 2.0.5 |
Aspectj | 1.8.13 | 1.8.13 |
javassist | 3.21.0-GA | 3.22.0-GA |
Jetty | 9.4.8 | 9.4.8 |
Tomcat | 8.5.27 | 8.5.28 |
Undertow | 1.4.22 | 1.4.22 |
Hibernate | 5.0.12 | 5.2.14 |
Jackson | 2.8.10 | 2.9.4 |
Gson | 2.8.2 | 2.8.2 |
JUnit | 4.12 | 4.12 |
JUnit-Jupiter | 5.1.0 | |
mockito | 1.10.19 | 2.15.0 |
Maven | 3.2+ | 3.2+ |
Gradle | 2.9 or later | 4 |
flyway | 3.2.1 | 5.0.7 |
liquibase | 3.5.3 | 3.5.5 |
kotlin | 1.2.21 |
Relaxed binding
- [Spring Boot Configuration Binding] (https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-Configuration-Binding)
プロパティの緩やかなバインディング(Relaxed binding)が改善されています。
詳細は[Relaxed Binding 2.0] (https://github.com/spring-projects/spring-boot/wiki/Relaxed-Binding-2.0)に説明があります。
プロパティの記述法
プロパティの記法には次のものがあります。
記法 | example | desc |
---|---|---|
camel-case | person.firstName | |
kebab-case | person.first-name | .propertiesと.ymlファイルでの使用を推奨 |
snake-case | person.first_name | kebab-caseの代替 |
upper-case | PERSON_FIRSTNAME | システム環境変数を使用する場合に推奨 |
プロパティは(1)prefixと(2)nameに分けることができ、prefixとnameでは使える記法が異なります。
my-app.my-module.foo.first-name= ruby
^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^ ^^^^
| | |
(1) (2) (3)
- prefix : kebab-case
- name : kebab-case、camel-case、snake-case
- value : プロパティの値
2.0以降は、ConfigurationPropertiesアノテーションのprefix属性の記述方法が変わっています。
例えば、次のプロパティをバインドするために
my-app.my-module.foo.first-name= ruby
my-app.my-module.foo.last-name= tomato
my-app.my-module.foo.age= 10
prefixをkebab-case("my-app.my-module.foo")で記述することはできますが、
@Component
@ConfigurationProperties(prefix = "my-app.my-module.foo")
data class FooConfig(
var firstName: String = "",
var lastName: String = "",
var age: Int = 0
)
snake-case("my_app.my_module.foo")やcamel-case("myApp.myModule.foo")で記述すると実行時に例外をスローします。
1.5まではこれらの記法でも例外は起こりませんでしたが、プロパティの書き方に統一性がないと期待通りにバインドされませんでした。
Caused by: org.springframework.boot.context.properties.source.InvalidConfigurationPropertyNameException: Configuration property name 'my_app.my_module.foo' is not valid
プロパティを設定する側では、次の記法はすべてバインドできます。
my-app.my-module.foo.first-name= ruby
my_app.my_module.foo.first_name= ruby
myApp.myModule.foo.firstName= ruby
OS環境変数
> SET MY_APP_MY_MODULE_FOO_FIRST_NAME= ruby
記法が混在した場合
my-app.my-module.foo.first_name= ruby
my-app.my-module.foo.firstName= ruby
my-app.my-module.foo.FIRST_NAME= ruby
[ConditionalOnProperty] (https://docs.spring.io/spring-boot/docs/2.0.0.RELEASE/api/org/springframework/boot/autoconfigure/condition/ConditionalOnProperty.html)アノテーションのprefixおよびname属性もkebab-caseで記述します。
ただし、こちらはsnake-caseやcamel-caseで記述しても例外がスローされないので注意が必要です。
@ConditionalOnProperty(prefix = "my-app.my-module.foo", name = ["test-flag"])
ちなみに、プロパティが特定の値のときにバインディングしたい場合はhavingValueで条件を指定します。
この例では"my-app.my-module.foo.test-flag= true"の時にBarPropertiesのオブジェクトがDIコンテナに登録されます。
@Bean
@ConditionalOnProperty(prefix = "my-app.my-module.foo", name = ["test-flag"], havingValue = "true")
fun bar(foo: FooProperties): BarProperties {
return BarProperties(foo.name)
}
data class BarProperties(val name: String)
またバインディングAPIは以下のように変更されました。
package | class | |
---|---|---|
old | [org.springframework.boot.bind] (https://docs.spring.io/spring-boot/docs/1.5.10.RELEASE/api/org/springframework/boot/bind/package-summary.html) | RelaxedDataBinder |
new | [org.springframework.boot.context.properties.bind] (https://docs.spring.io/spring-boot/docs/2.0.0.RELEASE/api/org/springframework/boot/context/properties/bind/package-summary.html) | Binder |
例外
プロパティの中には、Springが利用するのではなく組み込んだライブラリへ渡すためのプロパティ定義があります。
たとえば下記のspring.jpa.properties.hibernate.*がそうですが、決められた記法で記述しないと認識されませんでした。
この例で言えばname部分(show_sql)はsnake-caseでなければ認識されなかったです。
spring.jpa.properties.hibernate.show_sql= true
Feature Remove
- Remote CRaSH shell
- Spring Loaded
- Spring Boot CLIからテスト機能
組み込みコンテナのパッケージの変更
package | interface | |
---|---|---|
old | org.springframework.boot.context.embedded | [EmbeddedServletContainer] (https://docs.spring.io/spring-boot/docs/1.5.10.RELEASE/api/org/springframework/boot/context/embedded/EmbeddedServletContainer.html) |
new | org.springframework.boot.web.server | WebServer |
Jetty
package | class | |
---|---|---|
old | [org.springframework.boot.context.embedded.jetty] (https://docs.spring.io/spring-boot/docs/1.5.10.RELEASE/api/org/springframework/boot/context/embedded/jetty/package-summary.html) | JettyEmbeddedServletContainerFactory |
new | [org.springframework.boot.web.embedded.jetty] (https://docs.spring.io/spring-boot/docs/2.0.0.RELEASE/api/org/springframework/boot/web/embedded/jetty/package-summary.html) | JettyServletWebServerFactory |
JettyReactiveWebServerFactory |
Tomcat
package | class | |
---|---|---|
old | [org.springframework.boot.context.embedded.tomcat] (https://docs.spring.io/spring-boot/docs/1.5.10.RELEASE/api/org/springframework/boot/context/embedded/tomcat/TomcatEmbeddedServletContainerFactory.html) | TomcatEmbeddedServletContainerFactory |
new | [org.springframework.boot.web.embedded.tomcat] (https://docs.spring.io/spring-boot/docs/2.0.0.RELEASE/api/org/springframework/boot/web/embedded/tomcat/package-summary.html) | TomcatServletWebServerFactory |
TomcatReactiveWebServerFactory |
undertow
package | class | |
---|---|---|
old | [org.springframework.boot.context.embedded.undertow] (https://docs.spring.io/spring-boot/docs/1.5.10.RELEASE/api/org/springframework/boot/context/embedded/undertow/package-summary.html) | UndertowEmbeddedServletContainerFactory |
new | [org.springframework.boot.web.embedded.undertow] (https://docs.spring.io/spring-boot/docs/2.0.0.RELEASE/api/org/springframework/boot/web/embedded/undertow/package-summary.html) | UndertowServletWebServerFactory |
UndertowReactiveWebServerFactory |
Netty (補足)
- Nettyは2.0から追加されたWebFluxのデフォルトのengineです。
package | class | |
---|---|---|
new | [org.springframework.boot.web.embedded.netty] (https://docs.spring.io/spring-boot/docs/2.0.0.RELEASE/api/org/springframework/boot/web/embedded/netty/package-summary.html) | NettyReactiveWebServerFactory |
★デフォルトのdatasourceの変更
デフォルトのdatasourceが[tomcat] (https://tomcat.apache.org/tomcat-8.5-doc/jdbc-pool.html)から[HikariCP] (https://github.com/brettwooldridge/HikariCP)に変わっています。
HikariCP
明示的に指定する場合
デフォルトなので明示的に指定する必要はありませんが、あえて記述すると次のようになります。
spring.datasource.type= com.zaxxer.hikari.HikariDataSource
HikariCPのプロパティ設定
spring.datasource.hikari.*
property name | type | default |
---|---|---|
allow-pool-suspension | Boolean | false |
auto-commit | Boolean | true |
catalog | String | driver default |
connection-init-sql | String | none |
connection-test-query | String | none |
connection-timeout | Long | 30000 ms |
data-source-class-name | String | none |
data-source-j-n-d-i | String | |
data-source-properties | Map | |
driver-class-name | String | none |
health-check-properties | Map | |
health-check-registry | Object | none (HikariCP 2.3.2+ supports Dropwizard HealthChecks.) |
idle-timeout | Long | 600000 ms |
initialization-fail-timeout | Long | 1 s |
isolate-internal-queries | Boolean | false |
jdbc-url | String | none |
leak-detection-threshold | Long | 0 ms |
login-timeout | Integer | (officialに記載なし) |
max-lifetime | Long | 1800000 ms |
maximum-pool-size | Integer | 10 |
metric-registry | Object | none |
metrics-tracker-factory | Factory | (officialに記載なし) |
minimum-idle | Integer | same as maximumPoolSize |
password | String | none |
pool-name | String | auto-generated |
read-only | Boolean | false |
register-mbeans | Boolean | false |
scheduled-executor | service | none |
schema | String | driver default |
transaction-isolation | String | driver default |
username | String | none |
validation-timeout | Long | 5000ms |
Tomcat
tomcatに戻したい場合
Maven
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<exclusions>
<exclusion>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jdbc</artifactId>
</dependency>
Gradle
compile("org.springframework.boot:spring-boot-starter-data-jpa") {
exclude group: "com.zaxxer", module: "HikariCP"
}
compile("org.apache.tomcat:tomcat-jdbc")
デフォルトの設定を上書きします。
spring.datasource.type= org.apache.tomcat.jdbc.pool.DataSource
Tomcat Connection Poolのプロパティ設定
spring.datasource.tomcat.*
Common Attributes
property name | type | default |
---|---|---|
defaultAutoCommit | boolean | JDBC driver default |
defaultReadOnly | boolean | |
defaultTransactionIsolation | String | JDBC driver default |
defaultCatalog | String | |
driverClassName | String | |
username | String | |
password | String | |
maxActive | int | 100 |
maxIdle | int | same as maxActive |
minIdle | int | same as initialSize |
initialSize | int | 10 |
maxWait | int | 30000 ms |
testOnBorrow | boolean | false |
testOnConnect | boolean | false |
testOnReturn | boolean | false |
testWhileIdle | boolean | false |
validationQuery | String | |
validationQueryTimeout | int | -1 |
validatorClassName | String | null |
timeBetweenEvictionRunsMillis | int | 5000 ms |
numTestsPerEvictionRun | int | *Property not used |
minEvictableIdleTimeMillis | int | 60000 ms |
accessToUnderlyingConnectionAllowed | boolean | |
removeAbandoned | boolean | false |
removeAbandonedTimeout | int | 60 s |
logAbandoned | boolean | false |
connectionProperties | String | null |
poolPreparedStatements | boolean | *Property not used |
maxOpenPreparedStatements | int | *Property not used |
- Tomcat JDBC Enhanced Attributesは割愛します。
spring-boot-starter-webへの依存関係の削除
- spring-boot-starter-mustache
- spring-boot-starter-thymeleaf
これらのstarterからspring-boot-starter-webへの依存関係が削除されています。
具体的にはpom.xmlのdependencyから次の記述が削除されています。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
WebFluxのサポート
- spring-boot-starter-webflux
- spring-boot-starter-reactor-netty (default web engine)
Reactive dataのサポート
Reactive対応のData Repositoryが追加されています。
- MongoDB (spring-boot-starter-data-mongodb-reactive)
- Redis (spring-boot-starter-data-redis-reactive)
- Cassandra (spring-boot-starter-data-cassandra-reactive)
Proxying strategyのデフォルトの変更
デフォルトがCGLIBになりました。設定の変更はproxy-target-classプロパティで行います。
spring.aop.proxy-target-class= true
- true : CGLIB subclass-based
- false : JDK interface-based
application.propertiesの名前空間の変更
Servlet-specific server properties
old | new |
---|---|
server.context-parameters.* | server.servlet.context-parameters.* |
server.context-path | server.servlet.context-path |
server.jsp.init-parameters.* | server.servlet.jsp.init-parameters.* |
server.jsp.registered | server.servlet.jsp.registered |
server.path | server.servlet.path |
Multipart configuration
old | new |
---|---|
spring.http.multipart.* | spring.servlet.multipart.* |
webEnvironment flagの指定方法の変更
webfluxが追加されたことにより、新しいプロパティspring.main.web-application-typeが追加されています。
Spring Boot 1.5までは、webアプリケーションとして起動させない(組み込みwebコンテナが立ち上がらない)ようにするには、下記のように設定していましたが
spring.main.web-environment= false
2.0以降は新しいプロパティspring.main.web-application-typeにnoneを指定します。
spring.main.web-application-type= none
このプロパティに指定できる値は次の3種類です。
- none
- The application should not run as a web application and should not start an embedded web server
- servlet
- The application should run as a servlet-based web application and should start an embedded servlet web server.
- reactive
- The application should run as a reactive web application and should start an embedded reactive web server.
Mustache templateのデフォルト値変更
Mustacheテンプレートエンジンのテンプレートファイルのデフォルト拡張子が.htmlから.mustacheに変わっています。
spring.mustache.suffix= .mustache
★Gradle Pluginの変更
こちらのブログ([Spring Boot's new Gradle plugin] (https://spring.io/blog/2017/04/05/spring-boot-s-new-gradle-plugin))にポストされたとおり、Gradle Pluginの機能が変更されています。
実行可能なjar/warの生成
実行可能jar又はwarの生成は、従来bootRepackageタスクで行っていましたが、bootJar又はbootWarタスクに置き換えられました。
dependency-management-plugin
依存関係の管理を行う[dependency-management-plugin] (https://github.com/spring-gradle-plugins/dependency-management-plugin)を明示的に組み込む必要があります。
apply plugin: 'io.spring.dependency-management'
[Spring-Boot-2.0.0-M2-Release-Notes] (https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0.0-M2-Release-Notes)
- [Spring Boot 2.0.0 M2 Configuration Changelog] (https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0.0-M2-Configuration-Changelog)
spring-boot-starter-quartzの追加
[Quartz Job Scheduler] (http://www.quartz-scheduler.org/)というジョブスケジューラがspring-boot-starterとしてサポートされました。
Spring Data Web configuration
ページングやソートの設定がapplication.propertiesで出来るようになっています。
# SpringDataWebProperties
spring.data.web.pageable.default-page-size= 20
spring.data.web.pageable.max-page-size= 2000
# Whether to expose and assume 1-based page number indexes.
spring.data.web.pageable.one-indexed-parameters= false
spring.data.web.pageable.page-parameter= page
# General prefix to be prepended to the page number and page size parameters.
spring.data.web.pageable.prefix= ""
# Delimiter to be used between the qualifier and the actual page number and size properties.
spring.data.web.pageable.qualifier-delimiter= _
spring.data.web.pageable.size-parameter= size
spring.data.web.sort.sort-parameter= sort
- prefix
- 各パラメータのprefixを指定します。
- 例) prefix=p_ → パラメータ名はp_pageやp_sizeとなります。
- one-indexed-parameters
- false、ページ番号の始まりを0とします。
- page=0は1ページ目、page=1は2ページ目を指します。
- true、ページ番号の始まりを1とします。
- page=0とpage=1は同じ1ページ目、page=2は2ページ目を指します。
- false、ページ番号の始まりを0とします。
- qualifier-delimiter
- qualifierとパラメータの区切り文字を指定します。
- 1つのURLに2つ(以上)のページ情報を含めたい場合に利用します。
このプロパティはClass [SpringDataWebProperties] (https://docs.spring.io/spring-boot/docs/2.0.0.RELEASE/api/org/springframework/boot/autoconfigure/data/web/SpringDataWebProperties.html)に反映されます。
サンプルコード
import org.springframework.data.domain.Pageable
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
@RestController
@RequestMapping(path = ["index"])
class IndexController {
// 例1
@GetMapping
fun index(pageable: Pageable): String {
return pageable.toString()
}
// 例2
@GetMapping(path = ["2"])
fun index2(@Qualifier("foo") foo: Pageable
, @Qualifier("bar") bar: Pageable): String {
println("foo: ${foo}")
println("bar: ${bar}")
return "OK"
}
}
例1
http://localhost:9000/app/index
↓
Page request [number: 0, size 20, sort: UNSORTED]
http://localhost:9000/app/index?page=1&size=30&sort=id,desc&sort=name,asc
↓
Page request [number: 1, size 30, sort: id: DESC,name: ASC]
例2
http://localhost:9000/app/index/2?foo_page=1&foo_size=10&bar_page=3&bar_size=10
↓
foo: Page request [number: 1, size 10, sort: UNSORTED]
bar: Page request [number: 3, size 10, sort: UNSORTED]
注意
[Spring Data Rest] (https://projects.spring.io/spring-data-rest/)に似たパラメータがあるので注意が必要です。
# DATA REST (RepositoryRestProperties)
spring.data.rest.default-page-size= # Default size of pages.
spring.data.rest.limit-param-name= # Name of the URL query string parameter that indicates how many results to return at once.
spring.data.rest.max-page-size= # Maximum size of pages.
spring.data.rest.page-param-name= # Name of the URL query string parameter that indicates what page to return.
spring.data.rest.sort-param-name= # Name of the URL query string parameter that indicates what direction to sort results.
JDBCTemplate configuration
JDBCTemplateの設定がapplication.propertiesで出来るようになっています。
spring.jdbc.template.fetch-size= -1
spring.jdbc.template.max-rows= -1
spring.jdbc.template.query-timeout=
このプロパティはClass [JdbcProperties] (https://docs.spring.io/spring-boot/docs/2.0.0.RELEASE/api/org/springframework/boot/autoconfigure/jdbc/JdbcProperties.html)に反映されます。
Cassandraのプーリングオプションのconfiguration
プーリングオプションの設定がapplication.propertiesで出来るようになっています。
spring.data.cassandra.pool.heartbeat-interval= 30s
spring.data.cassandra.pool.idle-timeout= 120s
spring.data.cassandra.pool.max-queue-size= 256
spring.data.cassandra.pool.pool-timeout= 5000ms
このプロパティはClass [CassandraProperties] (https://docs.spring.io/spring-boot/docs/2.0.0.RELEASE/api/org/springframework/boot/autoconfigure/cassandra/CassandraProperties.html)に反映されます。
★spring-boot-starter-jsonの追加
jackson-databindの代わりになるspring-boot-starter-jsonが追加されています。
Maven
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
</dependency>
Gradle
compile("org.springframework.boot:spring-boot-starter-json")
また、以下の依存関係を別途追加しなくてもよくなりました。
- jackson-datatype-jdk8
- jackson-datatype-jsr310
- jackson-module-parameter-names
spring-boot-starter-thymeleafの変更
spring-boot-starter-thymeleafにthymeleaf-extras-java8timeへの推移的依存関係が含まれるようになったので依存関係を別途追加しなくてもよくなりました。
Mongo clientのconfiguration
MongoClientSettingsBuilderCustomizerでMongoクライアントの設定ができるようになっています。
Test
Unit Test用のアノテーションが追加されています。
- JooqTest
- DataRedisTest
[Spring-Boot-2.0.0-M3-Release-Notes] (https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0.0-M3-Release-Notes)
- [Spring Boot 2.0.0 M3 Configuration Changelog] (https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0.0-M3-Configuration-Changelog)
★Maven Pluginの変更
ゴールにパラメータを指定する場合、パラメータ名のprefixに"spring-boot"を付加するようになっています。
例
profileを指定するパラメータは、1.5まではrun.profiles
でしたが
mvn spring-boot:run -Drun.profiles=foo
2.0以降はspring-boot.run.profiles
になります。
mvn spring-boot:run -Dspring-boot.run.profiles=foo
DevToolsの変更
HTTP経由のリモートデバッグがサポートされなくなりました。
[Spring-Boot-2.0.0-M4-Release-Notes] (https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0.0-M4-Release-Notes)
- [Spring Boot 2.0.0 M4 Configuration Changelog] (https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0.0-M4-Configuration-Changelog)
application.propertiesの名前空間の変更
Liquibase and Flyway
old | new |
---|---|
liquibase.* | spring.liquibase.* |
flyway.* | spring.flyway.* |
Database initializer
Spring Batchが使用するデータベースの初期化をコントロールするプロパティが変わっています。
また、Spring Integration、Spring Session、QUARTZ SCHEDULERも同様の仕組みで初期化ができるようになりました。
指定できる値はDataSourceInitializationModeで定義されている次の3種類です。
- always
- Always initialize the datasource.
- embedded
- Only initialize an embedded datasource.
- never
- Do not initialize the datasource.
Spring Batch
spring.batch.initializer.enabled= true
spring.batch.initialize-schema= embedded
このプロパティはClass [BatchProperties] (https://docs.spring.io/spring-boot/docs/2.0.0.RELEASE/api/org/springframework/boot/autoconfigure/batch/BatchProperties.html)に反映されます。
Spring Integration
spring.integration.jdbc.initialize-schema= embedded
このプロパティはClass [IntegrationProperties] (https://docs.spring.io/spring-boot/docs/2.0.0.RELEASE/api/org/springframework/boot/autoconfigure/integration/IntegrationProperties.html)に反映されます。
Spring Session
spring.session.jdbc.initialize-schema=embedded
このプロパティはClass [JdbcSessionProperties] (https://docs.spring.io/spring-boot/docs/2.0.0.RELEASE/api/org/springframework/boot/autoconfigure/session/JdbcSessionProperties.html)に反映されます。
QUARTZ SCHEDULER
spring.quartz.jdbc.initialize-schema= embedded
このプロパティはClass [QuartzProperties] (https://docs.spring.io/spring-boot/docs/2.0.0.RELEASE/api/org/springframework/boot/autoconfigure/quartz/QuartzProperties.html)に反映されます。
compile flag
spring-boot-starter-parentを使うMavenプロジェクトでは-parameters
コンパイルフラグがデフォルトで有効になります。
[javac] (https://docs.oracle.com/javase/jp/8/docs/technotes/tools/windows/javac.html)
Reflection APIのメソッドjava.lang.reflect.Executable.getParametersが取得できるように、コンストラクタやメソッドの仮パラメータ名を、生成されたクラス・ファイルに格納します。
★Support
- Java9
[Spring-Boot-2.0.0-M5-Release-Notes] (https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0.0-M5-Release-Notes)
- [Spring Boot 2.0.0 M5 Configuration Changelog] (https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0.0-M5-Configuration-Changelog)
Maven Surefire pluginのバージョンアップ
Maven Surefire Pluginのinclude/excludeの設定が変更されています。
下記は1.5までのデフォルトの設定です。2.0では利用するSurefire Pluginのバージョンが上がっていてデフォルトの設定が変わっています。
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<includes>
<include>**/*Tests.java</include>
<include>**/*Test.java</include>
</includes>
<excludes>
<exclude>**/Abstract*.java</exclude>
</excludes>
</configuration>
</plugin>
また、JUnit5を利用する場合は使用するMaven Surefire pluginのバージョンを2.19.1に下げる必要があるようです。
- [Inclusions and Exclusions of Tests] (http://maven.apache.org/surefire/maven-surefire-plugin/examples/inclusion-exclusion.html)
プラグインのバージョン状況
デフォルトで利用するプラグインのバージョンをまとめました。
plugin | 1.5.10 | 2.0.0.RC2 |
---|---|---|
clean | 2.6.1 | 3.0.0 |
compiler | 3.1 | 3.7.0 |
deploy | 2.8.2 | 2.8.2 |
install | 2.5.2 | 2.5.2 |
jar | 2.6 | 3.0.2 |
resources | 2.6 | 3.0.1 |
site | 3.5.1 | 3.6 |
surefire | 2.18.1 | 2.20.1 |
spring-boot | 1.5.10 | 2.0.0 |
build-helper | 1.10 | - |
help | 2.2 | - |
xml | 1.0 | - |
Gradle pluginの変更
bootRunタスクでアプリケーションの引数(args)とJVM引数(jvmArgs)を指定できるようになっています。
例
bootRun {
args += ['-X', '-F']
jvmArgs += ['-Dspring.profiles.active=dev']
}
Redisドライバーの変更
spring-boot-starter-redisで組み込むRedisドライバーのデフォルトが[Jedis] (https://github.com/xetorthio/jedis)から[Lettuce] (https://github.com/lettuce-io/lettuce-core)へ変わっています。
Mockito 1.x
Mockito 1.xでは@MockBeanと@SpyBeanを使ってオブジェクトの注入ができません。
spring-boot-starter-testを使っていない場合は、手動でMockito 2.xへアップグレードする必要があります。
Support
- [OAuth 2.0] (http://projects.spring.io/spring-security-oauth/)
- Java9
- [Micrometer 1.0.0-rc.2] (http://micrometer.io/)
- [JSON-B] (http://json-b.net/)
★Spring Framework 5.0 GA
[Spring Framework 5.0 goes GA] (https://spring.io/blog/2017/09/28/spring-framework-5-0-goes-ga)
このマイルストーンから、Spring Framework 5.0 GA版が使用されています。
[Spring-Boot-2.0.0-M6-Release-Notes] (https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0.0-M6-Release-Notes)
- [Spring Boot 2.0.0 M6 Configuration Changelog] (https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0.0-M6-Configuration-Changelog)
Gradle pluginの変更
BootRun、BootJar、BootWarタスクで、mainClassNameプロパティを利用するようになりました。
それまではそれぞれのタスクのプロパティで設定していました。
JPAのopen-in-viewプロパティの警告
デフォルト値はtrueですが、このプロパティの値を明示的にtrueに設定しないと起動時に警告メッセージが表示されます。
spring.jpa.open-in-view= true
警告メッセージ
spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
Configuration location
2.0ではapplication.propertiesの検索パスを設定方法が変わっています。
1.5まではspring.config.locationに設定するパスはデフォルトの検索パスに追加されていました。
2.0からはspring.config.locationに設定するパスはデフォルトの検索パスを置き換えます。検索パスを追加したい場合はadditional-locationという新しいプロパティを使用します。
spring.config.additional-location= # Config file locations used in addition to the defaults.
ConfigurationPropertiesの検証
ConfigurationPropertiesアノテーションを付けたオブジェクトのバリデーションを行うにはValidatedアノテーションが必要です。
例
次のようなプロパティをFooConfigクラスへバインドする場合
my-app.my-module.foo.first-name= ruby
#my-app.my-module.foo.last-name= tomato
#my-app.my-module.foo.age= 77
my-app.my-module.foo.bar-settings.max-height= 999
my-app.my-module.foo.bar-settings.min-height= 100
@Component
@ConfigurationProperties(prefix = "my-app.my-module.foo")
@Validated
class FooConfig {
lateinit var firstName: String
lateinit var lastName: String
var age: Int? = null
var barSettings: BarConfig = BarConfig()
class BarConfig {
lateinit var maxHeight: Integer
lateinit var minHeight: Integer
override fun toString(): String {
return "BarConfig(maxHeight=$maxHeight, minHeight=$minHeight)"
}
}
override fun toString(): String {
return "FooConfig(firstName='$firstName', lastName='$lastName', age=$age, barSettings=$barSettings)"
}
}
Validatedアノテーションが付いているとアプリケーション起動時に検証が行われ、NotNullなプロパティが初期化できなかった場合、エラーを出力して終了します。
***************************
APPLICATION FAILED TO START
***************************
Description:
Failed to bind properties under 'my-app.my-module.foo.last-name' to java.lang.String:
Reason: Unable to get value for property last-name
Action:
Update your application's configuration
Validatedアノテーションが付いていない場合、アプリケーションは起動しますがFooConfigのプロパティに最初にアクセスしたタイミングでエラーが発生します。
kotlin.UninitializedPropertyAccessException: lateinit property lastName has not been initialized
Property Origins
プロパティがバインドできなかった場合(BindExceptionがスローされるなどの理由)、そのプロパティのソース(property source)が表示されるようになっています。
下記はコマンドラインから与えたプロパティ"-Dmy-app.my-module.foo.color=white"が、my-app.my-module.foo.colorにバインドできなかったときに表示されたメッセージです。
指定された"white"という値がEnum Color型のフィールドにバインドできなかったためですが、この値が"SystemProperties"から指定されたことがメッセージの"Origin"の行から確認できます。
***************************
APPLICATION FAILED TO START
***************************
Description:
Failed to bind properties under 'my-app.my-module.foo.color' to com.example.demomavenspring2.type.Color:
Property: my-app.my-module.foo.color
Value: white
Origin: "my-app.my-module.foo.color" from property source "systemProperties"
Reason: Failed to convert from type [java.lang.String] to type [@javax.validation.constraints.NotNull com.example.demomavenspring2.type.Color] for value 'white'; nested exception is java.lang.IllegalArgumentException: No en
um constant com.example.demomavenspring2.type.Color.white
Action:
Update your application's configuration. The following values are valid:
BLUE
CYAN
GREEN
MAGENTA
RED
YELLOW
★DataSource Initialization
データベースの初期化をコントロールするプロパティが変更されています。
spring.datasource.initialize= true
新しいプロパティは、デフォルトでは組み込みデータベース(H2、HSQL、Derby)に対してのみ機能します。
spring.datasource.initialization-mode= embedded
指定できる値はDataSourceInitializationModeで定義されている次の3種類です。
alwaysを指定すると組み込みデータベースでなくても機能してしまうので注意が必要です。
- embedded
- Only initialize an embedded datasource.
- always
- always initialize the datasource.
- never
- Do not initialize the datasource.
JPA mapping resources
マッピングリソースファイルを指定できるようになっています。
spring.jpa.mapping-resources= # Mapping resources (equivalent to "mapping-file" entries in persistence.xml).
HTTP/2
HTTP/2の有効/無効化をプロパティで設定できるようになっています。
このマイルストーンではTomcatとUndertowが対応します。(JettyはRC1で対応)
server.http2.enabled= true
HTTP/2が有効になったか確認するにはsslを有効にしてhttpsでアクセスする必要があります。
自動コンフィグレーションと依存関係の管理から除外
プロジェクトで利用する場合はバージョンを明示する必要があります。
- [Spring Mobile] (http://projects.spring.io/spring-mobile/)
Kotlin
runApplicationというインライン関数が用意されています。
1.5まで
fun main(args: Array<String>) {
SpringApplication.run(Application::class.java, *args)
}
2.0以降は次のようにも記述できます。
fun main(args: Array<String>) {
runApplication<Application>(*args)
}
または
fun main(args: Array<String>) {
runApplication<Application>(*args) {
// initialize code
}
}
[Spring-Boot-2.0.0-M7-Release-Notes] (https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0.0-M7-Release-Notes)
- [Spring Boot 2.0.0 M7 Configuration Changelog] (https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0.0-M7-Configuration-Changelog)
Jacksonのシリアライズのデフォルト値の変更
serialization.write-dates-as-timestampsプロパティのデフォルト値がfalseに変わっています。
サンプルコード
次のデータクラスのインスタンスがどのようにシリアライズされるか確認しました。
data class DateTimeDemo(
var lt: LocalTime = LocalTime.now(),
var ld: LocalDate = LocalDate.now(),
var dt: LocalDateTime = LocalDateTime.now(),
var ym: YearMonth = YearMonth.now(),
var md: MonthDay = MonthDay.now(),
var dd: Duration = Duration.of(1, ChronoUnit.HOURS),
var pp: Period = Period.ofDays(1),
var ii: Instant = Instant.now()
) : Serializable
2.0のデフォルトの場合
spring.jackson.serialization.write-dates-as-timestamps= false
spring.jackson.serialization.write-durations-as-timestamps= true
↓
{
lt: "14:06:20.9023938",
ld: "2018-02-16",
dt: "2018-02-16T14:06:20.9023938",
ym: "2018-02",
md: "--02-16",
dd: "PT1H",
pp: "P1D",
ii: "2018-02-16T05:06:20.904893500Z"
}
1.5のデフォルトの場合(jacksonのデフォルトでもあります)
spring.jackson.serialization.write-dates-as-timestamps= true
spring.jackson.serialization.write-durations-as-timestamps= true
↓
{
lt: [
14,
7,
43,
61545000
],
ld: [
2018,
2,
16
],
dt: [
2018,
2,
16,
14,
7,
43,
61545000
],
ym: [
2018,
2
],
md: "--02-16",
dd: 3600,
pp: "P1D",
ii: 1518757663.0645459
}
Kotlin
spring-boot-starter-jsonからjackson-module-kotlinの推移的依存関係が削除されました。
Kotlinでjacksonを利用している場合は手動で依存関係を追加する必要があります。
Maven
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-kotlin</artifactId>
</dependency>
Gradle
compile("com.fasterxml.jackson.module:jackson-module-kotlin")
Spring Data repositories enablement
reactive対応のData Repositoryの追加により、リポジトリの有効/無効化のプロパティが変わっています。
従来のプロパティではtrue/falseで設定します。
spring.data.cassandra.repositories.enabled=
spring.data.mongodb.repositories.enabled=
spring.data.redis.repositories.enabled=
spring.data.couchbase.repositories.enabled=
Redis以外は新しいプロパティで、auto(デフォルト)、imperative、reactive、noneから選択します。
Redisは従来通りで変更はないようです。
spring.data.cassandra.repositories.type= auto
spring.data.mongodb.repositories.type= auto
spring.data.redis.repositories.enabled= true
spring.data.couchbase.repositories.type= auto
- auto
- enables all repository types automatically based on their availability
- imperative
- enables imperative repisitories
- reactive
- enables reactive repositories
- none
- enables no repositories
Redisだけtypeプロパティが無い
それぞれのstarter(spring-boot-starter-data-redis、spring-boot-starter-data-redis-reactive)のpom.xmlを確認すると、reactiveは従来版のエイリアスのような設定で違いはないようです。
自動コンフィグレーションと依存関係の管理から除外
プロジェクトで利用する場合はバージョンを明示する必要があります。
- [Spring Social] (https://projects.spring.io/spring-social/)
- [commons-digester] (https://commons.apache.org/proper/commons-digester/)
Conditions evaluation delta on restart
DevToolsを有効にしたアプリケーションは、起動中にクラスパスが更新されると自動的に再起動(automatic restart)します。
クラスパスを更新させる方法はIDEによって異なります。Eclipseでは変更したファイルを保存したとき、IntelliJ IDEAではプロジェクトのビルド(Ctrol + F9)、ファイルの再コンパイル(Ctrl +
Shift + F9)でも同じ効果があります。
再起動を制御するには次のプロパティを設定します。
spring.devtools.restart.enabled= false
再起動時にアプリケーションの自動構成の変更が検出されると、その変更内容が「CONDITION EVALUATION DELTA」というレポートに出力されます。
(あくまでもautomatic restart時なので、停止&起動ではレポートは出力されません)
レポートの出力を制御するには次のプロパティを設定します。
spring.devtools.restart.log-condition-evaluation-delta= false
application.propertiesのDuration型
プロパティがJava Duration型の場合、"1h"のようにsuffixに単位を付けて指定することができるようになりました。
server.session.cookie.max-age= 1h
suffixを付けない場合は従来通りの単位になります。
# Maximum age of the session cookie. If a duration suffix is not specified, seconds will be used.
server.session.cookie.max-age= 3600
ただし、時間の長さを指定するプロパティでもDuration型を取らないパラメータもありますので注意が必要です。
たとえば次のmax-ageはLong型なのでsuffixを付けるとエラーになります。
spring.datasource.tomcat.max-age= 30s
キャッシュにRedisを使ったときのデフォルト設定
キャッシュにredisを使用した場合のデフォルト値をapllication.propetiesで設定できるようになっています。
キャッシュにRedisを選択
spring.cache.type= redis
次のプロパティでデフォルトを設定できます。
spring.cache.redis.time-to-live= 120s
spring.cache.redis.cache-null-values= true
spring.cache.redis.use-key-prefix= true
spring.cache.redis.key-prefix= myCache_
このプロパティはClass [CacheProperties] (https://docs.spring.io/spring-boot/docs/2.0.0.RELEASE/api/org/springframework/boot/autoconfigure/cache/CacheProperties.html)に反映されます。
cache-null-valuesプロパティをfalseにした場合、nullをキャッシュしようとするとIllegalArgumentExceptionがスローされます。
この場合は次のようにnullを除外するように設定します。
@Cacheable(cacheNames = ["myCache"], unless="#result == null")
spring-boot-starter-data-couchbase-reactiveの追加
Reactive対応のData Repositoryにcouchbaseが追加されています。
(MongoDB、Redis、CassandraはM1で対応)
- spring-boot-starter-data-couchbase
- spring-boot-starter-data-couchbase-reactive
[Spring Boot 2.0.0 RC1 Release Notes] (https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0.0-RC1-Release-Notes)
- [Spring Boot 2.0.0 RC1 Configuration Changelog] (https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0.0-RC1-Configuration-Changelog)
- [asciidoc] (https://github.com/spring-projects/spring-boot/tree/v2.0.0.RC1/spring-boot-project/spring-boot-docs/src/main/asciidoc)
spring-boot-starter-webへの依存関係の削除
spring-boot-starter-freemarkerからspring-boot-starter-webへの依存関係が削除されています。
(spring-boot-starter-mustache、spring-boot-starter-thymeleafはM1にて対応)
Spring MVC path matchingの変更
suffixパスマッチングのデフォルトの挙動が変わっています。
これに伴い新しいパラメータが追加されています。
spring.mvc.content-negotiation.favor-parameter= false
spring.mvc.content-negotiation.favor-path-extension= false
spring.mvc.content-negotiation.parameter-name=
1.5まで、たとえばこれらのリクエストはすべて
localhost://app/product
localhost://app/product.json
localhost://app/product.xml
次のハンドラメソッドにマッピングされていましたが、2.0以降はデフォルトではマッピングされなくなります。(404のNot Foundになります)
@GetMapping(path = ["/app/product"])
fun product(): String {
//... 省略
}
application.propertiesの名前空間の変更
old | new |
---|---|
banner.* | spring.banner.* |
spring.metrics.* | management.metrics.* |
server.* | server.servlet.* |
★Legacy properties migration
プロパティファイルの構成が大きく変わっているのでspring-boot-properties-migratorというプロパティファイルの移行モジュールが用意されています。
このモジュールを組み込むと、アプリケーション起動時に古いプロパティがあるとワーニングを表示し、且つ新しい構成に一時的に変換して起動してくれます。
なお、application.propertiesファイルをコンバートする訳ではないので修正作業は手動で行う必要があります。
Maven
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-properties-migrator</artifactId>
</dependency>
Gradle
runtime("org.springframework.boot:spring-boot-properties-migrator")
例
次のような古いフォーマットの記述があった場合
banner.charset: UTF-8 # Banner file encoding.
アプリケーション起動時にワーニングを表示してくれます。
WARN 7460 --- [ restartedMain] o.s.b.c.p.m.PropertiesMigrationListener :
The use of configuration keys that have been renamed was found in the environment:
Property source 'applicationConfig: [classpath:/application.properties]':
Key: banner.charset
Line: 2
Replacement: spring.banner.charset
Each configuration key has been temporarily mapped to its replacement for your convenience. To silence this warning, please update your configuration to use the new keys.
Hibernateプロパティのconfiguration
hibernateのプロパティをカスタマイズできます。
サンプルコード
@Bean
fun customizer() = HibernatePropertiesCustomizer { props ->
props["hibernate.show_sql"] = true
props["hibernate.format_sql"] = true
props["hibernate.use_sql_comments"] = true
props["hibernate.generate_statistics"] = true
}
application.propertiesでは次のプロパティでカスタマイズできます。
spring.jpa.properties.hibernate.*
- [User Guild / configurations]
(https://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/Hibernate_User_Guide.html#configurations)
Redisのconfiguration
RedisCacheConfigurationでRedisCacheManagerの設定ができるようになっています。
@Bean
fun redisCacheConfig() = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(2))
.disableCachingNullValues()
application.propertiesでは次のプロパティでカスタマイズできます。
spring.cache.redis.*
Support
- [GSon] (https://github.com/google/gson)
- HTTP/2 (Jetty)
[Spring Boot 2.0.0 RC2 Release Notes] (https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0.0-RC2-Release-Notes)
- [Spring Boot 2.0.0 RC2 Configuration Changelog] (https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0.0-RC2-Configuration-Changelog)
application.propertiesの変更
次のプロパティが再配置されています。(RC2で見直しされたようです)
ENDPOINTS WEB CONFIGURATION
management.endpoints.web.expose
↓
management.endpoints.web.exposure.include
management.endpoints.web.exposure.exclude
ENDPOINTS JMX CONFIGURATION
management.endpoints.jmx.expose
↓
management.endpoints.jmx.exposure.include
management.endpoints.jmx.exposure.exclude
Webjars Locatorの依存管理の変更
webjars-locatorの依存関係管理がwebjars-locator-coreに変更されています。
2.0より
Maven
<dependency>
<groupId>org.webjars</groupId>
<artifactId>webjars-locator-core</artifactId>
<version>0.35</version>
</dependency>
Gradle
compile("org.webjars:webjars-locator-core:0.35")
Kotlin
Spring Bootリファレンスのセクション47にKotlin supportが追加されています。
- [Part IV. Spring Boot features | 47. Kotlin support] (https://docs.spring.io/spring-boot/docs/2.0.0.RELEASE/reference/html/boot-features-kotlin.html)
また、Spring FrameworkのリファレンスにもKotlinのセクションがあります。
- [Spring Framework | Language Support] (https://docs.spring.io/spring/docs/5.0.4.RELEASE/spring-framework-reference/languages.html#kotlin)
Custom binding delimiter
以前からプロパティのタイプがコレクションで、バインドする値(例えばapplication.propertiesで定義する)がカンマ区切りのとき、自動的にコレクションへバインドしてくれていました。
my-app.my-module.foo.hobbies= shopping,trekking,movies
@Component
@ConfigurationProperties(prefix = "my-app.my-module.foo")
data class FooConfig(
var hobbies: List<String> = mutableListOf()
)
2.0よりDelimiterアノテーションを使って区切り文字を指定することができるようになっています。
my-app.my-module.foo.hobbies= shopping; trekking; movies
@field:Delimiter(value = ";")
var hobbies: List<String> = mutableListOf()
逆にカンマ区切りで分割したくない場合は次のように指定します。
@field:Delimiter(value = Delimiter.NONE)
Support
- [Micrometer 1.0-GA] (http://micrometer.io/)
- M5では1.0.0-RC2でしたが、GAをサポートします。
JOOQのSQLDialectの検出
デフォルトで自動検出が有効なので、通常はapplication.propertiesで明示する必要はありません。
spring.jooq.sql-dialect= # SQL dialect to use. Auto-detected by default.
- [org.jooq.SQLDialect] ( http://www.jooq.org/javadoc/3.7.3/org/jooq/SQLDialect.html)
リリースノートの補足
[Spring Boot 2.0 Migration Guide] (https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Migration-Guide)には、基本的にM1からRC2の内容がまとめられていますが、Migration Guideにだけ記載されている事項がありましたので補足します。
spring.jpa.hibernate.ddl-autoのデフォルトの挙動の変更
1.5までのデフォルトは、組み込みデータベース使用時にcreate-drop
、それ以外はnone
2.0以降のデフォルトは、組み込みデータベース使用時で且つflyway/Liquibaseを使用していない時にcreate-drop
、それ以外はnone
このプロパティに設定できる値のその内容については変更はないようです。
- create-drop
- Create and then destroy the schema at the end of session
- create
- Create the schema and destroy previous data
- none
- Disable DDL handling
- update
- Update the schema if necessary
- validate
- Validate the schema, make no changes to the database
Spring Dataのバージョンアップの影響
- [Spring Data JPA - Reference Documentation - Version 1.11.10.RELEASE] (https://docs.spring.io/spring-data/jpa/docs/1.11.10.RELEASE/reference/html/)
- [Spring Data JPA - Reference Documentation - Version 2.0.4.RELEASE] (https://docs.spring.io/spring-data/jpa/docs/2.0.4.RELEASE/reference/html/)
Spring Data CommonsおよびJpaもバージョンアップしています。
このバージョンアップによりインターフェースの変更が行われているため実装コードの修正が必要な場合があります。
例えばCrudRepositoryインターフェースについては、次の違いがあります。
@NoRepositoryBean
public interface CrudRepository<T, ID extends Serializable> extends Repository<T, ID>
@NoRepositoryBean
public interface CrudRepository<T, ID> extends Repository<T, ID>
signature | JPA 1.11.10 | JPA 2.0.4 |
---|---|---|
同じ | <S extends T> S save(S entity) | <S extends T> S save(S entity) |
違う | <S extends T> Iterable<S> save(Iterable<S> entities) | <S extends T> Iterable<S> saveAll(Iterable<S> entities) |
違う | T findOne(ID id) | Optional<T> findById(ID id) |
違う | boolean exists(ID id) | boolean existsById(ID id) |
同じ | Iterable<T> findAll() | Iterable<T> findAll() |
違う | Iterable<T> findAll(Iterable<ID> ids) | Iterable<T> findAllById(Iterable<ID> ids) |
同じ | long count() | long count() |
違う | void delete(ID id) | void deleteById(ID id) |
同じ | void delete(T entity) | void delete(T entity) |
違う | void delete(Iterable<? extends T> entities) | void deleteAll(Iterable<? extends T> entities) |
同じ | void deleteAll() | void deleteAll() |