스프링 Bean

황희윤·2023년 10월 16일

스프링 Bean

스프링 IoC 컨테이너에 의해 관리되는 자바 객체

IoC (Inversion of Control)

프로그램의 실행 흐름이나 객체의 생명 주기를 개발자가 제어하는 것이 아닌 외부에서 관리해주는 것

스프링 IoC 컨테이너

Spring Bean 객체를 인스턴스화하고 생명 주기를 관리


스프링 Bean을 사용하는 목적

1. 싱글톤으로 관리하기 위해서

  • 싱글톤 : 애플리케이션이 시작될 때 static을 사용해서 인스턴스를 메모리에 딱 하나만 할당하고, 다음부터는 만들어진 해당 인스턴스만을 사용하는 디자인 패턴

  • 생성자를 private로 만들기 때문에 외부에서 생성자를 통해 인스턴스를 만들 수 없다.

  • 장점 : 매번 새로운 인스턴스를 만들지 않기 때문에 대규모 트래픽을 처리하기 좋다.

  • 스프링 Bean 적용
    • 새로운 요청이 올 때마다 새로운 인스턴스가 생긴다.
    • 그에 따른 성능 저하 발생, 불필요한 메모리 사용
  • 스프링 Bean 적용
    • 클라이언트 요청에 해당 객체가 하나만 생성되고, 공유하도록 설계
    • 모든 요청에서 해당 객체 하나를 사용 가능
  • 주의사항
    • Stateless
      • 예를 들어 주문 객체를 stateful하게 만들어서 고객이 10000원인 음료를 주문했다고 하면 모든 고객이 10000원인 음료를 주문했다고 나올 것이다.
      • 또한 10000원을 20000원으로 변경하면 모든 고객이 20000원 음료를 주문했다고 바뀐다.
      • 인스턴스 내부는 값을 가지고 있지 않거나, 변수가 불변인 final로 구성되어야 한다.
      • 읽기 전용으로 만들어야 한다.

2. 의존성 관리

  • new 생성자를 이용한 경우 인스턴스간의 결합도가 높아서 하나의 인스턴스를 변경할 경우 해당 인스턴스에 의존적인 모든 인스턴스를 변경해줘야 한다. 또한 인스턴스간의 의존성 관계를 개발자가 모두 설정해줘야 한다.

  • Bean을 사용할 경우 스프링 컨테이너에서 의존성 관리를 해주고 외부에서 의존성을 주입하기 때문에 낮은 결합도를 가지고 있어 하나의 인스턴스를 변경해주어도 다른 인스턴스를 변경하지 않아도 된다.

  • 스프링 컨테이너가 스프링 Bean의 생명 주기를 관리하기 때문에 제어의 흐름을 외부에서 관리해준다(프레임워크의 역할).

3. ApplicationContext

Bean을 관리하는 스프링 컨테이너는 Bean Factory를 가지고 있는데, Bean Factory는 스프링 Bean을 관리하고 조회한다.

ApplicationContext는 Bean Factory의 하위 인터페이스로 Bean Factory의 기능 + 부가적인 기능(국제화 기능, 환경 변수 관련 처리, 애플리케이션 이벤트, 리소스 조회)을 가지고 있다. 그리고 ApplicationContext의 기본 생성자는 private로 되어 있기 때문에 수정할 수 없으며 조회만 가능하다.

그 중에서 가장 눈여겨 볼 기능은 ApplicationContext가 스프링 컨테이너 내부의 Bean 저장소에 Bean을 등록하고 싱글톤으로 관리한다는 것이다.

ApplicationContext의 Bean 처리 과정

  1. @Configuration이 붙은 클래스들을 설정 정보로 등록하고 @Bean이 붙은 메서드의 이름을 key로 그리고 메서드 객체를 value로 Bean 저장소에 저장한다.

    • 스프링 컨테이너는 @Configuration가 붙은 config 클래스를 가진 자바 파일을 설정 정보로 사용한다.
    • @Configuration 없이 @Bean만으로도 Bean을 만들 수 있지만, @Configuraton을 사용하면 CGLIB라는 라이브러리가 Bean 저장소의 key, value 값을 바탕으로 스캔을 해서 중복을 허용하지 않는다. 그래서 ApplicationContext가 싱글톤을 유지할 수 있다.
    • 또한 @Configuration이 없으면 Bean 객체를 클라이언트가 찾기 어렵다.
  2. 클라이언트가 Bean을 요청한다.

  3. Bean을 반환하거나 생성한다.

만약 ApplicationContext가 없다면 이 모든 기능을 할 클래스를 자바로 만들어 줘야 한다.

ApplicationContext의 주요 기능을 구현한 클래스

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

public class ApplicationContext {
	// 싱글톤을 위해 자기 자신을 참조하는 static 필드 선언. 바로 초기화
    // 외부에서는 사용이 안되지만, 내부에서는 사용이 가능하기 때문에 딱 하나의 인스턴스 생성 가능
	private static ApplicationContext instance = new ApplicationContext();
    
    // 위에서 만든 인스턴스를 반환하는 static 메서드
    // 외부에서 인스턴스를 받을 수 있도록 만들어줌
    public static ApplicationContext getInstance(){
    	return instance;
    }
    
    private Properties props;
    private Map objectMap;
    
