Spring Container 학습노트: ApplicationContext·Bean 조회·BeanDefinition

반응형

2022년 4월 21일에 공부한 Spring 기본 원리 중 container와 bean 부분을 다시 정리했다. 핵심은 ApplicationContext가 설정 metadata를 읽어 object를 생성하고 연결하며, application code는 concrete object 생성보다 필요한 dependency를 받는 데 집중하게 만드는 것이다.

Java Configuration으로 Container 만들기

@Configuration은 bean definition의 source가 되는 configuration class를 표시하고, @Bean method는 container가 관리할 object를 정의한다. 기본 bean name은 method name이며 직접 이름을 줄 수도 있다.

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

interface DiscountPolicy {
    int discount(int price);
}

final class RateDiscountPolicy implements DiscountPolicy {
    public int discount(int price) {
        return price / 10;
    }
}

final class OrderService {
    private final DiscountPolicy discountPolicy;

    OrderService(DiscountPolicy discountPolicy) {
        this.discountPolicy = discountPolicy;
    }

    int finalPrice(int price) {
        return price - discountPolicy.discount(price);
    }
}

@Configuration
class AppConfig {
    @Bean
    DiscountPolicy discountPolicy() {
        return new RateDiscountPolicy();
    }

    @Bean
    OrderService orderService(DiscountPolicy discountPolicy) {
        return new OrderService(discountPolicy);
    }
}

public class Main {
    public static void main(String[] args) {
        try (var context =
                 new AnnotationConfigApplicationContext(AppConfig.class)) {
            var service = context.getBean(OrderService.class);
            System.out.println(service.finalPrice(10_000));
        }
    }
}

@Bean method parameter로 dependency를 받으면 object graph가 분명해진다. 같은 configuration class의 다른 @Bean method를 직접 호출하는 방식도 볼 수 있지만, @Configuration(proxyBeanMethods = false) 같은 설정에서는 그 호출이 평범한 Java method call이 될 수 있다. method parameter injection을 쓰면 proxy 방식에 덜 의존한다.

Bean 이름과 중복

bean name은 container 안에서 식별자다.

@Bean("pricingPolicy")
DiscountPolicy discountPolicy() {
    return new RateDiscountPolicy();
}

같은 이름을 여러 곳에서 정의했을 때 조용히 덮어쓸 것이라고 기대하면 안 된다. 허용 여부는 context 구성과 Spring Boot의 bean overriding 설정에 따라 달라질 수 있다. 이름을 의도적으로 유일하게 만들고, 교체가 필요하면 profile·conditional configuration·명시적 test configuration으로 드러내는 편이 안전하다.

Bean을 조회할 때 생기는 세 가지 경우

직접 조회는 container를 학습하거나 diagnostic할 때 유용하다.

DiscountPolicy byName =
    context.getBean("discountPolicy", DiscountPolicy.class);

DiscountPolicy byType =
    context.getBean(DiscountPolicy.class);
  • 조건에 맞는 bean이 없으면 NoSuchBeanDefinitionException
  • type으로 찾았는데 후보가 둘 이상이면 NoUniqueBeanDefinitionException
  • getBeansOfType(DiscountPolicy.class)은 해당 type의 bean map을 반환

application logic 곳곳에서 getBean()을 호출하면 code가 container API에 결합된다. 일반 application component는 constructor injection으로 dependency를 받는 것이 기본이고, direct lookup은 framework integration이나 diagnostic처럼 이유가 분명한 곳에 제한한다.

상위 type으로 조회하면 assignable한 하위 implementation도 후보가 된다. Object.class처럼 너무 넓은 type으로 조회하면 application bean뿐 아니라 infrastructure bean까지 함께 볼 수 있다.

ApplicationContext와 BeanFactory의 관계

BeanFactory 계열 interface는 bean 생성·조회와 lifecycle의 기반을 제공한다. ApplicationContext는 이 기능에 더해 application에서 흔히 필요한 다음 contract를 함께 제공한다.

  • message와 internationalization
  • event publication
  • environment와 property source
  • resource pattern loading
  • 계층형 context와 여러 bean 조회

그래서 일반 Spring application에서는 ApplicationContext를 사용한다. 그렇다고 모든 부가 기능이 magic처럼 자동 동작한다는 뜻은 아니다. 필요한 implementation과 configuration이 있어야 하며, Spring Boot의 auto-configuration과 Spring Framework container 자체도 구분해야 한다.

Application Bean만 살펴보기

bean definition name과 role을 사용하면 user-defined bean과 infrastructure bean을 구분해 볼 수 있다.

import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

for (String name : context.getBeanDefinitionNames()) {
    BeanDefinition definition = context.getBeanDefinition(name);
    if (definition.getRole() == BeanDefinition.ROLE_APPLICATION) {
        System.out.println(name);
    }
}

ROLE_APPLICATION은 user-defined application bean의 일반적인 역할 표시다. 하지만 role만으로 security나 ownership boundary를 보장하지는 않는다. diagnostic view로 사용한다.

BeanDefinition은 무엇을 추상화하나

BeanDefinition은 container가 bean을 만들고 구성하기 위한 metadata다. configuration source에 따라 다음과 같은 정보가 들어갈 수 있다.

  • bean class 또는 factory bean·factory method
  • constructor argument와 property value
  • scope
  • lazy initialization 여부
  • initialization·destruction callback
  • dependency와 role metadata

Java configuration, component scanning, XML 같은 서로 다른 source가 내부적으로 bean definition을 등록하므로 container는 공통 model로 처리할 수 있다. 다만 @Bean factory method를 사용하면 direct bean class 대신 factory metadata가 중심이 될 수 있어 모든 field가 항상 채워진다고 가정하면 안 된다.

XML configuration도 여전히 지원된다. 다만 file만 바꾸면 실행 중인 context가 자동 갱신된다는 뜻은 아니다. 보통 configuration 변경을 반영하려면 context reload나 application restart가 필요하다.

다시 정리한 핵심

  • @Configuration@Bean은 Java code로 bean definition을 제공한다.
  • bean name과 type lookup은 후보가 없거나 여러 개일 때 각각 다른 오류를 낸다.
  • application component는 direct lookup보다 constructor injection을 우선한다.
  • ApplicationContext는 bean 관리 외에 event·message·environment·resource contract를 제공한다.
  • BeanDefinition은 설정 형식과 container 내부 처리를 잇는 metadata다.

Spring application 시작 단계와 성능 관점은 Spring 애플리케이션 부팅 과정, HTTP 계층과 MVC 흐름은 Spring MVC와 HTTP 기본에서 이어서 볼 수 있다.

참고 자료

반응형
KEEP READING
카테고리 전체 보기 →

댓글