Junhyunny's Devlogs

글 검색

기술 글 제목, 본문, 카테고리에서 검색합니다.

    이전 화면으로

    @ConditionalOn- 애너테이션(Annotations)

    • java
    • spring-boot
    이 글의 목차

    1. @ConditionalOn- Annotations

    스프링 빈(spring bean)을 선택적으로 주입받기 위해 @Qualifier, @Primary, @Autowired 애너테이션들을 사용한다. 이는 스프링 컨텍스트에 이미 생성된 스프링 빈들을 대상으로 어떤 빈을 주입 받을지 결정하는 방법이다. 반대로 @ConditionalOn- 애너테이션을 사용하면 스프링 빈을 선택적으로 생성해 스프링 컨텍스트에 등록할 수 있다. @ConditionalOn- 애너테이션이 선언된 경우 해당 조건이 만족되어야 스프링 빈으로 등록된다.

    스프링 프레임워크(spring framework)의 코드를 살펴보면 @ConditionalOn- 애너테이션들이 많이 사용된다. 일반적인 애플리케이션을 개발한다면 사용할 일이 별로 없지만, 프레임워크 기능을 확장한다거나 라이브러리를 만들 때 주로 사용한다. 다음과 같은 용도로 사용된다.

    • 특정 상황 혹은 설정 값이 만족했을 때 필요한 빈을 생성
    • 프레임워크에 필요한 스프링 빈이 별도로 선언되지 않았을 때 디폴트(default) 빈을 생성

    @ConditionalOn- 애너테이션 종류는 매우 많으므로 모두 정리하는 것은 사실 무의미하다. 선택적인 스프링 빈 생성이 필요한 상황일 때 적절한 조건을 만들 수 있는 애너테이션이 있는지 찾아보는 것이 좋다.

    2. Annotation Targets

    @ConditionalOn- 애너테이션을 적용할 수 있는 대상들을 하나씩 알아보겠다. 먼저 자주 사용되는 @ConditionalOnProperty 애너테이션을 예시로 살펴보겠다.

    2.1. Bean Method

    빈을 생성하는 메서드를 대상으로 적용할 수 있다. application.yml 파일에 지정된 설정 값을 기준으로 동작한다.

    • property.foo 설정 값이 enabled인 경우
      • 이름이 “foo.enabled”인 FooService 객체가 빈으로 등록된다.
    • property.foo 설정 값이 disabled인 경우
      • 이름이 “foo.disabled”인 FooService 객체가 빈으로 등록된다.
      • 매칭되는 설정이 없는 경우 해당 객체가 스프링 빈으로 등록된다.
    package action.in.blog.config;
    
    import action.in.blog.service.FooService;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    
    @Configuration
    public class FooConfig {
    
        @ConditionalOnProperty(
                prefix = "property",
                value = "foo",
                havingValue = "enabled"
        )
        @Bean
        public FooService fooService() {
            return new FooService("foo.enabled");
        }
    
        @ConditionalOnProperty(
                prefix = "property",
                value = "foo",
                havingValue = "disabled",
                matchIfMissing = true
        )
        @Bean
        public FooService disabledFooService() {
            return new FooService("foo.disabled");
        }
    }

    2.2. Configuration Class

    스프링 빈을 선언하는 @Configuration 객체에도 적용할 수 있다. 조건 만족 여부에 따라 해당 클래스에 선언된 모든 스프링 빈들이 선택적으로 스프링 컨텍스트에 등록된다.

    • property.ba-components 설정 값이 enabled인 경우 BarService, BazService 객체가 스프링 빈으로 등록된다.
    package action.in.blog.config;
    
    import action.in.blog.service.BarService;
    import action.in.blog.service.BazService;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @ConditionalOnProperty(prefix = "property", name = "ba-components", havingValue = "enabled")
    @Configuration
    public class BaConfig {
    
        @Bean
        public BarService barService() {
            return new BarService();
        }
    
        @Bean
        public BazService bazService() {
            return new BazService();
        }
    }

    2.3. Stereotype Bean

    @Component, @Controller, @RestController, @Service, @Repository 같은 애너테이션이 붙은 스테레오 타입 스프링 빈에도 적용할 수 있다. 이번엔 @ConditionalOnBean, @ConditionalOnMissingBean 애너테이션을 사용한 예제를 살펴보겠다. @ConditionalOnBean 애너테이션을 사용해 BarService 스프링 빈이 등록된 경우 함께 등록한다.

    package action.in.blog.service;
    
    import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
    import org.springframework.stereotype.Service;
    
    @ConditionalOnBean(value = {BarService.class})
    @Service
    public class FooBarService {
    }

    @ConditionalOnMissingBean 애너테이션을 사용해 BarService 스프링 빈이 없는 경우에만 등록한다.

    package action.in.blog.service;
    
    import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
    import org.springframework.stereotype.Service;
    
    @ConditionalOnMissingBean(value = {BarService.class})
    @Service
    public class BarFooService {
    }

    3. Test

    @ConditionalOn- 애너테이션을 적용한 위 예제 코드들이 예상대로 동작하는지 테스트 코드를 살펴보자.

    • BarService
      • property.ba-components 설정 값이 enabled인 경우에 등록
    • BazService
      • property.ba-components 설정 값이 enabled인 경우에 등록
    • BarFooService
      • BarService 스프링 빈이 없는 경우에 등록
    • FooBarService
      • BarService 스프링 빈이 등록된 경우 함께 등록
    • FooService
      • property.foo 설정 값이 enabled인 경우 이름이 “foo.enabled”인 FooService 객체가 빈으로 등록
      • property.foo 설정 값이 disabled인 경우 이름이 “foo.disabled”인 FooService 객체가 빈으로 등록되거나 매칭되는 설정이 없는 경우 해당 객체가 스프링 빈으로 등록

    3.1. DefaultProfileActiveTests Class

    각 객체들이 조건에 맞게 스프링 빈으로 생성되는지 살펴보겠다. 스프링 빈이 조건에 따라 선택적으로 생성되기 때문에 @Autowired 애너테이션의 필수 속성은 false 값으로 지정한다. 디폴트 프로파일을 사용할 때 빈 객체 주입에 관련된 테스트를 살펴보자.

    • property.ba-components 설정 값이 없다.
      • barService 객체가 스프링 빈으로 등록되지 않는다.
      • bazService 객체가 스프링 빈으로 등록되지 않는다.
    • barService 객체가 스프링 빈으로 등록되지 않았다.
      • barFooService 객체는 스프링 빈으로 등록된다.
      • foobarService 객체가 스프링 빈으로 등록되지 않는다.
    • property.foo 설정 값이 없다.
      • fooService 객체가 스프링 빈으로 등록된다.
      • 이름은 “foo.disabled”이다.
    package action.in.blog;
    
    import action.in.blog.service.*;
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    
    import static org.junit.jupiter.api.Assertions.*;
    
    @SpringBootTest
    class DefaultProfileActiveTests {
    
        @Autowired(required = false)
        BarFooService barFooService;
    
        @Autowired(required = false)
        BarService barService;
    
        @Autowired(required = false)
        BazService bazService;
    
        @Autowired(required = false)
        FooBarService foobarService;
    
        @Autowired(required = false)
        FooService fooService;
    
        @Test
        void contextLoads() {
    
            assertNotNull(barFooService);
            assertNull(barService);
            assertNull(bazService);
            assertNull(foobarService);
            assertNotNull(fooService);
            assertEquals("foo.disabled", fooService.getName());
        }
    }

    3.2. BaProfileActiveTests Class

    아래는 ba-components 설정이 enabled인 경우 빈 객체 주입에 관련된 테스트다.

    • property.ba-components 설정 값이 enabled이다.
      • barService 객체가 스프링 빈으로 등록된다.
      • bazService 객체가 스프링 빈으로 등록된다.
    • barService 객체가 스프링 빈으로 등록되었다.
      • barFooService 객체는 스프링 빈으로 등록되지 않는다.
      • foobarService 객체가 스프링 빈으로 등록된다.
    • property.foo 설정 값이 없다.
      • fooService 객체가 스프링 빈으로 등록된다.
      • 이름은 “foo.disabled”이다.
    package action.in.blog;
    
    import action.in.blog.service.*;
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    
    import static org.junit.jupiter.api.Assertions.*;
    
    @SpringBootTest(
            properties = "property.ba-components=enabled"
    )
    class BaProfileActiveTests {
    
        @Autowired(required = false)
        BarFooService barFooService;
    
        @Autowired(required = false)
        BarService barService;
    
        @Autowired(required = false)
        BazService bazService;
    
        @Autowired(required = false)
        FooBarService foobarService;
    
        @Autowired(required = false)
        FooService fooService;
    
        @Test
        void contextLoads() {
    
            assertNull(barFooService);
            assertNotNull(barService);
            assertNotNull(bazService);
            assertNotNull(foobarService);
            assertNotNull(fooService);
            assertEquals("foo.disabled", fooService.getName());
        }
    }

    3.3. FooProfileActiveTests Class

    foo 설정이 enabled일 때 빈 객체 주입에 관련된 테스트 코드를 살펴보자.

    • property.ba-components 설정 값이 없다.
      • barService 객체가 스프링 빈으로 등록되지 않는다.
      • bazService 객체가 스프링 빈으로 등록되지 않는다.
    • barService 객체가 스프링 빈으로 등록되지 않았다.
      • barFooService 객체는 스프링 빈으로 등록된다.
      • foobarService 객체가 스프링 빈으로 등록되지 않는다.
    • property.foo 설정 값이 enabled이다.
      • fooService 객체가 스프링 빈으로 등록된다.
      • 이름은 “foo.enabled”이다.
    package action.in.blog;
    
    import action.in.blog.service.*;
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    
    import static org.junit.jupiter.api.Assertions.*;
    
    @SpringBootTest(
            properties = "property.foo=enabled"
    )
    class FooProfileActiveTests {
    
    
        @Autowired(required = false)
        BarFooService barFooService;
    
        @Autowired(required = false)
        BarService barService;
    
        @Autowired(required = false)
        BazService bazService;
    
        @Autowired(required = false)
        FooBarService foobarService;
    
        @Autowired(required = false)
        FooService fooService;
    
        @Test
        void contextLoads() {
    
            assertNotNull(barFooService);
            assertNull(barService);
            assertNull(bazService);
            assertNull(foobarService);
            assertNotNull(fooService);
            assertEquals("foo.enabled", fooService.getName());
        }
    }

    CLOSING

    책 집필을 위해 스프링 시큐리티 프레임워크 내부를 탐구하면서 적용된 디자인 패턴이나 평소 사용하지 않던 프레임워크 기능들을 마주치면서 정말 많은 공부가 되는 것 같다.

    KEEP READING

    DISCUSSION

    댓글