의존관계 자동주입

고동현·2024년 4월 4일
0

Spring 기본

목록 보기
7/10

이번 시간부터는 의존관계 자동주입에 대해서 설명해 보도록하겠다.

이전시간까지 계속 DI에 대해서 설명을 했기 때문에, 뭐 다들 잘 아시겠지만, 천천히 처음부터 왜 써야하는지 복습 겸 생각해보자.

일단 스프링 컨테이너 이거 왜쓸까? 왜 싱글톤으로 만들까?

memberService를 new로 만들어서 클라이언트한테 반환해주면, 100명이 요청하면 memberService 인스턴스를 100개 만들어서 반환해줘야한다. => 자원낭비

싱글톤으로 관리하지 않을경우에 각각 다른 Repository가 생성되므로, 나한테는 1,민수가 있어도 다른 user한테는 1,민수가 없을수있다.=> 싱글톤 관리 필요
예를들어,
OrderServiceImplclass에 private MemberRepsoitory가 있다면, private MemberRepository=new MemoryMemberRository()이런식으로 관리하는경우

OK 알았다 그럼 스프링 컨테이너에 싱글톤으로 등록하고, 이 인스턴스를 공유하자.

스프링 컨테이너에 인스턴스를 등록할때, DI가 근데 필요하다. 왜 필요하냐?
스프링 컨테이너에 인스턴스를 등록할때는 두가지 순서로 작동한다.

  1. 빈등록
  2. 의존관계 주입

OderServiceImpl을 봐보자

어? Component가 있네 그러면 스프링 부트가 스프링 띄우면서 아 이거 빈으로 등록해서 사용해야겠구나 생각한다.
그러면 일단 OrderServiceImpl을 등록을 한다.
근데 생성자 주입이든 뭐 여러가지 뒤에 나오는데 이걸 할려면
MemberRepostiory도 있어야하고, DiscountPolicy도 있어야하고 이렇다.
그래서 여기서 스프링컨테이너에서 이 Type에 해당하는 인스턴스를 찾아와서 콱 주입해준다. 이게 2번째에 해당하는 의존관계주입이다.

대충 글을 읽으면서, 어느정도 스프링 컨테이너의 등록과 이유, DI에 대해서 알게 되었을 것이다.

그래서 이번시간부터는 DI에는 4가지 방식이 있는데 이것을 소개하고자 한다.

  • 생성자주입
    이름그대로 생성자를 통해서 의존관계를 주입하는 방법이다. 대표적으로 생성자 주입을 사용하며, 그냥 이걸 쓴다고 생각해도 무방하다.

특징: 생성자 호출시점에 딱 1번만 호출되는 것이 보장된다.
이 말은 다른곳에서 이 OrderServiceImpl을 변경하기 위해서 아무리 생성자 호출을 해도 스프링 컨테이너에 바뀌지 않는다는 말이다.

★★★불변,필수 의존관계에 사용한다.

생성자 호출시점에 딱 1번만 호출된다. 불변,필수 의존관계에서 사용된다.. 이말이 무엇일까? 그냥 보고 넘어가지 말고 이해를 해보자.

자 그러면 OrderServiceImpl 코드를 봐야하는데

이게 생성자 주입이다.
private final 이게 핵심이다. 그냥 이렇게 썻네 이게 아니라
왜 private final을 썻는지 이걸 알아야한다.

자 처음부터 코드를 내려가보자 Component가 달려있다. 그러면 ComponentScan을 할때 어? Component네 이거 빈으로 등록해서 내가 관리해야겠구나 싶다.

그러면 이것도 어쨋든 자바 객체인데, new를 통해서 인스턴스 생성을 해야하는게 당연한거 아니겠는가?
이게 바로 생성자 호출시점에 딱 1번만 호출된다는것이다.

만약 여기서 스프링을 띄울때 한번 호출해서 스프링컨테이너에 등록하면, 더이상 스프링컨테이너에 올라간 이 OrderServiceImpl이 변경되지 않는다는것이다.

어? 그러면 아에 OrderServieImpl이 안만들어지나? 그건아니다.
뭐 어쨋든 이것도 java class이니까 외부에서 OrderServiceImpl order = new OrderServiceImpl(); 이런식으로 order 객체를 만들수는 있으나, 이건 그냥 java class를 만든거고, 스프링컨테이너에 있는 OrderServiceImpl인스턴스랑은 아에 다르다는것이다.

