Home [football] Setter를 사용하지 않고 환경변수 호출을 위한 고민
Post
Cancel

[football] Setter를 사용하지 않고 환경변수 호출을 위한 고민

문제점


Redis 구성을 위해 application.yml에서 host, port, node 주소 등의 환경변수를 가져오는데 있어 @ConfigurationProperties는 Setter를 기반으로 바인딩을 하게 됩니다. 이로 인해 외부에서 node 값에 대해 조작해 데이터가 변경될 가능성이 있었습니다.

또한 @Value로 변수를 가져오는 방식 또한 중복된 주소값을 적어야 되고 final 변수로 설정하지 않아 immutable한 성격을 가지고 있다고는 보기 힘들었습니다.

그래서 이러한 데이터 변경의 위험을 제거하기 위한 고민을 할 필요가 있었습니다.

RedisConfig 클래스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Getter
@Setter // Setter로 인해 데이터가 변경 가능성이 존재
@Configuration
@ConfigurationProperties(prefix = "spring.redis.cluster")
public class RedisConfig {

  private List<String> nodes;

  @Value("${spring.redis.host}")
  private String redisHost;

  @Value("${spring.redis.port}")
  private int redisPort;

  @Bean
  public RedisConnectionFactory redisConnectionFactory() {

    LettuceClientConfiguration clientConfiguration = LettuceClientConfiguration.builder()
        .readFrom(ReadFrom.REPLICA_PREFERRED)
        .build();
    RedisClusterConfiguration redisClusterConfig = new RedisClusterConfiguration(nodes);
    return new LettuceConnectionFactory(redisClusterConfig, clientConfiguration);

  }

}

Application.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
spring:
  redis:
    host: localhost
    port: 7001
    
    # local 환경에서 redis cluster 활용시 사용하는 변수
    cluster:
      nodes:
        - 127.0.0.1:7001
        - 127.0.0.1:7002
        - 127.0.0.1:7003
        - 127.0.0.1:7004
        - 127.0.0.1:7005
        - 127.0.0.1:7006

해결방법


@ConstructorBinding

Spring Boot 2.3 버전 이후 생성자 주입방식으로 불변성을 가지고 Properties 파일을 만들 수 있는 방식이 추가되었습니다.

@ConstructorBinding 어노테이션을 이용하면 final 필드에 대해 값을 주입할 수 있고 중첩 클래스가 있다면 자동으로 중첩 클래스의 final 필드 또한 자동으로 값을 주입하는 대상이 됩니다.

final 키워드를 명시하지 않는다면 setter를 이용해서 값을 binding 하려하기 때문에 setter가 없다는 exception이 발생하게 됩니다.

다만, 이 방식을 사용하면 Properties 클래스에 직접적으로 @Configuration을 이용해서 직접적으로 Spring Bean으로 만들어 주지 않습니다. 그래서 다른 클래스에서 @EnableConfigurationProperties을 이용해서 생성할 Properties 클래스의 클래스 타입을 명시해주면 Spring Bean으로 등록해야 합니다.

변경된 코드

RedisPropertiesConfig & RedisProperties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Configuration
@EnableConfigurationProperties(value = RedisProperties.class) // Bean 등록을 위한 어노테이션
public class RedisPropertiesConfig {

  @Getter
  @RequiredArgsConstructor // 생성자 추가
  @ConstructorBinding // 생성자로 바인딩한다는 것을 명시
  @ConfigurationProperties(prefix = "spring.redis") // 접두사 지정
  public static class RedisProperties {

    // final 변수로 구성
    
    private final List<String> nodes;

    private final String host;

    private final int port;

  }

}

우선 RedisPropertiesConfig 클래스에 @EnableConfigurationProperties를 추가해 빈으로 등록해 줄 내부 클래스 타입을 지정해줍니다.

그리고 내부 클래스로 구성된 RedisProperties에서 Application.yml을 통해 가져올 환경변수들을 final 변수로 추가하고 @ConstructorBinding@RequiredArgsConstructor를 추가해 생성자를 통해서 변수가 바인딩 되도록 구성했습니다.

Application.yml

1
2
3
4
5
6
7
8
9
10
11
12
spring:
  redis:
    host: localhost
    port: 7001
    nodes: # local 환경에서 redis cluster 활용시 사용하는 변수
      - 127.0.0.1:7001
      - 127.0.0.1:7002
      - 127.0.0.1:7003
      - 127.0.0.1:7004
      - 127.0.0.1:7005
      - 127.0.0.1:7006

Application.yml에서도 하나의 prefix로 필요한 변수를 호출할 수 있도록 수정했습니다.

RedisConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Configuration
@RequiredArgsConstructor
public class RedisConfig {

  private final RedisProperties properties; // RedisProperties 타입을 DI

  @Bean
  public RedisConnectionFactory redisConnectionFactory() {

    LettuceClientConfiguration clientConfiguration = LettuceClientConfiguration.builder()
        .readFrom(ReadFrom.REPLICA_PREFERRED)
        .build();
    RedisClusterConfiguration redisClusterConfig = new RedisClusterConfiguration(properties.getNodes()); // RedisProperties 변수에서 Getter를 통해 원하는 변수를 호출
    return new LettuceConnectionFactory(redisClusterConfig, clientConfiguration); 

//    return new LettuceConnectionFactory(properties.getHost(), properties.getPort());

  }
  
}

그리고 실제 환경변수를 필요로 하는 RedisConfig에서 빈으로 등록된 RedisProperties 객체를 주입받아 저장된 변수들을 Getter를 통해 호출합니다.

마치며


이처럼 @ConstructorBinding를 통해 Setter가 아닌 생성자를 통해 바인딩이 되도록 수정하면서 외부에서 변수 조작의 위험을 줄이고 고정 상수에 대한 안정성을 확보했습니다.

참고자료


  • https://tecoble.techcourse.co.kr/post/2020-09-29-spring-properties-binding/
  • https://cheese10yun.github.io/immutable-properties/
  • https://velog.io/@lovi0714/%ED%95%B4%EA%B2%B0%ED%95%98%EA%B8%B0-%EC%84%B8%EC%85%98-%ED%81%B4%EB%9F%AC%EC%8A%A4%ED%84%B0%EB%A7%81
This post is licensed under CC BY 4.0 by the author.

TDD(테스트 주도 설계)

팩토리 메소드 패턴 & 템플릿 메소드 패턴 적용