자동 의존관계 주입
- 자동 의존관계 주입 방법
- 생성자 주입
- 수정자 주입(Setter 주입)
- 필드 주입
- 일반 메소드 주입
- 자동 의존관계 주입(@Autowired)은 스프링 컨테이너에서 관리하는 스프링 빈에서만 수행되는 기능이다.
public class MemberService implements Service {
private final Repository repository;
//생성자 주입
@Autowired
public MemberService(Repository repository) {
this.repository = repository;
}
}
- 생성자 주입 : 생성자를 통해서 의존관계를 주입 받는 방법이다.
- 생성자 호출시점에 딱 1번만 호출되는 것이 보장된다.
- 불변, 필수적 의존관계에 사용
- 생성자가 한 개만 존재하는 경우 @Autowired를 생략할 수 있다.
- 스프링 빈을 등록하려면 객체를 생성해야하므로 생성자를 호출하기에 생성자 주입을 사용하면 스프링 빈을 등록함과 동시에 자동으로 의존관계를 주입 받을 수 있다.
- 생성자 주입을 제외한 주입 방법들은 스프링 빈 등록 이후 의존관계 설정하는 과정이 따로있다.
public class MemberService implements Service {
private final Repository repository;
//수정자 주입
@Autowired
public void setRepository(Repository repository) {
this.repository = repository;
}
}
- 수정자 주입(Setter 주입) : 수정자 / Setter 메소드를 통해서 의존관계를 주입 받는 방법이다.
- 선택적, 변경 가능성이 있는 의존관계에 사용
- 자바빈 프로퍼티 규약의 수정자 메소드 방식을 사용하는 방법이다.
- @Autowired의 기본 동작은 주입할 대상이 없으면 오류가 발생한다.
- @Autowired(required = false)로 지정하면 주입 대상이 없어도 동작한다.
- 자바빈 프로퍼티 규약 : 필드에 접근할 때는 직접 접근하지 말고 setXxx(), getXxx() 메소드로 접근해야한다.
- setXxx() : 필드 값을 지정하는 용도의 메소드
- getXxx() : 필드 값을 반환하는 용도의 메소드
public class MemberService implements Service {
//필드 주입
@Autowired
private final Repository repository;
}
- 필드 주입 : 필드에 의존관계를 바로 주입하는 방법이다.
- 코드는 간결하지만 외부에서 변경이 불가능해서 테스트하기 불편하다.
- 외부에서 변경하기 위해서 Setter를 생성해서 사용해야한다.
- DI 프레임워크가 없으면 아무것도 할 수 없다.
- 순수 자바 코드로 테스트를 할 수가 없다.
- ★사용하지 않도록 권장하는 방식★
- 코드는 간결하지만 외부에서 변경이 불가능해서 테스트하기 불편하다.
- 필드 주입을 사용해도 괜찮은 영역
- 애플리케이션의 실제 코드와 관계 없는 테스트 코드
- 스프링 설정 클래스에서 특별한 용도로 사용
public class MemberService implements Service {
private final Repository repository;
//일반 메소드 주입
@Autowired
public void init(Repository repository) {
this.repository = repository;
}
}
- 일반 메소드 주입 : 일반 메소드를 통해서 주입 받는 방법이다.
- 한번에 여러 필드를 주입 받을 수 있다.
- 일반적으로 잘 사용하지 않는다.
- 주입할 스프링 빈이 없어도 동작해야할 때 옵션으로 처리해야 한다.
- @Autowired(required = false) : 자동 주입할 대상이 없으면 수정자 메소드 자체가 호출되지 않는다.
- org.springframework.lang.@Nullable : 자동 주입할 대상이 없으면 null이 입력된다.
- Optional<> : 자동 주입할 대상이 없으면 Optional.empty가 입력된다.
- 수동 빈 등록과 수동 의존관계 주입을 사용하는 경우
- 업무 로직 빈 (컨트롤러, 서비스, 레포지토리 등)은 숫자도 많고 유사한 패턴이 있기 때문에 자동 기능을 사용하는 것이 좋다.
- 기술 지원 빈 (DB연결, 공통 로그 처리 AOP 등)은 숫자도 매우 적고 잘 적용되고 있는지 파악하기 쉽지 않기에 수동 빈 등록을 사용해서 명확하게 설정 정보에 나타나게 하는 것이 좋다.
- 비지니스 로직 중 다형성을 적극 활용할 때 파악하기 쉽도록 별도의 설정 정보를 만들고 수동 빈 등록을 하거나 자동으로 하는 경우에는 특정 패키지에 같이 묶어두는 것이 좋다.
- 편리한 자동 기능(컴포넌트 스캔, 자동 의존관계 주입 등)을 기본으로 사용하자!
- 직접 등록하는 기술 지원 객체는 수동으로 등록하고, 다형성을 활용하는 비지니스 로직은 수동 등록을 고민해보자.
생성자 주입을 사용해야 하는 이유
- 스프링을 포함한 DI 프레임워크에서 생성자 주입을 권장한다.
- 불변 - 대부분 의존관계 주입은 한번 일어나면 애플리케이션 종료시점까지 의존관계를 변경할 일이 없고 변하면 안된다.(불변해야한다)
- 수정자 주입을 사용하려면 setXxx 메소드를 public으로 열어두어야 한다.
- 누군가 실수로 변경할 수 있고, 변경하면 안되는 메소드를 열어두는 것은 좋은 설계가 아니다.
- 생성자 주입은 객체 생성 시 딱 한 번만 호출되므로 이후에 호출될 일이 없다.
- 수정자 주입을 사용하려면 setXxx 메소드를 public으로 열어두어야 한다.
- 누락 - 순수 자바 코드로 단위 테스트하는 경우
- 다른 의존관계 주입 방법을 사용하면 누락될 수 있다.
- 생성자 주입을 사용하면 누락되는 경우 컴파일 오류가 발생하고 어떤 값을 필수로 주입해야하는지 알 수 있다.
- 생성자 주입을 사용하면 필드에 final 키워드를 사용할 수 있다.
- 생성자에서 값이 설정되지 않는 오류를 컴파일 시점에 막아준다.
- 생성자 주입 이외의 방법에서는 final 키워드를 사용할 수 없다.
- 생성자 주입을 사용해야 하는 이유?
- 프레임워크에 의존하지 않고 순수 자바 언어의 특징을 가장 잘 살릴 수 있다.
- 딱 한 번만 호출되므로 불변 의존관계를 보장해준다.
- 프레임워크 없이 자바 코드로 단위 테스트 시 의존관계 주입 누락을 방지할 수 있다.
- final 키워드를 사용할 수 있다.
- 기본으로 항상 생성자 주입을 사용하고 필수 값이 아닌 경우 수정자 주입 방식으로 옵션을 부여해라!
롬복(Lombok)
- 롬복(Lombok) : 별도의 코드를 작성할 필요 없이 어노테이션만으로 Getter, Setter, toString, 생성자 관련 기능을 제공하는 라이브러리이다.
- 자바의 어노테이션 프로세서 기능을 사용해서 컴파일 시점에 코드를 자동으로 생성해준다.
- intellij 롬복 설정
- build.gradle에 라이브러리, 설정 추가
- intellij에서 Lombok plugin 설치
- intellij 설정 -> Build, Execution, Deployment -> Compiler -> Annotation Processors -> Enable annotation proceesing 체크하기
- @RequiredArgsConstructor : 모든 final 필드를 매개변수로 받고 @Autowired로 선언된 생성자를 자동으로 만들어준다.
조회 빈이 2개 이상인 경우
- ex) Repository 인터페이스의 구현체로 MemoryMemberRepository와 MemberRepository가 있다고 가정
- 구현 클래스 둘 다 @Component로 컴포넌트 스캔 대상으로 지정해놨고 생성자 주입으로 구체 클래스에 의존하지 않고 추상적인 인터페이스(Repository)에 의존하는 상황
- 같은 타입의 스프링 빈이 2개 존재하므로 ( MemoryMemberRepository, MemberRepository) NoUniqueBeanDefinitionException 오류가 발생한다.
- 이 문제를 해결하기 위해서 하위 타입인 구체 클래스에 의존하면 DIP 원칙 위반
- 이름만 다르고 같은 타입의 스프링 빈이 2개 이상 있을 경우 해결방법
- @Autowired 필드명 매칭
- @Qualifier -> @Qualifier끼리 매칭 -> 빈 이름 매칭
- @Primary 사용
- @Autowired 필드명 매칭
- 먼저 타입 매칭으로 시도한다.
- 타입 매칭 결과가 여러 빈이 있으면 필드명, 매개변수 명으로 빈 이름을 추가 매칭한다.
@Component
@Qualifier("fixDiscountPolicy")
public class FixDiscountPolicy implements DiscountPolicy{...}
@Component
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
@Autowired
public OrderServiceImpl(MemberRepository memberRepository,
@Qualifier("fixDiscountPolicy") DiscountPolicy discountPolicy)
{
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
}
- @Qualifier 사용
- @Qualifier는 추가 구분자를 붙여주는 방법이다. (빈 이름을 변경하는 것x)
- @Qualifier끼리 매칭한다.
- @Qualifier 구분자를 찾지 못하는 경우 동일한 이름의 스프링 빈을 찾고 없다면 예외 발생
- 의존관계를 주입 받는 모든 코드에 @Qualifier를 붙여줘야한다는 단점이 있다.
@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy{...}
@Component
public class FixDiscountPolicy implements DiscountPolicy{...}
- @Primary 사용
- DiscountPolicy 타입의 Rate~와 Fix~가 있지만 @Primary가 사용된 Rate~ 스프링 빈이 우선권을 가진다.
- @Qualifier, @Primary 사용 예시
- 주로 사용하는 스프링 빈과 아주 가끔 특별할 때 사용하는 스프링 빈 2가지가 있을 때
주로 사용하는 빈에 @Primary를 사용해서 우선적으로 호출될 수 있도록 설정한다.
가끔 사용되는 빈에 @Qualifier를 지정해서 사용할 때만 가끔 사용되는 빈을 @Qualifier를 사용할 수 있도록 설정한다.
- 주로 사용하는 스프링 빈과 아주 가끔 특별할 때 사용하는 스프링 빈 2가지가 있을 때
- @Qualifier가 @Primary 보다 우선권이 높다.
어노테이션 직접 만들기
- @Qualifier("main~") 과 같이 문자를 적으면 컴파일 시 타입 체크가 안된다.
- 어노테이션을 직접 생성해서 사용하면 문제를 해결할 수 있다.
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Qualifier("mainDiscountPolicy")
public @interface MainDiscountPolicy {}
//선언할 때
@Component
@MainDiscountPolicy
public class RateDiscountPolicy implements DiscountPolicy {...}
//사용할 때
@Component
public class OrderService implements Service {
private final DiscountPolicy discountPolicy;
@Autowired OrderService (@MainDiscountPolicy DiscountPolicy discountPolicy) {
this.discountPolicy = discountPolicy;
}
}
- 예제에서 MainDiscountPolicy 어노테이션을 생성함으로 선언하는 곳, 사용하는 곳에서 모두 @MainDiscountPolicy 어노테이션을 사용하면 @Qualifier와 같이 사용할 수 있다.
출처 : [인프런 김영한 스프링 핵심 원리 - 기본편]
스프링 핵심 원리 - 기본편 강의 | 김영한 - 인프런
김영한 | 스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., 스프링 핵심 원리를 이해하고, 성장하는 백엔드 개발자가 되어보
www.inflearn.com
'Spring > [인프런 김영한 스프링 핵심 원리 -기본편]' 카테고리의 다른 글
[인프런 김영한 스프링 핵심 원리 - 기본편] 빈 스코프 (1) | 2024.08.15 |
---|---|
[인프런 김영한 스프링 핵심 원리 - 기본편] 빈 생명주기 콜백 (0) | 2024.08.14 |
[인프런 김영한 스프링 핵심 원리 - 기본편] 컴포넌트 스캔 (0) | 2024.08.14 |
[인프런 김영한 스프링 핵심 원리 - 기본편] 싱글톤 컨테이너 (0) | 2024.08.13 |
[인프런 김영한 스프링 핵심 원리 - 기본편] 스프링 컨테이너와 스프링 빈 (1) | 2024.08.13 |