컴포넌트 스캔 & 의존관계 자동 주입(DI)

sarah·2022년 8월 29일
0

spring

목록 보기
2/6
post-thumbnail

스프링 빈을 등록할 때 자바코드의 @Bean 이나 XML의 등을 통해서 설정 정보에 직접 등록했다. -> 등록할 빈이 많아지면 관리가 힘들다
설정 정보 없이 자동으로 스프링 빈을 등록 => 컴포넌트 스캔 기능

컴포넌트 스캔

✔ 컴포넌트 스캔 - 사용방법

컴포넌트 스캔 = @Component 어노테이션이 붙은 클래스를 스캔해서 스프링 빈으로 등록

  • 컴포넌트 스캔을 사용하려면 먼저 @ComponentScan 을 설정 정보에 붙여준다.
  • @Component가 붙은 클래스를 빈으로 등록할 때, 스프링 빈의 기본 이름은 클래스명을 사용하되 맨 앞글자만 소문자를 사용한다.
    • 빈 이름 기본 전략: MemberServiceImpl 클래스 -> memberServiceImpl
    • 빈 이름 직접 지정: @Component("memberService2")

✔ 컴포넌트 스캔 - 탐색위치

@ComponentScan(
	basePackages = "com.example"
)  
  • basePackages : 탐색할 패키지의 시작위치를 지정한다.
    (해당 패키지를 포함해서 하위 패키지 모두 탐색)
    • basePackages = {"com.example.service", "com.example.controller"} => 여러개의 시작 위치 지정 가능
  • 만약 시작 위치를 지정하지 않으면 @ComponentScan이 지정된 클래스의 package가 시작위치가 된다.

통상적으로 basePackages를 지정하지 않도록, @ComponentScan이 붙은 설정정보 클래스를 프로젝트의 최상단 위치에 둔다.

그래서, 스프링 부트 @SpringBootApplication 클래스가 프로젝트의 최상단에 위치하고 있다. @SpringBootApplication 어노테이션의 상세 코드를 보면 @ComponentScan이 있다.

✔ 컴포넌트 스캔 - 스캔 대상

  • @Component : 컴포넌트 스캔에서 사용
  • @Controller : 스프링 MVC 컨트롤러에서 사용
  • @Service : 스프링 비즈니스 로직에서 사용
  • @Repository : 스프링 데이터 접근 계층에서 사용
  • @Configuration : 스프링 설정 정보에서 사용

    실은 @Component 어노테이션을 제외한 어노테이션의 상세 코드를 보면 기본적으로 @Component 어노테이션이 선언되어 있다.

  • 컴포넌트 스캔의 용도 뿐만 아니라 다음 어노테이션이 있으면 스프링은 부가 기능을 수행한다.
    • @Controller : 스프링 MVC 컨트롤러로 인식
    • @Repository : 스프링 데이터 접근 계층으로 인식하고, 데이터 계층의 예외를 스프링 예외로 변환해준다.
    • @Configuration : 스프링 설정 정보로 인식, 스프링 빈이 싱글톤을 유지하도록 추가 처리
    • @Service : 비즈니스 계층을 인식하는데 도움

✔ 빈의 중복 등록

컴포넌트 스캔에서 같은 빈 이름을 등록하는 경우
1. 자동 빈 등록 vs 자동 빈 등록
2. 수동 빈 등록 vs 자동 빈 등록

1. 자동 빈 등록 vs 자동 빈 등록

  • 컴포넌트 스캔에 의해 자동으로 빈 등록되는데, 이름이 같은 경우 스프링은 오류를 발생시킨다.
    • ConglictingBeanDefinitionException 예외 발생
  1. 수동 빈 등록 vs 자동 빈 등록
  • 이 경우 수동 빈 등록이 우선권을 가진다.
    (수동 빈이 자동 빈을 오버라이딩 해버린다.)
Overriding bean definition for bean 'duplicateBean' with a different
definition: replacing

이런 경우는 개발자가 의도적으로 설정하기 보단, 설정 오류로 나온 결과로 잡기 어려운 버그가 만들어 질 수 있다.

그래서 최근 스프링 부트에서는 수동 빈 등록과 자동 빈 등록이 충돌나면 오류가 발생하도록 기본 값을 바꾸었다.

Consider renaming one of the beans or enabling overriding by setting
spring.main.allow-bean-definition-overriding=true

의존관계 자동 주입을 해주는 @Autowired
@Autowired는 타입으로 검색한다.

의존관계 자동 주입

✔ 의존관계 주입 방법

  1. 생성자 주입
  2. 수정자 주입(setter 주입)
  3. 필드 주입
  4. 일반 메서드 주입