아 그럼 생성자 한번 호출하는건 알겠다. 근데 왜 한번 스프링 컨테이너에 이게 올라가면 바꿀수가 없냐?

그러면 생성자로 가보자, 파라미터로 memberRepository,DiscountPolicy가 필요하다.
그러면 스프링컨테이너에서 뒤져서 DI를 콱 해준다.

그런데 여기서 discountpolicy와 memberRepository가 private final로 되어있는데

private final MemberRepository memberRepository를 보면

private는 당연하다.
뭐 public으로 해놓으면 외부에서 접근해서 memberRepository를 바꿀테니까

기가막힌건 final이다. 필수와 불변을 담당하는데
final이 있으면 무조건 이 값이 있어야한다는것이다.
그리고 불변을 담당하기도 하는데,
꼭 개발자들이 DiscountPolicy를 바꿔야겠는데하면서
public void setDiscountPlolicy (Discount policy){
this.discountpolicy = policy;
}
이런식으로 DiscountPolicy를 변경하려는 사람들이 꼭 있다.(원래는 RateDiscountPolicy인데 금액DiscountPolicy로 바꿔야징~이러면서)

그런데 final로 설정해놓으면 맨처음에 컨테이너에 인스턴스 등록할때, 한번 final로 등록해놓으면, 이 값이 변경이 불가능하게 한다.

그래서 생성자 호출 시점에 딱 1번 호출이 보장되고, 불변, 필수라는것이다.

Autowired애노테이션이 있으니까 스프링 빈으로 등록할때 필요한 인스턴스들을 컨테이너에서 찾아서 넣어준다.

생성자가 딱1개있다면 @Autowired를 생략해도 좋다. 2개이상이면 당연히 어떤 생성자를 사용해 인스턴스를 만들어 등록할지 모르니까 오류가 나는게 당연하다.

  • 수정자 주입
    setter라는 필드의 값을 변경하는 수정자 메서드를 통해서 의존관계를 주입한다.
    선택,변경 가능성이 있는 의존관계에서 사용한다.

이제 이 code에서 private (final) final이 없는지 알게 될것이다.
왜? final을 쓰면 불변인데 여기서는 선택, 변경을 해야하니까

set메서드를 만들어서 주입해주는것이다. 당연히 여기서 MemberRepository memberRepsoitory라는 파라미터에 값이 들어가야하니까 => memoryMemberRepsoitory, RateDiscountPolicy

Spring컨테이너에서 해당 Type에 맞는 인스턴스를 찾아서 넣어줘야하니까 @Autowired를 사용하였다.
만약 AutoWired를 안쓰면 당연히 오류가 난다 왜? 주입해줘야하는데 스프링 컨테이너에서 못찾아와서 넣어주니까.

아까처럼 당연히 생성자도 없고, 주입할 대상이 컨테이너에서 없으면 당연히 못찾아서 오류가 발생한다.

  • 필드 주입
    이름 그대로 필드에 바로 주입하는 방법이다.
    코드가 간결해서 많은 개발자들을 유혹하지만, IDE 자체에서도 밑줄을 그어주면서 field injection is not recommended라고 한다.=>사용하지말자.

필드 주입 방식이 간단하긴하다.

그냥 필드 앞에 Autowired를 쓰면 스프링 컨테이너에서 찾아서 콱 넣어준다.
생각으로는 이게 진짜 간편하고 좋을것 같은데 왜 쓰지 말라는걸까?

3가지 이유가 있다.
첫번쨰, 일단 Test하기가 힘들다 왜냐면 @SpringBootTest처럼 스프링 컨테이너를 테스트에 통합하지 않는이상 어렵다.

이런식으로 orderService를 new로 만들어서 실행하면 될까? 당연히 안된다. 이건 그냥 맨 앞에서 설명한것처럼 java class를 만든거고, 스프링 컨테이너에 있는 인스턴스를 가져온게 아니다.
고로, 현재 저 orderService 객체는 Autowired기능이 작동하지 않는다. 그냥 자바 객체니까
고로 set으로 또 열어서 이걸 설정해줘야한다.
이러면 또 orderService에서 set을만들고

