스프링에는 동적으로 환경을 바꿔야하는 경우가 있다 ex) 운영환경, 개발환경의 데이터소스 url,개발 ID, Password
가장 좋은 방법은 직접 정적인 파일에 정보를 넣는 것이겠지만
그렇다면 우리가 변경되는 값을 외부 설정으로 주입해주면 안정성을 보장할 수 있다.
유지 보수하기 좋은 애플리케이션 개발의 가장 기본 원칙은 변하는 것과 않는 것을 분리하는 것이다.
총 4가지의 외부 설정이 해당 빌드된 프로젝트에 영향을 미치게된다.
printenv
로 확인 가능한 해당 OS를 사용하는 모든 프로그램에서 읽을 수 있는 설정값
public static void main(String[] args) {
Map<String, String> envMap = System.getenv();
for (String key : envMap.keySet()) {
log.info("env {}={}", key, System.getenv(key));
}
System.getenv()로 설정을 가져올 수 있다
DBURL 등 지정이 가능하지만 단점은 이 프로그램뿐만 아니라 다른 프로그램에서도 해당 변수값이 사용가능하여 중복이 될 수 있다.
JVM 안에서 접근 가능한 외부 설정
사용법
java -Durl=dev -jar app.jar
스프링부트 조회 방법
public static void main(String[] args) {
Properties properties = System.getProperties();
for (Object key : properties.keySet()) {
log.info("prop {}={}", key,
System.getProperty(String.valueOf(key)));
}
key = value 형식으로 받을 수 있다.
동적으로 JVM위에서 빌드된 파일을 실행 시킬때 -D
옵션을 통해 속성을 전달 가능하고 자바 코드 내부에서도 추가가 가능하다.
: System.setProperty(propertyName, "propertyValue")
하지만 아래 방법은 코드에 박히는 것이라 장점을 크게 체감 할 수없다.
애플리케이션 실행 시점에 외부 설정 값을 main(args) 메서드의 args 파라미터로 전달
public static void main(String[] args) {
for (String arg : args) {
log.info("arg {}", arg);
}
}
커맨드 라인 인수는 공백으로 구분을 하며 Program argument 에 data1 data2
순으로 넣으면 된다.
직접 실행 할때는 $ java -jar app.jar data1 data2
(data1, data2) 가 전달된다.
하지만 이러한 데이터 전달은 데이터가 어떤 것을 뜻하는지 모르고 보통 애플리케이션을 개발할때는 key = value
형태로 활용이 가능하도록 개발을 한다.
그렇다면 $ java -jar app.jar A=data1 B=data2
로 보낸다면?
("A=data1","B=data2")로 총 두개의 문자열이 오게된다.( 공백을 연결하려면 ""를 사용하면 된다.)
하지만 우리가 원하는 것은 A = data1로 사용하고 싶은것이다.....
마구잡이로 =를 파싱하면 그것 요구사항에 따라 문제가 생길 수도 있다.
그래서 가이드라인이 잡혀있다.
--
를 두개 적고 000=1111을 쓰면 key = value 로 분리를 시켜준다.
$ java -jar app.jar --username=userA --username=userB
사용 방법은
ApplicationArguments appArgs = new DefaultApplicationArguments(args);
log.info("SourceArgs = {}", List.of(appArgs.getSourceArgs()));
log.info("NonOptionArgs = {}", appArgs.getNonOptionArgs());
log.info("OptionNames = {}", appArgs.getOptionNames());
Set<String> optionNames = appArgs.getOptionNames();
for (String optionName : optionNames) {
log.info("option args {}={}", optionName,
appArgs.getOptionValues(optionName));
}
List<String> url = appArgs.getOptionValues("url");
List<String> username = appArgs.getOptionValues("username");
List<String> password = appArgs.getOptionValues("password");
List<String> mode = appArgs.getOptionValues("mode");
log.info("url={}", url);
log.info("username={}", username);
log.info("password={}", password);
log.info("mode={}", mode);
}
ApplicationArguments appArgs = new DefaultApplicationArguments(args);
으로 커맨드 라인 옵션 인수를 받아서 일관된 가이드라인으로 key = value 가능하다.
하나의 키에 여러값을 포함할 수 있기때문에
appArgs.getOptionValues(key)
의 반환자료형은 LIST다
커맨드 라인 옵션인수는 자바 기능이 아니고 스프링이 제공하는 기술
동일하게 사용가능하다
ApplicationArguments
를 스프링 빈으로 등록해서 커맨드 라인을 저장해 놓고 어디서든 주입을 받아서 사용 가능하다는 장점이 있다. 위의args와 다름
처음 component 스캔 당시에 주입이 되고 선조립이 되어 초기화후 메소드 실행
@Slf4j
@Component
public class CommandLineBean {
private final ApplicationArguments arguments;
public CommandLineBean(ApplicationArguments arguments) {
this.arguments = arguments;
}
@PostConstruct
public void init() {
log.info("source {}", List.of(arguments.getSourceArgs()));
log.info("optionNames {}", arguments.getOptionNames());
Set<String> optionNames = arguments.getOptionNames();
for (String optionName : optionNames) {
log.info("option args {}={}", optionName,
arguments.getOptionValues(optionName));
}
}
}
이렇게 하나씩 구분을 하면 우리는 어디에 외부설정을 넣어놨는지 파악을 해야하고 그에 따른 코드가 계속해서 변경이 된다.
이러한 불편함을 해소하기 위해 스프링은 외부 설정값이 어디에 위치하든 상관없이 일관성 있고 편리하게 key=value 형식값을 제공한다.
이렇게 되면 외부에서 다양하게 조작이 가능하다.
PropertySource 라는 추상 클래스를 제공하고 각 구현체를 만들어놓았다.
스프링은 로딩 시점에 필요한 PropertySource 생성 후 Environment 사용
Environment 를 통해 특정 외부 설정에 종속되지 않는다.
environment.getProperty(key)를 통해 접근
yml, properties도 모두 추가된다.
@Slf4j
@Component
public class EnvironmentCheck {
private final Environment env;
public EnvironmentCheck(Environment env) {
this.env = env;
}
@PostConstruct
public void init() {
String url = env.getProperty("url");
String username = env.getProperty("username");
String password = env.getProperty("password");
log.info("env url={}", url);
log.info("env username={}", username);
log.info("env password={}", password);
}
}
spring.profiles.active
를 외부 설정에 넣으면 해당 프로필을 사용한다고 판단
그러면 해당 프로필의 정보로 바꿔줘야하는데
application-{profile}.properties
를 읽어온다.
커맨드 라인 인수로는 --spring.profiles.active=dev
로 넣어줄 수도 있다.
하지만 해당 방법으로 파일을 여러개 만들었을 경우 한눈에 들어오지 않는 단점이 존재한다.
물리적인 하나의 파일을 논리적으로 영역을 구분한다.
이렇게 논리적으로 구분이 가능하고 기본 application-dev.properties
와 동일하다
주의! 속성파일 구분 기호에는 선행 공백이 없어야하고 --- 3개 필수
주석이 구분자 위아래로 있으면 안됨
yml은 위에서 아래로 우선순위가 높은 값을 읽는다.
기본 프로필은 default
숫자가 높을 수록 우선순위가 높다
설정 데이터 우선순위
1. jar 내부 application.properties
2. jar 내부 프로필 적용 파일 application-{profile}.properties
3. jar 외부 application.properties
4. jar 외부 프로필 적용 파일 application-{profile}.properties
해당 방법을 활용하여 환경을 분리할 수 있으며 직접적인 파일을 손대지 않고 테스트 검증이 완료된 빌드 파일을 활용한다.
중간의 실수의 불안함을 제거하고 동일한 환경에서 설정을 바꿔서 실행 할 수 있다.
운영환경에서 문제가 생길 경우 크리티컬한 문제가 생기니 개발 환경의 환경 변수를 입력하여 프로필을 조절하고 해당 변수로 선 개발을 한뒤 실행시에 외부 활성 프로필 값으로 넣어주자!
또한 민감정보를 직접 파일로 넣는것은 유출 위험이 있으니 변수처리를 해서 입력을 해주자(이 또한 안전하게 실행해야겠지만....)