Spring/Spring 김영한

[Spring 기본편] (2) Spring 컨테이너의 필요성 - 스프링 컨테이너란, Bean의 관리

민돌v 2022. 5. 14. 22:22
728x90

이번에는 스프링 컨테이너의 역할과 아래의 키워드에 대해서 공부한다..!

  • 기능과 구현의 분리
  • 사용과 구성의 분리

 

 


JAVA DIP, OCP를 위해서

객체지향의 다형성을 지키기위해, 기능(Interface)와 구현(구현체 class)를 분리해서 설계하여 클라이언트가 기능에 의존하고 있다고 생각해도

순수 자바코드로는, 기능이 변경될 떄 클라이언트 코드도 변경이 되고 만다.

(사실상, 클라이언트는 기능과 구현 둘 다에 의존)

 

(예시)

public class OrderServiceImpl implements OrderService {
// private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
 private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
}

 

문제점

1. 우리는 역할과 구현을 충실하게 분리했다. OK

2. 다형성도 활용하고, 인터페이스와 구현 객체를 분리했다. OK

3. OCP, DIP 같은 객체지향 설계 원칙을 충실히 준수했다

  • -> 그렇게 보이지만 사실은 아니다.

4. DIP: 주문서비스 클라이언트( OrderServiceImpl )는 DiscountPolicy 인터페이스에 의존하면서 DIP를 지킨 것 같은데?

  • 클래스 의존관계를 분석해 보자. 추상(인터페이스) 뿐만 아니라 구체(구현) 클래스에도 의존하고 있다.
    • 추상(인터페이스) 의존: DiscountPolicy
    • 구체(구현) 클래스: FixDiscountPolicy , RateDiscountPolicy

5. OCP: 변경하지 않고 확장할 수 있다고 했는데!

  • 지금 코드는 기능을 확장해서 변경하면, 클라이언트 코드에 영향을 준다! 따라서 OCP를 위반한다.

 

 

문제의 해결

OCP와 DIP를 지키기 위해서는, 사용영역과 구성영역을 분리해야한다...!

즉, 클라이언트는 직접적으로 객체를 선택하지 않으며

이를, 구성영역에서 클라이언트가 사용하는 객체를 구성한다. (외부에서)

 

➡️ 이것이 바로 DI(의존관계 주입) : 외부에서 내부 객체가 무엇인지 주입해준다.

 

Appconfig

public class AppConfig {
     public MemberService memberService() {
         return new MemberServiceImpl(memberRepository());
     }
     public OrderService orderService() {
         return new OrderServiceImpl(
             memberRepository(),
             discountPolicy());
         }
     public MemberRepository memberRepository() {
    	 return new MemoryMemberRepository();
     }
     public DiscountPolicy discountPolicy() {
        // return new FixDiscountPolicy();
         return new RateDiscountPolicy();
     }
}

OrderServiceImpl - Repository 입장에서의 클라이언트

public class OrderServiceImpl implements OrderService {
     private final MemberRepository memberRepository;
     private final DiscountPolicy discountPolicy;
     
     public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
         this.memberRepository = memberRepository;
         this.discountPolicy = discountPolicy;
     }
 }

 

-> 정률 할인 정책이든, 고정 할인 정책이든 "클라인트의 코드는 변경되지 않지만, 기능의 확장은 자유롭게 이루어진다."

-> OCP, DIP 지킴...!

  • 코드의 변경에는 닫혀있고, 확장에는 열려있다
  • 클라언트는, 구현체는 전혀 의존하지않고(어떤 객체를 선택하는지 모름) / 기능에만 의존한다.

 


 

IoC, DI, 그리고 컨테이너

강의에서는 AppConfig라는 설정 파일을 만드는 과정도 진행하지만, 이건 건너뛰고, 스프링에서 이를 어떻게 관리해주는 알아보자.

 

