본문 바로가기
Spring/스프링 핵심 원리 - 기본편

3-2. 스프링 핵심 원리 이해2 - 객체 지향 원리 이용(스프링 컨테이너) + 프레임워크 vs 라이브러리

by hk27 2022. 1. 3.
스프링은 AppConfig를 어떻게 지원할까요?

 

안녕하세요.

오늘은 '섹션 3. 스프링 핵심 원리 이해 2 - 객체 지향 원리 이용' 중 후반부를 리뷰하겠습니다. 

전반부에서 SRP, OCP, DIP를 지키기 위해 AppConfig을 활용하는 것을 살펴보았습니다. 

이러한 AppConfig의 역할을 해주는 것이 스프링 컨테이너입니다. 

오늘의 핵심은 스프링 컨테이너가 무엇인지 이해하는 것입니다. 

 

IoC, DI, 컨테이너

먼저 IoC, DI, 컨테이너가 무엇인지 알아보겠습니다.

 

IoC(Inversion of Control): 제어의 역전

IoC는 객체의 생성부터 소멸까지 객체의 모든 생명주기를 개발자가 아닌 컨테이너가 담당하는 것을 의미합니다[2]. 

원래 개발자가 해왔던 일을 컨테이너라는 객체 관리 프로그램이 알아서 해준다는 것입니다.

무슨 말인지 더 자세히 알아봅시다.

AppConfig 도입 이전 코드는 구현 객체가 스스로 필요한 객체를 생성하고, 연결하고, 실행했습니다. 한마디로 구현 객체가 프로그램의 제어 흐름을 스스로 조종했습니다. 개발자 입장에서는 자연스러운 흐름입니다.

그런데 AppConfig 도입 이후, 구현 객체는 자신의 로직 수행만을 담당하고 프로그램 제어 흐름은 AppConfig가 갖습니다. AppConfig가 Impl 객체도 생성하고, 연결되는 객체를 선택하여 의존관계를 주입합니다. 

이렇듯 프로그램이 제어 흐름을 직접 제어하는 것이 아니라, 외부에서 관리하는 것이 제어의 역전(IoC) 입니다. 

 

DI(Dependency Injection): 의존관계 주입

의존 관계 주입은 애플리케이션 실행 시점(런타임)에 외부에서 실제 구현 객체를 생성하고 클라이언트에 전달해서 클라이언트와 서버의 실제 의존관계가 연결되는 것을 의미합니다.

참고로 클라이언트와 서버라는 단어가 종종 등장하는데, 클라이언트는 호출하는, 서버는 호출되는 것입니다(클라이언트=호출, 서버=응답). 예를 들어서 코드의 관점에서 호출되는 코드가 서버 코드, 이 코드를 호출하는 모든 코드(객체, 클래스)가 클라이언트 코드이고, 클래스의 관점에서  A객체가 B객체의 메서드를 호출하면 A클래스가 클라이언트, B클래스가 서버입니다[3].  

DI를 이해하기 위해서, 먼저 정적인 클래스 의존관계동적인 객체 인스턴스 의존 관계를 알아봅시다.

정적(static)이란 한번 정해놓으면 변하지 않고 계속 유지되는 성질을 말하며, 동적(dynamic)이란 상황에 따라서 실시간으로 변하는 성질을 말합니다[4]. 

정적인 클래스 의존관계는 코드만 보면 실행하지 않아도 알 수 있는 의존관계입니다. 아래 클래스 다이어그램을 보면, OrderServiceImpl이 MemberRepository와 DiscountPolicy 인터페이스에 의존하는 것이 정적인 클래스 의존관계입니다.

동적인 객체 인스턴스 의존 관계는 애플리케이션 실행 시점에 실제 생성된 객체 인스턴스의 참조가 연결된 의존관계입니다. 아래 객체 다이어그램이 동적인 의존관계를 나타내줍니다.

 

앞서 본 의존관계 주입의 개념을 다시 봅시다. 

의존 관계 주입은 애플리케이션 실행 시점(런타임)에 외부에서 실제 구현 객체를 생성하고 클라이언트에 전달해서 클라이언트와 서버의 실제 의존관계가 연결되는 것을 의미합니다.

AppConfig가 회원 저장소/할인 정책 객체 인스턴스를 생성해서, 참조 값을 주문 서비스 구현체에 전달해서 연결합니다. (자바 코드이니 당연히 참조 값으로 객체 인스턴스가 연결될 것입니다!) 이것이 DI입니다.

 

컨테이너

컨테이너는 저장소이죠. 스프링에서 컨테이너는 인스턴스의 생명주기를 관리하며, 생성된 인스턴스들에 추가적인 기능을 제공하도록 하는 것을 의미하고, 객체의 생성과 소멸을 컨트롤합니다[5].

특히 AppConfig 처럼 객체를 생성하고 관리하면서 의존관계를 연결해 주는 것을 IoC 컨테이너, DI 컨테이너라고 부릅니다. 스프링은 이러한 DI 컨테이너 역할을 제공합니다. 

 

