[Spring/Springboot] Bean/싱글톤 패턴/컴포넌트 스캔

2024. 12. 15. 23:06CS/Spring

 

스프링 컨테이너 생성 과정

//스프링 컨테이너 생성
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class); //구성 정보를 AppConfig.class로 설정
  1. 스프링 컨테이너를 생성한다(AppConfig.class) → 스프링 컨테이너 한 스프링 빈 저장소가 있음
  2. 구성 정보인 AppConfig.class를 활용하여 → 스프링 컨테이너는 파라미터로 넘어온 설정 클래스 정보를 사용하여 스프링 빈 저장소에 스프링 빈을 등록한다.
  3. 스프링 컨테이너에 빈 의존관계 설정을 준비하고, 설정 정보를 참고하여 의존관계를 주입(DI)한다.

Bean

  • Spring 컨테이너가 관리하는 자바의 객체 → Spring은 빈을 통해 객체를 인스턴스화한 후, 객체 간의 의존 관계를 관리함 = 객체가 의존관계를 등록할 때 Spring 컨테이너에서 해당하는 빈을 찾고, 그 빈과 의존성을 만들게 됨
  • 빈등록 방법
    1. @Component → @Autowired : 묵시적 빈 정의
      • 클래스에 어노테이션을 추가하고, Autowired로 다른 클래스에서 해당 Bean을 끌어옴
    2. @Configuration → @Bean : 명시적 빈 정의
      • Spring 설정 파일에 Configuration 어노테이션을 추가하고, Bean 어노테이션을 붙여 명시적으로 빈을 지정

싱글톤패턴 - @Configuration(명시적 빈 정의)

  • 싱글톤패턴
    • Spring 컨테이너는 싱글톤 패턴을 적용하지 않아도, 객체 인스턴스를 싱글톤으로 관리함
      1. 싱글톤 패텅르 위한 지저분한 코드가 들어가지 않아도 됨
      2. DIP, OCP, test, private 생성자로부터 자유롭게 싱글톤을 사용할 수 있음
    • 싱글톤 방식의 주의점 : 여러 클라이언트가 하나의 같은 객체 인스턴스를 공유하기 때문에 Stateless로 설계해야함
      • 특정 클라이언트에 의존적이거나, 특정 클라이언트가 값을 변경할 수 있는 필드가 있으면 안됨 → 가급적 읽기만
      • 필드 대신에 자바에서 공유되지 않는, 지역변수, 파라미터, ThreadLocal등을 사용해야 함
  • @Configuration
    • 스프링 컨테이너는 싱글톤 레지스트리라서 스프링 빈이 싱글톤이 되도록 보장해주어야 하지만, 자바 코드까지 적용하여 보장하기는 어려움 → @Configuration 탄생
    • @Configuration을 적용하지 않고, @Bean만 적용하면 어떻게 될까?
      • @Bean만 사용해도 스프링 빈으로 등록되지만, 싱글톤을 보장하지 않는다.
      • 의존관계 주입이 필요해서 메서드를 직접 호출할 때 싱글톤을 보장하지 않는다.
public class AppConfig{
	public MemberService memberService(){
		return new MemberServiceImpl(memberRepository());
	}
	
	public OrderService orderService(){
		return new OrderServiceImpl(memberRepository(), discountPolicy());
	}
	
	public MemberRepository memberRepository(){
		return new MemoryMemberRepository();
	}
	...
}
  • memberRepository가 3번 호출되고, 각각 다 다른 MemoryMemberRepository 인스턴스를 가지게 됨
  • @Configuration을 적용하면?
    • @Bean 이 붙은 메서드마다 이미 스프링 빈이 존재하면 존재하는 빈을 반환하고, 스프링 빈이 없으면 셍성해서 스프링 빈으로 등록하고 밚환하는 코드가 동적으로 만들어져 → 싱글톤이 보장됨
  • ⇒ 설정정보는 항상 @Configuration 사용할 것

컴포넌트 스캔(@Component) - @Autowired (묵시적 빈 정의)

  • 스프링 빈을 등록할 때 설정 정보에 직접 등록할 스프링 빈을 나열하면 → 만약 등록해야할 빈이 수백개가 되면 귀찮고, 설정정보가 커지고, 누락하는 문제 발생⇒ 의존 관계도 자동으로 주입하는 @Autowired 기능 제공
  • ⇒ 설정 정보가 없어도 자동으로 스프링이 빈을 등록하는 @ComponentScan 기능 제공

@ComponentScan 과정

  • 이전에는 AppConfig에서 @Bean으로 직접 설정 정보를 작성했고, 의존관계를 명시했지만 → 설정 정보 자체가 없기 때문에, 의존관계 주입도 클래스 안에서 해결해야함
    • 먼저 컴포넌트 스캔을 사용하면 @ComponentScan을 설정 정보에 붙여줘야함
      • 컴포넌트 스캔은 @Compoenent 애노테이션이 붙은 클래스를 스캔해서 스프링 빈으로 등록함
        • (cf) @Configuration이 붙은 설정 정보도 자동으로 등록되기 때문에, 중복을 피하려면 어노테이션 제거 필요
@Componenet
public class MemberServiceImpl implements MemberService{
}

@Componenet
public class OrderServiceImpl implements OrderService{
}

@Componenet
public class MemoryMemberRepository implements MemberRepository{
}

@Componenet
public class RateDiscountPolicy implements DiscountPolicy{
}

 

  • @Autowired의존관계 자동 주입
    • 생성자에 @Autowired를 지정하면, 스프링 컨테이너가 자동으로 해당 스프링 빈을 찾아서 주입
@Component
public class MemberServiceImpl implements MemberService{
	private final MemberRepository memberRepository;
	
	@Autowired
	public MemberServiceImpl(MemberRepository memberRepository){
		this.memberRepository = memberRepository;
	}
}

 

  • @Autowired를 사용하면, 생성자에서 여러 의존관계도 한번에 주입받을 수 있음 → 생성자에 파라미터가 많아도 다 찾아서 자동으로 주입
@Component
public class OrderServiceImpl implements OrderService{
	private final MemberRepository memberRepository;
	private final DiscoountPolicy discountPolicy;
	
	@Autowired
	public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy){
		this.memberRepository = memberRepository;
		this.discountPolicy = discountPolicy;
	}
}