[Spring] 운영체제 별로 다른 프로파일 로딩하기

유알·2023년 4월 4일
0

[Spring]

목록 보기
12/17

https://github.com/Onji-K/video-streaming-demo/tree/main/src/main/java/com/oj/videostreamingdemo/global/config

package com.oj.videostreamingdemo.global.config;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.bramp.ffmpeg.FFmpeg;
import net.bramp.ffmpeg.FFprobe;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;

import java.io.IOException;

@Slf4j
@Configuration
@RequiredArgsConstructor
public class FFmpegConfig {
    @Value("${ffmpeg-tools.ffmpeg.location}")
    private String ffmpegLocation;

    @Value("${ffmpeg-tools.ffprobe.location}")
    private String ffprobeLocation;

    @Bean(name = "ffmpeg")
    public FFmpeg ffmpeg() throws IOException{
        ResourceLoader resourceLoader = new DefaultResourceLoader();
        Resource resource = resourceLoader.getResource(ffmpegLocation);
        return new FFmpeg(resource.getURL().getPath());
    }

    @Bean( name = "ffprobe")
    public FFprobe ffprobe() throws IOException{
        ResourceLoader resourceLoader = new DefaultResourceLoader();
        Resource resource = resourceLoader.getResource(ffprobeLocation);
        return new FFprobe(resource.getURL().getPath());
    }

}

이러한 코드를 가지고 있었다. ffmpeg이라는 커널 프로그램을 감싸서 돌려주는 라이브러리인데, ffmpeg 커널이 깔려 있어야 하고 그 커널의 위치를 넘겨줘야 돌아갔다.

그런데 커널이 운영체제마다 다르니 운영체제마다 다른 파일 주소를 넘겨줘야 했다.
물론 환경마다 설치해서 할 수도 있지만, 나는 프로젝트 폴더 내에 운영체제별 커널을 미리 박아 놓고 실행시 운영체제를 감지해서 적절한 커널을 불러올 수 있도록 하고 싶었다.

그래서 아래와 같은 yml을 만들고

# ffmpeg 관련 설정
# 이 파일은 application.yml 에서 추가하지 마세요
# ProfileResolverEnvironmentPostProcessor 에서 OS에 맞게 Property 를 주입합니다.


# 공통 설정
spring:
  servlet:
    multipart:
      enabled: true
      max-file-size: 30MB # 한개당 제한 사이즈
      max-request-size: 80MB # 한번에 최대 업로드 용량
--- #MAC OS 설정
os-name: MAC
ffmpeg-tools:
  ffmpeg:
    location: file:ffmpeg/mac/ffmpeg
  ffprobe:
    location: file:ffmpeg/mac/ffprobe
--- #WIN OS 설정
os-name: WIN
ffmpeg-tools:
  ffmpeg:
    location: file:ffmpeg/window/bin/ffmpeg.exe
  ffprobe:
    location: file:ffmpeg/window/bit/ffprobe.exe
--- #Linux_arm64
os-name: LINUX_ARM64
ffmpeg-tools:
  ffmpeg:
    location: file:ffmpeg/linux_arm64/ffmpeg
  ffprobe:
    location: file:ffmpeg/linux_arm64/ffprobe
--- #Linux_amd64
os-name: LINUX_ARM64
ffmpeg-tools:
  ffmpeg:
    location: file:ffmpeg/linux_amd64/ffmpeg
  ffprobe:
    location: file:ffmpeg/linux_amd64/ffprobe

아래처럼 구현하였다.

package com.oj.videostreamingdemo.global.config;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.boot.env.YamlPropertySourceLoader;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.ObjectUtils;

import java.io.IOException;
import java.util.List;


@Order(Ordered.LOWEST_PRECEDENCE)
public class ProfileResolverEnvironmentPostProcessor implements EnvironmentPostProcessor {

