실무 프로젝트로 배우는 Kotlin & Spring/스프링 부트 스타트

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

webmaster 2022. 10. 23. 16:05
728x90

스프링 부트 스타터

이름 설명
spring-boot-starter-web SpringMVC 기반의 웹 애플리케이션 스타터, 임베디드 톰캣 포함
spring-boot-starter-security SpringSecurity 관련 설정과 라이브러리 포함
spring-boot-starter-data-jpa SpringTransaction, Hibernate, 히카리CP 등 포함
spring-boot-starter-test JUnit, Mokito, AssertJ 와 같은 테스팅 프레임워크 포함
spring-boot-starter-webflux Reactive 프레임워크인 ProjectReactor, Netty를 포함
  • 스프링 부트 스타터는 스프링 부트 기반의 애플리케이션에 다른 스프링 프로젝트를 쉽게 추가할 수 있도록 만들어진 라이브러리이다.
  • 각각의 스타터에는 스프링 프로젝트와 프로젝트에서 필요로 하는 라이브러리가 같이 포함되어 있다.
  • 스프링 부트 스타터를 사용하면, 스타터 외 필요한 라이브러리를 따로 추가, 관리할 필요가 없다.
  • 스프링 부트 공식 스타터는 명명 규칙에 따라 spring-boot-starter-* 와 같은 형태이다.

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

  • 공식 스프링 부트 스타터가 아닌 서드 파티 스프링 부트 스타터가 있다.
  • 외부 프로젝트에서 스프링 부트와 연동하기 위해 직접 만든 커스텀 스타터를 의미하며, sprint-boot-starter-*이라는 이름으로 만들 수 없다.
  • 서드 파티 스타터는 *-spring-boot-starter와 같은 명명규칙을 사용한다.
    • MyBatis 같은 경우 mybatis-spring-boot-starter라는 이름의 스타터를 직접 만들어서 제공

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

https://github.com/digimon1740/fastcampus-custom-spring-boot-starter

 

GitHub - digimon1740/fastcampus-custom-spring-boot-starter

Contribute to digimon1740/fastcampus-custom-spring-boot-starter development by creating an account on GitHub.

github.com

  • handgame : 가위바위보 게임의 상세 구현이 포함된 라이브러리
  • handgame-spring-boot-autoconfigure : handgame 라이브러리의 자동 설정이 포함
  • handgame-spring-boot-starter : handgame, handgame-spring-boot-autoconfigure을 합친 커스턴 spring-boot-starter
  • handgame-spring-boot-app : handgame-spring-boot-starter를 사용하는 스프링 부트 애플리케이션

최상위 build.gradle

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)
        }
    }
}

  • maven-publish는 Maven-repository에 배포할 수 있게 하는 기능을 하는 plugin으로, 스타터 프로젝트를 로컬 maven-repository에 배포해서, handgame-spring-boot-app에서 사용할 예정이다.
  • allproject는 최상위 프로젝트를 포함한 전체 프로젝트에서 사용하는 빌드 구성
  • subprojects는 settings.gradle.kts에서 include에 감싸진 프로젝트의 빌드를 구성

handgame/Handgame.kt

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

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")
}

publishing {
    publications {
        create<MavenPublication>("maven") {
            groupId = project.group.toString()
            artifactId = project.name
            version = project.version.toString()

            from(components["java"])
        }
    }
}
  • kapt는 코틀린에서 애노테이션 프로세서(컴파일 타임에 애노테이션을 읽어서 동적으로 코드를 생성하거나 변경하는 기능을 한다)가 동작하도록 하는 플러그인이다.
  • hangame 프로젝트의 의존성을 추가해 자동 설정 클래스에서 Handgame.kt 클래스를 불러올 수 있게 설정

HandgameAutoconfigure.kt

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

    @Bean
    @ConditionalOnMissingBean
    fun handgame() = Handgame()
}
  • Handgame 클래스의 인스턴스를 스프링 빈에 등록하는 자동 설정 클래스
  • 자동 설정 클래스는 설정 클래스임을 나타내기 위해 @Configuration 애노테이션을 선언
  • @ConditionalOnProperty를 사용해 "my.handgame.enabled=true" 일 경우에만 해당 설정 클래스가 동작하도록 로드 시점을 조정
  • 사용자가 handgame 빈을 재정의 할 경우 충돌이 발생하므로, @ConditionalOnMissingBean을 사용해서 handgame 빈이 존재하지 않는 경우에만 빈을 로드

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

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

publishing {
    publications {
        create<MavenPublication>("maven") {
            groupId = project.group.toString()
            artifactId = project.name
            version = project.version.toString()

            from(components["java"])
        }
    }
}
  • handgame 라이브러리와 자동 설정 구현체인 handgame-spring-boot-autoconfigure에 대한 의존성 추가

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")
}

HandgameApplication.kt

@SpringBootApplication
class HandgameApplication (
    // 의존성 주입된 Handgame
    val handgame: Handgame
) : CommandLineRunner {

    val logger = LoggerFactory.getLogger(Handgame::class.java)

    override fun run(vararg args: String) {
        val myCommands = buildList {
            add(GameCommand.바위)
            add(GameCommand.가위)
            add(GameCommand.보)
            add(GameCommand.가위)
        }

        myCommands.forEach {
            val (result, opponentCommand) = handgame.play(it)
            logger.info("나 : {}, 상대방 : {}, 결과 : {}", it, opponentCommand, result)
        }
    }
}

fun main(args: Array<String>) {
    runApplication<HandgameApplication>(*args)
}
  • 순서대로 handgame > handgame-spring-boot-autoconfigure > handgame-spring-boot-starter 순서대로 배포한다.
    • Tasks > publishing > publishingMavenLocal을 눌러 배포하면 된다.
  • 이대로 실행하면 오류가 발생하는데, program arguments로 --debug 옵션을 주면, 현재 설정된 자동 설정과 적용되지 않은 자동 설정을 확인할 수 있다.
    • 문제는 자동 설정 클래스를 제작할 때 my.handgame.enabled 프로퍼티가 true인 경우에만 동작하게 했기 때문이다.
    • application.properties에 "my.handgame.enabled=true"를 추가한 뒤 실행하면 잘 동작한다
728x90