공부 자료/Spring

[Spring] @Autowired를 사용한 DI 다양한 의존 관계 주입 방법

희희웃는청바지 2023. 11. 6. 09:07

 

@AutoWired를 이용한 의존성 주입

 

* 아래 4가지 방식 모두 의존 관계로 주입하려는 클래스가 스프링 컨테이너에 등록되어있는 Bean이어야 하며

Spring Bean이 아닌 클래스는 @Autowired가 불가능함

 

 

@Autowired란?

: 필요한 의존 객체의 타입에 해당하는 빈을 찾아 주입하며, 객체 타입에는 생성자/setter/필드/일반 메서드가 존재

: @Autowired는 기본값이 true 이기 때문에 의존성 주입 대상을 찾지 못하면 애플리케이션이 실행되지 않음

 


 

[생성자 주입]

 

생성자 주입)

: 생성자를 통해 의존 관계를 주입 받음

: @Autowired를 생성자에 하면 스프링 컨테이너에 @Component로 등록된 빈에서 생성자에 필요한 빈을 주입함

: 아래에 특징들로 인해 filed 나열 방법으로 의존성을 주입하는 것을 권고

 

특징)

: 생성자 호출 시점에 딱 1번만 호출되는 것이 보장 >> 생성자 호출 시점에서만 의존 관계가 주입되기 때문에 아래 불편과 필수 특성을 갖게 됨

: 불변과 필수(누락방지) 의존 관계에 사용

: 생성자가 1개만 존재하는 경우 @Autowired 생략을 하더라도 자동 주입됨 (스프링이 해당 클래스 객체를 생성하여 빈에 넣어야 하는데 생성할 때 생성자를 부를 수 밖에 없게 되면서 빈을 등록하면서 의존 관계 주입도 같이 발생하기 때문)

: NullPointerException 방지가 가능

: 주입받을 필드를 final로 선언 가능 (객체 생성 시점에 초기화가 이루어져야 하기 때문에 생성자를 이용해 final 타입을 가진 필드 변수를 초기화할 경우 쉽게 의존 관계 파악이 가능함)

@Component
public class OrderServiceImpl implements OrderService {

    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;

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

 

 


 

[수정자 주입 (setter 주입)]

 

수정자 주입)

: setter라 불리는 필드값을 변경하는 수정자 메서드를 통해 의존관계를 주입하는 방법

 

특징)

: 선택과 변경 가능성이 있는 의존 관계에 사용

: 자바빈 프로퍼티 규약의(필드의 값을 직접 변경하는 것이 아닌 getter와 setter 메서드를 사용해 값을 읽거나 수정하는 규약) 수정자 메서드 방식을 사용

 

: 수정자의 경우 @Autowired가 없으면 값이 의존 관계 주입이 되지 않음

: final 선언이 불가능

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

 

 


 

[필드 주입]

 

필드 주입)

: @Autowired를 붙여서 바로 주입하는 방법

 

특징)

: 가장 간단한 선언 방식

 

: 코드가 간결하지만 외부에서 변경이 불가능하다는 단점이 존재해 권장하지 않음

: DI 프레임워크가 없으면 아무것도 할 수 없음

: 실제 코드와 관계 없는 테스트 코드를 다룰때나 스프링 설정을 목적으로 만드는 @Configuration 에서만 한정적으로 사용

: 정상적으로 작동되려면 setter가 필요하기에 수정자 주입 사용이 더 편리함

: final 선언이 불가능함

@Component
public class OrderServiceImpl implements OrderService {

    @Autowired private MemberRepository memberRepository;

    @Autowired private DiscountPolicy discountPolicy;
}

 

 

[일반 메서드 주입]

일반 메서드) 

: 일반 메서드를 주입하는 방법으로 사실상 수정자 주입 방식과 거의 동일(setter가 아닌 일반 메서드를 이용한다는 차이)

 

특징)

: 한 번에 여러 필드를 주입받을 수 있다는 장점이 있지만 일반적으로 잘 사용하지는 않음

@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. 생성자 주입

@Service
public class MemberService{
	MemberRepository memberRepository;
    
    @Autowired
    public MemberService(MemberRepository memberRepository){
    	this.memberRepository = memberRepository;
    }
}