그다음에, 또 test로 와가지고

이걸 또 set해주고
결국 spring 컨테이너를 반드시 사용해야만하고, 이런것이 개발 속도를 저하시킬 수 있다.

두번째로는, 필드 주입을 사용하면 객체가 생성된 후에도 의존성이 변경될 수 있습다.
왜냐? final로 설정을 안해줬으니까 아까 위처럼 set으로 바꿀 수 있다는것이다. 그러면 생성자 주입에서는 memberRepository가 반드시 MemoryMemberRepository를 의존한느것과 달리,
여기서는 set으로 dbRepoistory,H2Repoistory등 외부에서 원하는대로 마음껏 바꿀 수 있다는것이다. 이건 큰 문제다

마지막으로, 필드 주입을 사용하면 순환 참조가 발생 할 수있다.
만약에 클래스 A가 필드 주입으로 클래스 B를 참조하고, 클래스 B도 필드 주입으로 클래스 A를 참조한다고 가정해보자.
당연히 스프링이 클래스 A의 인스턴스를 먼저 생성하고 그다음 의존관계 주입을 해야하니까 그다음, 스프링 컨테이너에서 인스턴스 B를 찾아서 주입하려고 할것이다.
근데 아직 클래스 B의 인스턴스도 완성되지 않았다. 왜? 클래스 A의 인스턴스를 스프링 컨테이너에서 찾아서 넣어줘야 클래스 B가 완성되니까.
이게 순환참조이냐
근데, 이런 순환참조가 생성자 주입이라고 안생기는건아니다. 그런데 생성자 주입을 사용하면 순환참조를 컴파일 시점에 발견할 수 있다. 왜냐하면, 스프링을 띄울때 스프링 컨테이너에 등록을 다 시키고 시작해야하는데 여기서는 순환참조가 일어나면 오류를 띄워줄수 있으니까 말이다.

  • 일반 메서드 주입
    일반 메서드를 통해서 주입받을 수 있다.
    잘 사용하지 않느다.

이경우도 결국 init함수에다가 Autowired를 붙여서 컨테이너에서 해당 type 인스턴스 찾고 파라미터에 넣어준다음에 this.memberRepository = memberRepository로 넣어주는데
생각해보면 굳이 이걸 쓸 이유가 없다. 생성자 주입과 비슷하지 않나? 굳이 생성자 주입이 있는데 이걸 쓸 이유도 없거니와 심지어 이건 public메서드니까 외부에서 OderServiceImpl.init(내가 바꾸고 싶은 거) 이런식으로 수정도 가능하다.
좋지않은 방식이다. 사용하지 말자.

옵션처리

가끔 주입해야할 스프링 빈이 없어도 동작해야 할 때가 있다.
그니까 의존관계 주입할때, 없는데도 동작해야 할때를 말하는것이다.

3가지 방법이 있다.

  1. @Autowired(required=false)
  2. org.springframework.lang.@Nullable
  3. Optional<>

setBean1메서드를 보면, 우선 Autowired가 있는데 이러면 Member member를 스프링 컨테이너에서 찾아서 주입해주려 할것이다.
그런데 Member는 순수한 자바 객체니까 없다.
그런데 원래 required = true인데 이걸 false로 바꾸어서 오류가 안나게 했다.

setBean2를 보면 @Nullable을 사용하였다.
여기서 그럼 setNoBean2 = null 이렇게 출력이 된다.

setBean3를 보면 Member를 Optional로 감싸주었는데 null일수도 있음을 나타내기 위해서 Optional을 사용하였다.
이렇게 되면, setNoBean3 = Optional.empty가 나온다.

생성자 주입을 선택해야하는 이유

앞에서 왜 생성자 주입을 써야하는지 많이 설명을 했지만,,, 다시 설명해보는 시간을 가져보겠다.

불변
대부분의 의존관계 주입은 한번 일어나면, 애플리케이션 종료 까지 변경 할 일이 거의 없다.
그러나 수정자 주입을 사용하려면, set으로 열어두어야한다.
그러면 어떤 초보 개발자가 외부에서 set으로 이 의존관계를 변경 할 수 도 있다.
생성자 주입은 객체를 생성할때 딱 한번만 호출 되므로 이후에 호출 될 일이 없다.=>따라서 불면하게 설계 가능하다.

