Updated:

1. 개요

스프링은 @Autowired Annotation을 통해 자동으로 의존관계를 주입할 수 있다. 이번에는 의존관계 주입(Dependency Injection)에 대해 알아보도록 하자.

2. 의존관계 주입 방법

2-1. 생성자 주입

생성자를 통해 의존관계를 주입받는 방법으로, 생성자 호출 시점에 한 번만 호출된다. 대부분 생성자 주입을 통해 의존관계를 주입하고, 스프링에서도 생성자 주입을 권장하고 있다. 생성자가 한 개만 존재하는 경우 @Autowired를 생략할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
@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;
    }
}

2-2. 수정자 주입

수정자를 통해 의존관계를 주입받는 방법으로, 생성자 주입과는 달리 실행 중간에 수정자를 통해 주입받는 객체를 변경할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Component
public class OrderServiceImpl implements OrderService {

    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;

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

2-3. 필드 주입

필드를 통해 의존관계를 주입받는 방법으로, 간결하지만 외부에서 변경이 불가능하여 테스트가 힘들다는 단점이 있다.

1
2
3
4
5
6
7
8
9
@Component
public class OrderServiceImpl implements OrderService {

    @Autowired
    private MemberRepository memberRepository;
    
    @Autowired
    private DiscountPolicy discountPolicy;
}

2-4. 메서드 주입

일반 메서드를 통해 주입받는 방법으로, 한 번에 여러 필드를 주입받을 수 있지만 잘 사용하지 않는다.

1
2
3
4
5
6
7
8
9
10
11
12
@Component
public class OrderServiceImpl implements OrderService {

    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;

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

3.의존관계 주입 옵션

빈이 존재하지 않더라도 동작해야 될 경우, 의존관계 주입의 옵션을 변경하여 설정가능하다.

3-1. @Autowired(required=false)

주입 대상이 없으면 해당 메서드 호출 안 됨

[Member.java]

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Member {

    private Long id;

    private String name;

    private Grade grade;

    public Member(Long id, String name, Grade grade) {
        this.id = id;
        this.name = name;
        this.grade = grade;
    }

[AutowiredTest.java]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class AutowiredTest {

    @Test
    void autowiredOption() {
        ApplicationContext ac = new AnnotationConfigApplicationContext(TestBean.class);
    }

    static class TestBean {

        @Autowired(required = false)
        public void setNoBean1(Member noBean1) {
            System.out.println("noBean1 = " + noBean1);
        }
    }
}

[실행 결과]

3-2. @Nullable

주입 대상이 없으면 null 주입

[Member.java]

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Member {

    private Long id;

    private String name;

    private Grade grade;

    public Member(Long id, String name, Grade grade) {
        this.id = id;
        this.name = name;
        this.grade = grade;
    }

[AutowiredTest.java]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class AutowiredTest {

    @Test
    void autowiredOption() {
        ApplicationContext ac = new AnnotationConfigApplicationContext(TestBean.class);
    }

    static class TestBean {

        @Autowired
        public void setNoBean2(@Nullable Member noBean2) {
            System.out.println("noBean2 = " + noBean2);
        }
    }
}

[실행 결과]

3-3. Optional

주입 대상이 없으면 Opeional.empty 주입

[Member.java]

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Member {

    private Long id;

    private String name;

    private Grade grade;

    public Member(Long id, String name, Grade grade) {
        this.id = id;
        this.name = name;
        this.grade = grade;
    }

[AutowiredTest.java]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class AutowiredTest {

    @Test
    void autowiredOption() {
        ApplicationContext ac = new AnnotationConfigApplicationContext(TestBean.class);
    }

    static class TestBean {

        @Autowired
        public void setNoBean3(Optional<Member> noBean3) {
            System.out.println("noBean3 = " + noBean3);
        }
    }
}

[실행 결과]

4. 중복 빈 처리

@Autowired를 통한 의존관계 주입은 타입을 기준으로 조회하기 때문에, 동일한 타입이 여러 개 존재하는 경우 문제가 발생한다. 따라서 여러 개 존재하는 경우 어떤 빈을 주입받을 지 선택이 필요하다.

4-1. 문제 상황

[ApplicationContextSameBeanFindTest.java]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class ApplicationContextSameBeanFindTest {

    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SameBeanConfig.class);

    @Test
    void findBeanByTypeDuplicate() {
        ac.getBean(DiscountPolicy.class);
    }

    @Configuration
    static class SameBeanConfig {

        @Bean
        public DiscountPolicy rateDiscountPolicy() {
            return new RateDiscountPolicy();
        }

        @Bean
        public DiscountPolicy fixDiscountPolicy() {
            return new FixDiscountPolicy();
        }
    }
}

[실행 결과]

4-2. 해결 방법

4-2-1. 빈 이름으로 매칭

의존관계 주입 시, 먼저 타입을 통한 매칭을 시도 후 동일한 타입의 빈이 여러 개 존재하는 경우 빈의 이름으로 매칭을 시도한다. 따라서 필드명을 주입받고자 하는 빈의 이름으로 변경하면, 해당 이름을 가진 빈이 주입된다.

[RateDiscountPolicy.java]

1
2
@Component
public class RateDiscountPolicy implements DiscountPolicy {}

[FixDiscountPolicy.java]

1
2
@Component
public class FixDiscountPolicy implements DiscountPolicy {}

[OrderServiceImpl.java]

1
2
3
4
5
6
7
8
9
10
11
12
13
@Component
public class OrderServiceImpl implements OrderService {

    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;

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

Line 8~9 : 파라미터 명을 빈 이름으로 변경

4-2-2. @Qualifier 사용

@Qualifier를 통해 추가적인 구분자를 지정해주고, 해당 구분자를 통해 원하는 빈을 주입받을 수 있다.

[RateDiscountPolicy.java]

1
2
3
@Component
@Qualifier("mainDiscountPolicy")
public class RateDiscountPolicy implements DiscountPolicy {}

Line 2 : 해당 빈의 추가 구분자 지정

[FixDiscountPolicy.java]

1
2
3
@Component
@Qualifier("fixDiscountPolicy")
public class FixDiscountPolicy implements DiscountPolicy {}

Line 2 : 해당 빈의 추가 구분자 지정

[OrderServiceImpl.java]

1
2
3
4
5
6
7
8
9
10
11
12
13
@Component
public class OrderServiceImpl implements OrderService {

    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;

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

Line 2 : 추가 구분자를 통해 원하는 빈 주입

4-2-3. @Primary 사용

@Primary를 통해 우선순위가 되는 빈을 지정할 수 있다. 여러 개의 빈이 존재하는 경우 @Primary가 붙은 빈이 주입된다.

[RateDiscountPolicy.java]

1
2
3
@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy {}

Line 2 : 우선순위 부여

[FixDiscountPolicy.java]

1
2
@Component
public class FixDiscountPolicy implements DiscountPolicy {}

[OrderServiceImpl.java]

1
2
3
4
5
6
7
8
9
10
11
12
13
@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;
    }
}

Updated:

Leave a comment