반응형

도서 '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

+ Recent posts