StatCounter - Free Web Tracker and Counter

스프링 부트 - 자동구성 2

반응형

자동 구성 직접 만들기 - 기반 예제 

자동 구성에 대해서 자세히 알아보기 위해 간단한 예제를 만들어보자. 실시간으로 자바의 메모리 사용량을 웹으로 확인할 수 있는 기능을 제공함.

@Getter
public class Memory {
    private Long used;
    private Long max;

    public Memory(Long used, Long max) {
        this.used = used;
        this.max = max;
    }

    @Override
    public String toString() {
        return "Memory{" +
                "used=" + used +
                ", max=" + max +
                '}';
    }
}


@Slf4j
public class MemoryFinder {

    public Memory get() {
        long max = Runtime.getRuntime().maxMemory();
        long total = Runtime.getRuntime().totalMemory();
        long free = Runtime.getRuntime().freeMemory();
        long used = total - free;
        return new Memory(used, max);
    }

    // 빈으로 등록할 것인데, 잘 등록되었는지 확인하기 위해 로그를 남김
    @PostConstruct
    public void init() {
        log.info("init memory Finder");
    }
}

@Slf4j
@RestController
@RequiredArgsConstructor
public class MemoryController {

    private final MemoryFinder memoryFinder;

    @GetMapping("/memory")
    public Memory system() {
        final Memory memory = memoryFinder.get();
        log.info("memory = {}", memory);
        return memory;
    }
}

예제 코드는 다음과 같이 작성한다. 

  • Memory
  • MemoryFinder
  • MemoryController
@Configuration
public class MemoryConfig {
    
    @Bean
    public MemoryController memoryController() {
        return new MemoryController(memoryFinder());
    }
    
    @Bean
    public MemoryFinder memoryFinder() {
        return new MemoryFinder();
    }
}
  • 현재 Memory 패키지는 분리되어있음. 분리된 Memory 패키지는 분리해서 라이브러리로 사용하기도 편리함. 
  • Memory 패키지에 있는 기능을 가져다 쓰고 싶다면, 스프링 컨테이너에 스프링빈으로 등록해야한다. 

현재의 패키지 구조는 다음과 같다.

패키지 위치

  • 패키지를 memory / hello로 나눈 이유는 두 개가 완전히 별도의 모듈이라는 것을 의미한다. 
  • 결론적으로 hello에서 memory 패키지의 기능을 불러다 사용한다고 이해하면 된다. 

 


@Conditional

  • 앞서 만든 메모리 조회 기능을 항상 사용하는 것이 아니라 특정 조건일 때만 해당 기능이 활성화 되도록 해보자.
  • 개발 서버에서 확인 용도로만 해당 기능을 사용, 운영 서버에서는 해당 기능을 사용하지 않는 것. 
  • 여기서 핵심은 소스코드를 고치지 않고 이런 것이 가능해야 한다는 점이다. (프로젝트를 빌드해서 나온 빌드 파일을 개발 서버에도 배포, 운영서버에도 배포해야함) 
  • 같은 소스 코드인데 특정 상황일 때만 특정 빈들을 등록해서 사용하도록 도와주는 기능이 @Conditional임.
package org.springframework.context.annotation; 

@FunctionalInterface
public interface Condition {
   boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}

@Conditional 기능을 사용하려면 먼저 Condition 인터페이스를 구현해야한다.  Condition 인터페이스는 다음과 같다. 

  • matches() 메서드 : 판단함.
    • true 반환 → 조건 만족으로 판단. 동작함.
    • false 반환 → 조건 만족하지 않음으로 판단. 동작하지 않음. 
  • 매개변수
    • ConditionContext : 스프링 컨테이너, 환경 정보등을 담고 있음.
    • AnnotatedTypeMetadata: 어노테이션 메타 종브를 담고 있음. 

 

구현 목표

$ java -Dmemory=on -jar project.jar

여기서 구현할 컨디셔널 빈등록 목표는 자바 실행 옵션에 memory=on이 들어오는 경우에 스프링 빈을 등록하도록 한다. 

@Slf4j
public class MemoryCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        // -Dmemory=on
        final String memory = context.getEnvironment().getProperty("memory");
        log.info("memory = {}", memory);
        return "on".equals(memory);
    }
}

