Backend/Spring(이론)

스프링 컨테이너 :: IoC - 자바에서 스프링으로의 전환

재성스 2023. 9. 28. 14:30
반응형

참고 - 2023.09.26 - [Spring] - SOLID 원칙 :: 관심사의 분리와 DI컨테이너


앞서 포스팅한 글에서 순수 Java코드로 SOLID 원칙에 따라 객체 간의 관심사를 분리시켜 추상화에만 의존하도록 클라이언트 코드를 설계하고, 애플리케이션 동작을 위해 객체 간의 의존성을 연결해주는 DI컨테이너에 대해 서술해보았다.  
즉, 각 구현체들은 추상화에만 의존하므로 다른 구현체에 대한 존재를 모르고 자신의 로직만을 수행하고, 프로그램의 전반적인 제어 흐름을 DI컨테이너가 담당한다.

이와 같은 흐름으로 프로그램의 제어를 프로그래머가 직접 하는 것이 아니라 외부에 의해서 관리되는 것을 '제어의 역전(IoC)'이라고 말한다. 그렇기 때문에, DI 컨테이너를 'IoC 컨테이너'라고 부르기도 한다. 

순수 자바로 DI와 IoC를 구현하려면 모든 객체의 생성과 의존성 관리 등 직접 처리해야하므로 코드의 양과 복잡성의 문제가 있다. 

이런 문제들을 스프링에서는 자동으로 관리해주기 때문에 개발자가 훨씬 편리하고 유지보수가 쉬운 방식으로 애플리케이션을 보다 더 효율적으로 개발할 수 있다. 


스프링으로의 전환

이전 글에서의 DI컨테이너 AppConfig의 역할을 스프링에서는 '스프링 컨테이너'라는 개념이 그 역할을 대신한다. 즉, 객체의 생성, 관리 및 의존성 주입을 스프링 컨테이너가 자동으로 관리해준다. 


아래 코드를 통해 알아보자.

@Configuration
public class AppConfig {

    @Bean
    public static MemoryClientRepository clientRepository() {
        return new MemoryclientRepository();
    }
    @Bean
    public ClientService clientService(){		
        return new ClientServiceImpl(clientRepository()); 
    }
    @Bean
    public static DiscountPolicy discountPolicy() {		
        return new RateDiscountPolicy();
    }
    @Bean
    public OrderService orderService(){
        return new OrderServiceImpl(clientRepository(), discountPolicy());	
    }
}

이전 코드에서 몇 가지 추가된 에너테이션이 있다. 우선 @Configuration은 Java 기반의 설정 클래스를 선언할 때 사용된다. 즉, 여기서는 AppConfig 클래스를 설정 클래스로 선언하겠다는 것이다. 이 에너테이션은 스프링 컨테이너에게 해당 클래스가 스프링 빈을 포함하고 있다는 것을 알려준다.
또한, 이 에너테이션은 스프링 컨테이너의 싱글톤 패턴과 연관이 있는데, 싱글톤 패턴 관련한 내용은 다음 포스팅 글에서 다룰 예정이다.
@Bean은 이름으로도 유추할 수 있듯이 스프링 빈으로 선언하겠다는 에너테이션이다.

*참고* 스프링 컨테이너, 스프링 빈이란? 스프링 컨테이너는, 객체의 생성, 의존성 주입 등 스프링 애플리케이션을 관리하는 IoC컨테이너이며, 스프링 빈은 스프링 컨테이너에 의해 생성되고 관리되는 객체이다.

자 이제 실행코드에서 스프링 컨테이너의 생성과 스프링 빈을 등록하는 방법에 대해 알아보자.

public class ClientApp{
	public static void main(String[] args){
    // 스프링 컨테이너 선언 (인터페이스)
    ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class); 
    ClientService clientService = applicationContext.getBean("clientService", ClientService.class);
    }
}

코드에서 ApplicationContext가 스프링 컨테이너이며, 인터페이스(역할)이다. 스프링 컨테이너는 XML 기반으로 만들 수도 있고, 에너테이션 기반의 자바 설정 클래스로 만들 수 있다.  AppConfig는 애너테이션 기반의 자바 설정 클래스로 구성됐기 때문에, 스프링 컨테이너의 구현체로 AnnotationConfigApplicationContext를 선택했다.


