[Spring] 의존관계 주입(Dependency Injection)
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;
}
}
Leave a comment