본문 바로가기

Spring

[Spring] Spring Bean 정리

Spring Bean이란 무엇인가?

  • Spring에서 POJO를 ‘Beans’라고 부른다.
  • Beans는 애플리케이션의 핵심을 이루는 객체이며, Spring IoC 컨테이너에 의해 인스턴스화, 관리, 생성된다.
  • Spring에 의하여 생성되고 관리되는 자바 객체이다.

Spring Container란 무엇인가?

  • Application Context를 스프링 컨테이너라고 하고, Application Context는 인터페이스이다.
  • 스프링 컨테이너는 XML을 기반으로 만들 수 있고, 어노테이션 기반의 자바 설정 클래스로 만들 수 있다.
  • 스프링 컨테이너는 파라미터로 넘어온 설정 클래스 정보를 사용하여 스프링 빈을 등록함
  • 스프링 빈의 생명 주기를 관리하며, 생성된 스프링 빈들에게 추가적인 기능을 제공하는 역할을 한다. IoC와 DI의 원리가 스프링 컨테이너에 적용됨

Spring Bean 등록 방식

Component Scan

  • 컴포넌트 스캔은 @Component를 명시하여 빈을 추가하는 방법이다. @Component를 붙이면 스프링이 알아서 스프링 컨테이너에 빈을 등록한다.
  • ElementType.TYPE 설정이 있으므로 Class 혹은 interface에만 붙일 수 있다.

Component Scan의 대상

  • @Component 외에도 @Controller, @Service, @Repository, @Configuration은 @Component의 상속을 받고 있으므로 모두 컴포넌트 스캔의 대상이다.
  • @Controller
    • 스프링 MVC 컨트롤러로 인식
  • @Repository
    • 스프링 데이터 접근 계층으로 인식하고, 해당 계층에서 발생하는 예외는 모두 DataAccessException으로 반환
  • @Service
    • 특별한 처리는 하지 않으나, 개발자들이 핵심 비즈니스 계층을 인식하는데 도움을 줌
  • @Configuration
    • 스프링 설정 정보로 인식하고, 스프링 빈이 싱글톤을 유지하도록 추가 처리를 한다.

Spring Bean을 Spring loC Container에 등록하는 법

Bean Configuration File에 직접 Bean 등록

@Configuration
public class AppConfig {

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

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

빈을 등록하기 위해 인스턴스를 생성하는 메소드 위에 @Bean을 명시하면 된다. 여기서 @Bean은 ElementType 설정이 METHOD 혹은 ANNOTATION_TYPE 이므로 메소드나 어노테이션에만 붙일 수 있다.

Component Scan

@Configuration에는 @Component가 있으므로 컴포넌트 스캔이 대상이 되어 자동 스캔을 통해 빈 등록이 가능하다

@Bean vs @Component

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

@Bean

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

@Configuration과 싱글톤

  • @Configuration은 Bean에 무조건 싱글톤을 보장한다.위 코드에서 memberService와 orderService에서 merberRepository 메소드를 호출하는 것을 알 수 있으며, 각각 다른 2개의 MemryMemberRepository가 생성되어 싱글톤이 깨진다고 생각할 수 있다.
  • 하지만, @Configuration은 클래스의 바이트 코드를 조작하는 라이브러리인 CGLIB을 사용하여 싱글톤을 보장한다.
  • @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 Lite Mode
    • CGLIB을 이용하여 바이트 코드 조작을 하지 않는 방식을 의미한다. 즉, 스프링 빈의 싱글톤을 보장하지 않는다.
    @Component
    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 Lite Mode로 설정하려면, @Conponent로 설정하면 된다. 이러면 매번 다른 객체로 반환해 줄 수 있다.

스프링 빈 스코프

Singleton Bean

  • 스프링 컨테이너에서 한 번만 생성되며, 컨테이너가 사라질 때 제거된다.
  • 생성된 하나의 인스턴스는 스프링 빈 캐시에 저장되고, 해당 빈에 대한 요청과 참조가 있으며 캐시된 객체를 반환한다.
  • 모든 빈은 스코프가 명시적으로 지정되지 않으면 싱글톤이다
  • 싱글톤 타입으로 적합한 객체
    • 상태가 없는 공유 객체
    • 읽기 전용인 객체
    • 쓰기가 가능한 상태를 지니면서도 사용 빈도가 매우 높은 객체 → 동기화 전략이 필요함

Prototype Bean