이것을 위한 Condition 인터페이스를 구현했다. 

@Configuration
@Conditional(MemoryCondition.class)
public class MemoryConfig {
...
}

구현한 Condition 인터페이스를 사용하기 위해서 @Conditional(MemoryCondition.class)를 MemoryConfig에 붙였다. 이것은 다음과 같이 동작한다.

  • MemoryCondition.class가 True를 반환 → MemoryConfig가 설정 정보로 등록됨.
  • MemoryCondition.class가 False를 반환 → MemoryConfig가 설정 정보로 등록되지 않음.

실행해보기

memory.MemoryCondition : memory = null
  • IDE로 실행해보면, 스프링이 시작될 때 다음과 같은 로그가 나온다.
  • MemoryCondition 클래스에서 스프링부트가 시작할 때, matchs() 메서드가 호출된다. 이 때, 실행변수로 주입된 -Dmemory의 값이 on인지 확인하는 절차를 진행할 때 찍힌 로그다.
  • memory = null이면, MemoryCondition은 false를 반환하고 MemoryConfig에 있는 설정 정보는 스프링 컨테이너에 등록되지 않는다. 즉, MemoryController는 자동구성으로 등록되지 않는다. 

  • IDE로 자동 구성을 사용하려면 add VM Option에서 -Dmemory=on을 추가해줘야한다. 

추가하면 스프링이 시작될 때, MemoryCondition의 matchs() 메서드가 평가된다. 

 


@Conditional - 다양한 기능

  • 앞에서는 Condition 인터페이스의 구현체를 직접 구현했다.
  • 스프링은 이미 필요한 대부분의 Condition 구현체를 만들어 두었고, 이것을 가져다 쓰기만 하면 된다. 
@Configuration
// @Conditional(MemoryCondition.class)
@ConditionalOnProperty(name = "memory", havingValue = "on") // 위와 같은 기능임. 구현하지 않아도 됨.
public class MemoryConfig {

    @Bean
    public MemoryController memoryController() {
        return new MemoryController(memoryFinder());
    }

    @Bean
    public MemoryFinder memoryFinder() {
        return new MemoryFinder();
    }
}
  • @ConditionalOnProperty(name = "memory", havingValue = "on")을 추가하자.
    • 앞서 Condition 인터페이스를 직접 구현해서 ConditionOn에 넣어준 부분이 모두 포함된다. 
...
@Conditional(OnPropertyCondition.class) // OnPropertyCondition 클래스 바라봄.
public @interface ConditionalOnProperty {
...
}

@Order(Ordered.HIGHEST_PRECEDENCE + 40) // OnPropertyCondition은 SpringBootCondition과 관련
class OnPropertyCondition extends SpringBootCondition {
...
}

// SpringBootCondition은 Condition 인터페이스 구현.
public abstract class SpringBootCondition implements Condition {

위의 코드의 동작을 살펴보기 위해서 @ConditionalOnProperty 어노테이션을 살펴보면 @ConditionOn + Condition 인터페이스 구현체의 조합으로 동작하는 것을 볼 수 있다. 


@ConditionalOnXxx

스프링은 @Conditional과 관련해서 개발자가 편리하게 사용할 수 있도록 많은 @ConditionalOnXxx를 제공함.

  • @ConditionalOnClass, @ConditionalOnMissingClass : 클래스가 있는 경우 동작함. 나머지는 반대.
  • @ConditionalOnBean, @ConditionalOnMissingBean : 빈이 등록되어 있는 경우 동작함. 나머지는 그 반대
  • @ConditionalOnProperty : 환경정보가 있는 경우 동작함.
  • @ConditionalOnResource : 리소스가 있는 경우 동작함.
  • @ConditionalOnWebApplication, @ConditionalOnNotWebApplication : 웹 어플리케이션인 경우 동작함.
  • @ConditionalOnExpression : SpEL 표현식에 만족하는 경우 동작함. 

@ConditionalOnXxx는 주로 스프링부트의 자동 구성에 사용되고 있다. 


참고

@Conditional 자체는 스프링부트가 아니라 스프링 프레임워크가 제공하는 기능이다. 

댓글

Designed by JB FACTORY