파란하늘의 지식창고
반응형

기본적인 @ConfigurationProperties , @PropertySource 사용

Spring Boot 기반 프로젝트에서 @ConfigurationProperties 로 지정된 bean은 처음 application이 startup 할 때 bean이 생성되고 여러 properties 파일에서 읽어와 Environment에 저장된 값을 가져와 해당 bean에 바인딩해 준다.

다음과 같이 @ConfigurationProperties 를 선언하고

@ConfigurationProperties(prefix = "someProperties")
@Data
public class SomeProperties {

    private String someKey1;

    private long someKey2;

    //.. 이하 생략
}

값을 읽어올 properties 파일을 configuration에서 다음과 같이 호출해 준다.

@AutoConfiguration
@PropertySource("classpath:some.properties")
public class SomeConfiguration {

}

해당 properties 파일에 다음과 같이 키/값을 선언하였다면

someProperties.someKey1=key1
someProperties.someKey2=1234

해당 properties의 값이 SomeProperties bean에 바인딩되어 사용하게 된다.

Spring Cloud Config Server 나 외부 설정 값 사용

@PropertySource 로 로컬 파일에서 값을 관리하지 않고 spring cloud config server를 사용하거나 외부 URL이나 File 주소를 지정하여 properties를 호출하여 사용할 수도 있다.

# spring cloud config server 사용 예
spring.cloud.config.url=http://config-server/
spring.config.import=optional:configserver:

# http 주소 호출 예
spring.config.import=optional:http://anotherConfigLocation.dev/test.properties

위 예는 각각의 경우를 나누어 소개하였는데 외부 설정 소스를 여러 곳을 지정하여 사용하는 것도 가능하다.

# spring cloud config server 사용 예
spring.cloud.config.url=http://config-server/
spring.config.import=optional:configserver:,optional:http://anotherConfigLocation.dev/test.properties

위의 예에서는 config server와 외부 http 주소 호출을 같이 지정한 경우인데

N개의 spring cloud config server를 호출하거나 N개의 외부 http 주소를 호출하거나, N 개의 파일을 호출하거나 또는 앞서 열거한 여러 소스의 호출을 같이 섞어 지정할 수도 있다.

만약 @PropertySource 를 사용하여 호출한 로컬의 properties 파일에도 동일한 property 키가 선언되어 있더라도 spring.cloud.import 로 호출한 property 키의 값이 우선 적용된다.

configserver나 외부 주소의 test.properties 에서 제공하는 properties에 다음과 같은 키/값이 있는데

someProperties.someKey1=key1
someProperties.someKey2=1234

어느 시점에 해당 값이 다음처럼 변경되어 제공되고 있다면

someProperties.someKey1=key2
someProperties.someKey2=5678

그 변경된 값을 다시 호출하여 현재 실행 중인 application을 재시작하지 않고 적용하고 싶게 된다.

@RefreshScope 사용하기

@ConfigurationProperties bean의 값이 application이 시작 시 바인딩된 이후 변경된 값을 갱신할 수 있도록 Spring Cloud Context는 @RefreshScope 를 제공하고 있다.

@RefreshScope 을 기존 사용 중인 @ConfigurationProperties class에 다음과 같이 추가해 준다.
(상위 interface에 추가해도 구현한 클래스에 일괄 적용된다.)

@RefreshScope
@ConfigurationProperties(prefix = "someProperties")
@Data
public class SomeProperties {

    private String someKey1;

    private long someKey2;

    //.. 이하 생략
}

dependency에 Spring의 spring-boot-starter-actuator를 추가하고

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

다음과 같이 refresh endpoint를 노출해 준다.

management.endpoints.web.exposure.include=health,refresh,beans

RefreshEndpoint를 활성화하면 application을 운영하다 앞서 예처럼 config 서버의 properties가 변경된 경우 다음 예처럼 /actuator/refresh 주소로 POST 요청을 통해 변경된 값을 반영할 수 있다.

curl -X POST http://localhost:8080/actuator/refresh
fetch("/actuator/refresh", {
    "method": "POST",
    "headers": {
        "Content-Type": "application/json",
        "Accept" : "application/json",
        "Accept-Language" : "ko-KR"
    }
})
.then(response => response.json());

서버가 실시간으로 변경된 properties를 반영하는 것을 확인할 수 있다.

refresh가 되면 bean 객체가 다시 재등록되기 때문에 properties bean 등록 후 추가해야 하는 작업이 있다면 InitializingBean interface를 구현하여 afterPropertiesSet() method를 정의해 두면 refresh 될 때마다 수행된다.

여러 application refresh 요청 처리

앞서 refresh는 하나의 application에 refresh 요청을 하여 갱신한 경우이다.