  • 프로토 타입 빈은 DI가 발생할 때 마다 새로운 객체가 생성되어 주입된다.
  • 빈 소멸에 스프링 컨테이너가 관여하지 않고, gc에 의해 빈이 제거된다.
  • 프로토 타입으로 적합한 객체
    • 사용할 때 마다 상태가 달라져야 하는 객체
    • 쓰기가 가능한 상태가 있는 객체

Singleton과 Prototype을 같이 사용할 때 생기는 문제

  • 프로토 타입 객체가 싱글톤 객체를 가지고 있는 것은 문제가 되지 않으나, 싱글톤 객체가 프로토 타입 객체를 가지고 있는 경우에는 의도한 것과 다른 결과를 낼 수도 있다. 이미 싱글톤 빈으로 생성되는 시점에 프로토 타입 빈이 생성되어 들어오기 때문에 싱글톤 빈 내부의 프로토 타입 빈을 호출하게 되면 매번 같은 값을 가져오게 된다.

문제 해결방법

Provider

@Component
class ClientBean {

    @Autowired
    private Provider<PrototypeBean> provider;    //javax.inject 하위 클래스로 import해야함

    public int logic() {
        PrototypeBean prototypeBean = provider.get();    // 컨테이너에 빈 요청
        prototypeBean.addCount();
        return prototypeBean.getCount();
    }
}

logic() 메소드를 호출할 때 마다 다른 PrototypeBean 인스턴스가 호출된다. Provider은 자바 표준이라 스프링에 독립적이라는 장점이 존재한다.

@Scope의 proxyMode 설정

@Component
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class ProtoProxy {}

@Component
@AllArgsConstructor
public class ScopeWrapper {
    ...

    @Getter
    ProtoProxy protoProxy;
}

Prototype에 proxyMode 설정을 추가한다. 프록시 적용 대상이 클래스면 TARGET_CLASS, 인터페이스면 INTERFACE를 선택함

Spring Bean의 생명주기

Singleton Bean의 생명주기

스프링 컨테이너 생성 → 스프링 빈 생성 → 의존 관계 주입 → 초기화 콜백 → 사용 → 소멸전 콜백 → 스프링 종료

Prototype Bean의 생명주기

스프링 컨테이너 생성 → 스프링 빈 생성 → 의존 관계 주입 → 초기화 콜백 → 사요 → GC에 의해 수거

싱글톤 Bean은 스레드에 안전한가?

public class Singleton {

    private static Singleton instance = new Singleton();

    private Singleton() {
    }

    public static Singleton getInstance() {
        return instance;
    }
}

싱글톤 패턴은 위와 같은 형태로, 인스턴스가 전체 애플리케이션 중에서 단 한번만 초기화되어 애플리케이션이 종료될 때 까지 메모리에 상주한다는 특징이 있다. 만약, 싱글톤이 상태를 갖게 된다면 멀티 스레드 환경에서 동기화 문제가 발생할 수 있다는 사실은 다 잘 알것이다.

스프링은 싱글톤 레지스트리를 통해 private 생성자, static 변수 등의 코드 없이 비즈니스 로직에 집중하고 테스트 코드에 용이한 싱글톤 객체를 제공해주는거지, 동기화 문제는 개발자가 처리해야한다.

만약에 싱글톤 빈이 상태를 가지게 되고, 아무런 동기화 처리를 해주지 않는다면 멀티 스레드 환경에서 부작용이 발생할 수 있으니 주의해야한다.

참고 링크


https://melonicedlatte.com/2021/07/11/232800.html

https://steady-coding.tistory.com/594

https://gmlwjd9405.github.io/2018/11/10/spring-beans.html

https://chobopark.tistory.com/200

'Spring' 카테고리의 다른 글

스프링 부트 테스트 코드 작성 방법  (0) 2022.09.02
Spring MVC 패턴  (0) 2022.07.30