누락
만약 수정자 주입을 사용했다고 치자.
Test를 하는 중에서 프레임워크 없이 딱 순수한 자바 코드로만 단위 Test를 수행하고 싶을 수 있다.

그러니까 의존관계 주입같은 걸 해주는 프레임워크 없이 순전히
진짜 OrderSerivceImpl에서의 createOrder 메서드가 제대로 수행되는지 이걸 확인 하고 싶은것이다.


여기있는 이 메서드가 제대로 작동하는지를 확인 해보고 싶은것이다.

그래서 TestCode를 짜는데

@Test
void createOrder() {
    OrderServiceImpl orderService = new OrderServiceImpl();
    orderService.createOrder(1L, "itemA", 10000);
}

이렇게 짯다고 치자, 그러면 당연히 order 메서드가 실행이 안될것이다. 왜냐하면, 여기서 OrderServiceImpl class에서는 memberRepository와 discountPolicy가 설정이 안되어있기 때문이다

그래서 이러한 경우에는 아까 만들어놨던 set을 사용해서 할당을 해주고 그다음에 해야한다.

그러나 생성자 주입을 사용하면, 이런식으로 돌렸을때 컴파일 오류가 발생한다.

왜냐하면 생성자의 파라미터에 들어갈 인자를 넣어줘야하는데 안넣어줬으니까 말이다.

그래서 여기서 Test를 해보려면 뭐 더미라도 넣어서 실행해야한다.
요즘은 Mock를 사용해서 많이 쓴다고 한다.
어쨋든 한번 코드를 완성해보면

스프링 프레임워크를 사용하지 않고 하려면, memberRepoistory와 FixDiscountPolicy를 직접 new해서 만들고 그다음에 OrderServiceImple에 new 생성자 인자로 넣어줘야한다.

fianl키워드
생성자 주입을 사용하면 필드에 final 키워드를 사용할수있다. 그래서 만약 생성자에서 실수로 값이 설정이 안되면 컴파일 시점에 막아준다.

예를들어,

이런식으로 생성자를 작성하는데 까먹고 discountPolicy를 설정하는 부분을 적지 않았더라면, 컴파일 오류를 보여준다.

정리를 해보자면,
생성자 주입 방식을 선택하는 여러가지 이유가 있지만, 프레임워크에 의존하지 않고, 순수한 자바 언어의 특징을 잘 살릴수있다.
기본으로 생성자 주입을 사용하고, 필수 값이 아닌경우에 setter로 열어서 수정자 주입방식을 옵션으로 부여하면 되다.

롬복을 이용한 생성자주입

이제는 @RequiredArgsConstructor만 붙여주면 된다.
그러면 앞에서 만들었던 생성자와 동일하게 final이 붙은 필드를 모아서 생성자를 자도응로 만들어준다.

@Component
 @RequiredArgsConstructor
 public class OrderServiceImpl implements OrderService {
       private final MemberRepository memberRepository;
       private final DiscountPolicy discountPolicy;
 }

롬복이 자바의 애노테이션 프로세서라는 기능을 이용해서 컴파일 시점에 생성자 코드를 자동으로 생성해준다.

조회 빈이 2개 이상 - 문제

@Autowired는 Type으로 조회를 한다.
그러니까 Type으로 조회하기 때문에 이와같이 비슷하게 동작한다.
ac.getBean(DiscountPolicy.class)
그런데 만약 DiscountPolicy를 상속받은 자식이 두개이상이면 어떻게할까?
RateDiscountPolicy와 FixDiscountPolicy두개가 스프링 빈으로 선언되어있는것이다.

Like

@Component
public class FixDiscountPolicy implements DiscountPolicy{}
@Component
public class RateDiscountPolicy implements DiscountPolicy{}

그리고

@Autowired
private DiscountPolicy discountPolicy

이렇게하면 NoUniqueBeanDefinitonException 오류가 발생한다.
당연하지, 둘중에 뭘 가져와서 DI해줄지 모르니까 맗이다.
이때 하위타입으로 지정을 해줄 수 있지만, 하위타입으로 지정하는것은 DIP를 위배하고 유연성이 떨어진다.
그리고 이름만 다르고, 완전히 똑같은 타입의 스프링 빈이 2개 있을때 해결이안된다.