    private final YamlPropertySourceLoader yamlPropertySourceLoader = new YamlPropertySourceLoader();
    private final ClassPathResource ffmpegYml = new ClassPathResource("application-ffmpeg.yml");
    private enum OsName {WINDOW,MAC,LINUX_ARM64,LINUX_AMD64};
    private final String OS_NAME_PROPERTY = "os-name";
    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
        //실행중인 OS 감지
        OsName osName = null;
        final String systemOs = System.getProperty("os.name").toLowerCase();
        final String arch = System.getProperty("os.arch").toLowerCase();

        if (systemOs.contains("mac")){
            osName = OsName.MAC;
        } else if (systemOs.contains("win")) {
            osName = OsName.WINDOW;
        } else if (systemOs.contains("linux")) {
            if (arch.contains("aarch64")){
                osName = OsName.LINUX_ARM64;
            } else if (arch.contains("amd64")) {
                osName = OsName.LINUX_AMD64;
            } else throw new IllegalStateException("Unsupported Linux Architecture");
        } else {
            throw new IllegalStateException("Unsupported Operating System");
        }

        //propertySources 만들기
        List<PropertySource<?>> ymlPropertySources;
        try {
            ymlPropertySources = yamlPropertySourceLoader.load(ffmpegYml.getFilename(), ffmpegYml);
        } catch (IOException e){
            throw new IllegalStateException(e);
        }

        MutablePropertySources environmentPropertySources = environment.getPropertySources();

        final String matchingWord = osName.name();
        //공통설정
        ymlPropertySources.stream()
                .filter(source -> ObjectUtils.isEmpty(source.getProperty(OS_NAME_PROPERTY)))
                .forEach(environmentPropertySources::addLast);
        //OS에 맞는 설정 주입
        ymlPropertySources.stream()
                .filter(source -> matchingWord.equals(source.getProperty(OS_NAME_PROPERTY)))
                .forEach(environmentPropertySources::addLast);
    }
}

EnvironmentPostProcessor을 상속받아 프로파일,환경변수를 주입할 때 주의 해야할 점은, 여기서 profile active 시켜도 한 문서 내에서 아래처럼 나눠져 있다면, 환경 변수들을 로드 하지 않는다.

spring:
  profiles:
    active: local

--- #test 설정
spring:
  config:
    activate:
      on-profile: "test"
  h2:
    console:
      enabled: true
      path: /h2-console
  datasource:
    driver-class-name: org.h2.Driver
    url: jdbc:h2:~/test
    username: sa
    password:
  jpa:
    hibernate:
      ddl-auto: create
    show-sql: true

logging:
  level:
    root: warn

--- #local 설정
spring:
  config:
    activate:
      on-profile: "local"
  h2:
    console:
      enabled: true
      path: /h2-console
  datasource:
    driver-class-name: org.h2.Driver
    url: jdbc:h2:~/test
    username: sa
    password:
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true

server:
  port: 8081

logging:
  level:
    root: info

--- #prod 설정
spring:
  config:
    activate:
      on-profile: "prod"
logging:
  level:
    root: info

이미 읽어 들였기 때문이다. 따라서 Spring 에 Enviroment에 직접 넣어줘야한다.
아래 그림은 디버깅 한 것인데 enviroment안에 어떻게 들어있는지 보여준다.

정리

  • MutablePropertySources environmentPropertySources = environment.getPropertySources();
    를 통해 받아온 MutablePropertySources에 addLast() 같은 메서드를 사용해서PropertySource<?>를 넣어준다.
  • 그러면 다른 빈에서 @Value나 다른 방법을 통해 환경변수를 불러올 수 있다.
  • EnvironmentPostProcessor는 초기에 실행되므로, 이러한 프로파일을 불러오거나, 환경변수를 넣을 때 자주 실행된다.
  • 이미 불러온 yml은 profile을 바꿔도 로딩되지 않는다.
  • yamlPropertySourceLoader 는 yml 파일 불러올때 사용할 수 있다.
profile
더 좋은 구조를 고민하는 개발자 입니다

1개의 댓글

comment-user-thumbnail
2023년 5월 20일

이때는 이렇게 생각했지만, 지금은 다르게 생각합니다.
그냥 시스템 환경변수로 빼서, 도커같은 곳에서 환경변수를 설정하는게 더 좋지 않나 싶네요

답글 달기