1. 생성자 주입

  • 생성자를 통해서 의존관계를 주입 받는 방법
  • 생성자 호출 시점에 딱 1번만 호출되는 것을 보장
  • 불변, 필수 의존관계에서 사용
@Component
public class OrderServiceImpl implements OrderService {
	private final MemberRepository memberRepository;
  	private final DiscountPolicy discountPolicy;
  
  	@Autowired
  	public OrderServiceImple(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
      this.memberRepository = memberRepository;
      this.discountPolicy = discountPolicy;
  }
}  

+ 빈의 생성자가 딱 1개만 있으면 @Autowired 생략 해도 자동주입 가능

2. 수정자 주입(setter 주입)

  • setter라 불리는 필드의 값을 변경하는 수정자 메서드를 통해서 의존관계를 주입하는 방법
  • 선택, 변경 가능성이 있는 의존관계에 사용
@Component
public class OrderServiceImpl implements OrderService {
	private MemberRepository memberRepository;
	private DiscountPolicy discountPolicy;
 
  	@Autowired
 	public void setMemberRepository(MemberRepository memberRepository) {
 		this.memberRepository = memberRepository;
 	}
 	@Autowired
 	public void setDiscountPolicy(DiscountPolicy discountPolicy) {
 		this.discountPolicy = discountPolicy;
 	}
}

3. 필드 주입

  • 코드가 간결해서 많은 개발자들을 유혹하지만 외부에서 변경이 불가능해서 테스트 하기 힘들다는
    치명적인 단점
  • DI 프레임워크가 없으면 아무것도 할 수 없다.
  • 사용하지 말자!
    • 애플리케이션의 실제 코드와 관계 없는 테스트 코드
    • 스프링 설정을 목적으로 하는 @Configuration 같은 곳에서만 특별한 용도로 사용
@Component
public class OrderServiceImpl implements OrderService {
 	@Autowired
 	private MemberRepository memberRepository;
	 @Autowired
 	private DiscountPolicy discountPolicy;
}

순수한 자바 테스트 코드에는 당연히 @Autowired가 동작하지 않는다. @SpringBootTest 처럼
스프링 컨테이너를 테스트에 통합한 경우에만 가능하다.

4. 일반 메서드 주입

  • 일반 메서드를 통해 주입 가능
  • 한번에 여러 필드를 주입 받을 수 있다.
  • 일반적으로 잘 사용하지 않는다.
@Component
public class OrderServiceImpl implements OrderService {
	private MemberRepository memberRepository;
 	private DiscountPolicy discountPolicy;
 	@Autowired
 	public void init(MemberRepository memberRepository, DiscountPolicy
discountPolicy) {
 		this.memberRepository = memberRepository;
 		this.discountPolicy = discountPolicy;
 	}
}

✔ 생성자 주입을 권장하는 이유

1. 불변

  • 대부분의 의존관계 주입은 한번 일어나면 애플리케이션 종료시점까지 의존관계를 변경할 일이 없다. 오히려 대부분의 의존관계는 애플리케이션 종료 전까지 변하면 안된다.(불변해야 한다.)
  • 수정자 주입을 사용하면, setXxx 메서드를 public으로 열어두어야 한다.
  • 누군가 실수로 변경할 수 도 있고, 변경하면 안되는 메서드를 열어두는 것은 좋은 설계 방법이 아니다.
  • 생성자 주입은 객체를 생성할 때 딱 1번만 호출되므로 이후에 호출되는 일이 없다. 따라서 불변하게 설계할 수 있다.

2. 누락

  • 생성자 주입을 사용하면 필드에 final 키워드를 사용할 수 있다. 그래서 생성자에서 혹시라도 값이 설정되지 않는 오류를 컴파일 시점에 막아준다.

수정자 주입을 포함한 나머지 주입 방식은 모두 생성자 이후에 호출되므로, 필드에 final 키워드를 사용할 수 없다. 오직 생성자 주입 방식만 final 키워드를 사용할 수 있다.

[정리]

  • 생성자 주입 방식을 선택하는 이유는 여러가지가 있지만, 프레임워크에 의존하지 않고, 순수한 자바 언어의 특징을 잘 살리는 방법이기도 하다.
  • 기본으로 생성자 주입을 사용하고, 필수 값이 아닌 경우에는 수정자 주입 방식을 옵션으로 부여하면 된다. 생성자 주입과 수정자 주입을 동시에 사용할 수 있다.
  • 항상 생성자 주입을 선택해라! 그리고 가끔 옵션이 필요하면 수정자 주입을 선택해라. 필드 주입은 사용하지 않는게 좋다.

0개의 댓글