그래서 해결방법으로는 대략 3가지가 있다.

  • @Autowired에 필드명을 매칭시키는것
    Autowired는 타입매칭을 시도하고, 자식 타입까지 전부 뒤져서 가져오므로, 구체적으로 타입을 지정하는것이다.


여기서 DiscountPolicy에서 rateDiscountPolicy로 구체적인 타입을 명시해주는것이다.

  • Qualifier 사용
    추가적으로 구분자를 넣어주는것이다.

이런식으로 각 component에 구분할 수 있는 별칭같은걸 적어주는것이다.


그러면 생성자 주입할때 이런식으로 @Qualifier를 붙일 수 있다.
그러면 이 구분자에 맞춰서 인스턴스를 가져와서 di를 해준다.

만약에 @Qualifier("mainDiscountPolicy")를 못찾으면 어떻게 될까? 그러면 mainDiscountPolicy라는 빈 이름을 가진 인스턴스를 다시 찾는다.

  • Primary 사용
    여러개 있으면 이걸 넣어줘 하는것이다.
 @Component
 @Primary
 public class RateDiscountPolicy implements DiscountPolicy {}
 
 @Component
 public class FixDiscountPolicy implements DiscountPolicy {}

이런식으로 RateDisocuntPolicy에다가 프라이머리를 붙여 같은 type인경우 RateDiscountPolicy가 인젝션 되게 하였다.

그러면 기존의 생성자 주입에서 바꿀것이 없이, DiscountPolicy discountPolicy에서 가져올때 Primary인 Rate를 가져와서 넣어준다.

@Primary vs @Qualifier
그러면 어떤걸 써야할까?
Qualifier의 단점은 주입 받을때 마다 모든 코드에 @Qualifier를 붙여줘야 하는것이다. 약간 귀찮다
그래서
코드에서 자주 사용하는 메인 데이터베이스의 커넥션을 획득하는 스프링 빈이 있고, 크드에서 특별한 기능으로 가끔 사용하는 서브 데이터베이스의 커넥션을 획득하는 스프링 빈이 있다고 가정하자,

메인 데이터베이스의 스프링 빈을 획득하는것은 @Primary를 써서 @Qualifier없이 편리하게 조회하고,
서브 데이터베이스 커넥션 빈을 획득할 때는 @Qualifier를 지정해서 명시적으로 획득하는 방식을 사용하면 코드를 깔끔하게 유지 할 수 있다.

만약 둘다 사용된다면 , 스프링은 항상 구체적인것에 우선순위를 주기 때문에 Qualifier가 우선권이 높다.

애노테이션 직접만들기

아 귀찮은데 뭘 만들고있냐? Qualifier쓰자 할수 있다.
그런데 Qualifier를 쓰면 단점이 있다.
바로 컴파일 때 오류를 잡을수 없다는 뜻이다.
이게 무슨말이냐
@Qulifier("mainDDiscountPolicy")
public class RateDiscountPolicy implements DiscountPolicy{}
이런식으로 내가 만약 실수로 DD라고 잘못입력했다고 가정해보자.
그러면, 이건 ""안에들어가있는 문자열이다. 그러니까 컴파일 타임에 오류를 잡지 못한다.
이말이 조금 어렵다면, 이게 나중에 OrderServiceImpl에서 생성자에,

이런식으로 붙어져있고, 그러면 mainDiscountPolicy가 붙은 인스턴스를 찾으려는데 우리는 mainDDiscountPolicy로 등록을 해놓았다.
그래서 업으니까 또 mainDiscountPolicy빈 자체가 있는지 뒤지는데 없다.
그러니까 이제 오류가 발생하는것이다.
이러한 오류를 실행시점에 아는게 아니라, 컴파일 타임에 아는것이 좋다.

그래서 애노테이션을 만드는것이다.

Targer,Retention,Documented는 애노테이션 만들때 필요한거고
여기다가 Qualifier를 붙여준다.

그러면 앞으로는 이제

@Component
@MainDiscountPolicy
public class RateDsicountPolcy...

이런식으로 쓰면 된다.
그러면 MainDDiscountPolicy 이런식으로 쓰면 오류가 컴파일 타임에 잡을 수 있다.