요약하자면, AppConfig라는, 우리의 어플리케이션에 객체들에 어떤 객체를 선택해줄건지 구성해주는 파일을

스프링에서는 IOC컨테이너 or DI 컨테이너라고 한다.

 

 

 

제어의 역전 IoC(Inversion of Control)

  • 기존 프로그램는 클라이언트 구현 객체가 스스로 필요한 서버 구현 객체를 생성하고, 연결, 실행한다.
    • 개발자가 직접 작성한 구현 객체가 프로그램의 제어 흐름을 조정한다.
  • 반면, AppConfig가 등장한 이후에는 구현 객체는 자신의 로직을 "실행하기만" 하고 프로그램의 제어 흐름은 "AppConfig"가 가져간다.
  • 제어의 역전이란, 이렇든 프로그램의 제어 흐름을 직접 제어하는 것이 아니라 외부에서 관리하는 것을 말한다.
  • 외부의 객체에서 내부객체가 어떤 객체를 의존할지 결정하는 것이다.

 

 

프레임워크 vs 라이브러리

1. 프레임워크 : 프레임워크가 내가 작성한 코드를 제어하고, 대신 실행하면 그것은 프레임워크가 맞다. (JUnit)

2. 라이브러리 : 반면에 내가 작성한 코드가 직접 제어의 흐름을 담당한다면 그것은 프레임워크가 아니라 라이브러리다

 

 

의존관계 주입 DI(Dependency Injection)

  • 위에서, OrderServiceImpl 클래스는 인터페이스에 의존한다.
  • 어떤 구현객체가 내부에서 적용되는지 알지못하고, 로직 실행코드만을 실행한다.
  • 의존관계는 정적인 클래스 의존관계와, 실행 시점에 결정되는 동적인 객체(인스턴스) 의존 과계가 있다.
  • 정적인 클래스 의존관계 : Import코드만 보고 의존관계를 판단할 수 있고, 클래스 의존관계가 눈에 보이는 것
  • 동적인 객체 의존 관계 : 실행시점(런타임)시 외부에서 실제 구현 객체를 생성하고 클라이언트에게 전달하는 것
    • 의존관계 주입을 사용하면 정적인 클래스 의존관계를 변경하지 않고, 동적인 객체 인스턴스 의존관계를 쉽게 변경할 수 있다.

 

 

 


 

스프링으로 전환하기 (스프링 컨테이너 이용하기)

 

AppConfig를 스프링에서 관리하기

Bean에 등록하기

  • AppConfig에 설정을 구성한다는 뜻의 @Configuration 을 붙여준다.
  • 각 메서드에 @Bean 을 붙여준다. 이렇게 하면 스프링 컨테이너에 스프링 빈으로 등록한다.
@Configuration
public class AppConfig {
    //생성자로 레포지토리를 결정
    @Bean
    public MemberService memberService() {
        return new MemberServiceImpl(memberRepository());
    }

    @Bean
    public OrderService orderService() {
        return new OrderServiceImpl(
                memberRepository(),
                discountPolicy()
        );
    }

    @Bean
    public MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }
    @Bean
    public DiscountPolicy discountPolicy() {
        return new RateDiscountPolicy();
    }
}

 

 

Bean의 사용(스프링 컨테이너 사용하기)

  • applicationContext는 스프링 컨테이너 인터페이스 객체이다.
  • 스프링 컨테이너를 사용하여, 관리되는 Bean들을 불로올 수 있다. -> AppConfig의 역할을 스프링 컨테이너가 대신함
public class OrderApp {
    public static void main(String[] args) {
        // AppConfig appConfig = new AppConfig();
        // MemberService memberService = appConfig.memberService();
        // OrderService orderService = appConfig.orderService();
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
        MemberService memberService = applicationContext.getBean("memberService", MemberService.class);

        OrderService orderService = applicationContext.getBean("orderService", OrderService.class);

        long memberId = 1L;
        Member member = new Member(memberId, "memberA", Grade.VIP);
        memberService.join(member);

        Order order = orderService.createOrder(memberId, "itemA", 10000);
        System.out.println("order = " + order);
    }
}

 


