어플리케이션 정보를 properties 에서 key-value 로 지정할 수 있는 property 는 두 종류로 나눌 수 있다:
- built-in property: Spring Boot 에서 자동으로 제공하는 property
- custom property: 사용자가 임의로 정하는 property, 필요한 만큼 사용이 가능
이전까지는 built-in property 들을 보았지만, 이제부터는 custom property 들을 본다.
📂 목차
📚 본문
앞서서 Environment 인스턴스에 바인딩 되는 프로퍼티들을 출력해보았는데 Environment 인스턴스를 자동주입(@Autowired
)하고 프로퍼티 값들을 읽고 사용할 수 있기 때문에 어디서든 Environment 를 주입 받아서 프로퍼티를 읽어올 수 있다.
여기서 Environment 는 런타임에 한해서만 존재되는 인스턴스, 빈이다. 이렇듯 properties 를 파일로 선언하여 Env 로 가져오면 편리하지만 몇 가지 단점이 있다:
- 프로퍼티 값의 타입 안전성(type-safety)이 보장되지 않음(URL인지 아닌지, 이메일 주소 인지 아닌지) -> 런타임 에러가 발생 우려
- 프로퍼티 값을 일정 단위로 묶어서 읽을 수 없고,
@Value
애너테이션이나 스프링의 Environment 인스턴스를 사용해서 하나하나 개별적으로만 읽을 수 있음
이를 방지하기 위해 커스텀 프로퍼티를 사용해 type-safety 와 valiation, 그리고 여러 개를 한꺼번에 가져올 수 있게 해야 한다.
이에 대한 방법은 여러가지 이다.
@ConfigurationProperties 를 사용하여 커스텀 프로퍼티 정의
@ConfigurationProperties
애너테이션을 사용하면 특정 prefix 에 대한 프로퍼티 정보를 담는 클래스를 만들 수 있고, 이를 통해 값을 보장하고 유효성을 검증한다. @Value
애너테이션을 사용하거나 Environment
빈을 자동 주입 받지 않아도 되는 방법이다.
우선 custom property 로 쓸 것들을 application.properties
에 넣어주자
app.sbip.ct.name=StudyApplication
app.sbip.ct.ip=127.0.0.1
app.sbip.ct.port=9090
app.sbip.ct.security.enabled=true
app.sbip.ct.security.token=abc123
app.sbip.ct.security.roles=USER,ADMIN
위 property 는 전부 app.sbip.ct 의 prefix를 가진다. CustomProperties.class 하나를 생성하여 다음을 입력하자. 여기서는 Getter, Setter 등등의 보일러플레이트 코드(boilerplate code)의 불편성을 위해 Lombok 을 사용했다.
import lombok.*;
import org.springframework.boot.context.properties.*;
import java.util.*;
@AllArgsConstructor
@Getter
@ToString
@ConfigurationProperties(prefix="app.sbip.ct")
public class CustomProperties {
private final String name;
private final String ip;
private final String port;
private final Security security;
@AllArgsConstructor
@Getter
@ToString
public static class Security {
private final boolean enabled;
private final String token;
private final List<String> roles;
}
}
위처럼 app.sbip.ct
인 프로퍼티만 읽을 수 있고, 더 세부적으로 .으로 나뉘었다면, 그 안에 public static class를 선언하여 할 수도 있다(public static 으로 nested class 를 선언하는 이유)
지금은 Lombok 을 써서 인자들을 가져오지만 원한다면 생성자에서 검증하는 코드를 넣을 수도 있다.
@EnableConfigurationProperties 로 Bean 등록하기
기본적으로 ConfigurationProperties
에는 Bean 으로 등록하는 메커니즘이 없다.
다음 CustomService
클래스를 만들어 Bean이 자동 주입되는지 살펴보자.
import com.example.study.properties.*;
import lombok.*;
import org.springframework.beans.factory.annotation.*;
import org.springframework.stereotype.*;
@Service
@Getter
public
class CustomService {
private final CustomProperties customProperties;
@Autowired
public CustomService(CustomProperties customProperties) {
this.customProperties = customProperties;
}
}
이제 아래와 같이 작성한다.
@EnableConfigurationProperties(CustomProperties.class)
public class DemoApplication {
...
public static void main(String[] args) {
...
CustomService customService = applicationContext.getBean(CustomService.class);
System.out.println(customService.getCustomProperties().toString());
}
}
SpringApplication 을 밑과 같이 작성하여 실행해보고 실행하면
public class DemoApplication {
...
public static void main(String[] args) {
SpringApplication springApplication = new SpringApplication(DemoApplication.class);
ConfigurableApplicationContext applicationContext = springApplication.run(args);
CustomService customService = applicationContext.getBean(CustomService.class);
System.out.println(customService.getCustomProperties().toString());
}
}
Consider defining a bean of type 'com.example.study.properties.CustomProperties' in your configuration.
와 같은 메시지를 볼 수 있다.
@ConfigurationProperties
자체에 아래와 같이 interface 가 구현되고 있다.
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface ConfigurationProperties {
이는 그냥 프로퍼티를 만들고 어플리케이션에 적용을 안한 것과 같다. 선언한 CustomProperties
를 적용하고 싶은 Application의 클래스에 @EnableConfigurationProperties
애너테이션을 사용하여 Bean 으로 등록할 수 있다.
@EnableConfigurationProperties(CustomProperties.class)
public class DemoApplication {
...
public static void main(String[] args) {
...
CustomService customService = applicationContext.getBean(CustomService.class);
System.out.println("\n\n\ngetCustomProperties");
System.out.println(customService.getCustomProperties().toString());
System.out.println("\n\n\n");
}
}
이제 Bean 이 제대로 불러와짐을 알 수 있고, 출력 또한 잘 되는 것을 볼 수 있다.
🔗 출처
📁 관련 글
✒️ 용어
보일러플레이트 코드
항상 비슷한 형태로 여기저기 반복적으로 작성해야 하는 코드이며 기능 구현과는 큰 관련이 없지만, 기능이나 구현에 있어서 자주 사용하여 필수적으로 작성해야하는 코드를 말한다. 예시로는 생성자, Getter, Setter 등이 있다.
Lombok
보일러플레이트 코드를 줄일 수 있도록 애너테이션을 지원해주는 패키지이다. 의존성 추가를 통해 사용가능하다.
dependencies {
compileOnly 'org.projectlombok:lombok:(최신 버전)'
annotationProcessor 'org.projectlombok:lombok(최신 버전)'
}
따로 Lombok 을 정리한 글이다.