Quaifier를 이용해서 생성자 주입을 할때도
생성자에서 이런식으로 쓰면된다.


 public OrderServiceImpl(MemberRepository memberRepository,
 @MainDiscountPolicy DiscountPolicy discountPolicy) {
    this.memberRepository = memberRepository;
    this.discountPolicy = discountPolicy;
 }

조회한 빈이 모두 필요할때,List,Map

의도적으로 코드를 짤때 해당 타입의 스프링 빈이 모두 필요한 경우도 있다.
만약 내가 VIP라서 RateDiscountPolicy와 FixDiscountPolicy 둘다 사용이 가능하고,
이걸 클라이언트가 고를 수 있는 상황이라고 가정해보자.
이러면 DiscountPolicy에 해당하는 모든 빈을 다 가져와야 할 것이다.

public class AllBeanTest {
    @Test
    void findAllBean(){
        ApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class,DiscountService.class);
        DiscountService discountService = ac.getBean(DiscountService.class);
        Member member = new Member(1L,"userA", Grade.VIP);
        int discountPrice = discountService.discount(member,1000,"fixDiscountPolicy");
        Assertions.assertThat(discountPrice).isEqualTo(1000);
    }
    static  class DiscountService{
        private final Map<String, DiscountPolicy> policyMap;
        private final List<DiscountPolicy> policies;

        @Autowired
        public DiscountService(Map<String,DiscountPolicy> policyMap,List<DiscountPolicy>poliicies){
            this.policyMap=policyMap;
            this.policies = poliicies;
            System.out.println("policyMap = "+ policyMap);
            System.out.println("policies = " + poliicies);
        }

        public int discount(Member member,int price, String discountCode){
            DiscountPolicy discountPolicy = policyMap.get(discountCode);
            System.out.println("discountCOde = " + discountCode);
            System.out.println("discountPolicy = " + discountPolicy);
            return discountPolicy.discount(member,price);
        }
    }
}

여기 Testcode를 봐보자
우선 finadAllBean() test에서 AutoAppConfig에 ComponentScan이 달려있으니까.
RateDiscountPolicy,FixDiscountPolicy가 빈으로 스프링컨테이너에 등록이 될것이다.
그다음에, DiscountService를 등록해야하는데

여기서 생성자를 보면, Map<String,DiscountPolicy> policyMap, List<DiscountPolicy'> policy 가 있다. 그러면 스프링이 DiscountPolicy type에 맞는 넣어줄게 있나? 뒤져본다.
앞에서 AutoAppConfig를 통해서 RateDiscountPolicy FIxDiscountPolicy가 있으니까 이게 Map에 들어가게되고 List에는 이 인스턴스가 들어가게된다.

그다음에 뭐 동일하게 this.으로 할당해주면된다.

그래서 Testcode에서
discountService에서 discount를 적용할 이름을 넘겨주게 되고
discount메서드로 가보면

PolicyMap.get("fixDiscountPolicy")를 가져오게된다.
그런데 이제 우리가 빈등록될때 메서드 명에서 앞에껄 소문자로 만들고 등록되므로 Map에서 fixDiscountPolicy인스턴스를가져와서 discount메서드를 실행시킨다.

요약하자면
처음에 AutoAppConfig를 통해서 => 스프링컨테이너에 DiscountPolicy type모든 빈 등록 => 그다음 이 DiscountPolicy type의 모든 빈을 Map으로 등록 => 그다음 Map에서 꺼내쓰기
이런 방식으로 실행 된다.

당연히 이러면 FixDiscountPolicy,RateDiscountPolicy에 discount 메서드를 각각 만들어 줘야한다.

참고
이전 포스트에서도 설명했지만
new AnnotationApplicationContext(AutoAppConfig,DiscountService.class)이렇게하면
AutoAppConfig와 DiscountService의 해당 클래스도 자동으로 스프링 빈으로 등록한다.

자동,수동의 올바른 기준

자 그러면, 어떤경우에

  • 컴포넌트 스캔과 컴포넌트를 이용해서 자동 주입을 쓰고,
  • 어떤 경우에 AppConfig를 만들어서 수동으로 빈을 등록하고, 의존관계도 수동으로 주입해야할까요?

결론적으로 스프링이 나오고 시간이 갈수록 자동을 선호하게한다.
사실, 어차피 스프링 빈을 등록할때 @Component만 넣어서 자동 빈으로 등록하더라도, OCP,DIP를 지키면서 사용할 수 있는데

