[SpringBoot] 2. SpringBoot Common Task
어플리케이션 설정 관리 : 여러 환경에 따른 설정 정보의 다중화
- 개발 환경의 다중화 → 개발 (
dev), 테스트 (test), 스테이징 (staging), 배포 (prod)- 여러 환경에 배포할 때 설정 정보는 매번 변경되어야 하지만, 소스 코드는 변경되어선 안됨
- 즉, 설정 정보를 외부화하여 관리할 필요성이 있음
어플리케이션 설정 관리 방법의 우선순위
- 명령행 인자
- 운영 체제의 환경 변수
- 설정 정보 파일 (
application.properties혹은application.yml)@PropertySourceSpringApplication
SpringApplication 클래스 사용
- 소스 코드로 정의하는 방식이므로 한 번 정의하면 나중에 바뀌지 않는 경우에 적합함
java.util.Properties나java.util.Map<String, Object>가 인자인setDefaultProperties()메소드 호출Properties나Map<String, Object>를 통해 설정 정보를 어플리케이션에 적용
application.properties파일은 다른 설정 파일들을spring.config.import프로퍼티를 통해 임포트해서 사용spring.config.import=classpath:additional-application.properties를 추가하면 스프링부트는additional-application.properties파일에 있는 설정 정보를 읽어 사용할 수 있음- 만약 클래스패스에 파일이 없으면,
ConfigDataLocationNotFoundException예외가 발생 spring.config.on-not-found에ignore를 지정하면, 클래스파일에 설정 파일이 없을 때 예외 처리를 하고 종료하는 대신 어플리케이션 시동 작업을 계속 진행하게 할 수 있음
- 만약 클래스패스에 파일이 없으면,
@PropertySource 사용
- 설정 파일의 위치를 어노테이션을 사용해 지정 (자바 8 이후로는 동일한 어노테이션을 여러 번 사용할 수 있음)
src/main/resources/의 파일은JAR로 패키징된 후 클래스패스에 위치하므로, 해당 디렉터리에 설정 파일 작성
- 스프링이 제공하는
Environment인스턴스를 주입받으면, 설정 파일에 있는 정보를 읽을 수 있음
@SpringBootApplication
public class SpringBootApplication {
public static final Logger log = Logger.getLogger(SpringBootApplication.class);
public static void main(String[] args) {
ConfigurableApplicationContext configurableApplicationContext
= SpringApplication.run(SpringBootApplication.class, args);
CustomConfiguration customConfiguration
= applicationContext.getBean(CustomConfiguration.class);
log.info(customConfiguration.toString());
}
}
@PropertySource로는.yml파일 지정해서 사용할 수 없으므로, 추가적인 작업이 필요
환경 설정 파일 (application.properties 또는 application.yml)
- 환경 설정 파일에 명시된 설정 프로퍼티 정보는 스프리의
Environment객체에 로딩- 어플리케이션 클래스에서
Environment인스턴스나@Value어노테이션을 통해 설정 정보를 읽을 수 있음 spring.config.name프로퍼티로 환경 설정 파일의 이름을 지정할 수 있음
- 어플리케이션 클래스에서
스프링부트가 기본적으로 읽는 환경 설정 파일의 위치는,
- 클래스패스 루트
- 클래스패스
/config패키지- 현재 디렉터리
- 현재 디렉터리
/config디렉터리/config디렉터리의 바로 하위에 위치한 디렉터리추가로,
spring.config.location프로퍼티를 통해 상대 경로나 절대 경로의 환경 설정 파일을 읽을 수 있음
(optional:접두어로 해당 경로에 환경 설정 파일이 없더라도, 예외 처리 없이 스프링부트 기본 설정값으로 실행 가능)java -jar target/config-data-file.jar --spring.config.location=optional:data/application.yml
spring.config.name과spring.config.properties는 환경 설정 파일에 지정할 수 없음
→SpringApplication.setDefaultProperties()메소드나 환경 변수, 혹은 명령행 인자를 통해 지정
application-{profile}.properties: 프로파일 (profile)별로 프로퍼티 파일 지정 가능- 환경 설정 파일의
spring.profile.active프로퍼티를 통해 프로파일를 지정하면, 프로퍼티 파일의 내용이 로딩
- 환경 설정 파일의
@Configuration(proxyBeanMethods = false)
@Profile("production")
public class ProductionConfiguration {
// ...
}
설정 파일의 로딩 순서
- 어플리케이션
JAR파일 안에 패키징되는 환경 설정 파일- 어플리케이션
JAR파일 안에 패키징되는profile별 환경 설정 파일- 어플리케이션
JAR파일 밖에서 패키징되는 환경 설정 파일- 어플리케이션
JAR파일 안에 패키징되는profile별 환경 설정 파일
운영 체제의 환경 변수
application.properties파일에서app.timeout커스텀 프로퍼티 활용
app.timeout=${APP_TIMEOUT}
ConfigurableApplicationContext인스턴스에 접근Environment빈을 가져옴- 프로퍼티 값을
env.getProperty()메소드로 읽어 콘솔 로그로 출력
@ConfigurationProperties : 커스텀 프로퍼티 생성
스프링부트 빌트인 프로퍼티 전체 목록은 공식 래퍼런스 문서에서 확인 가능
- 스프링 어플리케이션이 복잡해지고 기능이 많아질수록, 커스텀 프로퍼티의 필요성이 높아짐
- 외부
REST웹 서비스URL이나 특정 기능 활성화 여부를 지정할boolean플래그 등
- 외부
Environment 인스턴스 주입을 통한 프로퍼티 값 사용
- 스프링의
Environment인스턴스에 프로퍼티가 바인딩 →Environment인스턴스를 주입받아 프로퍼티 값 사용 가능 - 프로퍼티 값의 타입 안정성 (
Type-Safety)이 보장되지 않아, 이로 인해 런타임 에러가 발생할 수 있음URL이나 이메일 주소를 프로퍼티로 사용할 때 유효성 검증 (Validation)을 수행할 수 없음
- 프로퍼티 값을 일정한 단위로 묶어서 읽을 수 없음
@Value나 스프링의Environment인스턴스를 사용해서 하나하나 개별적으로만 읽을 수 있음
@ConfigurationProperties 어노테이션을 사용한 커스텀 프로퍼티 정의
요구사항 : 커스텀 프로퍼티에 대한 타입 안정성을 보장하고, 값의 유효성을 검증해야 한다.
- 설정 처리기 (
Configuration Processor)로@ConfigurationProperties가 붙은 클래스 메타데이터 생성spring-boot-configuration-processor의존 관계를 추가해 스프링부트 설정 처리기를 활성화- 생성된 메타데이터는 환경 설정 파일에 기술된 프로퍼티에 대한 자동 완성이나 문서화 지원
spring.config.import,@ConfigurationProperties으로 연관 프로퍼티들을 그룹화한 프로퍼티 파일로 관리@ConfigurationProperties을 클래스 안에서 빈을 생성하는@Bean메소드에도 붙일 수 있음- 아래처럼 생성자를 사용해서 바인딩하는 방식이 아닌,
setter메소드를 통해 바인딩하는 방식 또한 가능
- 커스텀 프로퍼티를 사용하려면 환경 설정 파일에 해당 프로퍼티에 대한 정보를 추가해야 함
- 프로퍼티 정의를 담은
AppProperties클래스로 서비스 클래스에서AppProperties객체를 주입받을 수 있음
- 스프링부트 어플리케이션 클래스는 서비스 클래스를 사용해서
AppProperties객체에 접근하여 프로퍼티 값 사용@EnableConfigurationProperties는@ConfigurationProperties가 붙은 클래스를 스프링 컨테이너에 등록@ConfigurationPropertiesScan으로 기준 패키지을 지정하면, 지정 패키지 하위에 있는@ConfigurationProperties가 붙은 클래스를 모두 탐색해서 스프링 컨테이너에 등록@ConfigurationProperties가 붙은 클래스를 자동 탐색해서 등록하는 것이 아니라 직접 명시해야 함
ConstructorBinding을POJO클래스에 사용하면, 생성자를 통해 프로퍼티 정보값이 설정- 생성자가 하나면 클래스에, 생성자가 여러 개라면 프로퍼티 정보값 설정에 사용할 생성자에 어노테이션을 붙이면 됨
- 설정 정보 클래스의 불변성을 보장하기 위해선 세터 바인딩 대신 생성자 바인딩으로 프로퍼티 값을 설정해야 함
- 생성자 바인딩 대신 세터 메소드를 사용하는 세터 바인딩 방식으로 프로퍼티 값을 설정할 수도 있음
- 설정 정보의 불변성을 보장하기 위해 세터 메소드 대신
@ConstructorBinding을 사용해야 함 @DefaultValue로 프로퍼티 기본값 지정 가능
- 설정 정보의 불변성을 보장하기 위해 세터 메소드 대신
CommandRunner, ApplicationRunner로 스프링부트 어플리케이션 시작 시 코드 실행
요구사항 : 어플리케이션 초기화 이전에
DB초기화 스크립트를 실행하거나, 외부REST서비스를 호출해서 데이터를 가져오는 상황이 있을 수 있다.
- 스프링부트 메인 클래스가
CommandRunner인터페이스를 구현
CommandRunner구현체에@Bean을 붙어 스프링 빈으로 정의CommandRunner는run(String... args)메소드 하나만 가진 함수형 인터페이스
→ 람다식을 사용해서CommandRunner구현체를 작성할 수 있음
CommandRunner구현체에@Component를 붙어서 스프링 컴포넌트로 정의- 어플리케이션을 실행하면 스프링부트 컴포넌트 스캔을 통해 컴포넌트의 인스턴스가 생성되고 빈으로 등록
@Bean vs @Component? : 모두 스프링에 의해 빈으로 등록된다는 공통점
- 빈으로 등록할 클래스의 코드에 직접 접근할 수 없으면, 클래스의 인스턴스를 반환하는 메소드에
@Bean- 빈으로 등록할 클래스의 코드에 직접 접근할 수 있으면, 클래스의 인스턴스를 반환하는 메소드에
@Component
CommandRunner안에서 스프링 의존 관계 주입으로 빈을 주입받아 사용 가능CommandRunner구현체는 빈 등록을 포함한 초기화를 거의 마친 뒤 실행되므로, 어떤 빈이든 주입받아 사용 가능
스프링부트 어플리케이션 로깅 커스터마이징
- 로거 (
Logger) : 한 개 이상의 어펜더를 사용해 로그 메시지 표시를 담당하는 로깅 프레임워크 컴포넌트- 어펜더 (
Appender) : 로그가 출력되는 대상과 로깅 포맷 지정 가능ConsoleAppender: 어플리케이션의 콘솔에 로그 출력FileAppender: 하나의 파일에 로그 출력RollingFileAppender: 시간과 날짜 기반으로 별도의 파일에 로그 출력SmtpAppender: 정해진 이메일 주소로 로그 출력
- 어펜더 (
요구사항 : 어플리케이션에 발생하는 중요 이벤트와 어플리케이션 동작에 대한 로그를 출력하는 로깅은 필수적이다.
- 스프링부트 어플리케이션의 콘솔 로그는 기본으로 제공 :
Apache Commons로깅 프레임워크 사용Logback,Log4j2등 로깅 프레임워크, 자바에서 제공하는java.util.logging또한 지원
- 로그 출력 패턴 : 로그를 구성하는 여러 요소들을 출력하는 형태 및
ANSI색상을 지정할 수 있음 - 로그 롤링 (
Log Rolling) : 나중에 확인할 수 있도록 로그의 양, 기간에 따라 별도의 파일에 나누어 저장
로그를 구성하는 여러 요소?
- 일시 : 로그가 출력되는 날짜와 시간
- 로그 레벨 : 로그의 중요도에 따라
FATAL,ERROR,WARN,INFO,DEBUG,TRACE로 구분- 구분자 : 실제 로그 메시지의 시작 부분
- 스레드 이름 : 현재 로그를 출력한 스레드 이름
(TaskExecutor로 생성할 때 지정된 스레드풀에서 사용할 이름)- 로거 이름 : 축약된 클래스 이름
- 메시지 : 실제 로그 메시지
스프링부트 어플리케이션에 Log4j2 사용
- 기본으로 내장된
logback의존 관계를 제거하고,Log4j2의존 관계를 추가
XML,JSON,YAML형식 중 하나로Log4j2설정 파일 작성
Configuration: # 로그 설정
name: DEFAULT # 설정 이름
status: "${env:LOGGING_LEVEL}" # 설정 상태
Appenders: # 설정 Appender
Console:
name: Console_Appender
target: SYSTEM_OUT # 출력 대상 (SYSTEM_OUT, SYSTEM_ERR)
PatternLayout: # 출력 패턴
charset: "${env:LOGGING_CHARSET}"
pattern: "${env:LOGGING_PATTERN}"
disableAnsi: false # ANSI 색상 미사용 여부
RollingFile:
name: RollingFile_Appender
fileName: "${env:LOGGING_FILENAME}" # 파일명
filePattern: "${env:LOGGING_FILE_PATTERN}" # 파일 패턴
PatternLayout:
charset: "${env:LOGGING_CHARSET}"
pattern: "${env:LOGGING_PATTERN}"
Policies:
SizeBasedTriggeringPolicy:
size: "${env:LOGGING_FILE_SIZE}" # 파일 크기
TimeBasedTriggeringPolicy:
interval: "${env:LOGGING_FILE_INTERVAL}" # 시간 간격
Loggers:
Root:
level: "${env:LOGGING_LEVEL}" # 루트 로거 레벨
AppenderRef:
- ref: Console_Appender # 루트 로거 출력 대상
- ref: RollingFile_Appender # 루트 로거 출력 대상
Logger:
name: vw.temp # 로거 이름
additivity: false # 상위 로거 출력 여부
level: "${env:LOGGING_LEVEL}" # 로거 레벨
AppenderRef:
- ref: Console_Appender
- ref: RollingFile_Appender
LoggerFactory클래스의getLogger메소드로 로거 인스턴스 생성SLF4j: 로깅 프레임워크를 빌드 타임에 플러그인 방식으로 사용할 수 있게 해주는 추상화 라이브러리
Bean Validation으로 사용자 입력 데이터 유효성 검증
요구사항 : 사용자 입력 데이터가 비즈니스 요구 사항에 적합한지 검증해야 한다.
Bean Validation: 유효성 검사를 간단한 어노테이션으로 쉽게 구현 가능 + 커스텀 벨리데이터 생성 가능
- 유효성 검사를 할 필드에 어노테이션을 붙어 비즈니스 제약 사항을 수행하는지 확인
- 제약 사항이 충족되지 않으면, 어노테이션에서 지정한 에러 메시지 표시
Hibernate Validation:Bean Validation스펙을 참조하는 구현체
커스텀 Bean Validation 어노테이션을 사용한 POJO 빈 유효성 검증
요구사항 : 사용자 입력 데이터가 비즈니스 요구 사항에 적합한지 검증할 때, 유효성 검사 커스텀마이징이 필요하다.
passay라이브러리 : 비밀번호 규칙을 강제
ConstraintValidator: 커스텀 어노테이션을 정의할 때, 제약 사항 준수를 위해 호출되는 인터페이스isValid메소드는 비밀번호 유효성 검증에 사용하는 커스텀 로직이 정의 →true/false반환- 첫번째 인자
Password는 커스텀 벨리데이터 로직을 적용하는 어노테이션 →@String - 두번째 인자
String은 커스텀 어노테이션을 적용해야 하는 데이터 타입