Application Properties를 이용한 프로파일 사용하기

|

프로파일 사용하기

하나의 서비스 인터페이스와 이를 구현하는 서비스 구현체가 여러 개가 있을 때 프로파일을 이용해서 원하는 서비스가 연결되도록 할 수 있습니다.

HelloService.kt

interface HelloService {
    fun hello(name: String): String
}


HelloServiceImpl.kt

class HelloServiceImpl : HelloService {
    override fun hello(name: String): String {
        return "Hello, $name"
    }
}


GoodMorningServiceImpl.kt

class HelloServiceImpl : HelloService {
    override fun hello(name: String): String {
        return "Hello, $name"
    }
}

기존에는 각 서비스 구현 클래스에 @Service 어노테이션을 붙였지만, 여기서는 붙이지 않았습니다. 대신 Application 클래스안에 Bean 클래스를 명시적으로 생성하도록 합니다.


HelloApplication.kt

@SpringBootApplication
class HelloApplication {
    @Bean
    fun helloService(): HelloService = HelloServiceImpl()

//    @Bean
//    fun goodMorinigServie(): HelloService = GoodMorningServiceImpl()
}

fun main(args: Array<String>) {
    runApplication<HelloApplication>(*args)
}

위의 주석 부분을 제거하면 서비스가 2개가 되어 실행시 오류가 발생합니다. 이제 Application Properties를 이용해서 선택적으로 인스턴스를 생성하는 부분을 적용합니다.


application.yaml

service:
  greet: "goodmorning"


HelloApplication.kt(수정 후)

@SpringBootApplication
class HelloApplication {
    @Bean
	@ConditionalOnExpression("#{'\${service.greet}'=='hello'}")
    fun helloService(): HelloService = HelloServiceImpl()

    @Bean
	@ConditionalOnExpression("#{'\${service.greet}'=='goodmorning'}")
    fun goodMorinigServie(): HelloService = GoodMorningServiceImpl()
}


fun main(args: Array<String>) {
    runApplication<HelloApplication>(*args)
}

Application Properties 설정 방법

|

어플리케이션 설정 값 정의 방법

스프링 부트는 어플리케이션 설정 값을 정의하기 위해 다음과 같은 방법을 제공하고 있습니다.

  • application.properties 파일 이용
  • yaml 파일
  • CLI 명령어 매개변수


application.properties

SERVICE.MESSAGE.TEXT="hello"


application.yaml

service:
    message:
        text: "hello"

좀 더 복잡한 설정을 사용할 때는 yaml 파일을 이용하는 것이 가독성이나 관리가 더 쉽습니다.


CLI 매개 변수

java jar ./snowdeer.jar --service.message.text="hello"

의존 관계 자동 설정

|

의존 관계 자동 설정

앞서 포스팅한 PathVariable 간단한 예제의 예제 코드는 다음과 같습니다.


SimpleService.kt

@Service
class SimpleService {
    fun hello(name: String): String {
        return "hello $name"
    }
}


SimpleController.kt

@Controller
class SimpleController(val simpleService: SimpleService) {

    @RequestMapping(value = ["/user/{name}"], method = arrayOf(RequestMethod.GET))
    @ResponseBody
    fun hello(@PathVariable name: String): String {
        return simpleService.hello(name)
    }
}

SimpleController 클래스에서 SimpleService 인스턴스를 클래스 생성자의 매개변수로 전달하고 있습니다. 만약 해당 인자를 생성자의 매개 변수를 전달하고 싶지 않을 때는 다음과 같이 @Autowired 어노테이션을 이용할 수 있습니다.

@Controller
class SimpleController() {

    @Autowired
    private lateinit var service: SimpleService

    @RequestMapping(value = ["/user/{name}"], method = arrayOf(RequestMethod.GET))
    @ResponseBody
    fun hello(@PathVariable name: String): String {
        return service.hello(name)
    }
}

이와 같이 사용하는 것을 의존성 삽입(Dependency Injection)이라고 합니다.


인터페이스와 구현체 분리

위에서 만들었던 SimpleService 클래스를 다음과 같이 인터페이스와 구현체로 분리할 수 있습니다.

interface SimpleService {
    fun hello(name: String): String
}
@Service
class SimpleServiceImpl : SimpleService {
    override fun hello(name: String): String {
        return "hello $name"
    }
}

그리고 SimpleController도 다음과 같이 수정합니다.

@Controller
class SimpleController() {

    @Autowired
    private lateinit var service: SimpleService

    @RequestMapping(value = ["/user/{name}"], method = arrayOf(RequestMethod.GET))
    @ResponseBody
    fun hello(@PathVariable name: String): String {
        return service.hello(name)
    }
}

SimpleController 코드 안에는 SimpleServiceImpl과 관련된 코드가 하나도 없습니다. 그런데도 스프링 부트가 SimpleService 인터페이스를 구현하는 적절한 인스턴스를 연결해서 매핑하는 것을 확인할 수 있습니다. 만약 SimpleService 인터페이스를 구현한 SimpleServiceImpl2 클래스를 추가로 생성하면 실행할 때 다음과 같은 오류가 발생합니다.

