tonykang22 / study

0 stars 0 forks source link

[Kotlin & Spring] 04. 커스텀 스프링 부트 스타터 만들기 #124

Open tonykang22 opened 1 year ago

tonykang22 commented 1 year ago

커스텀 스프링 부트 스타터 만들기

스프링 부트 스타터란

이름 설명
spring-boot-starter-web 스프링 MVC 기반의 웹 애플리케이션 스타터. 임베디드 톰캣을 포함
spring-boot-starter-security 스프링 시큐리티 관련 설정과 라이브러리를 포함
spring-boot-starter-data-jpa 스프링 트랜잭션, 하이버네이트, 히카리 CP 등을 포함
spring-boot-starter-test 테스팅 프레임워크인 JUnit, Mokito, AssertJ 등을 포함
spring-boot-starter-webflux 리액티브 프레임워크인 프로젝트 리액터와 리액터 네티를 포함



서드 파티 스프링 부트 스타터



커스텀 스프링 부트 스타터 만들기



프로젝트 구조

image



최상위 build.gradle.kts

import org.springframework.boot.gradle.plugin.SpringBootPlugin

plugins {
    id("org.springframework.boot") version "2.7.0" apply false
    id("io.spring.dependency-management") version "1.0.11.RELEASE"

    id("maven-publish")
    kotlin("jvm") version "1.6.21"
    kotlin("plugin.spring") version "1.6.21"
    kotlin("kapt") version "1.6.21"
}

allprojects {
    group = "com.fastcampus.springboot"
    version = "1.0-SNAPSHOT"

    repositories {
        mavenLocal()
        mavenCentral()
    }
}

subprojects {
    apply(plugin = "kotlin")
    apply(plugin = "kotlin-kapt")
    apply(plugin = "kotlin-spring")
    apply(plugin = "maven-publish")
    apply(plugin = "io.spring.dependency-management")

    dependencies {
        implementation("org.jetbrains.kotlin:kotlin-reflect")
        implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
    }

    dependencyManagement {
        imports {
            mavenBom(SpringBootPlugin.BOM_COORDINATES)
        }
    }
}
  1. maven-publish는 메이븐 리파지토리에 배포할 수 있게 하는 기능을 하는 플러그인이다.
    • 제작한 스타터 프로젝트를 로컬 메이븐 리파지토리에 배포해서 handgame-spring-boot-app에서 사용할 예정
  2. allprojects는 차상위 프로젝트를 포함한 전체 프로젝트에서 사용하는 빌드를 구성한다.
  3. subprojects는 settings.gradle.kts에서 include에 감싸진 프로젝트의 빌드를 구성한다.
    • 예를 들면, include("handgame", "handgame-spring-boot-starter")와 같은 프로젝트가 서브 프로젝트이다.



handgame/Handgame.kt

package com.fastcampus.springboot.handgame

import java.util.*

class Handgame {

    fun play(player: GameCommand): Pair<GameResult, GameCommand> {
        val opponent = GameCommand.values()[Random().nextInt(3)]
        return if (player == opponent) {
            return Pair(GameResult.동점, player)
        } else if (player == GameCommand.바위 && opponent == GameCommand.가위) {
            return Pair(GameResult.승리, GameCommand.가위)
        } else if (player == GameCommand.가위 && opponent == GameCommand.보) {
            return Pair(GameResult.승리, GameCommand.보)
        } else if (player == GameCommand.보 && opponent == GameCommand.바위) {
            return Pair(GameResult.승리, GameCommand.바위)
        } else Pair(GameResult.패배, opponent)
    }
}

enum class GameCommand(num: Int) {
    바위(0), 가위(1), 보(2);
}

enum class GameResult {
    동점, 승리, 패배
}



