Spring

[SPRING] 의존관계 자동 주입

chanyoun 2023. 3. 5. 20:56

✔의존관계 주입 방법

의존관계 주입은 크게 4가지 방법이 있다.

  1. 생성자 주입
   public class MyService {    
   	private final MyRepository myRepository;     
   	
       @AutoWired
   	public MyService(MyRepository myRepository) {        
   		this.myRepository = myRepository;    
   	}     
   	// ... rest of the class implementation 
   }

위 예시처럼 생성자에 @AutoWired 설정을 통해 의존관계를 주입한다.

생성자를 통해 의존관계를 주입할때 생성자 호출시점에 1번만 호출되는것을 보장하도록 만들며 이는 불편,필수 의존관계에 사용한다.(주로)

❗❗이때, 생성자가 1개있다면 @AutoWired 생략이 가능하다.

 

📌 4가지의 의존관계 주입중 생성자 주입을 사용하는것이 권장된다.

  • 대부분의 의존관계 주입은 한번 발생하면 에플리케이션 종료 시점까지 의존관계를 번경할 일이없기때문에 (불변) 생성자 주입 사용을 권장한다.
  • 수정자를 통해 의존관계를 주입한다면 test코드를 짤때 수정자를 통해 어떠한 값을 설정해야하는지 파악이 어렵기때문에(클래스에 들어가봐야 알수있다) 누락될수있다. 하지만 생성자 주입이라면 테스트 코드를짤때 객체생성시 생성자에 어떠한값을 넣을지 파악할수있기때문에 누락될일이 없다.
  • 생성자 주입을 사용한다면 Final 을 사용할수있다. 이렇게되면 우리가 최기화 해야하는 필드를 초기화하지 않는 실수를 막을수있다. (생성자 주입만이 final 키워드 사용가능, 나머지 주입 방식은 모두 생성자 이후에 호출되기때문에 final 선언 불가!)
  •  

 

  1. 수정자 주입(setter 주입)
public class MyService {    
    private MyRepository myRepository;     
    
    @AutoWired
    public void setMyRepository(MyRepository myRepository) {        
    	this.myRepository = myRepository;    
    }     
    	// ... rest of the class implementation 
}

위 예시처럼 클래스에 존재하는 setter를통해 의존관계를 주입할수 있다. 이때는 @AutoWired 를 생략할수 없으며 이러한 방식은 선택,변경 가능성이 있는 의존관계에 사용한다.

@Autowired는 주입할 대상이없을때(여기선 myRepository) 오류를 발생시키는데, 주입할 대상이 없더라도(myRepository) 동작하게 하려면 @Autowired(required = false) 로 지정하면 된다.

 

📌생성자 를 통한 의존관계 주입은 bean을 등록할때 같이 일어나지만 수정자 즉 setter로 의존관계를 주입할때는 bean등록 이후에 의존관계 설정이 된다.

 

 

  1. 필드 주입
public class MyService {
    @Autowired
    private MyRepository myRepository;

    // ... rest of the class implementation
}

위 예시처럼 사용하면 필드를 통해 의존관계를 주입할수 있다. 하지만 필드를 통해 의존관계를 주입하는것은 권장되지않는다.

setter가 존재하지않는이상 외부에서 myRepository에 접근할수 있는 방법이 없기때문에 테스트하기 힘들다는 단점때문에 사용하지 않는다

 

  1. 일반 메서드 주입
private MemberRepository memberRepository;
private DiscountPolicy discountPolicy;

@Autowired
public void init(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
	this.memberRepository = memberRepository;
	this.discountPolicy = discountPolicy;
}

위 예시처럼 일반 메서드에 @Autowired 를 붙여서 의존관계 주입을 해줄수도있다. 이렇게하면 한번에 여러필드를 주입받을수있지만 앞서말한 생성자주입이나 수정자주입을 통해 대체할수 있으므로 잘 사용하지 않는다.

 

 

 

✔옵션처리

