[Spring Core] Bean

컨테이너·3일 전
0

SpringFramework

목록 보기
15/15

1. Bean

아래 bean의 scope를 예제들을 통해 설명할 것이다. 설명할 내용은 아래와 같다.

이걸 “빵, 음료, 장바구니”라고 생각하면 되고, 스프링 빈으로 등록해서

“장바구니를 singleton/prototype으로 만들면 뭐가 다른가”를 보여주는 용도라고 생각하면 된다.


2. Bean Scope (빈이 몇 개나 만들어지냐)

“스코프 = 이 타입의 빈 객체를 컨테이너가 몇 개 관리할지에 대한 범위”

Scope의미
singleton애플리케이션 전체에서 이 타입의 빈은 딱 1개
prototypegetBean() 할 때마다 새로 1개씩 생성
requestHTTP 요청 하나당 1개 (웹 환경에서만)
session세션 하나당 1개 (웹 환경에서만)

2-1. singleton (기본값)

@Bean
@Scope("singleton")   // 안 써도 기본값이 singleton
public ShoppingCart cart() {
    return new ShoppingCart();
}
  • 첫 번째 손님:
    ShoppingCart cart1 = context.getBean("cart", ShoppingCart.class);
    cart1.addItem(carpBread);
    cart1.addItem(milk);
    
  • 두 번째 손님:
    ShoppingCart cart2 = context.getBean("cart", ShoppingCart.class);
    cart2.addItem(water);
    

결과:

Bean의 기본 SCOPE는 Singleton 이다.

  • cart1, cart2 안의 내용이 섞여 있음 → 같은 객체를 두 변수가 가리키고 있기 때문:
  • hashCode도 둘이 동일함 → 같은 인스턴스
  • 카트가 하나밖에 없는 마트 → 누가 담든 전부 같은 카트

Depends on@lazy

@Bean
@DependsOn({"carpBread", "milk", "water"})
@Lazy
@Scope("singleton")   // 안 써도 기본값이 singleton
public ShoppingCart cart() {
    System.out.println("쇼핑 카트 생성 시점");
    return new ShoppingCart();
}
  • Depends on과 lazy가 없을 때 결과
붕어빵 생성 시점
딸기우유 생성 시점
물 생성 시점
쇼핑 카트 객체 꺼내기전=========
쇼핑 카트 생성 시점
쇼핑 카트 객체 꺼낸 다음========
cart에 담긴 물품 : [붕어빵 1000 Fri Nov 21 15:32:25 KST 2025, 딸기우유 1500 500]
cart2에 담긴 물품 : [붕어빵 1000 Fri Nov 21 15:32:25 KST 2025, 딸기우유 1500 500, 지리산 절벽 암반수 3000 400]
  • Depends on과 lazy를 붙였을 때 결과
붕어빵 생성 시점
딸기우유 생성 시점
물 생성 시점
쇼핑 카트 생성 시점
쇼핑 카트 객체 꺼내기전=========
쇼핑 카트 객체 꺼낸 다음========
cart에 담긴 물품 : [붕어빵 1000 Fri Nov 21 15:33:48 KST 2025, 딸기우유 1500 500]
cart2에 담긴 물품 : [붕어빵 1000 Fri Nov 21 15:33:48 KST 2025, 딸기우유 1500 500, 지리산 절벽 암반수 3000 400]

Lazy : 컨테이너 동작시점이 아니라 해당 객체 필요 시점에 인스턴스를 생성한다.

Depends on : 나열한 빈 인스턴스가 모두 생성된 뒤 생성되도록 설정

2-2. prototype

@Bean
@Scope("prototype")
public ShoppingCart cart() {
    return new ShoppingCart();
}

Prototype계속해서 새로운 인스턴스가 생성된다.

결과

cart에 담긴 물품 : [붕어빵 1000 Fri Nov 21 15:40:43 KST 2025, 딸기우유 1500 500]
쇼핑 카트 생성 시점
cart2에 담긴 물품 : [지리산 절벽 암반수 3000 400]
  • “손님마다 카트를 새로 꺼내는 마트 → cart와 cart2 가 구분됨. → getBean("cart") 할 때마다 새로 만들어 줌

2-3. XML로 설정하면

<!-- singleton -->
<bean id="cart" class="...ShoppingCart" scope="singleton"/>

<!-- prototype -->
<bean id="cart" class="...ShoppingCart" scope="prototype"/>

3. init / destroy (Bean 라이프사이클 훅)

Bean의 “생명주기”는 다음과 같다.

  1. 생성자 호출

  2. 초기화 메서드(init)

  3. 사용

  4. 소멸 전 destroy 메서드

    → 초기화할 때 이 메서드를 한 번 부름.

→ 끝날 때 이 메서드 한 번 부르는 것

⇒ init/destroy.

3-1. Java 설정에서 init/destroy

Owner 예제:

public class Owner {
    public void openShop() {
        System.out.println("사장님: 가게 문 염");
    }
    public void closeShop() {
        System.out.println("사장님: 가게 문 닫음");
    }
}

설정:

@Bean(initMethod = "openShop", destroyMethod = "closeShop")
public Owner owner() {
    return new Owner();
}

컨테이너 생성:

ApplicationContext ctx =
    new AnnotationConfigApplicationContext(ContextConfiguration.class);
// ... 로직 수행 후
((AnnotationConfigApplicationContext) ctx).close();

실행 순서:

  • 컨테이너 시작 → Owner 생성 → openShop() 자동 호출
  • 컨테이너 종료(close) → closeShop() 자동 호출

즉,

“빈이 올라올 때 할 일, 내려갈 때 할 일”을 지정하는 것.

3-2. 애노테이션 방식(@PostConstruct, @PreDestroy)

@Component
public class Owner {