Field service in com.snowdeer.board.controller.SimpleController required a single bean, but 2 were found:
	- simpleServiceImpl: defined in file [/Users/snowdeer/Workspace/SpringBoot/board/build/classes/kotlin/main/com/snowdeer/board/service/SimpleServiceImpl.class]
	- simpleServiceImpl2: defined in file [/Users/snowdeer/Workspace/SpringBoot/board/build/classes/kotlin/main/com/snowdeer/board/service/SimpleServiceImpl2.class]


의존 관계 자동 설정을 통한 장점

이와 같이 의존 관계 자동 설정을 이용하면 다음과 같은 장점을 얻을 수 있습니다.

  • 세부 구현 정보 숨기기: 서비스 구현체에 종속성이 없더라도 해당 인스턴스를 사용가능하며, 따라서 서비스 작동 방식을 외부에 노출하지 않음
  • 디커플링: 기존 서비스에 영향을 주지 않고, 새로운 서비스 변경이나 구현의 사용이 가능
  • 쉬운 변경 처리: 스프링 설정을 통해 코드 수정 없이 서비스 변경이 가능함

PathVariable 간단한 예제

|

PathVariable 간단한 예제

샘플 프로젝트를 생성하고 다음 파일들을 생성합니다.


SimpleService.kt

import org.springframework.stereotype.Service

@Service
class SimpleService {
    fun hello(name: String): String {
        return "hello $name"
    }
}

위 클래스는 아직 큰 의미는 없고, 단순 메소드를 제공하는 클래스입니다.


SimpleController.kt

import com.snowdeer.board.service.SimpleService
import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestMethod
import org.springframework.web.bind.annotation.ResponseBody

@Controller
class SimpleController(val simpleService: SimpleService) {

    @RequestMapping(value = ["/user/{name}"], method = arrayOf(RequestMethod.GET))
    @ResponseBody
    fun hello(@PathVariable name: String): String {
        return simpleService.hello(name)
    }
}

위와 같이 코드를 만들면, /user/name에 해당하는 URL의 name 부분을 경로 파라메터로 받을 수 있습니다.

H2 Database 사용하기

|

H2 Database

H2 데이터베이스는 자바 기반의 RDBMS(Relational DataBase Management System)입니다. 용량이 적으며, 브라우저 기반의 콘솔 등을 지원하여 장점이 많습니다. Spring Boot에서 별도 데이터베이스를 설치하지 않고 바로 사용할 수 있는 점도 장점입니다.

Spring Initializer 웹사이트에서 의존성(Dependencies) 부분에서 ‘H2 Database’ 항목과 ‘Spring Data JPA’ 항목을 선택하고 프로젝트를 생성해주면 자동으로 build.gradle에 반영이 됩니다.


application.properties

그리고 application.properties 파일에 속성값을 지정해줘야 합니다. 여기서는 application.yaml 파일을 이용해서 설정하도록 하겠습니다. 기존의 application.properties 파일을 지우고 application.yaml 파일을 생성하고 아래의 내용을 작성합니다.

server:
  port: 8000

spring:
  h2:
    console:
      enabled: true
      path: /h2_db

  datasource:
    driver-class-name: org.h2.Driver
    url: jdbc:h2:file:./h2_db;AUTO_SERVER=TRUE
    username: snowdeer
    password: 1234

url 항목에서 jdbc:h2:file는 데이터베이스를 파일에 저장하겠다는 의미이며, ./h2_db는 파일 경로입니다.

위와 같이 설정한 다음 http://localhost:8000 주소에 접속하면 H2 Console 웹페이지가 열립니다.

JDBC URL 항목이 jdbc:h2:~/test으로 되어 있을텐데, 위에서 yaml 파일에 작성한 jdbc:h2:file:./h2_db 값을 넣어줘야 합니다.


데이터베이스 연결하기

Hikari CP

Spring Boot 2.0.0 M2 버전부터 기본적으로 사용하는 커넥션 풀(Connection Pool)이 Tomcat에서 HikariCP로 변경되었습니다.

application.yaml에 HikariCP 연결 정보를 반영해서 다음과 같이 수정합니다.

server:
  port: 8000

spring:
  h2:
    console:
      enabled: true
      path: /h2_db

  datasource:
    hikari:
      driver-class-name: org.h2.Driver
      jdbc-url: jdbc:h2:file:./h2_db
      username: snowdeer
      password: 1234
      connection-test-query: SELECT 1


DatabasConfiguration.kt

데이터베이스 사용을 위한 기본적인 설정을 합니다.

import com.zaxxer.hikari.HikariConfig
import com.zaxxer.hikari.HikariDataSource
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.PropertySource
import javax.sql.DataSource

@Configuration
@PropertySource("classpath:application.yaml")
class DatabaseConfiguration {

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.hikari")
    fun hikariConfig(): HikariConfig {
        return HikariConfig()
    }
    
    @Bean
    @Throws(Exception::class)
    fun dataSource(): DataSource {
        val dataSource = HikariDataSource(hikariConfig())
        println(dataSource.toString())
        return dataSource
    }
}

지금까지 문제없이 작성되었다면 빌드 및 실행이 가능할 것입니다.