앞서 @Configuration 에너테이션 관련해서 설명할 때, 이 에너테이션이 붙은 클래스는 설정 클래스로 정의하는 것을 의미하며 스프링 빈을 포함하고 있다는 것을 스프링 컨테이너에게 알린다고 서술했다. 이때, 설정 클래스의 에너테이션을 AnnotationConfigApplicationContext라는 구현체가 클래스를 스캔해서 설정 정보들을(설정 클래스의 구현체들) 스프링 컨테이너의 스프링 빈으로 등록해주는 역할을 한다.
그림을 통해 스프링 컨테이너 생성과 빈의 등록 과정을 좀 더 쉽게 파악해보자

스프링 컨테이너 생성과 빈 등록

 
흐름 

  • ApplicationContext, 즉, 스프링 컨테이너를 생성한다.
  • 스프링 컨테이너 생성 시에는 구성 정보를 지정해주어야 하고, 여기서는 에너테이션 기반인 AnnotationConfigApplicationContext 구현체를 사용하여 파라미터로 AppConfig를 받아 구성 정보를 지정해주었다.
  • 스프링 컨테이너는 파라미터로 넘어온 설정 클래스 정보(AppConfig)를 스캔해서 @Bean 에너테이션이 붙은 객체들을 스프링 빈으로 등록한다. 
  • 등록된 빈의 이름은 메서드 이름을 사용하며, 반환 값이 빈의 객체가 된다.
*참고* @Bean(name= "beanNameEx") 이와 같은 형식으로 빈 이름을 직접 부여할 수도 있으며, 주의할 점은 빈 이름은 고유하게 부여해야 한다. 중복된 빈의 이름이 있는 경우, 오류가 발생하거나 기존 빈의 값을 덮어버리는 불상사가 발생할 수 있다. 따라서, 중복된 빈이 있다면 에너테이션에 빈 이름을 고유하게 부여해주자.

위의 그림처럼 스프링 컨테이너에 설정 클래스 (AppConfig)의 구성이 빈으로 다 등록이 되었다면, 스프링 컨테이너는 설정 클래스를 참고하여, 의존 관계를 주입(DI) 시킨다. 
예) 

의존 관계 주입

*참고* 일반적으로, 스프링 빈의 생성과 의존관계를 주입하는 단계는 나누어진다. 그러나, 이와 같이 자바 코드로 스프링 빈을 등록하면 생성자를 호출하는 동시에 의존 관계 주입도 처리된다. 

빈 탐색

지금까지 스프링 컨테이너의 생성과 빈의 등록 과정에 대해서 그림으로 살펴보았다. 
테스트 코드를 작성하여 빈이 컨테이너에 제대로 등록됐는지 알아보자.


스프링 컨테이너의 모든 빈 출력하기

class ApplicationContextInfoTest{
    ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);

    @Test
    @DisplayName("모든 빈 출력")
    void findAllBean(){
    	String[] beanDefNames = ac.getBeanDefinitionNames(); // 등록된 모든 빈의 이름을 문자열 배열로 반환
        for(String bDN : beanDefNames){
        	Object bean = ac.getBean(bDN);		// 빈의 이름으로 컨테이너에 탐색하여 반환.
            System.out.println("빈의 이름 = "+ bDN + "//// 빈의 객체 = "+bean);
        }
    }
}

출력 결과를 보면, 명시적으로 등록한 AppConfig의 빈이 출력되지만 처음 보는 형식의 빈도 출력되는 것을 알 수 있다. (org.springframework....)
이 빈의 정체는 스프링 내부에서 사용되는 빈이며, 주로 스프링 자체의 동작과 관련된 기능을 수행한다. 하나의 예로, 첫 번째로 보이는 ' internalConfigurationAnnotationProcessor'빈은 @Configuration 에너테이션이 붙은 클래스를 구성 클래스로 등록하는 역할을 한다. 
사용 메서드

  • getBeanDefinitionNames() : 스프링에 등록된 모든 빈 이름을 조회
  • getBean() : 빈 이름으로 빈 객체(인스턴스)를 조회

