Java/Spring Boot

[Spring Boot] 외부 설정

hh_lin 2022. 3. 13. 16:36

1. 외부 설정

  • 어플리케이션에서 사용할 여러 설정 값들을 어플리케이션의 밖이나 안에 정의할 수 있는 기능
  • ex) application.properties
    : Spring Boot가 어플리케이션을 구동할 때 자동으로 로딩하는 파일

 

① src/main/resources/application.properties

key-value 형태로 정의하면 어플리케이션에서 참조해서 사용할 수 있음

 

 

② 사용 예제 -> 우선순위 15위

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;

@Component
public class SampleRunner implements ApplicationRunner {

	@Value("${human.name}")
	private String name;

	@Override
	public void run(ApplicationArguments args) throws Exception {
		System.out.println("================");
		System.out.println(name);
		System.out.println("================");
	}
}

 

 

③ 실행 결과

 

 

 

 

 

 

 

 

 

 

 

 

 

2. 프로퍼티 우선순위

  1. 유저 홈 디렉토리에 있는 spring-boot-dev-tools.properties

  2. 테스트에 있는 @TestPropertySource
    ex) @TestPropertySource(properties = ["key=value"])

  3. @SpringBootTest 어노테이션의 properties 속성
    ex) @SpringBootTest(properties = ["key=value"])

  4. Command Line Argument
    ex) java -jar XXXX.jar --key=value

  5. SPRING_APPLICATION_JSON(환경변수 또는 시스템 프로퍼티)에 들어있는 프로퍼티
  6. ServletConfig 파라미터
  7. ServletContext 파라미터
  8. java:comp/env JNDI 속성
  9. System.getProperties() 자바 시스템 프로퍼티
  10. OS 환경 변수
  11. RandomValuePropertySource
  12. JAR 밖에 있는 특정 프로파일용 application.properties
  13. JAR 안에 있는 특정 프로파일용 application.properties
  14. JAR 밖에 있는 application.properties
  15. JAR 안에 있는 application.properties
  16. @PropertySource
  17. 기본 프로퍼티 (SpringApplication.setDefaultProperties)

 

 

 

 

 

 

 

 

 

3. Test용 Property 추가하기 - application.properties

① Test Code

import static org.assertj.core.api.Assertions.*;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.env.Environment;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringInitApplicationTests {

   @Autowired
   Environment environment;

   @Test
   public void contextLoads() {
      assertThat(environment.getProperty("human.name")).isEqualTo("hhlin");
   }
}

-> 이 상태로 실행하면 "Test passed"

 

 

② src/test/resources/application.properties 

-> test 디렉토리의 application.properties 변경 후 위의 Test Code 그대로 실행하면 "Test failed"

 

# Test Code 빌드 시

src/main 밑에 있는 것들을 빌드해서 classpath에 놓음 (application.properties 포함)
그 다음에 test code를 빌드해서 classpath에 놓는데,
이 때, application.properties 파일이 test에 있는 것으로 바뀜

 

 

 

③ Test Code 수정

assertThat(environment.getProperty("human.name")).isEqualTo("test123");

-> 수정 후 재 실행하면, "Test Passed"

 

 

 

 

 

# 테스트 용도로 프로퍼티 파일을 변경해야 하는 경우

  • test/resources에 application.properties 파일 추가
  • File -> Project Structure -> Modules -> test/resources 디렉토리 클릭 -> Test Resources 클릭
  • (우측 하단 Test Resource Folders에 srs/test/resources가 확인된다면 할 필요없음)
  • 이 상태여야 application.properties 파일 좌측에 Spring 모양이 확인되면서 자동완성 가능

 

 

 

 

 

 

 

 

4. 랜덤값 설정하기

ex) src/main/resources/application.properties 에 random.int 값을 사용한 경우
주의) server.port에 random.XXX 사용하면 안됨 (port 랜덤은 server.port=0)

 

# src/main/java/HumanRunner

@Component
public class HumanRunner implements ApplicationRunner {

   @Value("${human.name}")
   private String name;

   @Value("${human.age}")
   private int age;

   @Override
   public void run(ApplicationArguments args) throws Exception {
      System.out.println("================");
      System.out.println(name);
      System.out.println(age);
      System.out.println("================");
   }
}

 

-> 어플리케이션 실행 시 문제 없음

 

 

이 상태로 mvn package를 하게 되면 test가 돌아가는데 test가 깨지게 됨

 

main 빌드 후에 test 빌드하면서 application.properties가 덮어써졌기 때문에
test/resources/properties에는 human.age라는 값이 없는데, 
어플리케이션에서 age값을 참조하려고 하니까 에러 발생

 

-> 오류 안나게 하려면 src/test/resources/application.properties 에도 age 추가해주거나

test 용 프로퍼티 파일 따로 만들고, classpath 명시해주면 됨

(아래 설명 -> 6. Test용 프로퍼티 추가하기 - xxx.properties)

 

 

 

 

 

 

 

 

 

 

5. 프로퍼티 우선순위 확인하기 - 2, 3순위

src/test/resources/application.properties 보다
@SpringBootTest와 @TestPropertySource properties 속성으로 준 값이 우선시 됨

 

① 2순위

@RunWith(SpringRunner.class)
@TestPropertySource(properties = "human.name=kvp")
@SpringBootTest
public class SpringInitApplicationTests {

   @Autowired
   Environment environment;

   @Test
   public void contextLoads() {
      assertThat(environment.getProperty("human.name")).isEqualTo("kvp");
   }
}

 

 

 

② 3순위

@RunWith(SpringRunner.class)
@SpringBootTest(properties = "human.name=kvp")
public class SpringInitApplicationTests {

