[Spring] Bean

배창민·2025년 10월 14일
post-thumbnail

스프링 Bean


1) Bean과 예제 도메인

  • 공통 예제: Product(추상), Beverage, Bread, ShoppingCart
    ShoppingCart는 내부에 List<Product>를 보유하고 addItem()으로 담는 구조.

2) Bean Scope 핵심

스코프의미
singleton컨테이너당 인스턴스 1개 공유(기본값)
prototypegetBean() 호출 시마다 새 인스턴스
requestHTTP 요청마다 1개(웹 컨텍스트 한정)
sessionHTTP 세션마다 1개(웹 컨텍스트 한정)
@Configuration
public class ContextConfiguration {
  @Bean public Product carpBread() { return new Bread("붕어빵", 1000, new Date()); }
  @Bean public Product milk() { return new Beverage("딸기우유", 1500, 500); }

  @Bean
  @Scope("singleton")            // 기본값(명시 가능)
  public ShoppingCart cart() { return new ShoppingCart(); }

  // @Scope("prototype") 로 전환하면 호출할 때마다 새 카트
}
  • singleton은 메모리 절감 및 공유에 유리, prototype은 독립 인스턴스가 필요한 경우에 사용.

3) singleton vs prototype 예시 포인트

  • singleton cart: 서로 다른 손님이 cart를 꺼내도 같은 인스턴스(hashCode 동일).

  • prototype cart: getBean("cart") 호출마다 새 인스턴스(hashCode 상이).

  • 시나리오: 손님1이 붕어빵·딸기우유 담고, 손님2가 물 담을 때

    • singleton: 한 카트에 모두 담김
    • prototype: 각자 카트에 따로 담김

4) Bean 라이프사이클: init/destroy

자바 설정(@Bean 속성)

public class Owner {
  public void openShop() { System.out.println("문 오픈"); }
  public void closeShop() { System.out.println("문 클로즈"); }
}

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

// 종료 시 destroy 호출을 보려면 컨테이너 close 필요
((AnnotationConfigApplicationContext) ctx).close();

애노테이션 방식

@Component
public class Owner {
  @PostConstruct public void openShop() { /* init */ }
  @PreDestroy   public void closeShop() { /* destroy */ }
}

XML 방식

<bean id="owner" class="...Owner"
      init-method="openShop" destroy-method="closeShop"/>
  • 초기화(init)는 빈 생성 직후, 소멸(destroy)는 컨테이너 종료 시점에 호출.

5) Properties 기반 설정 주입

properties 파일 예시

# product-info.properties
bread.carpbread.name=붕어빵
bread.carpbread.price=1000
beverage.milk.name=딸기우유
beverage.milk.price=1500
beverage.milk.capacity=500

읽기 및 주입

@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;

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

  @Bean
  public Product milk(
    @Value("${beverage.milk.name:}") String name,
    @Value("${beverage.milk.price:0}") int price,
    @Value("${beverage.milk.capacity:0}") int capacity) {
    return new Beverage(name, price, capacity);
  }
}
  • ${key:기본값} 형태로 치환자(placeholder) 사용, 필드·파라미터에 주입 가능.

6) 국제화(i18n) 메시지

다국어 파일

# 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}

MessageSource 빈

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

사용

String m404 = ctx.getMessage("error.404", null, Locale.KOREA);
String m500 = ctx.getMessage("error.500", new Object[]{"여러분", new Date()}, Locale.KOREA);
  • getMessage(code, args, locale)로 코드·인자·로케일을 전달해 메시지를 조회.

7) 체크 리스트

  • 스코프 선택: 공유가 목적이면 singleton, 사용자별/요청별 독립성 필요 시 prototype·request·session.
  • 라이프사이클 훅: 연결 풀/캐시 초기화는 init, 자원 반납은 destroy에 배치.
  • 설정 분리: 프로퍼티 파일에 환경 값 저장, ${key:default} 로 안전 주입.
  • 다국어 지원: MessageSource와 로케일로 오류/알림 메시지 관리.

profile
개발자 희망자

0개의 댓글