반응형

Spring Boot 및 일반 Spring 환경에서 외부 서비스 기반 설정 관리 방법

어플리케이션을 구동할 때 필요한 설정값들은 보통 환경변수(Environment Variables)나 파일에 저장하여 관리합니다. 하지만 최근에는 설정값을 Zookeeper, AWS Secrets Manager와 같은 외부 서비스에서 불러오거나, 보안을 강화하기 위해 메모리 내에서만 설정값을 저장하는 방식이 점점 더 선호되고 있습니다. 이러한 요구사항을 충족시키기 위해 Spring Boot와 일반 Spring 환경에서 설정값을 효과적으로 관리하는 방법을 소개합니다.

Spring Boot에서 환경 설정값 관리하기

Spring Boot는 EnvironmentPostProcessor를 활용하여 어플리케이션 시작 시점에 환경 설정값을 동적으로 변경하거나 추가할 수 있습니다. 이를 통해 외부 서비스에서 설정값을 불러와 적용하는 것이 가능합니다.

EnvironmentPostProcessor 활용 예제

import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;

import java.util.Map;
import java.util.stream.Collectors;
import java.util.Arrays;
import java.util.List;

public class PriceCalculationEnvironmentPostProcessor implements EnvironmentPostProcessor {

    private static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";
    private static final List<String> names = Arrays.asList("config.key1", "config.key2"); // 예시 키 리스트

    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
        var system = environment.getPropertySources().get(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME);

        Map<String, Object> prefixed = names.stream()
            .collect(Collectors.toMap(this::rename, system::getProperty));
        environment.getPropertySources()
            .addAfter(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, new MapPropertySource("prefixer", prefixed));
    }

    private String rename(String originalName) {
        return "prefix." + originalName; // 원하는 방식으로 키 이름 변경
    }
}

적용 방법

  1. Spring Boot의 spring.factories 파일에 등록

src/main/resources/META-INF/spring.factories 파일에 다음 내용을 추가합니다:

org.springframework.boot.env.EnvironmentPostProcessor=\
com.example.PriceCalculationEnvironmentPostProcessor
  1. 어플리케이션 실행 시 자동으로 적용

Spring Boot는 spring.factories에 등록된 EnvironmentPostProcessor를 자동으로 감지하여 적용합니다.

Spring Boot를 사용하지 않는 환경에서 설정값 관리하기

Spring Boot가 아닌 일반 Spring 환경에서도 설정값을 외부 서비스에서 불러오거나 메모리 내에서 관리할 수 있습니다. 이를 위해 ApplicationContextInitializer를 활용할 수 있습니다.

ApplicationContextInitializer 활용 예제

import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.boot.context.properties.bind.Binder;

public class PropertyOverrideContextInitializer 
        implements ApplicationContextInitializer<ConfigurableApplicationContext> {

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        var environment = applicationContext.getEnvironment();
        MyConfigProps configProps = Binder.get(environment)
                                         .bind("my-config", MyConfigProps.class)
                                         .orElse(new MyConfigProps());
        System.out.println(configProps.getHomekey());
        // 추가 설정 로직 구현
    }
}

적용 방법

ApplicationContextInitializer를 적용하는 방법은 여러 가지가 있습니다. 아래에 네 가지 주요 방법을 소개합니다.

  1. web.xml 또는 Java Config에서 contextInitializerClasses 추가

web.xml을 사용하는 경우:

<context-param>
    <param-name>contextInitializerClasses</param-name>
    <param-value>com.example.PropertyOverrideContextInitializer</param-value>
</context-param>

Java Config를 사용하는 경우:

import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;

@ContextConfiguration(initializers = PropertyOverrideContextInitializer.class)
public class AppConfig {
    // Bean 정의 등
}
  1. Spring만 사용하는 경우
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MainApp {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
        ctx.addInitializer(new PropertyOverrideContextInitializer());
        ctx.register(AppConfiguration.class);
        ctx.refresh();

        JobLauncher launcher = ctx.getBean(JobLauncher.class);
        // 어플리케이션 로직 실행
    }
}
  1. spring.factories 파일을 통한 자동 설정 (Spring Boot)

src/main/resources/META-INF/spring.factories 파일에 다음 내용을 추가합니다:

org.springframework.context.ApplicationContextInitializer=\
com.example.PropertyOverrideContextInitializer
  1. 어플리케이션 실행 시 코드로 추가 (Spring Boot)

SpringApplication을 사용하는 경우:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class YourApp {
    public static void main(String[] args) {
        SpringApplication application = new SpringApplication(YourApp.class);
        application.addInitializers(new PropertyOverrideContextInitializer());
        application.run(args);
    }
}