AppConfig를 만들고 @Configuration과 @Bean을 적고, 주입할 대상을 일일히 적는건 굉장히 번거롭고 부담이 된다.

그러면 수동빈은 언제 사용하면 좋냐?

애플리케이션은 두가지로 나눌수있다.
업무로직
기술지원로식

  • 업무로직
    웹을 지원하는 컨트롤러, 비지니스 로직이 있는 서비스, 데이터 계층을 처리하는 리포지토리등
  • 기술지원 빈: 데이터베이스 연결, 공통 로그 처리업무,기술적인 문제나 공통관심사(AOP)등을 처리할때 사용된다.

업무로직은 숫자도 많고, 어느정도 유사한 패턴이 있다.=> 자동기능을 적극 사용

기술지원 로직은 수가 적고, 보통 애플리케이션 전반에 걸쳐서 광범위하게 영향을 미친다. 그리고 업무로직에 비해 문제가 어디서 발생했는지 아는게 아니라 제대로 작동이 되고있는지 알기 조차 어렵다.
그래서 이런 기술 지원 로직들은 가급적 수동 빈 등록을 사용하는것이 좋다.

그래서 이러한 애플리케이션에 광범위하게 영향을 미치는 기술 지원 객체는 수동 빈으로 등록해서
딱 설정 정보에 바로 나타나게 해서 유지보수하는것이 좋다.

예시를 하나만 들어보겠습니다.
데이터베이스 연결을 위해서 DataSource객체를 수동으로 빈으로 등록해보겠습니다.

DataSource객체를 생성하고 @Configuration으로 스프링의 설정정보로 사용됨을 명시하고
DataSource빈을 생성하는 메서드에 @Bean어노테이션을 붙여 해당 메서드가 빈을 생성,관리함을 보여줍니다.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
import org.apache.commons.dbcp2.BasicDataSource;

@Configuration
public class DataSourceConfig {

    @Bean
    public DataSource dataSource() {
        BasicDataSource dataSource = new BasicDataSource();
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/your_database");
        dataSource.setUsername("your_username");
        dataSource.setPassword("your_password");
        
        return dataSource;
    }
}

위의 예시에서는 dataSource 메서드를 통해 DataSource객체를 생성하고있습니다.
여기서 이 빈은 데이터베이스 연결 정보를 포함고있습니다.

이렇게 생성된 dataSource빈은 스프링 애플리케이션 컨텍스트에서 관리되며,
만약, DB연결이 필요한 다른 빈들이 이걸 주입받아 사용할 수 있습니다.

또 다른 예시를 들어보면,
만약에 이전 차시에서 조회한 빈이 모두 필요할때, List,Map을 다시보자

DiscountService가 의존관계자동 주입으로, Map<Stirng,DiscountPolicy>로 DiscountPolicy에 해당하는 모든 type을 Map에다가 의존관계자동 주입을 했습니다.

그런데 막상 다른 사람이 이 코드를 받으면, 이 DiscountPolicy에 어떤 타입이 있는지 모를것입니다. 그래서 Alt+ENter로 해당 타입을 다 뒤져서 봐야한다는 말입니다. 만약에 Component어노테이션으로 등로했다면,

그런데 이러한 경우 수동빈으로 등록해서 특정 패키지에 묶어둔다면,
아 DiscountPolicy에 rateDiscountPolicy와 fixDiscountPolicy가 있구나 알기 편하다.

@Configuration
 public class DiscountPolicyConfig {
 @Bean
 public DiscountPolicy rateDiscountPolicy() {
 return new RateDiscountPolicy();
 }
 @Bean
 public DiscountPolicy fixDiscountPolicy() {
 return new FixDiscountPolicy();
 }
 }

이 설정 정보만 봐도 한눈에 빈의 이름은 물론이고, 어떤 빈들이 주입되는지 파악가능하다.

만약에 Component를 사용하더라독
파악하기 쉽게 DiscountPolicy의 구현 빈들을 따로 모아서 특정 패키지에 모아두는것이 중요하다.

profile
항상 Why?[왜썻는지] What?[이를 통해 무엇을 얻었는지 생각하겠습니다.]

0개의 댓글