: Autowired의 의존성 주입을 위해 빈이 등록되어 있는 객체 중에서 빈을 찾게 되는데 만약 MemberRepository가 빈으로 등록되어 있지 않을 경우, 빈이 없기 때문에 의존성 주입에 실패해 에러가 발생함

: MemberRepository가 빈 등록이 되어 있지 않은 경우, MemberService도 빈으로 등록되지 못하는 경우가 발생

 

 

2. setter 주입

@Service
public class MemberService{
	MemberRepository memberRepository;
    
    @Autowired
    public void setMemberService(MemberRepository memberRepository){
    	this.memberRepository = memberRepository;
    }
}

: MemberRepositoryry가 빈으로 등록되어 있지 않은 경우 여전히 Autowired가 붙은 setter에서도 에러가 발생함

: setter의 경우 MemberService는 빈으로 등록이 가능함

 

@Service
public class MemberService{
	MemberRepository memberRepository;
    
    @Autowired(required=false)
    public void setMemberService(MemberRepository memberRepository){
    	this.memberRepository = memberRepository;
    }
}

: 기본값을 false로 바꿀 경우 MemberService는 빈으로 등록되지만, MemberRepository는 빈으로 등록이 안됨

 

 

3. 필드 주입

@Service
public class MemberService{
	MemberRepository memberRepository;
    
    @Autowired
    MemberRepository memberRepository
}

: 필드에도 Autowired 사용 가능하며, MemberRepository가 빈으로 등록되어 있지 않을 경우 MemberService는 빈 등록이 가능

 

* MemberRepository가 빈으로 등록되지 않은 경우 생성자는 MemberService도 빈으로 등록되지 못할 수 있지만, setter/필드 주입은 빈으로 등록이 가능

 

 

추가) 주입할 스프링 빈이 없을 때 동작해야 하는 경우

: @Autowired 만 사용하는 경우 required 옵션 기본값인 true가 사용되어 자동 주입 대상이 없을 경우 오류가 발생하는 경우가 있을 수 있음

 - @Autowired(required=false) : 자동 주입할 대상이 없으면 수정자 메서드 자체가 호출되지 않게 함

 - org.springframework.leng.@Nullable : 자동 주입할 대상이 없으면 null 이 입력

 - Optional<> : 자동 주입할 대상이 없으면 Optional.empty가 입력

 

 


 

 

[빈이 여러개인 경우]

 

MemberRepository 인터페이스 아래 MyMemberRepository/YourMemberRepository 클래스가 존재하고

둘 다 빈이 등록되어 있는 경우에는 Autowired 적용 시 주입이 불가능한데,

이는 등록해야 할 빈이 2개이기 때문이다.

이 때에는 어떻게 해야할까?

 

 

1. @Primary 애너테이션을 통해 빈 등록

@Repository @Primary
public class MyMemberRepository implements MemberRepository{}

: 등록할 빈 위에 @Primary 애너테이션을 작성

: 3가지 방법 중에 제일 추천

 

 

2. @Qualifier 애너테이션을 통해 빈 등록

 

: 위에 처럼 작성해도 되지만 @Qualifier 애너테이션을 사용하여 빈 등록도 가능

: MemberRepository 중 등록하고자 하는 빈의 이름을 적어주면 됨

: @Qualifier 애너테이션을 사용하는 것 보다 @Primary가 더 안전한 방법이기에 @Primary 방법을 추천

@Service
public class MemberService{
	@Autowired @Qualifier("myMemberRepository")
    MemberRepository memberRepository;
}

 

 

3. List를 통해 해당하는 타입의 빈을 모두 등록

@Service
public class MemberService{
	@Autowired
    List<MemberRepository> memberRepository;
}

: List를 통해 MemberRepository에 해당하는 타입의 빈을 모두 주입 가능함

 


 

@Autowired 애너테이션으로 의존성 주입이 가능한 이유는?

 

BeanPostProcessor 라는 라이프 사이클 인터페이스의 구현체인

AutowiredAnnotationBeanPostProcessor에 의해 의존성 주입이 이루어짐

 

- BeanPostProcessor는 빈의 initializing 라이프 사이클 이전/이후에 필요한 부가 작업을 할 수 있는 라이프 사이클 콜백

- AutowiredAnnotationBeanPostProcessor는 빈의 초기화 라이트 사이클 이전(빈이 생성되기 이전) @Autowired가 붙어있으면 해당하는 빈을 찾아서 주입해주는 작업 진행