👻

MicronautアプリケーションをSpringアプリケーションのように作成する

2022/01/02に公開

MicronautにはSpringでお馴染みの @Service@Repository などを使用できる仕組みが用意されている。
この仕組みを使うことでMicronautアプリケーションをSpringアプリケーションのように作成できる。

https://micronaut-projects.github.io/micronaut-spring/

環境

  • Micronaut 3.2.3
  • Kotlin 1.6.10
  • Java 17

準備

簡単なMicronautアプリケーションを用意する。

Pet.kt
data class Pet(
    val name: String
)
PetContoller.kt
import io.micronaut.http.annotation.Body
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
import io.micronaut.http.annotation.PathVariable
import io.micronaut.http.annotation.Post

@Controller("/pets")
class PetController(
    private val petService: PetService
) {

    @Get("/{id}")
    fun pet(@PathVariable id: Int): Pet? = petService.findPetById(id)

    @Post
    fun add(@Body pet: Pet) {
        petService.addPet(pet)
    }
}

PetService.kt
import jakarta.inject.Singleton

@Singleton
class PetService(
    private val petRepository: PetRepository
) {

    fun findPetById(id: Int): Pet? = petRepository.findById(id)

    fun addPet(pet: Pet) {
        petRepository.add(pet)
    }
}

PetRepository.kt
import jakarta.inject.Singleton

@Singleton
class PetRepository {

    private val pets: MutableList<Pet> = mutableListOf()

    fun findById(id: Int): Pet? = pets.getOrNull(id)

    fun add(pet: Pet) {
        pets.add(pet)
    }
}

Springのアノテーションを使って作り直す

以下の依存関係を追加する。

build.gradle.kts
dependencies {
...
     kapt("io.micronaut.spring:micronaut-spring-annotation")
     kapt("io.micronaut.spring:micronaut-spring-boot")
     implementation("org.springframework.boot:spring-boot-starter")
     implementation("org.springframework.boot:spring-boot-starter-web")
     runtimeOnly("io.micronaut.spring:micronaut-spring-boot")
...
}

各アノテーションを置き換える

PetContoller.kt
- import io.micronaut.http.annotation.Body
- import io.micronaut.http.annotation.Controller
- import io.micronaut.http.annotation.Get
- import io.micronaut.http.annotation.PathVariable
- import io.micronaut.http.annotation.Post
+ import org.springframework.web.bind.annotation.GetMapping
+ import org.springframework.web.bind.annotation.PathVariable
+ import org.springframework.web.bind.annotation.PostMapping
+ import org.springframework.web.bind.annotation.RequestBody
+ import org.springframework.web.bind.annotation.RequestMapping
+ import org.springframework.web.bind.annotation.RestController

- @Controller("/pets")
+ @RestController
+ @RequestMapping("/pets")
class PetController(
    private val petService: PetService
) {

-    @Get("/{id}")
+    @GetMapping("/{id}")
     fun pet(@PathVariable id: Int): Pet? = petService.findPetById(id)

-    @Post
-    fun add(@Body pet: Pet) {
+    @PostMapping
+    fun add(@RequestBody pet: Pet) {
        petService.addPet(pet)
     }
PetService.kt
- import jakarta.inject.Singleton
+ import org.springframework.stereotype.Service

- @Singleton
+ @Service
class PetService(private val petRepository: PetRepository) {
PetRepository.kt
- import jakarta.inject.Singleton
+ import org.springframework.stereotype.Repository

- @Singleton
+ @Repository
class PetRepository {

ビルドエラー

これで置き換え終了だが、ビルドするとエラーが出る。

エラー: Cannot apply AOP advice to final class. Class must be made non-final to support proxying: com.example.PetController
public final class PetController {
             ^

AOPを使うのでfinalクラスを使うとダメっぽい。Kotlinの場合はopenを付与しないといけない。

@RestController
@RequestMapping("/pet")
- class PetController {
+ open class PetController {

毎回openを付与するのは面倒なのでRestControllerアノテーションが付与されたクラスはopenされるようにall-openプラグインを使う。build.gradle.ktsに以下を追加する。

build.gradle.kts
allOpen {
    annotation("org.springframework.web.bind.annotation.RestController")
}

これでビルドが成功するようになる。

最終形

PetContoller.kt
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController

@RestController
@RequestMapping("/pets")
class PetController(
    private val petService: PetService
) {

    @GetMapping("/{id}")
    fun pet(@PathVariable id: Int): Pet? = petService.findPetById(id)

    @PostMapping
    fun add(@RequestBody pet: Pet) {
        petService.addPet(pet)
    }
}

PetService.kt
import org.springframework.stereotype.Service

@Service
class PetService(
    private val petRepository: PetRepository
) {

    fun findPetById(id: Int): Pet? = petRepository.findById(id)

    fun addPet(pet: Pet) {
        petRepository.add(pet)
    }
}

PetRepository.kt
import org.springframework.stereotype.Repository

@Repository
class PetRepository {

    private val pets: MutableList<Pet> = mutableListOf()

    fun findById(id: Int): Pet? = pets.getOrNull(id)

    fun add(pet: Pet) {
        pets.add(pet)
    }
}

まとめ

MicronautアプリケーションをSpringアプリケーションのように作成することができた。
Springで作成したコンポーネントをMicronautで再利用するなどができるようになるので、Springユーザーならお試しでMicronautアプリケーション作ってみるのも良いと思う。

Discussion