[ loc/DI ] 컴포넌트 스캔의 다양한 대상들과 DI 에 대한 해결방법

msung99·7일 전
4

Spring

목록 보기
19/19
post-thumbnail

시작에 앞서

본 포스팅은 제 지난 포스팅 시리즈 컴포넌트 스캔과 @Autowired 의 메커니즘 : 필요성에 대해 에 이어서 진행되는 내용입니다. 이전 포스팅을 읽고 오시는 것을 권장드립니다!

객체지향 시리즈 포스팅 진행현황

현재 진행중인 포스팅 내용은 아래와 같습니다. 그 중 6번째 마지막 포스팅을 지금 진행해볼까합니다!

  • 객체지향을 아는척하지 말자 : 오해하고 있었던 객체지향의 정체
  • 예제로 이해하는 SOLID 5원칙, 그리고 스프링 DI 컨테이너의 등장
  • 직접 만들어보며 이해하는 SOLID 원칙과 DI 설계 : 수동으로 직접 의존관계 주입해보기
  • 싱글톤(SingleTon) : 왜 스프링 컨테이너를 써야할까?
  • 컴포넌트 스캔과 @Autowired 의 메커니즘 : 필요성에 대해
  • 알면 도움될 컴포넌트 스캔의 다양한 대상들과 DI 에 대한 해결방법(현재 포스팅)

컴포넌트 스캔의 다양한 대상들

컴포넌트 스캔은 @Component 뿐만 아니라 다음과 같은 내용도 추가로 대상으 로 포함됩니다.

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

즉 위와 같은 어노테이션을 붙여놓으면, 자동으로 컴포넌트 스캔 대상이 되어서, 이 어노테이션이 붙은것들은 전부 스프링 빈으로 등록됩니다.

컴포넌트 스캔의 용도 뿐만 아니라, 다음 어노테이션들이 붙은것들에 대해 스프링은 부가 기능을 수행합니다.

  • @Controller : 스프링 MVC 컨트롤러로 인식

  • @Repository : 스프링 데이터 접근 계층으로 인식하고, 데이터 계층의 예외를 추상화된 스프링 예외로 반환해줍니다.

    (DB에 따라서 리턴되는 예외가 달라서 Service 까지 악영향을 줘서 코드를 수정할 수도 있는건데, 이를 쓰면 어떤 DB 든지 신경쓰이게 하지 않도록 예외처리를 추상화시켜서 리턴해줍니다.)

  • @Configuration : 앞서 봤듯이 스프링 설정 정보로 인식하고, 스프링 빈이 싱글톤을 유지하도록 추가 처리를 해준다.

  • @Service : 사실 @Service 는 특별한 처리를 하지 않습니다. 대신 개발자들이 핵심 비즈니스 로직이 여기에 있겠구나 라고 비즈니스 계층을 인식하는데 도움이 됩니다.


다양한 의존관계 주입방법

의존관계 주입방법은 앞서 살펴본 생성자를 통한 주입방법외에도 더 존재합니다.

  • 생성자 주입 / 수정자 주입(setter 주입) / 필드 주입 / 일반 메소드 주입

저희는 이 중에서 수정자 주입만 추가적으로 살펴봅시다. 나머지 방법은 위험성이 커서 잘 사용되지 않는 방법이기 때문이죠.

생성자 주입

지금까지 우리가 진행했던 방법이 바로 생성자 주입입니다. 이름 그대로 생성자를 통해서 의존 관계를 주입받는 방법입니다.

@Component
public class CarServiceImpl implements CarService{

    private final CarRepository carRepository;

    @Autowired
    public CarServiceImpl(CarRepository carRepository){
        this.carRepository = carRepository;
    }
    
    @Override
    public void makeCar(Car car) {
        carRepository.makeCar(car);
    }

    // ... (이하 세부구현 내용 생략)
}

특징

  • 생성자 호출시점에 딱 1번만 호출되는 것이 보장됩니다.
    => 장점 : 이로인해 1번만 초기에 값을 셋팅하고, 추후 변하지 못하도록 막을 수 있다는 얘기가됩니다.

  • 이에따라 불편, 필수 의존관계에 사용된다.

  • 주의! 생성자가 딱 1개만 있으면 @Autowired 를 생략해도 자동 주입됩니다.(= 생성자가 1개인 경우 @Autowired를 안붙여도 마치 붙여준 것과 동일한것)

    즉 생성자가 2개 이상이면 @Autowired 를 생성자가 반드시 명시해야합니다. 몰론 스프링 빈에만 해당합니다.


수정자 주입

아래처럼 setMemberRepository, setDiscountPolicy 로 수정할 수 있는 것들을 수정자라고 합니다. 수정자에다 @Autowired 를 명시해주면 의존관계가 주입된다.

선택, 변경 가능성이 있는 의존관계에 사용하면 됩니다. 당연히 생성자는 필요없으니 없애주면 된다. 수정자들에 의해서 자동으로 의존관계가 주입되기 때문이죠.

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

생성자 주입을 선택하자!

최근에는 스프링을 비롯한 대부분 DI 프레임워크들이 생성자 주입을 권장합니다.그 이유는 아래와 같습니다.

이유1. 불변성을 지니게 된다.

  • 대부분의 의존관계 주입은 한번 일어나면 애플리케이션 종료시점까지 의존관계를 변경할 일이 없습니다. 오히려 대부분의 의존관계는 애플리케이션 종료 전까지 변하면 안됩니다. (불변해야 한다)

  • 또한 누군가 실수로 변경할수도 있고, 변경하면 안되는 메소드를 열어두는 것은 좋은 설계 방법이 아닙니다.

