ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • @Profile 과 @ActiveProfiles
    카테고리 없음 2023. 4. 23. 14:05
    오늘은 Spring의 @Profile 어노테이션과 @ActiveProfiles 어노테이션에 대해서 알아보도록 하자. 가끔 헷갈리는 개발자분들이 있으니 다시 한번 짚고 넘어 가면 좋을 듯하다.

    @Profile

    @Profile 어노테이션은 굉장히 유용한 어노테이션이다. 각 환경에 맞게 Spring의 Bean들을 올릴 수 있어 아주 자주 사용되는 어노테이션이다. 물론 이 어노테이션은 Spring 3.1 부터 생성된 어노테이션이니 그 이하에서는 사용할 수 없다. 물론 xml에서도 사용할 수 있으니 참고하면 되겠다. 어떻게 사용하지는 한번 살펴보자.
    public interface HelloService {
    
      String hello(String name);
    }
    
    
    일단 위와 같은 인터페이스가 있다고 가정하자. 그리고 구현체 두개를 만들어보자.
    public class DefaultHelloService implements HelloService {
    
      @Override
      public String hello(String name) {
        return "hello " + name;
      }
    }
    
    public class WorldHelloService implements HelloService {
      @Override
      public String hello(String name) {
        return "hello world " + name + "!";
      }
    }
    
    각각 다른 일을 한다고 가정해서 만들었다. DefaultHelloService 클래스 경우에는 "hello " + name 를 리턴하고 WorldHelloService 경우에는 "hello world " + name + "!" 위와 같이 리턴한다고 가정하자. 그런데 만약 DefaultHelloService 경우에는 로컬환경에서 개발할 때 사용하고 WorldHelloService 클래스는 개발서버, 운영서버에 사용된다고 해보자. 그럼 매번 개발서버에 배포할 때 DefaultHelloService 클래스를 WorldHelloService 클래스로 변경해서 배포할 수 없는 노릇이다. 만약 그렇게 한다면 실수의 여지도 있고 매번 귀찮은 작업이 된다. 그때 유용한 어노테이션이 바로 @Profile 어노테이션이다. 어떻게 사용하는지 코드로 보자.
    @Configuration
    public class HelloServiceConfig {
    
      @Configuration
      @Profile("default")
      static class DefaultHelloConfig {
        @Bean
        HelloService helloService() {
          return new DefaultHelloService();
        }
      }
    
    
      @Configuration
      @Profile({"dev", "prod"})
      static class DevHelloConfig {
        @Bean
        HelloService helloService() {
          return new WorldHelloService();
        }
      }
    }
    
    
    필자의 경우에는 중첩클래스를 사용했는데 그러지 않고 메서드 위에 @Profile 어노테이션을 줘도 무관하다.
    @Configuration
    public class HelloServiceConfig {
    
      @Bean
      @Profile("default")
      HelloService defaultHelloService() {
        return new DefaultHelloService();
      }
      @Bean
      @Profile({"dev", "prod"})
      HelloService worldHelloService() {
        return new WorldHelloService();
      }
    }
    
    하지만 필자의 경우에는 위와 같이 중첩클래스를 이용해서 사용한다. 필자 생각에는 좀 더 보기가 쉽다. 그리고 혹시나 빈명이 달라서 에러가 날 수도 있으니 아예 다른 클래스로 분리하는게 더 좋아 보인다. 위와 같이 @Profile 어노테이션에는 String 배열이 들어간다. 그래서 다수의 환경을 한번에 설정할 수 있다. 또한 반대의 경우도 가능하다. 만약 dev환경이 아닌 경우에만 빈을 설정한다면 다음과 같이 설정하면 된다.
    @Bean
    @Profile("!dev")
    HelloService defaultHelloService() {
      return new DefaultHelloService();
    }
    
    이렇게 위와 같이 !dev 로 작성한다면 profile이 dev가 아닐때에만 해당 클래스가 빈으로 등록이 된다.

    @ActiveProfiles

    ActiveProfiles 어노테이션은 Test 할때 유용한 어노테이션이다. Test 할 경우 profile을 지정할 수 있는데 그 어노테이션이 바로 @ActiveProfiles 이다. 바로 위에서 했던 그 설정 그대로 테스트를 해보자.
    @RunWith(SpringRunner.class)
    @SpringBootTest
    @ActiveProfiles("dev")
    public class SpringProfilesTests {
    
      @Autowired
      private HelloService helloService;
    
      @Test
      public void profilesTest() {
        assertThat(helloService.hello("wonwoo")).isEqualTo("hello world wonwoo!");
      }
    }
    
    바로 위에서 했던 dev환경의 빈은 아래와 같은 클래스였다.
    public class WorldHelloService implements HelloService {
      @Override
      public String hello(String name) {
        return "hello world " + name + "!";
      }
    }
    
    dev환경으로 테스트를 실행하므로 위의 코드는 테스트를 통과해야 한다. 한번 돌려보도록하자. 그럼 우리가 원하던 초록색불이 들어온다. 이와 같이 테스트할때 특정한 환경을 맞추어야 한다면 우리는 @ActiveProfiles 어노테이션을 이용하면 된다. @ActiveProfiles 어노테이션에는 몇가지 속성이 있는데 resolverinheritProfiles 이다. 나머지는 위에 했던 그 내용이기에 생략한다. resolver 경우에는 자신에 맞게 커스텀하게 구현할 수 있는 기능이다. Spring의 기본적인 구현체는 DefaultActiveProfilesResolver 클래스이다. 만약 DefaultActiveProfilesResolver 를 사용하지 않고 커스텀하게 사용하고 싶다면 다음과 같이 작성하면 된다.
    public class SimpleActiveProfilesResolver implements ActiveProfilesResolver {
    
      @Override
      public String[] resolve(Class<?> testClass) {
        return new String[] {"test"};
      }
    }
    
    ActiveProfilesResolver를 구현하고 @ActiveProfiles 속성 중 resolver에 다음의 클래스를 작성하면 된다.
    @RunWith(SpringRunner.class)
    @SpringBootTest
    @ActiveProfiles(resolver = SimpleActiveProfilesResolver.class)
    public class SpringProfilesTests {
      //...
    }
    
    다음은 inheritProfiles 속성이다. 이 속성은 속성명 그대로 profile을 상속할 것인가 상속하지 않을 것인가를 주는 옵션이다. 기본적으로는 true이며 false로 설정할 경우에는 슈퍼클래스의 profile 적용하지 않는다.
    @ActiveProfiles("dev")
    public class AbstractSpringProfilesTests {
    }
    
    
    @RunWith(SpringRunner.class)
    @SpringBootTest
    @ActiveProfiles(inheritProfiles = false)
    public class SpringProfilesTests extends AbstractSpringProfilesTests {
      // ...
    }
    
    위와 같이 inheritProfiles를 false로 설정했을 경우 dev로 profile이 설정 되지 않는다. 기존과 동일하게 default로 profile이 설정된다.

    @IfProfileValue

    보너스로 @IfProfileValue 어노테이션도 살짝 살펴보자. 이 어노테이션은 어떤 환경에서 테스트를 실행할지 하지 않을지 결정하는 어노테이션이다. 예를 들어 다음과 같다.
    @RunWith(SpringRunner.class)
    @SpringBootTest
    @IfProfileValue(name = "java.vendor", value = "Oracle Corporation")
    public class SpringIfProfileValueTests {
    
      @Test
      public void ifTest() {
        assertThat(1).isEqualTo(2);
      }
    }
    
    위와 같이 설정해서 사용할 경우에 java vendor가 오라클일 경우에만 테스트를 실행시킨다는 것이다. 만약 여러분의 컴퓨터에 java vendor가 오라클이라면 이 테스트를 실패로 돌아갈 것이다. 또하나의 예제는 환경정보를 외부에서 받아와서 해보자.
    @RunWith(SpringRunner.class)
    @SpringBootTest
    @IfProfileValue(name="test-groups", values={"unit", "integration"})
    public class SpringIfProfileValueTests {
    
      @Test
      public void ifTest() {
        assertThat(1).isEqualTo(2);
      }
    }
    
    위와 같이 설정해서 IDEA로 돌릴 경우 테스트는 실행 되지 않는다. 왜냐하면 test-groups란 이름의 환경변수가 없기 때문이다. 메이븐으로 테스트를 실행시킨다면 다음과 같이 작성하면 된다.
    mvn -Dtest-groups=unit test
    
    그러면 test-groups이 unit이기 때문에 위의 테스트는 동작을 해여 실패로 돌아간다. 만약 다음과 같이 작성한다면 테스트를 실행하지 않고 통과가 된다.
    mvn -Dtest-groups=unit1 test
    
    test-groups에 맞는 value가 없기 때문에 위의 테스트는 실행시키지 않느다. values들은 OR로 동작한다. 작성된 values 중 하나만 동일해도 테스트가 동작을 한다. 오늘은 이렇게 @Profile@ActiveProfiles 그리고 보너스로 @IfProfileValue에 대해서 알아봤다. 조금 헷갈려하는 개발자들을 위해서 작성하였다. 해당 소스는 여기에 올라가 있으니 한번씩 이것저것 만져보도록 하자.

    댓글

Designed by Tistory.