Spring Framework

DI - 스프링 컨테이너(Spring Container), 빈(bean)

ChaeHing 2023. 4. 5. 00:03

DI(Dependency Injection)

  • 의존성 주입
  • 생성자를 통한 의존관계 주입이 스프링에서 공식적으로 추천하는 방법

 

스프링 컨테이너(Spring Container)

  • 스프링 컨테이너는 스프링 프레임워크의 핵심 컴포넌트
    • 컴포넌트 : 재사용 가능한 웹의 구성요소(각각의 독립된 모듈)
  • 자바 객체(Bean)의 생명 주기를 관리하며, 생성된 자바 객체들에게 추가적인 기능을 제공
  • Bean 생성, 관리, 제거 등의 역할 (생명주기)
  • 애플리케이션 컨텍스트라고도 한다.

 

빈(bean)

  • 인스턴스화된 객체를 의미
  • 스프링은 스프링 컨테이너를 통해 객체를 관리, 스프링 컨테이너에서 관리되는 객체를 빈(Bean)
  • bean은 애플리케이션에서 사용하는 객체, 설정 정보와 함께 스프링 컨테이너에 등록된 객체(인스턴스화된 객체)를 의미

 

BeanDefinition

  • 빈(bean) 설정 메타정보
  • @Bean 당 각 1개씩 메타 정보가 생성
  • 다양한 형태의 설정 정보를 BeanDefinition으로 추상화
  • 스프링 컨테이너는 Configuration Metadata를 사용
  • 스프링 컨테이너는 파라미터로 넘어온 설정 클래스 정보를 사용해서 스프링 빈을 등록
  • new AnnotationConfigApplicationContext(구성정보.class)로 스프링에 있는 @Bean의 메서드를 등록
static AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(DenpendencyConfig.class)

 

스프링 컨테이너의 종류

  • BeanFactory
    • 스프링 컨테이너의 최상위 인터페이스
    • 빈을 등록하고 생성하고 조회하고 돌려주는 등 빈을 관리
  • ApplicationContext
    • BeanFactory의 기능을 상속
    • BeaFactory 기능 + 그외 부가기능 제공
      • MessageSource: 메세지 다국화를 위한 인터페이스
      • EnvironmentCapable: 개발, 운영 등 환경변수 등으로 나눠 처리하고, 애플리케이션 구동 시 필요한 정보들을 관리하기 위한 인터페이스
      • ApplicationEventPublisher: 이벤트 관련 기능을 제공하는 인터페이스
      • ResourceLoader: 파일, 클래스 패스, 외부 등 리소스를 편리하게 조회

 

BeanFactory와 ApplicationContext의 차이

 

BeanFactory

  • Lazy(게으른) Loading
  • 빈을 실질적으로 사용할때만 로딩 하기 때문에 가벼운 경량 컨테이너 -> 메모리를 적게 차지 한다 (효율성)
  • 빈을 사용할때 오류 유무를 확인 (미리 확인 불가)

ApplicationContext

  • Eager Loading
  • 런타임 실행 시 모든 빈을 로딩 -> 바로 사용안하는 빈들도 로딩하기때문에 메모리를 많이 차지
  • 문제가 있는 bean 객체가 있을때 오류의 유무를 미리 확인 (컴파일 단계에서)
  • 컨테이너는 런타임 실행 시점에 등록된 모든 빈을 로딩해야하기 때문에 부담이 크다.
  • 모든 빈을 생성하기 때문에 런타임이 오래걸린다.
    • 간단한 테스트를 해야하는데에도 런타임 시간이 오래걸려 사용이 불편해진다.
    • 이러한 문제를 해결하기위해 Lazy Loading을 사용할 수도 있다. -> BeanFactory를 상속받았기 때문에

 

 

 

싱글톤(singleton) 스코프

  • 클래스의 인스턴스가 딱 1개만 생성되는 것을 보장하는 디자인 패턴
  • 스프링 컨테이너에서 빈 스코프의 기본값은 싱글톤 스코프이다.
  • 여러 클라이언트가 하나의 객체 인스턴스를 공유하기 때문에 싱글톤 객체는 무상태로 설계
  • 싱글톤 사용시 read only를 의미한다.
public class SingletonService {

	// 객체를 static으로 딱 1개만 생성
    private static final SingletonService instance = new SingletonService();

	// 객체 인스턴스 필요시 해당 public static 메서드를 통해 조회만 가능
    public static SingletonService getInstance(){
        return instance;
    }

   // 생성자를 private로 만들어 new를 통해 객체 생성을 막는다.
    private SingletonService() {}

}

싱글톤 컨테이너를 통한 생성

public class SingletonTest {

    static AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(DenpendencyConfig.class);

    static  MemberService memberService1 = ac.getBean("memberService", MemberService.class);
    static  MemberService memberService2 = ac.getBean("memberService", MemberService.class);


    public static void main(String[] args) {
        System.out.println("memberService1 : " + memberService1);
        System.out.println("memberService2 : " + memberService2);
    }
@Configuration
public class DenpendencyConfig {

    @Bean
    public MemberService memberService(){
        return new MemberService(memberRepository());
    }

    @Bean
    public MemberRepository memberRepository(){
        return new MemberRepository();
    }

    @Bean
    public CoffeeService coffeeService() {
        return new CoffeeService(coffeeRepository());
    }

    @Bean
    public CoffeeRepository coffeeRepository() {
        return new CoffeeRepository();
    }

}

 

컴포넌트 스캔 (@ComponentScan)

