스프링 기본 원리 3. 스프링 Bean과 의존성 주입

xellos·2022년 4월 30일
2

Spring

목록 보기
3/6
post-custom-banner

스프링 Bean의 특징

앞의 글에서 스프링 Bean이 무엇인지 살펴보았다. 간단히 말하면 스프링이 의존관계 주입을 위해 생성하고 관리하는 객체이다. 그러면 일반적인 의존성 주입과 스프링 빈을 통한 의존성 주입에는 어떠한 차이가 있을까?

이에 대한 답은 스프링은 Bean을 싱글톤으로 관리한다는 것이다. 기존에는 아래와 같이 문제점이 있었다.

1) 스프링 Bean 적용 전

  • 새로운 요청이 올 때마다 매번 새로운 인스턴스가 생성된다.
  • 그에 따라 매 요청시 인스턴스가 생성되고 소멸되어 많은 자원이 소모된다.

2) 스프링 Bean 적용후

  • 하나의 인스턴스만 생성 후 해당 인스턴스를 모든 요청에서 접근한다.
  • 하나의 인스턴스를 공유하므로 컴퓨터 자원을 효율적으로 사용할 수 있다.

3) 단점

그러나 위와 같은 싱글톤 방식은 하나의 커다란 단점을 가지는데 이는 해당 인스턴스는 값을 소유하지 않는 stateless 상태로 만들어져야한다는 것이다.
A라는 요청에서 값이 변하면 그 이후의 요청에서는 모두 변한 값을 사용하게 되므로 인스턴스 내부는 값을 지니지 않게 만들어지거나 변수가 불변인 final로 구성되어야 한다.
→ 즉, 읽기 전용 인스턴스를 만들어야 한다.


스프링의 Bean 의존성 등록

스프링에서 의존성을 주입하기 위한 여러가지 방법이 있는데 여기서는 그중에서 일반적으로 사용되는 방법에 대해서만 알아보자.

1) @Component 방식

우리는 일반적으로 컴포넌트 스캔이라고 하는 방식을 사용하여 스프링 빈을 등록한다. 이때 사용하는 애노테이션은 @Service, @Controller, @Repository, @Component 등이 있다.

위의 @Service, @Controller, @Repository 애노테이션은 모두 내부에 @Component를 가지고 있는데 실질적으로 이 애노테이션이 스프링에 의하여 빈으로 등록되게하는 표식이다.

@Component
public class CacheMemoryRepository implements MemberRepository {
	//...
}

2) @Configuration 파일을 통한 방식(XML도 방식만 다르지 개념은 같다)

다음은 이전에 보았던 방식으로 수동 @Bean 애노테이션과 의존성을 주입한 빈을 생성하여 메서드 내부에서 인스턴스를 리턴하는 방식이다. (이때 스프링 빈의 이름은 인스턴스를 반환한 메서드의 이름이다)

@Configuration
public class AppConfig {
	
    @Bean
    public MemberService memberService() {
    	return new MemberServiceImpl(memberRepository());
    }
    
    @Bean
    public MemberRepository memberRepository() {
    	return new CacheMemberRepository();
    }
}

스프링의 Bean 의존성 주입

스프링에서 일반적으로 @Configure나 XML 없이 의존성을 주입받는데 일반적으로는 크게 2가지 방식이 있다.
하나는 컴포넌트 @Autowired 애노테이션을 통해 생성자나 Setter를 통해 인자로 주입받는 방식이고, 다른 하나는 처음부터 변수에 의존성을 물리는 필드주입 방식이다.

1) 인자로 주입 받는 방법

  • 생성자에 @Autowired를 붙인다. 이렇게 하면 해당 빈을 찾아서 스프링이 의존성을 주입해준다.
  • 이때, 중요한 것은 스프링은 해당 클래스의 하위 객체까지 모두 찾는다는 것이다. 따라서 우리가 인터페이스를 인자로 넘기면 이 인터페이스를 구현한 구현체가 빈으로 주입된다.
  • 이 방식을 사용하여 생성자 뿐만 아니라 일반적인 Setter를 통해서도 의존성을 동적으로 주입할 수도 있다.
@Component
public class OrderServiceImpl implements OrserService {
	
    private final MemberRepository memberRepository;
    
    @Autowired
    public OrderSericeImpl(MemberRepository memberRepository) {
    	this.memberRepository = memberRepository;
    }
}

2) 필드 주입

  • 클래스의 멤버변수에 바로 @Autowired를 사용해서 주입하는 방식이다. 굉장히 편해보이지만 코드가 스프링에 의존적이게 된다는 단점이 있다. 따라서 스프링 독립적인 상황에서의 Unit테스트가 불가능해진다.
@Component
public class OrderServiceImpl implements OrderService {

	@Autowired
    private MemberRepository memberRepository;
    
    //...
}

3) 한 인터페이스를 구현한 구현체가 여러개라면?

이러한 경우에는 @Qualifier나 @Primary 방식을 사용한다.

@Qualifier: 빈에 별도의 이름을 붙여 구분하는 방식

@Component
@Qualifier("mainDiscountPolicy")
public class MainDiscountPolicy implements DiscountPolicy { ... }

@Component
@Qualifier("vipDiscountPolicy")
public class VipDiscountPolicy implements DiscountPolicy { ... }
@Autowired
public OrderServiceImpl(
	MemberRepository memberRepository,
    @Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy
) {
	this.memberRepository = memberRepository;
    this.discountPolicy = discountPolicy;
}

@Primary: 빈의 우선 순위를 적용하여 중복될 경우 해당 빈이 적용되도록 하는 방식

@Component
@Primary //적용
public class RateDiscountPolicy implements DiscountPolicy { ... }

@Component
public class FixDiscountPolicy implements DisocuntPolicy { ... }
post-custom-banner

0개의 댓글