도서 'Spring in Action' 제 5판을 보고 책 내용과 그 이외의 부족한 부분을 채워가며 공부한 내용입니다.
키워드 : 구성, 속성, 프로파일
1. 구성 속성(Configuration Property)
스프링 애플리케이션 컨텍스트에서의 구성 속성 = 빈의 속성
1. 자동-구성 세부 조정하기
구성
- 빈 연결(Bean wiring)
스프링 애플리케이션 컨텍스트에서 빈으로 생성되는 애플리케이션 컴포넌트 및
상호 간에 주입되는 방법을 선언하는 구성
- 속성 주입(Property injection)
스프링 애플리케이션 컨텍스트에서 빈의 속성 값을 설정하는 구성
- 위 2가지 구성은 스프링의 XML 구성과 자바 기반 구성 모두 종종 같은 곳에서 선언
2. 스프링 환경 추상화 이해하기

스프링 환경 추상화(Environment abstraction)
구성 가능한 모든 속성을 한 곳에서 관리하는 개념
- 속성의 근원을 추상화하여 각 속성을 필요로 하는 빈이 스프링 자체에서 해당 속성을 사용할 수 있게 해줌
- 스프링 부트에 의해 자동으로 구성되는 빈들은 스프링 환경으로부터 가져온 속성들을 사용해서 구성
속성의 근원
- JVM 시스템 속성
- 운영체제 환경 변수
- 명령행 인자(command-line argument)
- 애플리케이션 속성 구성 파일
사용예시
1. 애플리케이션을 실행하는 서블릿 컨테이너가 8080기본 포트가 아닌 다른 포트로 작동하길 원함
- src/main/resources/application.properties
server.port=9090
- 구성 속성을 설정할 때 (YAML(YAML Ain't Markup Language)를 사용한다면 application.yml 사용
server:
port: 9090
- 애플리케이션을 실행할 때 명령행 인자로 server.port 속성을 지정 가능
$ java -jar tacocloud-0.0.5-SNAPSHOT.jar --server.port=9090
- 운영체제 환경 변수에 설정 가능
$ export SERVER_PORT=9090
2. 데이터 소스 구성
DataSource의 경우 우리가 빈을 명시적으로 구성 가능
but, 스프링 부트 사용 시는 그럴 필요가 없고, 대신 구성 속성을 통해서 해당 데이터베이스의 URL 인증을 구성
- 로컬 호스트의 MySQL 데이터베이스를 사용한다면 다음 구성을 application.yml에 추가
spring:
datasource:
url: jdbc:mysql://localhost/tacocloud
username: tacodb
password: tacopassword
driver-class-name: com.mysql.jdbc.Driver
//구체적인 JDBC드라이버 클래스를 지정할 필요는 없음
//스프링 부트가 데이터베이스 URL로부터 찾을 수 있음
- Datasource 빈을 자동-구성할 때 스프링 부트가 이런 속성 설정을 연결 데이터로 사용
- 또한, 톰캣의 JDBC 커넥션 풀을 classpath에서 자동으로 찾을 수 있다면 Datasource 빈이 그것을 사용
- 그러나 그렇지 않다면 스프링 부트는 다음 중 하나의 다른 커넥션 풀을 classpath에서 찾아 사용
- 스프링 부트의 자동-구성을 통해서 사용 가능한 커넥션 풀
- HikariCP
- Commons DBCP 2
- 애플리케이션이 시작될 때 데이터베이스를 초기화 하는 SQL 스크립트의 실행
spring:
datasource:
schema:
- order-schema.sql
- ingredient-schema.sql
- taco-schema.sql
- user-schema.sql
data:
- ingredients.sql
- 명시적인 데이터 소스 구성 대신 JNDI(Java Naming and Directory Interface)에 구성하는 것을 원할 수도 있음
- 단 아래의 속성 지정 시 기존에 설정된 다른 데이터 소스 구성 속성은 무시
spring:
datasource:
jndi-name: java:/comp/env/jdbc/tacoCloudDs
3. 내장 서버 구성
- server.port = 0 ?
- 서버포트는 0번으로 동작하진 않고 사용 가능한 포트를 무작위로 선택하여 시작
- 동시적으로 실행되는 어떤 테스트도 같은 포트번호로 인한 충돌이 생기지 않기에 유용
server:
port: 0
- 서버 -> 중요한 설정 -> port, HTTP 요청처리를 위한 컨테이너 관련 설정
- JDK의 keytool 명령행 유틸리티를 사용해서 키스토어(keystore) 생성
$ keytool -keystore mykeys.jks -genkey -alias tomcat -keyalg RSA
- keytool
- 저장 위치 등의 여러정보를 입력 받음(우리가 입력한 비밀번호 포함)
- 키스토어 생성이 끝난 후 내장 서버의 HTTPS를 활성화 하기 위해 몇 가지 속성을 설정
- application.properties 보다는 application.yml 파일에 설정
- 키스토어 파일이 생성된 경로로 설정 되어야 함
- 운영체제의 파일 시스템
- file://를 URL로 지정
- JAR파일에 키스토어 파일 넣기
- classpath: 를 URL로 지정해 참조
- 운영체제의 파일 시스템
server:
port: 8443 //개발용 https 서버에 많이 사용되는 포트
ssl:
key-store: file://path/to/mykeys.jks //키스토어 파일이 생성된 경로로 설정해야 함
key-store-password: letmein
key-password: letmein
4. 로깅 구성
- 스프링 부트
- INFO 수준(level)으로 콘솔에 로그메세지를 쓰기위해 Logback을 통해 로깅 구성
- 로깅 구성을 제어할 때는 classpath의 루트(src/main/resources)에 logback.xml 파일을 생성
<configuration>
<appender name="STDOUT" class="ch.qos. logback.core.ConsoleAppender">
<encoder>
<pattern>
%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} – "ómsg%n
</pattern>
</encoder>
</appender>
<logger name="root" level="INFO"/>
<root level="INFO">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
- 로깅에 사용되는 패턴을 제외하면 이 Logback 구성은 logback.xml 파일이 없을 때의 기본 로깅 구성과 동일
- 스프링 부트의 구성 속성을 사용 시 logback.xml 파일을 생성하지 않고 로깅 수준과 로그 수록을 변경 가능
- 로깅 수준
- logging.level
- 그 다음 로깅 수준을 설정하기 원하는 로거의 이름을 붙임
- 로깅 수준
logging:
level:
root: WARN
org:
springframework:
security: DEBUG
알아보기 쉽게 스프링 시큐리티 패키지 이름을 붙여서 한줄로 지정
logging:
level:
root: WARN
org.springframework.security: DEBUG
로그 항목들을 /var/logs/ 경로의 TacoCloud.log 파일에 수록
logging:
file:
path: /var/logs/
name: TacoCloud.log
level:
root: WARN
org.springframework.security: DEBUG
- 애플리 케이션이 /var/logs/에 대해 쓰기 퍼미션을 갖고 있다면 로그 항목들이 /var/logs/TacoCloud.log에 수록
- 기본적인 로그 파일의 크기인 10MB가 가득 차게 되면 새로운 로그 파일이 생성되어 로그항목이 계속 수록
- 스프링 2.0부터는 날짜별로 로그파일이 남고 지정된 일 수가 지난 로그 파일은 삭제
5. 다른 속성의 값 가져오기
하드코딩된 String과 숫자 값으로만 속성 값을 설정하는 것이 아닌 다른 구성 속성으로부터 값을 가져올 수 있음
- greeting.welcom이라는 속성을 또 다른 속성인 spring.application.name의 값으로 설정
greeting:
welcome: ${spring.application.name}
- 다른 텍스트 속에 ${}를 포함시킬 수도 있음
greeting:
welcome: You are using ${spring.application.name}
2. 구성 속성 생성하기
스프링 애플리케이션 컨텍스트에서의 구성 속성 = 빈의 속성
구성 속성의 올바른 주입 지원
- @ConfigurationProperties
- 어떤 스프링 빈이건 해당 어노테이션이 지정되면 해당 빈의 속성들이 스프링 환경의 속성으로부터 주입
<OrderController.java>
...
@Slf4j
@Controller
@RequestMapping("/orders")
@SessionAttributes("order")
@ConfigurationProperties(prefix = "taco.orders")
public class OrderController {
private int pageSize = 20;
private OrderRepository orderRepo;
public void setPageSize(int pageSize) {
this.pageSize = pageSize;
}
public OrderController(OrderRepository orderRepo) {
this.orderRepo = orderRepo;
}
...
@GetMapping
public String ordersForUser(@AuthenticationPrincipal User user, Model model) {
Pageable pageable = PageRequest.of(0, pageSize);
model.addAttribute("orders", orderRepo.findByUserOrderByPlacedAtDesc(user, pageable));
return "orderList";
}
- 사용자가 여러번 주문을 했을 때 유용하게 사용
- Pageable
- 가장 최근의 주문 n개 만큼을 조회제한 하기위해 사용
- @ConfigurationProperties(prefix="taco.orders")
- pageSize 구성 속성 값을 설정할 때 taco.orders.pageSize의 이름을 사용하면 됨
<application.yml>
taco:
orders:
pageSize: 10
<OrderRepository.java>
public interface OrderRepository extends CrudRepository<Order, Long> {
List<Order> findByUserOrderByPlacedAtDesc(User user, Pageable pageable);
}
- findByUserOrderByPlacedAtDesc
- 가장 최근 주문부터 오래된 주문의 순서로 정렬
- OrderByPlacedAt -> PlacedAt을 정렬
- Desc -> 내림차순
- 가장 최근 주문부터 오래된 주문의 순서로 정렬
구성 속성 홀더 정의
@ConfigurationProperties는 구성 데이터의 홀더로 사용되는 빈에 지정되는 경우도 많음
이렇게 하면 컨트롤러와 이외의 다른 애플리케이션 클래스 외부에 구성 관련 정보를 따로 유지할 수 있고 여러 빈에 공통적인 구성 속성을 쉽게 공유 가능
속성 홀더
구성 속성 값을 관리하는 클래스
- OrderController의 pageSize 속성은 별개의 홀더 클래스로 추출
<OrderProps.java>
@Component
@ConfigurationProperties(prefix="taco.orders")
@Data
@Validated
public class OrderProps {
@Min(value=5, message="must be between 5 and 25")
@Max(value=25, message="must be between 5 and 25")
private int pageSize = 20;
}
- 위에서 주문과 관련된 구성 속성들을 한군데 모아둘 수 있음
- 해당 속성의 값이 최소 5, 최대 25인지 검사
<OrderController.java 변경하기>
@Slf4j
@Controller
@RequestMapping("/orders")
@SessionAttributes("order")
public class OrderController {
private OrderProps props;
private OrderRepository orderRepo;
public OrderController(OrderRepository orderRepo, OrderProps props) {
this.orderRepo = orderRepo;
this.props = props;
}
...
@GetMapping
public String ordersForUser(
@AuthenticationPrincipal User user, Model model) {
Pageable pageable = PageRequest.of(0, props.getPageSize());
model.addAttribute("orders",
orderRepo.findByUserOrderByPlacedAtDesc(user, pageable));
return "orderList";
}
- OrderController가 직접 pageSize 구성 속성을 처리할 필요가 없음
- 해당 속성이 필요한 다른 빈에서 OrderProps의 속성들을 재사용
구성 속성 메타데이터 선언
spring-boot-configuration-processor
스프링 부트 구성 처리기(속성들에 관한 메타데이터 작성을 위해 의존성 주입)
- @ConfigurationProperties 어노테이션이 지정된 어플리케이션 클래스에 관한 메타데이터를 생성하는 처리기
{
"properties": [
{
"name": "taco.orders.page-size",
"type": "int",
"description": "Sets the maximum number of orders to display in a list."
}
]
}
- JSON 형식으로 된 taco.orders.pageSize 속성의 메타데이터
- 참고된 속성의 이름(name)을 taco.orders.page-size로 지정
- 스프링 부트의 처리로 인해 taco.orders.pageSize가 같은 것으로 처리
3. 프로파일 사용해서 구성
애플리케이션이 서로 다른 런타임 환경에 배포, 설치될 경우 대개 구성 명세가 달라짐
- 각 환경의 속성들을 application.properties 나 application.yml에 정의하는 대신, 운영체제의 환경 변수를 사용해서 구성
- but 하나 이상의 구성 속성을 환경 변수로 저장하는 것은 번거로움
- 환경 변수의 변경을 추적 관리 하거나 오류가 있을 경우 변경 전으로 바로 되돌릴 수 있는 방법이 마땅치 않음
-> 스프링 프로파일의 사용 선호
프로파일
런타임 시에 활성화(active)되는 프로파일에 따라 서로 다른 빈, 구성 클래스, 구성 속성들이 적용/무시되도록 하는것
예시) 개발과 디버깅 목적으로 내장 H2 DB를 사용하고 타코 클라우드 코드의 로깅 수준을 DEBUG로 설정
logging:
level:
tacos: DEBUG
- 개발 시 데이터 소스 속성을 따로 설정하지 않아도 자동-구성된 H2 데이터베이스를 충분히 사용 가능
- 로깅 수준의 경우 tacos 기본패키지의 logging.level.tacos 속성을 appplication.yml에 DEBUG로 설정
예시) 프로덕션 환경에서는 외부의 MySQL DB를 사용하고 로깅 수준은 WARN으로 설정
- 환경별로 적합한 속성들을 설정하는 프로파일을 정의
프로파일 특정 속성 정의
프로파일에 특정한 속성을 정의하는 한 가지 방법은 프로덕션 환경의 속성들만 포함하는 또 다른 .yml을 생성하는 것
이때 파일 이름은 다음 규칙을 따라야 함
application-[프로파일 이름].yml
or
application-[프로파일 이름].properties
- 해당 프로파일에 적합한 구성 속성들을 각 파일에 지정
<application-prod.yml>
spring:
datasource:
url: jdbc:mysql://localhost/tacocloud
username: tacouser
password: tacopassword
logging:
level:
tacos: WARN
YAML 구성에서만 사용할 수 있는 방법인데 프로파일에 특정되지 않고 공통으로 적용되는 기본 속성과 함께 프로파일 특정 속성을 application.yml에 지정
- 즉, 프로파일의 특정되지 않는 기본 속성 다음에 3개의 하이픈(---)을 추가하고 그 다음에 해당 프로파일의 이름을 나타내는 spring.profiles속성을 지정
logging:
level:
tacos: DEBUG
---
spring:
profiles: prod
datasource:
url: jdbc:mysql://localhost/tacocloud
username: tacouser
password: tacopassword
logging:
level:
tacos: WARN
- ---
- 구분선
- 첫번째 부분은 spring.profiles 지정 x -> 모든 프로파일에 공통으로 적용
- 만일 이 부분의 속성과 같은 속성을 활성화된 프로파일에서 설정하지 않으면 해당 속성의 기본 설정이 됨
- 두번째 부분에서 spring.profiles의 값을 지정
- 이후 속성은 prod에만 지정됨을 뜻함
4. 프로파일 활성화
프로파일 특정 속성들의 설정은 해당 프로파일이 활성화 되어야 유효
- spring.profiles.active 속성에 지정하면 됨
spring:
profiles:
active:
-prod
- 가장 좋지 않은 활성화 방법
- 프로덕션 환경 특정 속성을 개발 속성과 분리시키기 위해 프로파일을 사용하는 장점을 전혀 살릴 수 없게 됨
- 활성화를 하드코딩하게 되기 때문에 특정 속성을 개발 속성과 분리시킬 수 없다.
환경 변수를 사용해서 활성화 프로파일을 설정하는 것이 더 나은 방법
$export SPRING_PROFILES_ACTIVE=prod
실행 가능한 JAR 파일로 애플리케이션을 실행한다면 다음과 같이 명령행 인자로 활성화 프로파일을 설정하면 애플리케이션별로 활성화를 설정
% java -jar taco-cloud.jar --spring.profiles.active=prod
여러개 지정
% java -jar taco-cloud.jar --spring.profiles.active=prod,audit,ha
위를 YAML에서 지정하는 방법
spring:
profiles:
active:
- prod
- audit
- ha
※ 클라우드 파운드리(Cloud Foundry)
애플리케이션의 개발, 배포, 확장을 위한 오픈 소스/멀티 클라우드 PaaS(Platform as a Service)
PaaS는 인터넷으로 애플리케이션 설계/개발/배포할 때 필요한 하드웨어와 스프트웨어를 제공하는 플랫폼
- 스프링 애플리케이션을 클라우드 파운드리에 배포할 때는 cloud라는 이름의 프로파일이 자동으로 활성화
- 클라우드 파운드리가 프로덕션 환경이라면 cloud 프로파일 아래에 프로덕션 환경의 특전 속성들을 지정해야 함
프로파일을 사용해서 조건별로 빈 생성
서로 다른 프로파일 각각에 적합한 빈들을 제공하는 것이 유용할 때가 있음
일반적으로 자바 구성 클래스에 선언된 빈은 활성화되는 프로파일과는 무관하게 생성
그러나 특정 프로파일이 활성화될 때만 생성되어야 하는 빈들이 있다고 가정해보면 @Profile 애노테이션을 사용하면 지정된 프로파일에만 적합한 빈들을 생성할 수 있음
ex) TacoCloudApplication에는 CommandLineRunner 빈이 선언
- 애플리케이션이 시작될 때마다 Ingredient 데이터를 내장 DB에 로드하기 위해 CommandLineRunner 빈이 사용
- 프로덕션 환경에서는 비활성화하는 것이 나을 것
- CommandLineRunner 빈 메서드에 @Profile을 지정하면 프로덕션 환경에서 식자재 데이터가 로드되는 것을 방지
@Bean
@Profile("dev")
public CommandLineRunner dataLoader(IngredientRepository repo) {
return new CommandLineRunner() {
@Override
public void run(String... args) throws Exception {
repo.save(new Ingredient("FLTO", "Flour Tortilla", Ingredient.Type.WRAP));
repo.save(new Ingredient("COTO", "Corn Tortilla", Ingredient.Type.WRAP));
repo.save(new Ingredient("GRBF", "Ground Beef", Ingredient.Type.PROTEIN));
repo.save(new Ingredient("CARN", "Carnitas", Ingredient.Type.PROTEIN));
repo.save(new Ingredient("TMTO", "Diced Tomatoes", Ingredient.Type.VEGGIES));
repo.save(new Ingredient("LETC", "Lettuce", Ingredient.Type.VEGGIES));
repo.save(new Ingredient("CHED", "Cheddar", Ingredient.Type.CHEESE));
repo.save(new Ingredient("JACK", "Monterrey Jack", Ingredient.Type.CHEESE));
}
};
}
- @Profile({"dev", "qa"})
- dev 프로파일이나 qa 프로파일 중 하나가 활성화될 때 CommandLineRunner 빈이 생성되어야 한다면 위와같이 설정
- 개발환경에서는 애플리케이션이 실행 될 때 dev 프로파일을 활성화 해주어야 함
@Profile("!prod")
- @Profile("!prod")
- prod 프로파일이 활성화되지 않을 때 CommandLineRunner 빈이 항상 생성되도록 하면 더 편리
- !는 부정이므로 prod 파일이 활성화되지 않을 경우 CommandLineRunner 빈이 생성
@Profile({"!prod", "!qa"})
@Configuration
public class DevelopmentConfig {
@Bean
public CommandLineRunner dataLoader(IngredientRepository repo,
UserRepository userRepo, PasswordEncoder encoder) {
...
}
}
- @Profile은 @Configuration이 지정된 클래스 전체에 대해 사용 가능
- 위의 경우 prod 프로파일과 qa 프로파일 모두 활성화되지 않을 때만 CommandLineRunner 빈이 생성
참고 자료
1. 크레이그 월즈, Spring in Action, Fifth Edition(출판지 : 제이펍, 2020)
'Spring' 카테고리의 다른 글
Spring - logging (0) | 2022.06.08 |
---|---|
Spring 구성 속성 2 (0) | 2022.06.02 |
Spring Security 4 (0) | 2022.05.26 |
Spring Security 3 (0) | 2022.05.25 |
JDBC - 데이터 UPDATE, INSERT, DELETE (0) | 2022.05.23 |