다음은 명시적으로 등록한 빈만 출력하는 방법이다.
명시적으로 등록한 빈만 출력하기

AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);

@Test
@DisplayName("명시적으로 등록한 빈 출력하기")
void findMyBean(){
	String[] beanDefNames = ac.getBeanDefinitionNames();	// 등록된 모든 빈의 이름을 문자열 배열로 반환
    for(String bDN : beanDefNames){
    	BeanDefinition beanDefinition = ac.getBeanDefinition(bDN);	// 빈 메타 데이터를 반환
        
        if(beanDefinition.getRole()== BeanDefinition.ROLE_APPLICATION){
        Object bean = ac.getBean(bDN);		//빈의 객체 저장
        System.out.println("빈의 이름 = " + bDN + "//// 빈의 객체 = " + bean);
        }
    }
}

 

이 테스트 코드는 스프링 내부에서 사용하는 빈은 제외하고 명시적으로 등록한 빈만 출력해보았다.

*참고* 위 코드에서 보면 이번에 ApplicationContext가 아닌 AnnotationConfigApplicationContext 타입으로 명시한 것은 getBeanDefinition 메서드를 활용하기 위함이다. Spring.io API에 따르면 getBeanDefinition() 메서드는 빈의 메타데이터를 얻기 위한 메서드로, AnnotationConfigApplicationContext가 GenericApplicationContext로 부터 상속 받은 메서드라고 한다. 

출처 - https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/AnnotationConfigApplicationContext.html

코드의 흐름 정리

  1. 컨테이너에 등록된 모든 빈의 이름을 문자열 배열에 저장
  2. 모든 빈을 순회하면서, 빈의 메타 정보를 저장.
  3. 저장된 빈의 메타정보 역할이 명시적으로 등록한 빈의 역할과 같은지 조건 확인
  4. 조건이 true라면, 빈의 객체를 Object 타입으로 저장 후 출력

사용 메서드

  • getBeanDefinitionNames() : 스프링에 등록된 모든 빈 이름을 조회
  • getBeanDefinition() : 빈의 메타데이터를 반환
  • getBean() : 빈 이름으로 빈 객체(인스턴스)를 조회
  • getRole() : 스프링 빈의 역할을 나타내는 정수 값을 반환
*참고* getRole() 추가 설명 - 이 메서드는 빈의 정의된 여러가지 역할 중 어떤 역할인지를 반환해주는 메서드이다.
Spring.io API에 따르면 BeanDefinition은 빈의 메타데이터를 나타내는 인터페이스이며, 이 인터페이스에는 빈의 역할(Role)을 정의하는 필드를 가진다. 
위 코드에서 확인되는 ROLE_APPLICATION (애플리케이션 빈을 나타냄.)이 그 중 하나이다. 이 외에도 ROLE_INFRASTRUCTURE(모든 빈 출력 예제 출력 결과에서 확인한 스프링 내부에서 사용하는 빈의 역할을 표시하는 열거형 상수) 등 같은 역할들이 정의 되어 있다. 

즉, 쉽게 말해 bean의 역할은 ROLE_XXX 중 하나이다.

출처 - https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/beans/factory/config/BeanDefinition.html

핵심 정리 

  • 객체의 생성, 관리 및 의존성 주입을 스프링 컨테이너가 자동으로 관리해준다.
  • 여기서 관리되는 객체를 스프링 빈이라고 말한다.  
  • @Congfiguration 에너테이션은 설정 클래스에 지정하며, 이 에너테이션이 붙은 설정 클래스는 스프링 빈을 포함하고 있다는 것을 스프링 컨테이너에게 알려준다. 
  • @Bean은 스프링 빈으로 선언하겠다는 에너테이션이다. 
  • ApplicationContext는 스프링 컨테이너이다. 
  • AnnotationConfigApplicationContext()는 ApplicationContext의 구현체이며, 파라미터로 설정 클래스(AppConfig)를 받아서 설정 클래스의 에너테이션(@Configuration, @Bean) 정보를 스캔하여 객체를 스프링 컨테이너의 빈으로 등록해주는 역할을 한다. 

 

반응형