handgame-spring-boot-autoconfigure/build.gradle.kts

  1. kapt는 코틀린의 애노테이션 프로세서이다.
    • 애노테이션 프로세서는 컴파일 타임에 애노테이션을 읽어서 동적으로 코드를 생성하거나 변경하는 등의 기능을 말하는데 kapt는 코틀린 언어에서 이러한 애노테이션 프로세서가 동작하도록 하는 플러그인이다.
  2. 자동 설정 클래스에서 Handgame.kt 클래스를 불러올 수 있어야 하기 때문에 handgame 프로젝트에 대한 의존성을 추가한다.
  3. spring-boot, spring-boot-autoconfigure는 스프링 부트 자동 설정 기능을 사용하려면 필수적인 의존성이다.


dependencies {
    kapt("org.springframework.boot:spring-boot-autoconfigure-processor")
    kapt("org.springframework.boot:spring-boot-configuration-processor")

    api(project(":handgame"))

    implementation("org.springframework.boot:spring-boot")
    implementation("org.springframework.boot:spring-boot-autoconfigure")
}



handgame-spring-boot-autoconfigure/HandgameAutoconfigure.kt

  1. HandgameAutoconfigure은 Handgame 클래스의 인스턴스를 스프링 빈에 등록하는 자동 설정 클래스이다.
    • 자동 설정 클래스는 설정 클래스임을 나타내기 위해 @Configuration 애노테이션을 선언한다.
  2. @ConditionalOnProperty를 사용해서 my.handgame으로 시작하고 enabled라는 프로퍼티가 true인 경우에만 해당 설정 클래스가 동작하도록 로드 시점을 조정한다.
  3. 만약 사용자가 handgame 빈을 재정의한 경우에는 충돌이 발생할 수 있으므로 @ConditionalOnMissingBean을 사용해서 handgame 빈이 존재하지 않는 경우에만 빈을 로드한다.


package com.fastcampus.springboot.autoconfigure.handgame

import com.fastcampus.springboot.handgame.Handgame
import org.springframework.boot.autoconfigure.AutoConfiguration
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.context.annotation.Bean

@AutoConfiguration
@ConditionalOnClass(Handgame::class)
@ConditionalOnProperty(prefix = "my.handgame", name = ["enabled"], havingValue = "true")
class HandgameAutoconfigure {

    @Bean
    @ConditionalOnMissingBean
    fun handgame() = Handgame()
}



handgame-spring-boot-autoconfigure/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfigure.imports

  1. 작성한 자동 설정 클래스 HandgameAutoconfigure를 스프링에 알려주기 위해 해당 파일에 명시한다.
  2. Spring Boot 2.7 이하 버전에서는 META-INF/spring.factories 파일에 자동 설정 클래스를 명시한다.


com.fastcampus.springboot.autoconfigure.handgame.HandgameAutoconfigure



handgame-spring-boot-starter/build.gradle.kts


dependencies {
        api(project(":handgame"))
        api(project(":handgame-spring-boot-autoconfigure"))
}



스프링 부트 스타터 앱 실행하기

image



handgame-spring-boot-app/build.gradle.kts

dependencies {
    implementation("org.springframework.boot:spring-boot-starter")

    // 우리가 만든 커스텀 스프링 부트 스타터
    implementation("com.fastcampus.springboot:handgame-spring-boot-starter:1.0-SNAPSHOT")
}



HandgameAutoconfiguration: 
    Did not match:
        - @ConditionalOnProperty (my.handgame.enabled=true) did not find property 'enabled' (OnPropertyCondition) 
    Matched:
        - @ConditionalOnClass found required class 'org.example.springboot.handgame.Handgame' (OnClassCondition)



HandgameAutoconfiguration matched:
    - @ConditionalOnClass found required class 'org.example.springboot.handgame.Handgame' (OnClassCondition) 
    - @ConditionalOnProperty (my.handgame.enabled=true) matched (OnPropertyCondition)
HandgameAutoconfiguration#handgame matched:
    - @ConditionalOnMissingBean (types: org.example.springboot.handgame.Handgame; SearchStrategy: all) did not find any beans (OnBean)