SpringApplicationBuilder을 사용하는 경우:

import org.springframework.boot.builder.SpringApplicationBuilder;

public class YourApp {
    public static void main(String[] args) {
        new SpringApplicationBuilder(YourApp.class)
            .initializers(new PropertyOverrideContextInitializer())
            .run(args);
    }
}

프로퍼티 파일을 통한 설정:

application.properties 또는 application.yml 파일에 다음과 같이 설정합니다:

context.initializer.classes=com.example.PropertyOverrideContextInitializer

결론

외부 서비스에서 설정값을 불러오거나, 보안을 강화하기 위해 메모리 내에서만 설정값을 관리하고자 할 때, Spring Boot와 일반 Spring 환경에서 제공하는 EnvironmentPostProcessor와 ApplicationContextInitializer를 활용하면 효과적으로 설정값을 관리할 수 있습니다. 각 방법은 어플리케이션의 구조와 요구사항에 따라 적절히 선택하여 적용할 수 있으며, 이를 통해 보다 유연하고 안전한 설정 관리가 가능합니다.

참고 자료

  1. Baeldung - Spring Tests: Override Properties
  2. Stack Overflow - How to add custom ApplicationContextInitializer to a Spring Boot application
  3. Stack Overflow - Spring ApplicationContextInitializer and properties
  4. Stack Overflow - ApplicationContextInitializer in a non-web Spring context

이 글이 Spring 기반 어플리케이션에서 설정값을 효과적으로 관리하는 데 도움이 되길 바랍니다. 추가적인 질문이나 의견이 있으시면 댓글로 남겨주세요!

반응형
반응형

스프링 프레임워크에서 ResourceLoader를 활용한 파일 접근

스프링(Spring) 프레임워크를 사용하다 보면 webapps 폴더 내의 파일들에 접근해야 하는 경우가 종종 발생합니다. 예를 들어, 설정 파일이나 정적 자원에 접근해야 할 때가 그렇습니다. 전통적으로는 ServletContext를 이용하여 파일에 접근할 수 있지만, 이 방법은 ServletContext에 의존적이라는 단점이 있습니다. 특히 특정 동작만을 위한 모듈을 추가하거나 테스트 환경에서 유연하게 사용하기 어렵습니다.

스프링에서는 이러한 문제를 해결하기 위해 ResourceLoader를 제공하여 보다 유연하게 파일에 접근할 수 있도록 지원합니다. 이번 포스트에서는 ResourceLoader를 활용한 파일 접근 방법을 살펴보고, 실용적인 예제를 통해 이해를 돕겠습니다.

ResourceLoader를 이용한 파일 접근

ResourceLoader는 스프링의 핵심 인터페이스 중 하나로, 다양한 위치에 있는 리소스(파일, 클래스패스 리소스 등)에 접근할 수 있는 기능을 제공합니다. 이를 통해 ServletContext에 의존하지 않고도 필요한 파일에 접근할 수 있습니다.

예제 코드 설명

아래는 ResourceLoaderAware 인터페이스를 구현하여 ResourceLoader를 주입받고, 이를 통해 webapps 폴더 내의 config.txt 파일을 읽어오는 예제입니다.

import java.io.IOException;
import java.io.InputStream;

import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;

public class ArbitraryResourceLoader implements ResourceLoaderAware {
    private ResourceLoader resourceLoader;

    // 파일을 읽어오는 메소드
    public void readConfigFile() throws IOException {
        // "file:" 접두사를 사용하여 파일 시스템의 절대 경로로 리소스를 로드
        Resource resource = resourceLoader.getResource("file:webapps/config.txt");
        InputStream is = resource.getInputStream();
        try {
            int i = 0;
            while ((i = is.read()) != -1) {
                char c = (char) i;
                System.out.print(c);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (is != null)
                is.close();
        }
    }

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }
}

주요 포인트

  1. ResourceLoaderAware 인터페이스 구현: ResourceLoaderAware를 구현하면 스프링 컨테이너가 ResourceLoader를 자동으로 주입해줍니다. 이를 통해 ResourceLoader를 직접 의존하지 않고도 리소스에 접근할 수 있습니다.

  2. 리소스 로드 방식: resourceLoader.getResource("file:webapps/config.txt")에서 "file:" 접두사는 파일 시스템의 절대 경로를 의미합니다. 필요에 따라 클래스패스 리소스나 URL 리소스 등 다양한 방식으로 리소스를 로드할 수 있습니다.

  3. 입출력 처리: InputStream을 사용하여 파일의 내용을 읽어오며, 예외 처리와 자원 해제를 철저히 합니다.