주입할 스프링빈이 없더라도 @Autowired 가 동작하도록 만들수 있게 하려면 자동주입 대상으로 옵션처리를 하면된다.

 

  1. @Autowired(required=false)
    • 자동 주입할 대상이 없다면 수정자 메서드 호출 자체가 안된다.
  2. org.springframework.lang.@Nullable
    • 자동 주입할 대상이 없으면 null이 입력된다.
    • 📌@Nullable optional은 스프링 전반에 걸쳐 사용할수가 있다.
  3. Optional<>
    • 자동 주입할 대상이 없으면 Optional.empty가 입력된다

 

 

 

✔ lombok

롬복 라이브러리가 제공하는 @RequiredArgsConstructor 기능을 사용한다면 final로 선언한 필드에 대한 생성자를 자동적으로 만들어준다.

@RequiredArgsConstructor
public class Person {
    private final String name;
    private final int age;
    private String address;
    
    // constructor
    public static void main(String[] args) {
        Person person = new Person("kim",20);
    }
}

이런식으로 생성자를 따로 만들지 않더라도 @RequiredArgsConstructor 를 통해 final 필드에 대한 생성자가 존재하는것을 볼수있다.

 

 

✔조회하는 bean이 2개 이상일때

@Autowired 를 사용하면 Type을 통해 bean을 조회하는 방식을 사용한다. 이때 타입이 같은 2개의 빈이 존재한다면 (이름은 다르지만) 오류가 발생하는데 이를 해결하는 방법은 다음과 같다.

 

  1. @Autowired 필드 명 매칭
   @Autowired
   private DiscountPolicy discountPolicy 

FixDiscountPolicy, RateDiscountPolicy 클래스는 DiscountPolicy 인터페이스를 implement받는다. 즉 위와같이 적으면 type이 같은 bean이 2개이므로 오류가난다.

   @Autowired
   private DiscountPolicy rateDiscountPolicy

이때 위와같이 필드명을통해 우리가 사용할 bean의 이름을 명시해주면 오류를 제거해줄수 있다.

 

 

  1. @Qualifier
  2. @Qualifier 를 통해 추가 구분자를 붙여줄수 있다.
   @Component
   @Qualifier("mainDiscountPolicy")
   public class RateDiscountPolicy implements DiscountPolicy {}
   
   @Component
   @Qualifier("fixDiscountPolicy")
   public class FixDiscountPolicy implements DiscountPolicy {}

위와같이 추가구분자를 붙여준후

   public OrderServiceImpl(MemberRepository memberRepository,
   				@Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy){
   		...
   }

생성자에 @Qualifier 를 사용하여 우리가 어떠한 bean을 사용할건지 명시해줄수 있다.

이때 @Qualifier 는 생성자 주입 뿐만아니라 수정자 주입 등에도 사용할수 있다.

 

 

  1. @Primary
  2. @Primary를 통해 위와같은 상황에서 RateDiscountPolicy, FixDiscountPolicy 2개가 존재할때
   @Component
   @Primary
   public class RateDiscountPolicy implements DiscountPolicy {}

@Primary 를 지정해주면 우선적으로 RateDiscountPolicy을 사용할수 있다.

 

❗❗ @Primary@Qualifier가 존재할때 우선권은 @Qualifier 이 더 높다.

Spring에서는 자동보다는 수동이, 넒은 범위의 선택권 보다는 좁은 범위의 선택권이 우선순위가 높다. 따라서 여기서도 @Qualifier(세세한 설정이 가능한)의 우선순위가 더 높은것이다.

 

 

 

✔ Annotation 만들기

Annotation을 만들면 컴파일 단계에서 잡을수없는 @Qualifier("mainDiscountPolicy") 을 (문자를 넣을때 실수할수 있기때문에 ex) mmainDiscountPolicy) Annotation을 만들면 추후 문자열을 잘못 입력했을때 컴파일단계에서 오류를 잡을수 있다.

 

 

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

위의 예시에서 FixDiscountPolicy, RateDiscountPolicy 클래스는 DiscountPolicy 인터페이스를 implement받는다. 이때 DiscountPolicy 타입의 bean을 모두 조회하고싶다면 List,Map을 사용하면 된다.

🙏Reference