    @PostConstruct
    public void openShop() {
        System.out.println("사장님: 가게 문 염");
    }

    @PreDestroy
    public void closeShop() {
        System.out.println("사장님: 가게 문 닫음");
    }
}
  • @PostConstruct = init-method 역할
  • @PreDestroy = destroy-method 역할

조건:

  • javax.annotation 라이브러리 추가 필요

3-3. XML 방식

<bean id="owner"
      class="...Owner"
      init-method="openShop"
      destroy-method="closeShop"/>

컨테이너 close() 할 때 destroy-method 호출되는 구조는 동일.


4. Properties로 외부 설정 빼기

“코드에 하드코딩하지 말고 설정 값을 파일에 분리하자”라는 내용.

4-1. properties 파일 형태

product-info.properties

bread.carpbread.name=붕어빵
bread.carpbread.price=1000

beverage.milk.name=딸기우유
beverage.milk.price=1500
beverage.milk.capacity=500

beverage.water.name=지리산암반수
beverage.water.price=3000
beverage.water.capacity=500

4-2. @PropertySource + @Value

설정 클래스:

@Configuration
@PropertySource("section03/properties/subsection01/properties/product-info.properties")
public class ContextConfiguration {

    @Value("${bread.carpbread.name:팥붕어빵}")
    private String carpBreadName;

    @Value("${bread.carpbread.price:0}")
    private int carpBreadPrice;

    @Value("${beverage.milk.name:}")
    private String milkName;

    @Value("${beverage.milk.price:0}")
    private int milkPrice;

    @Value("${beverage.milk.capacity:0}")
    private int milkCapacity;

    @Bean
    public Product carpBread() {
        return new Bread(carpBreadName, carpBreadPrice, new Date());
    }

    @Bean
    public Product milk() {
        return new Beverage(milkName, milkPrice, milkCapacity);
    }
}

포인트:

  • @PropertySource("경로")
    • resources 기준 경로
  • @Value("${키:기본값}")
    • ${} 안에 properties의 key 치환자문법(playceholder)를 이용하여 properties에 저장된 key 값을 입력하면 value에 해당하는 값이 가져와져서 주입된다. 양 옆에 공백이 있을 경우 값을 읽어오지 못하므로 주의한다.
    • :기본값은 키 없을 때 사용할 값

필드 말고 파라미터에도 가능:

@Bean
public Product water(
    @Value("${beverage.water.name:}") String waterName,
    @Value("${beverage.water.price:0}") int waterPrice,
    @Value("${beverage.water.capacity:0}") int waterCapacity
) {
    return new Beverage(waterName, waterPrice, waterCapacity);
}

즉,

  • 설정 값 = properties 파일
  • 코드 = 그 값을 @Value로 끌어다 쓰기

5. 국제화(i18n) – 다국어 메시지

“에러 메시지, 안내 문구 등을 한국어/영어로 나누어 관리”할 때 사용.

5-1. properties 파일 준비

message_ko_KR.properties

error.404=페이지를 찾을 수 없습니다!
error.500=개발자의 잘못입니다. 개발자는 누구? {0} 입니다. 현재시간 {1}

message_en_US.properties

error.404=Page Not Found!!
error.500=something wrong! The developer''s fault. who is devloper? It''s {0} at {1}
  • 파일 이름 끝의 _ko_KR, _en_US가 Locale
  • {0}, {1}은 자리 표시자 (매개변수로 치환)

5-2. MessageSource 빈 등록

@Configuration
public class ContextConfiguration {

    @Bean
    public ReloadableResourceBundleMessageSource messageSource() {
        ReloadableResourceBundleMessageSource ms =
            new ReloadableResourceBundleMessageSource();
        ms.setBasename("section03/properties/subsection02/i18n/message");
        ms.setCacheSeconds(10);
        ms.setDefaultEncoding("UTF-8");
        return ms;
    }
}

여기서 basename은

message_ko_KR.properties, message_en_US.properties의 “공통 앞부분” 이름.

5-3. 코드에서 사용

ApplicationContext ctx =
    new AnnotationConfigApplicationContext(ContextConfiguration.class);

String msg404kr = ctx.getMessage("error.404", null, Locale.KOREA);
String msg500kr = ctx.getMessage("error.500",
        new Object[]{"여러분", new Date()}, Locale.KOREA);

String msg404us = ctx.getMessage("error.404", null, Locale.US);
String msg500us = ctx.getMessage("error.500",
        new Object[]{"you", new Date()}, Locale.US);
  • 첫 번째 인자: key
  • 두 번째 인자: {0}, {1} 등에 들어갈 값 배열
  • 세 번째 인자: Locale

→ Locale에 따라 알아서 ko/US 파일에서 메시지를 가져옴.


6. 핵심만 한 번에 요약

  1. Bean scope
  • singleton: 컨테이너당 1개 (기본)
  • prototype: 필요할 때마다 새로 생성 → “공용 카트 vs 손님마다 카트”
  1. init/destroy
  • init: 빈 생성 후, 초기작업 메서드
    • @Bean(initMethod="...")
    • @PostConstruct
  • destroy: 컨테이너 종료 시, 정리작업 메서드
    • @Bean(destroyMethod="...")
    • @PreDestroy
  • XML에서는 <bean init-method="" destroy-method="">
  1. Properties
  • 설정 값 파일로 분리 (.properties)
  • @PropertySource로 파일 등록
  • @Value("${key:기본값}")으로 값 주입
  1. 국제화(i18n)
  • 언어별 properties 파일: message_ko_KR.properties, message_en_US.properties
  • ReloadableResourceBundleMessageSource 빈 등록
  • getMessage("key", args, Locale)로 언어별 메시지 조회
profile
백엔드

0개의 댓글