스프링 빈 설정

위에서 작성한 ArbitraryResourceLoader 클래스를 스프링 빈으로 등록하기 위해 XML 설정 파일을 사용합니다. 프로젝트 구조에 맞게 패키지 경로를 수정해야 합니다.

<beans xmlns="http://www.springframework.org/schema/beans"
     xmlns:context="http://www.springframework.org/schema/context"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://www.springframework.org/schema/beans
     http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
     http://www.springframework.org/schema/context
     http://www.springframework.org/schema/context/spring-context-4.0.xsd">

    <!-- ArbitraryResourceLoader 빈 등록 -->
    <bean id="arbitraryResourceLoader" class="org.test.sample.ArbitraryResourceLoader"/>

</beans>

주요 포인트

빈 등록: <bean> 태그를 사용하여 ArbitraryResourceLoader 클래스를 빈으로 등록합니다. 스프링 컨테이너가 관리하게 되며, 의존성 주입을 통해 ResourceLoader가 자동으로 주입됩니다.

JavaConfig를 이용한 설정

XML 설정 파일 대신 Java 기반 설정을 사용할 수도 있습니다. JavaConfig를 사용하면 더욱 타입 안전하고, IDE의 지원을 받을 수 있습니다.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean
    public ArbitraryResourceLoader arbitraryResourceLoader() {
        return new ArbitraryResourceLoader();
    }
}

주요 포인트

  • @Configuration 어노테이션: 이 클래스가 스프링의 설정 클래스임을 명시합니다.
  • @Bean 어노테이션: 메소드를 통해 빈을 등록합니다. 메소드 이름이 빈의 ID가 됩니다.

실용적인 예제: 설정 파일 로드 및 활용

이제 실제로 ArbitraryResourceLoader를 활용하여 설정 파일을 로드하고, 애플리케이션에서 활용하는 간단한 예제를 살펴보겠습니다.

설정 파일 (config.txt)

먼저 webapps 폴더 내에 config.txt 파일을 생성하고, 다음과 같은 내용을 추가합니다.

database.url=jdbc:mysql://localhost:3306/mydb
database.username=root
database.password=secret

ArbitraryResourceLoader 클래스 수정

설정 파일을 읽어와서 프로퍼티로 저장하는 기능을 추가합니다.

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;

public class ArbitraryResourceLoader implements ResourceLoaderAware {
    private ResourceLoader resourceLoader;
    private Properties properties = new Properties();

    public void loadConfig() throws IOException {
        Resource resource = resourceLoader.getResource("file:webapps/config.txt");
        try (InputStream is = resource.getInputStream()) {
            properties.load(is);
        }
    }

    public String getProperty(String key) {
        return properties.getProperty(key);
    }

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }
}

애플리케이션에서 사용하기

스프링 애플리케이션 컨텍스트를 로드하고, ArbitraryResourceLoader 빈을 사용하여 설정 값을 가져오는 예제입니다.

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Application {
    public static void main(String[] args) {
        // JavaConfig를 사용하는 경우
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

        // XML 설정을 사용하는 경우
        // ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

        ArbitraryResourceLoader loader = context.getBean(ArbitraryResourceLoader.class);

        try {
            loader.loadConfig();
            String dbUrl = loader.getProperty("database.url");
            String dbUser = loader.getProperty("database.username");
            String dbPassword = loader.getProperty("database.password");

            System.out.println("Database URL: " + dbUrl);
            System.out.println("Database User: " + dbUser);
            System.out.println("Database Password: " + dbPassword);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

실행 결과

Database URL: jdbc:mysql://localhost:3306/mydb
Database User: root
Database Password: secret

장점 및 결론

ResourceLoader를 활용하면 ServletContext에 의존하지 않고도 다양한 위치에 있는 리소스에 접근할 수 있어, 모듈의 재사용성과 테스트 용이성이 크게 향상됩니다. 또한, JavaConfig를 사용하면 설정이 더욱 직관적이고 관리하기 쉬워집니다.

스프링의 ResourceLoader는 단순히 파일 접근뿐만 아니라, 클래스패스 리소스, URL 리소스 등 다양한 리소스 타입을 지원하므로, 애플리케이션의 요구사항에 맞게 유연하게 사용할 수 있습니다. 이를 통해 보다 견고하고 유지보수하기 쉬운 애플리케이션을 개발할 수 있습니다.

스프링을 활용한 리소스 접근 방법을 이해하고, 실제 프로젝트에 적용해보세요. 다양한 상황에서 유용하게 사용할 수 있을 것입니다.

반응형

+ Recent posts