스프링 컨테이너와 스프링 빈

 

스프링 컨테이너 생성

스프링 컨테이너가 생성되는 과정

//스프링 컨테이너 생성
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);

 

  • ApplicationContext 를 스프링 컨테이너라 하며, 인터페이스 이다.
  • AnnotationConfigApplicationContext(AppConfig.class) 가 인터페이스의 구현체이다.

 

 

스프링 컨테이너 생성과정

  1. new AnnotationConfigApplicationContext(AppConfig.class) 를하면, 스프링 컨테이너 생성되어, 빈을 저장할 저장소를 만든다.
  2. AppConfig,Class는, 스프링 컨테이너의 구성정보로, 생성할 때 지정해주어야한다.
  3. 스프링 컨테이너는 파라미터로 넘어온 설정 클래스 정보를 사용해서 스프링 빈을 등록한다.

스프링 컨테이너 빈 저장소 생성
스프링 빈 등록

 

빈 이름

  • 빈 이름은 메서드 이름을 사용한다.
  • 빈 이름을 직접 부여할 수 도 있다. (@Bean(name="memberService2"))
  • 빈 이름은 항상 다른 이름을 부여해야 한다. 같은 이름을 부여하면, 다른 빈이 무시되거나, 기존 빈을 덮어버리거나 설정에 따라 오류가 발생한다.

 

스프링 빈 의존관계 설정

  1. 스프링 컨테이너에 빈이 모두 등록이 되면
  2. 스프링 컨테이너는 설정정보를 참고해서 의존관계를 주입(DI) 한다.

-> 순서가 나누어져 있음 (싱글톤 패턴)

 

 


BeanFactory와 ApplicationContext

beanFactory VS ApplicationContext 뭐가 다를까

 

스프링 공부를 하면서 구글링을 하다보면, 스프링 컨테이너의 이름이 다양함을 느낀다.

빈팩토리, 어플리케이션 컨텍스트, IOC컨테이너,,, 등등

 

먼저 BeanFactory 와 ApplicationContext의 차이점에 대해 알아보자

 

 

BeanFactory

  • BeanFactory란, 스프링 컨테이너의 최상위 인터페이스이다.
  • 스프링 빈을 관리하고 조회하는 역할을 담당한다, (getBean, getBeansOfType 등등의 기능을 가지고 있음)
  • Bean 직접적으로 조회하고, 관리하는 메소드는 대부분 BeanFactory가 제공하는 기능이다.

 

ApplicationContext

  • BeanFactory 기능을 모두 상속받아서 제공한다.
  • 애플리케이션을 개발할 때는 빈은 관리하고 조회하는 기능은 물론이고, 수 많은 부가기능이 필요하다.
  • 이 부가기능들을 ApplicationContext에서 상속받아 사용한다.
  • ApplicationContext는 빈 관리기능 + 편리한 부가 기능을 제공한다.
  • BeanFactory를 직접 사용할 일은 거의 없다. 부가기능이 포함된 ApplicationContext를 사용한다.
  • BeanFactory나 ApplicationContext를 스프링 컨테이너라 한다.

 

ApplicatonContext가 제공하는 부가기능

|부가기능|

  1. 메시지소스를 활용한 국제화 기능
    • 예를 들어서 한국에서 들어오면 한국어로, 영어권에서 들어오면 영어로 출력
  2. 환경변수
    • 로컬, 개발, 운영등을 구분해서 처리
  3. 애플리케이션 이벤트
    • 이벤트를 발행하고 구독하는 모델을 편리하게 지원
  4. 편리한 리소스 조회
    • 파일, 클래스패스, 외부 등에서 리소스를 편리하게 조회

 

 

 

 

 

반응형