   @Autowired
   Environment environment;

   @Test
   public void contextLoads() {
      assertThat(environment.getProperty("human.name")).isEqualTo("kvp");
   }
}

 

 

 

 

 

 

 

 

 

6. Test용 Property 추가하기 - xxx.properties

① test/resources/test.properties

main/resources/application.properties에만 있고
test/resources/application.properties에는 없는 경우 packaging하다가 오류가 발생하기 때문에, test/resources/application.properties 파일을 지우고 다른 이름의 파일을 만들어줌

 

② test code 내에 locations 속성을 지정

@RunWith(SpringRunner.class)
@TestPropertySource(locations = "classpath:/test.properties")
@SpringBootTest
public class SpringInitApplicationTests {

   @Autowired
   Environment environment;

   @Test
   public void contextLoads() {
      assertThat(environment.getProperty("human.name")).isEqualTo("test123");
   }
}

-> main 빌드 시에 application.properties에 있던 human.name 값이
test 빌드 시에 test.properties에 있는 값으로 덮어써지고, 

application.properties에 있던 age값도 그대로 가지고 있기 때문에 오류 발생하지 않음

 

 

 

 

 

 

 

7. application.properties 우선순위

위의 예제처럼 같은 위치에 있는 경우 덮어써지면서 문제가 생김

ex) main/resources/application.properties, test/resources/application.properties 

 

우선순위 높은 게 낮은 걸 덮어씀 (1, 4가 있다면 1이 4를 덮어씀)

 

  1. file:./config/
    : root 아래 config 디렉토리를 만들고 application.properties 파일 넣기
  2. file:./
    : root에 넣기 (jar 파일을 실행하는 위치)
  3. classpath:/config
  4. classpath:/

 

 

 

 

 

 

 

 

8. 플레이스 홀더

application.properties 내에 정의한 값은 재사용이 가능함

ex) human.name

 

 

 

 

 

 

 

 

 

 

9. 타입-세이프 프로퍼티 @ConfigurationProperties

① 같은 Key로 시작하는 프로퍼티는 묶어서 읽어올 수 있음

② @Component, @Bean을 사용해서 빈으로 등록해서 다른 빈에 주입할 수 있음

 

 

@ConfigurationProperties("key")
: 이렇게만 하면 값을 바인딩 받을 수 있게만 한 것

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties("human")
public class HumanProperties {

   private String name;

   private int age;

   private String fullName;
   
   // Getter, Setter 생략
}

 

 

원래는 아래와 같이 @EnableConfigurationProperties 어노테이션을 사용해서

@ConfigurationProperties 어노테이션을 달고 있는 클래스들을 명시해줘야 HumanProperties를 Bean으로 등록해주고, 

@ConfigurationProperties 어노테이션을 처리해주지만

@EnableConfigurationProperties가 자동으로 등록이 되어있기 때문에 생략 가능함

@SpringBootApplication
@EnableConfigurationProperties(HumanProperties.class)
public class Application {

   public static void main(String[] args) {

      SpringApplication app = new SpringApplication(Application.class);
      app.run(args);
      
   }
   
}

 

-> Application class에서 @EnableConfigurationProperties를 제거하고,

위의 HumanProperties class에 @Component만 추가하면 됨

 

 

@Component
public class HumanRunner implements ApplicationRunner {

   @Autowired
   HumanProperties humanProperties;

   @Override
   public void run(ApplicationArguments args) throws Exception {
      System.out.println("================");
      System.out.println(humanProperties.getName());
      System.out.println(humanProperties.getAge());
      System.out.println("================");
   }
}

 

-> getter() 등의 메소드를 통해서 값을 가져올 수 있어서 타입-세이프함

(@Value의 경우에는 key를 그대로 적어줘야만 가능해서 타입-세이프하지 않음)

 

 

# 실행 결과

 

 

 

 

 

③ 융통성 있는 바인딩
ex) humanAge(캐멀), human_age(언더스코어), human-age(케밥), HUMANAGE 등 가능

 

 

④ 프로퍼티 타입 컨버전 지원
ex) properties 파일 내에 human.age = 100 이라고 정의했다면,
properties 파일 내에서는 타입이라는 게 없고 전부 다 문자 값이지만
HumanProperties에 알아서 int 값으로 변환되어 들어감
-> Spring Framework가 제공하는 컨버전 서비스를 통해서 타입 컨버전이 일어남

 

 

⑤ 프로퍼티 값 검증
프로퍼티에 들어오는 값을 검증하려면 @Validated 어노테이션 사용
spring-boot-starter-validation 의존성 추가해줘야 함

@Component
@ConfigurationProperties("human")
@Validated
public class HumanProperties {

   @NotEmpty
   private String name;
   
}

 

@NotEmpty를 사용했기 때문에 properties 파일에 "human.name =" 과 같이 name이 빈 값이라면

아래와 같은 오류 발생됨

 

 

 

@Value는 SpEL(Spring Expression Language)를 사용할 수 있지만
key를 그대로 적어줘야만 하고, 위의 기능(①~⑤)들은 사용할 수 없음

 

 

 

 

 

 

 

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8/dashboard

 

스프링 부트 개념과 활용 - 인프런 | 강의

스프링 부트의 원리 및 여러 기능을 코딩을 통해 쉽게 이해하고 보다 적극적으로 사용할 수 있는 방법을 학습합니다., - 강의 소개 | 인프런...

www.inflearn.com

https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.external-config

 

Core Features

Spring Boot lets you externalize your configuration so that you can work with the same application code in different environments. You can use a variety of external configuration sources, include Java properties files, YAML files, environment variables, an

docs.spring.io