  • 스프링은 설정 정보 없이 자동으로 스프링 빈을 등록하는 컴포넌트 스캔이라는 기능을 제공
    • @bean으로 직접 작성시 설정 정보가 커지고, 누락하는 등 다양한 문제가 발생
    • 이러한 문제를 해소 하기 위한 방법
  • @ComponentScan - @ComponentScan이 등록된 곳에서 @Component를 가져오기 위해 사용
  • @Componenet 애너테이션이 붙은 클래스를 찾아서 스프링 빈으로 등록
  • @Configuration 해당 애너테이션에는 @Component가 적용되어있기 때문에 ComponentScan할 때 자동으로 스프링 빈 등록
  • @Autowired - 생성자 의존성 주입에 필요한 설정 정보 대신 의존관계 자동 주입을 해주게 된다.
  • @ComponentScan이 붙은 설정 정보 클래스의 패키지가 시작 위치, 해당 패키지부터 하위 패키지 모두 탐색
    • 설정 정보 클래스의 위치를 프로젝트 최상단에 두고 패키지 위치는 지정하지 않는 방법이 가장 편할 수 있다.
package com.example.section2week4;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;

@Configuration
@ComponentScan
public class AudoDependencyConfig {
}
package com.example.section2week4.coffee;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class CoffeeService {
    private static CoffeeRepository coffeeRepository;

    @Autowired
    public CoffeeService(CoffeeRepository coffeeRepository) {
        this.coffeeRepository = coffeeRepository;
    }

    public void createCoffee(Coffee coffee) {
        coffeeRepository.postCoffee(coffee);
    }

    public Coffee editCoffee(Long coffeeId, String korName, int price) {
        return coffeeRepository.patchCoffee(coffeeId, korName, price);
    }

    public Coffee getCoffee(Long coffeeId) {
        return coffeeRepository.getCoffee(coffeeId);
    }
    public void deleteCoffee(Long coffeeId) {
        coffeeRepository.deleteCoffee(coffeeId);
    }
}
package com.example.section2week4.coffee;

import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

@Component
public class CoffeeRepository {

    private static Map<Long, Coffee> drinks = new HashMap<>();

    public void postCoffee(Coffee coffee) {
        drinks.put(coffee.getCoffeeId(), coffee);
    }

    public Coffee patchCoffee(Long coffeeId, String korName, int price) {
        Coffee drink = drinks.get(coffeeId);
        drink.setKorName(korName);
        drink.setPrice(price);

        return drinks.put(coffeeId, drink);
    }

    public Coffee getCoffee(Long coffeeId) {
        return drinks.get(coffeeId);
    }

    public void deleteCoffee(Long coffeeId) {
        drinks.remove(coffeeId);
    }
}

 

애너테이션 정리

  • @Configuration - 해당 객체가 bean definitions의 소스임을 나타내는 애너테이션
  • @Bean - @Bean-annoted 메서드를 통해 bean을 선언
  • @ComponentScan - 설정 정보 없이 자동으로 스프링 빈을 등록하는 컴포넌트 스캔
  • @Component가 붙은 모든 클래스를 스프링 빈으로 등록
  • @Autowired  - 의존관계를 자동으로 주입

 

다양한 의존관계 주입 방법

  • 생성자 주입 - 권장되는 방법
    • 생성자에 @Autowired를 하면 스프링 컨테이너에 @Component로 등록된 빈에서 생성자에 필요한 빈들을 주입합니다.
  • 수정자 주입 (setter 주입)
  • 필드 주입
  • 일반 메서드 주입

 

순환참조

  • 개발을 하다보면 여러 서비스들 간에 의존관계 발생
  • ex) service1 -> service2 의존, service2 -> service 1 의존
  • 어느 객체가 생성이 되어야 의존 객체가 생성되는데 그게 불가능 -> 누가 먼저 인지 모른다. 누구를 먼저 생성해야 되는지 모른다.
  • 생성자 주입은 빈을 생성할 시점에 바로 주입하기때문에 순화 참조 문제를 인지 할 수 있다.
  • 필드 주입, 수정자 주입 -> 나중에 주입하기 때문에 참조 문제 인지 불가 - 이러한 이유로 현재는 사용 하지 않음
  • 생성자 주입을 사용하는 가장 큰 이유중 하나

 

 

간단 정리 (요약) - 이것들만은 알고가자

  • DI는 IoC라는 원칙을 구현하기 위해서 사용되는 방법중 하나로 의존성 주입을 의미한다.
  • 스프링 컨테이너(Spring Container)는 빈의 생명주기를 관리한다.
  • 빈(bean)은 인스턴스화된 객체를 의미합니다.
  • BeanDefinition는 빈(bean) 설정 메타정보
  • 스프링컨테이너에는 BeanFactory와 ApplicationContext 두개가 있다.
  • ApplicationContext가 스프링 컨테이너
  • @Configuration이 붙은 DependencyConfig를 설정 정보로 사용
  • @Bean이 적힌 메서드를 모두 호출해서 반환된 객체를 스프링 컨테이너에 등록
  • 스프링은 설정 정보 없이 자동으로 스프링 빈을 등록하는 컴포넌트 스캔(Component Scan)이라는 기능을 제공
  • @ComponentScan은 @Component가 붙은 모든 클래스를 스프링 빈으로 등록해주기 때문에 설정 정보에 붙여주면 된다.
  • 의존관계도 자동으로 주입하는 @Autowired 기능도 제공