스프링 컨테이너

스프링 컨테이너는 자바 객체의 생명 주기를 관리하며, 생성된 자바 객체들에 추가적인 기능을 제공하는 역할을 하는 컨테이너입니다[6]. 여기서 말하는 자바 객체를 스프링에서는 빈(Bean)이라고 부릅니다. 

이해를 위해서 예를 살펴봅시다. 

 

AppConfig 스프링 기반으로 변경

먼저 앞서 만든 AppConfig를 스프링 기반으로 변경해봅시다. 

클래스 정의 윗줄에 @Configuration을 추가하고, 각 함수 위에 @Bean을 추가합니다. 

@Configuration
public class AppConfig {
	@Bean
	public MemberService memberService() {
		return new MemberServiceImpl(memberRepository());
	}
  	@Bean  
	public OrderService orderService() {
		return new OrderServiceImpl(memberRepository(),discountPolicy());
	}
    ...
}

@Configuration이 있으면 스프링은 구성정보 클래스임을 인지하고, 메소드 위에 @Bean이 있으면 스프링 컨테이너에 스프링 빈으로 등록됩니다.

 

MemberApp에 스프링 컨테이너 적용

AppConfig 를 활용해 객체를 생성했던 MemberApp을 수정합니다.

public class MemberApp {
	public static void main(String[] args) {
		// AppConfig appConfig = new AppConfig();
		// MemberService memberService = appConfig.memberService();
		ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
		MemberService memberService = applicationContext.getBean("memberService", MemberService.class);
       	 	Member member = new Member(1L, "memberA", Grade.VIP);
		memberService.join(member);
	}
}

5번째 줄의 ApplicationContext가 스프링 컨테이너입니다. 'Application Context'라는 단어에서 전체 애플리케이션의 문맥을 담고 있다는 뜻을 읽을 수 있습니다. 

이전에는 AppConfig를 이용해 개발자가 직접 객체를 생성하고 DI를 했지만, 이제부터는 스프링 컨테이너를 사용합니다.

스프링 컨테이너는 @Configuration이 붙은 파일을 구성 정보로 사용하고, @Bean이 붙은 메서드를 호출해 반환된 객체를 스프링 컨테이너에 스프링 빈으로 등록합니다. 

스프링 빈은 applicationContext.getBean() 메서드로 찾을 수 있습니다.

즉 이제는 스프링 컨테이너에 객체를 스프링 빈으로 등록하고, 스프링 컨테이너에서 스프링 빈을 찾아서 사용하도록 변경되었습니다. 다음 시간에는 스프링 컨테이너 이용의 여러 장점과 활용 방법을 알아볼 것입니다.

 

Further Study

프레임워크 vs 라이브러리

 

이번 시간의 초반부에서 IoC(제어의 역전)를 배웠습니다. IoC는 프레임워크와 라이브러리를 구분하는 중요한 기준이 됩니다. 

https://passionate.tistory.com/3에서 스프링은 '객체 지향 애플리케이션을 개발할 수 있게 도와주는 프레임워크'이며 프레임워크는 '소프트웨어의 구체적인 부분에 해당하는 설계와 구현을 재사용이 가능하게끔 일련의 협업화된 형태로 클래스들을 제공하는 것'이라고 소개해 드렸습니다.

그렇다면 프레임워크와 라이브러리의 차이는 무엇일까요? 위의 정의 만으로는 둘의 관계를 확인할 수 없습니다. 

프레임워크와 라이브러리의 차이점은 애플리케이션 흐름의 주도권에 있습니다[7]. 

프레임워크는 IoC를 지원한다는 중요한 특징이 있습니다. IoC는 프로그램이 제어 흐름을 직접 제어하는 것이 아니라, 외부에서 관리하는 것입니다. 즉 프레임워크가 제가 작성한 코드를 제어하고, 대신 실행합니다. 반면에 제가 작성한 코드가 직접 제어의 흐름을 담당한다면 그것은 프레임워크가 아니라 라이브러리입니다. 

 

 

인프런  '스프링 핵심 원리 - 기본편' 강의를 듣고 공부하며 정리한 자료입니다. 

잘못된 부분은 피드백 주시면 감사하겠습니다. 

글 읽어주셔서 감사합니다 :-)

 

참고자료
[1] 스프링 핵심 원리 - 기본편, 섹션 3. 스프링 핵심 원리 이해2 - 객체 지향 원리 적용

https://www.inflearn.com/course/스프링-핵심-원리-기본편

[2] https://codevang.tistory.com/241

[3] https://www.inflearn.com/questions/117662

[4] https://mamu2830.blogspot.com/2021/09/what-is-difference-between-static-and-dynamic.html

[5] https://jobjava00.github.io/language/java/framework/spring/container/

[6] https://steady-coding.tistory.com/459

[7] https://velog.io/@tjdud0123/API-vs-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-vs-%ED%94%84%EB%A0%88%EC%9E%84%EC%9B%8C%ED%81%AC

 

 

댓글