보통 여러 application을 띄우고 사용하기 때문에 refresh 요청을 여러 application에 전파하기 위해서는 몇 가지 방법들을 추가로 사용해야 한다.

먼저 eureka 같은 service discovery를 사용하면 현재 k8s에 떠있는 pod 목록 같은 것들을 확인할 수 있다.
이를 통해 요청을 해야 할 대상 application 목록을 확보하여 refresh 요청을 일괄로 할 수 있다.

또는 Spring Cloud Bus를 통해 refresh message를 queue에 쌓고 각 서버가 주기적으로 refresh 요청을 확인하여 갱신할 수도 있다.

자세한 사용은 관련 글을 찾아보면 된다.

Spring Cloud @RefreshScope 문서 참고

Spring Cloud 문서의 @RefreshScope 관련 내용을 옮겨보았다.
https://docs.spring.io/spring-cloud-commons/reference/spring-cloud-commons/application-context-services.html#refresh-scope

 

configuration이 변경되면 @RefreshScope 를 적용한 Spring @Bean은 특별하게 처리된다.
이 기능은 초기화될 때만 configuration이 inject 되는 stateful bean의 문제를 해결한다.

예를 들어, 앞서 사용한 예처럼 @ConfigurationProperties 를 통해 설정된 datasource url이 refresh 요청으로 인해 변경될 때 DataSource 에 열려있는 connection이 있는 경우, 해당 연결의 holder가 현재 수행 중인 작업을 완료할 수 있기를 원할 수 있다.
그런 다음 pool에서 새 connection을 획득할 때 새 URL을 가진 connection을 얻게 된다.

때로는 한 번만 초기화할 수 있는 일부 bean에 @RefreshScope 를 적용해야 할 수도 있다.
bean이 불변(immutable)인 경우 @RefreshScope annotation을 달거나 spring.cloud.refresh.extra-refreshable property에 classname을 지정해야 한다.

 

[!WARNING]
다만 HikariDataSourceDataSource bean이 있는 경우 refresh 되지 않는다.
이 bean은 spring.cloud.refresh.never-refreshable 의 기본 값이다.
refresh 해야 하는 경우 다른 DataSource 구현을 사용해야 한다.

Refresh scope bean은 사용 시 (즉, method가 호출될 때) 초기화되는 lazy proxy이며, scope는 초기화된 값의 캐시 역할을 한다.

다음 method 호출 시 bean을 강제로 다시 초기화하려면 해당 cache 항목을 무효화(invalidate) 해야 한다.

RefresnEndpoint 는 모든 대상 bean을 refresh 하는 RefreshScoperefreshAll method 호출만 제공하고 있다.
하지만 RefreshScope 엔 개별 bean만 refresh 할 수 있는 refresh(Class type) , refresh(String name) method를 제공하고 있다.

/refresh endpoint를 노출하려면 다음과 같은 설정을 추가하면 된다.

management:
  endpoints:
    web:
      exposure:
        include: refresh

 

 

[!NOTE]
@RefreshScope 는 (기술적으로는) @Configuration class에서만 동작하지만, 의외의 동작이 발생할 수 있다.
예를 들어 해당 class에 정의된 모든 @Bean@RefreshScope 에 있다는 의미는 아니다.
특히, 해당 bean에 의존하는 모든 것은 @RefreshScope 에 있지 않는 한 refresh가 시작될 때 업데이트 되는 것에 의존할 수 없다.

 

 

[!NOTE]
configuration value를 제거한 이후 refresh를 수행해도 configuration value의 존재가 업데이트되지 않는다.
refresh 후 value를 업데이트하려면 configuration property가 있어야 한다.
application에 값이 있는지 여부에 의존하는 경우, 해당 값의 부재에 의존하도록 로직을 전환하는 것이 좋다.
또 다른 방법은 application의 configuration에 존재하지 않고 값 변경에 의존하는 것이다.

 

 

[!WARNING]
context refresh는 Spring AOT 변환 및 native image에는 지원되지 않는다.
AOT 및 native image의 경우 spring.cloud.refresh.enabledfalse 로 설정해야 한다.

 

Refresh Scope on Restart

restart 시 bean을 원활하게 refresh 하는 기능은 특히 JVM checkpoint restore와 함께 실행되는 (Project CRaC 같은) application에 유용하다.
이 기능을 허용하기 위해 이제 재시작 시 context refresh를 trigger 하여 configuration property를 rebinding 하고 @RefreshScope 로 annotation이 달린 모든 bean을 새로 고치는 RefreshScopeLifecycle bean을 instance화 한다.
spring.cloud.refresh.on-restart.enabledfalse 로 설정하여 이 동작을 비활성화 할 수 있다.

반응형
profile

파란하늘의 지식창고

@Bluesky_

도움이 되었다면 광고를 클릭해주세요