이유2. final 키워드를 사용할 수 있다.

  • 생성자 주입을 사용하면 필드에다 final 키워드를 사용할 수 있습니다. 그래서 생성자에서 혹시라도 값을 실수로 설정하지 않는 오류를 컴파일 시점에 막아줍니다.
@Component
public class CarServiceImpl implements CarService{

    private final CarRepository carRepository;  // final 생략하면 빨간줄 뜸!

    @Autowired
    public CarServiceImpl(CarRepository carRepository){
        this.carRepository = carRepository;
    }
    
    @Override
    public void makeCar(Car car) {
        carRepository.makeCar(car);
    }

    // ... (이하 세부구현 내용 생략)
}

롬복(lombok) 을 활용해 생성자를 자동 생성하는 방법

막상 개발을 해보면 대부분이 다 불변이고, 그래서 final 키워드를 사용하게 됩니다.

그런데 생성자도 만들어야 하고, 주입받은 값을 대입하는 코드도 만들기 귀찮지 않을까요? 즉 필드 주입처럼 좀 편리하게 사용하는 방법은 없을까요?

@RequiredArgsConstructor : 생성자를 자동생성

  • 이 어노테이션은 필수값인 final 이 붙은 것들을 파라미터로 받는 생성자를 자동으로 만들어줍니다.

  • 생성자를 가끔씩 만들어줘야할때만 직접 만들어주고, 그게 아니라면 이 어노테이션을 쓰는 것이 편할겁니다.

@Component
@RequiredArgsConstructor
public class CarServiceImpl implements CarService{

    private final CarRepository carRepository;

    @Autowired
    public CarServiceImpl(CarRepository carRepository){
        this.carRepository = carRepository;
    }
    
    @Override
    public void makeCar(Car car) {
        carRepository.makeCar(car);
    }

    // ... (이하 세부구현 내용 생략)
}

조회 해야할 Bean이 2개 이상일 경우 해결방법

@Autowired 는 타입을 기준으로 빈을 조회합니다. 그러고 의존관계를 주입하는 것입니다.

타입으로 조화하기 때문에, 타입으로 조회하면 선택된 빈이 2개 이상일때 문제가 발생합니다.

예를들어 CarRepository 의 하위 타입인 SonarTarRepository, StarRexRepository 둘다 스프링 빈으로 선언하면 "NoUniqueBeanDefinitionException" 오류가 발생한다.

  • 이때 하위 타입을 지정할 수 있지만, 하위 타입으로 지정하는 것은 DIP 를 위배하고 유연성이 떨어집니다. 그리고 이름만 다르고, 완전히 똑같은 타입의 스프링 빈이 2개 있을 때 해결이 안됩니다.

해결방법

  • @Autowired 필드명 매칭
  • @Quilifier -> @Quilifier 끼리 매칭 -> 빈 이름 매칭
  • @Primary 사용

1. @Autowired 필드명 매칭

  • @Autowired는 타입 매칭을 우선 시도하고, 이때 만약 여러 빈이 있으면 필드 이름, 파라미터 이름으로 빈 이름을 추가 매칭해봅니다.

기존코드

@Autowired
private CarRepository carRepository;

리팩토링 코드

@Autowired
public CarRepository(SonarTarRepository sonartarRepository){
  this.carRepository = sonartarRepository;
}

2. @Quilfier 사용하기

@Quilfier 라는 추가 구분자를 붙여주는 방법입니다. 주입시 추가적인 방법을 제공하는 것이므로, 빈 이름을 변경하는 것은 아닙니다.

@Component
@Quilifier('mainCarRepository")
public class SonarTarRepository implements CarRepository{
    ... 
}
@Component
@Quilifier('subCarRepository")
public class SonarTarRepository implements CarRepository{
    ... 
}

이렇게 구분자로 이름을 구분지어 놓고, CarServiceImpl 에서 생성자부분에 원하는 것을 호출 가능합니다.

@Autowired
public CarServiceImpl(@Quailfier("mainCarRepository) CarRepository carRepository){
  this.carRepository = carRepository;
}

3. @Primary

  • 동일한 타입의 두 Bean 중에서 어떤 것이 더 우선순위를 가지는지 명시하는 방법이다. 이 어노테이션이 붙은 클래스에 대한 빈이 선택됩니다.
@Component
@Primary
public class SonarTarRepository implements CarRepository {
   ... 
}

@Component
public class StarRexRpository implements CarRepository {
   ... 
}

마치며

이로써 스프링의 DI 와 관련해 추가적으로 알면 좋을 컴포넌트 스캔의 다양한 방법과, 롬복, 중복된 빈이 있을경우등에 대해 다양한 해결방법과 리팩토링 코드를 알아봤습니다.

이번 포스팅을 마지막으로 객체지향 관련 포스팅을 마무리 지어볼까합니다.
추가적으로 Bean 생명주기 콜백, RestAPI 기반의 SOLID 설계등에 대한 포스팅을 할까말까 고민중이긴합니다. 만일 시간 여유가 되면 한번 해볼까합니다.

그 동안 제 객체지향 관련 시리즈 블로깅 내용을 봐주신 분들께 정말 감사드립니다! 😁

profile
꾸준히 성장하는 과정속에서, 제 지식을 많은 사람들과 공유하기 위한 블로그입니다 😉

0개의 댓글