    // private 생성자 사용
    // 왜냐하면 외부에서 new 생성자를 막아서 인스턴스를 생성하지 못하게 한다.
    private ApplicationContext(){
    	props = new Properties();
        objectMap = new HashMap<String, Object>();
        try{
        	props.load(new FileInputStream("src/main/resources/Beans.properties"));
        } catch (IOException e){
        	e.printStackTrace();
        }
    }
    
    public Object getBean(String id) throws Exception{
    	Object beanObject = objectMap.get(id); // HashMap 구조
        if(beanObject == null){
        	String className = props.getProperty(id);
            // className에 해당하는 클래스 찾기
            Class clazz = Class.forName(className); 
            // 찾은 클래스를 가지고 인스턴스 만들기
            Object o = clazz.newInstance(); 
            objectMap.put(id, o);
            beanObject = objectMap.get(id);
        }
        return beanObject;
    }
}

이렇게 되면 싱글톤 패턴으로 만들어진 인스턴스를 사용 가능하다.

ApplicationContext context1 = ApplicationContext.getInstance();
ApplicationContext context2 = ApplicationContext.getInstance();

따라서 위의 context1과 context2는 같은 객체가 된다.

하지만 위와 같이 직접 코드를 짜면 private 생성자를 갖고 있어 상속이 불가능하다. 또한 테스트하기 어렵다.

Bean Factory vs ApplicationContext

  • Bean Factorylazy-loading으로 getBean() 메서드가 호출되면 그 때서야 Bean을 인스턴스화한다. (On-Demand)

  • ApplicationContextpre-loading으로 런타임 실행 시 모든 Bean들을 로딩된다.

  • 사용해야 할 Bean이 많으면 ApplicationContext가 좋고 사용해야 할 Bean이 많지 않다면 Bean Factory가 좋지만 스프링에서는 부가 기능이 많은 ApplicationContext 사용을 권장하고 있다.


스프링 Bean 등록 방법

1. 컴포넌트 스캐닝(@Component)

@Component를 붙여서 자동으로 스프링 컨테이너에 Bean을 등록하는 방법

컴포넌트 스캔 대상

  • @Component : 자동으로 스프링 컨테이너에 Bean을 등록
  • @Configuration : 스프링 Bean이 싱글톤을 유지하도록 만들어준다.
  • @Controller : 스프링 MVC 컨트롤러로 인식해서 HTTP 요청 처리, URL 매핑, 뷰 렌더링 등을 돕는다.
  • @Repository : 스프링 데이터 접근 계층으로 인식하고 발생하는 예외는 모두 DataAccessException으로 변환한다.
  • @Service : 딱히 하는 역할은 없고 서비스 계층이라는 것을 알려준다.

주의사항

@Component는 ElementType.TYPE 설정이 있으므로 Class 혹은 Interface에만 붙일 수 있다.

2. 자바 코드로 등록(@Bean)

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

@Configuration
public class AppConfig {

    // Bean 정의
    @Bean
    public MyBean myBean() {
        return new MyBean();
    }

    @Bean
    public AnotherBean anotherBean() {
        return new AnotherBean(myBean()); // 다른 Bean을 주입
    }
}

@Configuration을 class 위에 붙여서 싱글톤 패턴을 가지도록 하고, @Bean을 사용한다.

주의사항

@Bean은 ElementType 설정이 METHOD 혹은 ANNOTATION_TYPE이므로 메서드나 어노테이션에만 붙일 수 있다. 클래스에 붙일 수는 없다.

@Component vs @Bean

  • @Component
    • 개발자가 직접 컨트롤이 가능한 클래스들의 경우에 사용된다.
    • 클래스 또는 인터페이스 단위에 붙일 수 있다.
  • @Bean
    • 개발자가 컨트롤이 불가능한 외부 라이브러리들을 Bean으로 등록하고 싶은 경우에 사용된다.
    • 메서드나 어노테이션에 사용한다.

스프링 Bean의 생명 주기

1. 스프링 컨테이너 생성

2. 스프링 Bean 생성

3. 의존 설정

4. 초기화 콜백

5. Bean 사용

6. Bean 소멸전 콜백(Bean 소멸)

7. 컨테이너 종료


Bean scope

  1. 싱글톤(Singleton) : 기본 스코프로 스프링 컨테이너에 하나의 Bean 정의에 하나의 객체만 생성

  2. 프로토타입(Prototype) : 스프링 컨테이너에 하나의 Bean에 여러 개의 객체가 있을 수 있다.

  3. 웹 관련 스코프

    1. HTTP Request : 하나의 Bean 정의에 하나의 HTTP Request 생명주기 안에 단 하나의 객체가 존재한다. 즉, 각각의 HTTP Request는 자신만의 Bean 객체를 가진다.

    2. HTTP Session : 하나의 Bean 정의에 하나의 HTTP Session 생명주기 안에 단 하나의 객체가 존재한다.

    3. application : 웹의 서블릿 컨텍스트와 같은 범위로 유지되는 스코프

싱글톤 VS 프로토타입

프로토타입 빈 스코프는 컨테이너가 생성, 의존성 주입, 초기화까지만 처리해준다. 종료 메서드가 호출되지 않기 때문에 프로토타입 빈을 사용한 클라이언트가 관리해줘야 하며, 사용이 끝난 프로토타입 빈은 GC가 처리한다.

반면에 싱글톤을 생성부터 종료까지 컨테이너에서 관리해준다.

profile
HeeYun's programming study

0개의 댓글