이 게시글은 인프런의 유료 강의 스프링 핵심 원리 - 기본편을 수강하며 공부한 내용을 담고있습니다.
저작권에 의해 강의내용에 해당하는 전체 코드는 공개할 수 없습니다.
저작권에 의해 모든 내용을 담을 수 없습니다. 중복 및 반복되는 내용은 생략했습니다.
의존관계 주입 방법
- 생성자 주입
- 필드 주입
- setter 주입
- 일반 메서드 주입
생성자 주입 (Constructor Injectioin)
이전에 배웠던 방법인 생성자 위에 @Autowired 를 붙여서 의존관계를 주입하는 방법이다.
1
2
3
4
5
6
7
8
9
10
@Component
public class MemberServiceImpl implements MemberService{
private final MemberRepository memberRepository;
@Autowired // 생략가능
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
// (이하 생략)
}
특징
- 생략이 가능하다. But 생성자가 여러개면 생략하면 안된다.
- 불변/필수 의존관계에 사용
불변한 의존관계는 다음과 같은 경우를 말한다.
만약 의존관계가 한번 설정이되고 다신 바꿀 필요가 없다면 생성자를 통해 의존관계를 주입해주면 된다.
생성자를 통해 주입했으므로 추후 변경할 수 없다.
필수 의존관계는 다음과 같은 경우를 말한다.
이 Bean A 가 동작하는데 반드시 Bean B 가 필요한 경우를 말한다.
즉 Bean A 가 생성될 때 Bean B를 반드시 주입받아야 되는 경우이다.
사실 이전까지 계속 생성자 주입만 사용해왔다. 그래서 Bean 생성 할 때 의존관계를 반드시 주입해야되는 것처럼 느껴지지만, Bean 을 생성하고 나중에 의존관계를 주입할 수 있다.
왜냐하면 Bean 내부에 다른 의존관계가 필요없이 동작하는 로직을 넣을 수도 있기 때문이다.
결론적으로 Bean 을 생성할 때 불변/필수적으로 의존관계가 필요하면 ‘setter 주입’ 또는 ‘일반 메서드 주입’ 말고 ‘생성자 주입’을 사용하는게 좋다.
필드 주입 (Field Injection)
필드 주입은 의존관계를 연결해야하는 객체 앞에 @Autowired 를 붙이는 방법이다.
1
2
3
4
5
6
7
@Component
public class MemberServiceImpl implements MemberService{
@Autowired
private MemberRepository memberRepository ;
// 이하 생략
}
이렇게 간단하게 의존관계가 필요한 객체에 @Autowired 를 붙여주면 Spring 프레임워크에서 memberRepository 를 찾아서 주입해준다.
특징
- 외부에서 의존관계 변경이 불가능
- 테스트 코드에만 사용하는 것을 권장
필드 주입은 외부에서 의존관계 변경이 불가능 하기 때문에 테스트 코드에만 사용하는 것이 권장된다.
외부에서 의존관계 변경이 가능한 예시는 다음과 같다.
1
2
3
4
5
6
7
8
9
@Configuration
public class AppConfig {
@Bean
public MemberService memberService() {
return new MemberServiceImpl(memberRepository()); // memberRepository() 를 다른 reposiotry 로 변경 가능
}
// 이하 생략
}
‘생성자 주입 방식’을 사용한다면 위 코드처럼 외부에서 어떤 생성자를 주입할 지 지정할 수 있다.
‘setter 주입’이나 ‘일반 메서드 주입’도 마찬가지다.
하지만 ‘필드 주입’은 불가능하다.
Setter 주입 (Setter Injection)
Java 의 Getter Setter 문법을 사용하여 의존관계를 주입하는 방법이다.
1
2
3
4
5
6
7
8
9
10
@Component
public class MemberServiceImpl implements MemberService{
private MemberRepository memberRepository;
@Autowired(required = true) // default 값은 true
public void setMemberRepository(MemberRepository memberRepository) {
this.memberRepository = memberRepository
}
// (이하 생략)
}
흔히 아는 Setter 문법을 차용하고 위에 @Autowired 가 붙었다고 생각하면된다.
@Autowired 에 required 를 true 로 주면 객체가 생성 될 때 주입할 의존관계가 반드시 존재해야한다.
즉 위 코드로 따지면 memberRepository 가 존재해야한다.
만약 MemberServiceImpl 빈이 생성될 때 의존관계가 바로 필요하지 않은 경우(또는 생성되지 않은 경우)라면 required = false 로 주면된다.
일반 메서드 주입
Setter 주입이랑 똑같다. 다만 Setter 문법이 아닐뿐이다.
1
2
3
4
5
6
7
8
9
10
11
@Component
public class MyService {
private ServiceA serviceA;
private ServiceB serviceB;
@Autowired
public void myMethod(ServiceA serviceA, ServiceB serviceB) {
this.serviceA = serviceA;
this.serviceB = serviceB;
}
}
옵션 처리
여러가지 이유로 의존관계를 주입할 때, Bean 이 존재하지 않을 수 있다.
그런 경우 다음과 같은 방법을 사용할 수 있다.
다음 코드들은 memberRepository 라는 Bean 이 없을 때 옵션처리하는 방법이다.
@Autowired(required = false)
1
2
3
4
5
6
7
8
9
@Component
public class MemberServiceImpl implements MemberService{
@Autowired(required = false)
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
System.out.println(memberRepository); // 호출 안됨.
}
// 이하 생략
}
이렇게 required = false 로 주고 memberRepository 빈이 존재하지 않으면 생성자는 호출되지 않는다.
@Nullable
1
2
3
4
5
6
7
8
9
@Component
public class MemberServiceImpl implements MemberService{
@Autowired
public MemberServiceImpl(@Nullable MemberRepository memberRepository) {
this.memberRepository = memberRepository;
System.out.println(memberRepository); // null 출력
}
// 이하 생략
}
위 코드처럼 작성하면된다.
의존관계를 주입하는 타이밍에 memberRepository 가 없으면 memberRepository 에 null 값이 들어간다.
memberRepository 를 출력해보면 null 이 출력된다.
Optional<>
Optional 은 자바 프로그래밍을 이미 해봤다면 익숙할 것이다.
어떤 객체가 null 이 올 수도 있다는 것을 대비해서 감싸는 역할을 해준다.
1
2
3
4
5
6
7
8
9
@Component
public class MemberServiceImpl implements MemberService{
@Autowired
public MemberServiceImpl(Optional<MemberRepository> memberRepository) {
this.memberRepository = memberRepository;
System.out.println(memberRepository); // Optional.empty 출력.
}
// 이하 생략
}
이렇게 작성하고 memberRepository 라는 빈이 없으면 memberRepository 출력 시 Optional.empty 가 출력된다.
생성자 주입을 권장
생성자 주입을 권장하는 이유는 다음과 같다.
- 대부분의 경우에는 의존관계를 변경할 일이 없다.(불변하다)
- 보통의 경우에는 오히려 의존관계를 변경하면 안된다.
- Setter 로 변경할 수 있게 열어두면 다른 개발자가 변경할 가능성이 반드시 존재한다.
- 따라서 의존관계를 final 키워드를 붙여 변경할 수 없게 설계하는게 좋다.
- final 을 붙이면 의존관계 설정(생성자 주입)을 누락했을 때 컴파일 오류가 발생하므로 발견하기가 쉽다.
1
2
3
4
5
6
7
8
9
10
@Component
public class MemberServiceImpl implements MemberService{
private final MemberRepository memberRepository;
@Autowired
public void setMemberRepository(MemberRepository memberRepository) {
this.memberRepository = memberRepository
}
// (이하 생략)
}
java 의 final 변수는 생성자에서 초기화가 가능하므로 생성자 주입을 통해 초기화 한다.
필드 주입을 사용해 초기화 하는 방법은 권장되지 않는다.
필드 주입은 테스트 코드에서 임의로 의존관계를 변경할 수 없기 때문이다.
Lombok 과 최신 트렌드(?)
우선 Lombok 라이브러리를 추가하고 애노테이션 프로세서를 활성화 하고오자.
Lombok 라이브러리가 제공하는 기능 중 일부는 다음과 같다.
- Getter Setter 애노테이션 제공
- 다양한 생성자 애노테이션 제공
자바로 코딩을 하면 Getter Setter 를 정말 많이 사용하게 되는데, 이를 간편하게 작성할 수 있다.
물론 이미 IDE 의 단축키를 이용하는 것이 익숙해져 더 간편해질 수 있을까 싶지만, Getter Setter 코드를 눈에 보이지 않게 하여 더욱 간결한 코드가 된다.
Getter Setter 는 사용자가 커스텀하는 경우도 없으니 눈에 보이지 않는다고 해서 문제될 것도 없다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Getter
@Setter
@ToString // toString 함수도 직접 오버라이딩 하지 않고 간편하게 사용할 수 있게된다.
public class LombokTest {
private int num;
private String name;
@Test
public void getterSetterTest() {
LombokTest myTest = new LombokTest();
myTest.setNum(1);
myTest.setName("test");
System.out.println("myTest : "+ myTest); // myTest : LombokTest(num=1, name=test) 츨력 됨
}
}
위 코드를 보면 Setter 를 직접 구현하지 않았음에도 Setter 를 사용할 수 있게 된다.
@Setter 애노테이션이 붙어 있으면 애노테이션 프로세서에 의해 Setter 코드를 자동으로 작성해서 삽입된다.
실제로 컴파일된 .class 파일을 디컴파일 해보면 Setter 코드가 작성되 있는걸 볼 수 있다고 한다.
그럼 이걸 응용하면 생성자 주입도 자동으로 작성할 수 있다는 것을 알 수 있다.
1
2
3
4
5
6
7
8
9
10
11
@Component
@RequiredArgsConstructor
public class MemberServiceImpl implements MemberService{
private final MemberRepository memberRepository;
// @RequiredArgsConstructor 가 아래 생성자 코드를 작성해줌.
// public void setMemberRepository(MemberRepository memberRepository) {
// this.memberRepository = memberRepository
// }
}
우선 생성자 주입은 @Autowired 를 생략해도 된다. 이말은 즉 생성자만 있어도 된다는 의미이다.
여기에 @RequiredArgsConstructor 를 이용하면 final 이 붙은 멤버변수를 이용해 생성자를 자동으로 작성해준다.
그러면 해당 애노테이션 하나만으로 생성자 코드를 직접 작성할 일이 없어지게 된다. 필드 주입처럼 간편하면서 동시에 생성자 주입으로 구현이 된다.
물론 특정 로직이 생성자에 들어가야하면 직접 작성해야한다.
의존관계 중복
문제 원인
앞서 빈 생성을 할 때 이름이 같은 빈을 여러개 만들면 충돌 나는 것을 공부했었다.
이것도 마찬가지로 동일한 Type 의 빈이 여러개 있으면 발생하는 문제이다.
1
2
3
4
5
6
7
8
9
10
@Component
public class MemberServiceImpl implements MemberService{
private final MemberRepository memberRepository;
@Autowired // 생략 가능
public void setMemberRepository(MemberRepository memberRepository) {
this.memberRepository = memberRepository
}
// (이하 생략)
}
생성자 주입을 하게되면 Type 을 가지고 빈 검색을 한다.
위 코드를 보면 MemberRepository Type 인 Bean 을 찾게 되는데, 만약 OracleMemberRepository, MemberRepository, MySQLMemberRepository 등 여러개 존재하면 어떤 의존관계를 주입해야할지 모르기 때문에 예외가 발생한다.
해결 방법
- @Autowired 의 필드명 매칭
- @Qualifier
- @Primary
@Autowired 필드명 매칭
1
2
3
4
5
6
// 생략
@Autowired
public MemberServiceImpl(MemberRepository memoryMemberRepository) {
this.memberRepository = memoryMemberRepository;
}
// 생략
생성자에 Autowired 를 붙이면 MemberRepository Type 인 Bean 을 찾는다.
하지만 MemberRepository Type 인 Bean 이 여러개면 필드명을 가지고 찾는다.
위 코드를 보면 필드명을 memoryMemberRepository 로 주었다. 따라서 memoryMemberRepository 라는 이름의 Bean 을 찾아서 의존관계를 주입해준다.
@Qualifier
Qualifier 는 수식어, 한정어 라는 의미이다.
쉽게 생각해서 별명을 붙여준다 생각하면 된다.
1
2
3
4
5
@Component
@Qualifier("mainDB")
public class MemoryMemberRepository implements MemberRepository{
// 생략
}
memoryMemberRepository 빈에 mainDB 라는 별명을 붙여주었다.
1
2
3
4
5
6
// 생략
@Autowired
public MemberServiceImpl(@Qualifier("mainDB") MemberRepository memberRepository) {
this.memberRepository = memoryMemberRepository;
}
// 생략
이렇게 @Autowired 로 주입되는 곳에 @Qualifier 를 사용하여 mainDB 를 주입할 수 있다.
만약 mainDB 를 주입하려는데, 해당 이름을 가진 Qualifier 가 없다면 mainDB 라는 이름의 Bean 을 찾아서 주입한다.
@Primary
primary 는 주된 이라는 의미이다.
즉 여러개의 Bean 이 존재할 때 primary 가 붙은 Bean 이 기본 값으로 주입이된다.
1
2
3
4
5
6
@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy{}
@Component
public class FixDiscountPolicy implements DiscountPolicy{}
예를 들어 위 코드처럼 작성하면 DiscountPolicy 를 Autowired 로 주입해야할 때, @Primary 가 붙은 컴포넌트가 우선 주입이된다.
애노테이션 직접 만들기
바로 위에서 @Qualifier 를 사용해 Bean 을 특정지을 수 있긴 하지만 오타가 발생했을 때, 컴파일 단계에서 잡아낼 수 없다.
예를 들어 @Qualifier(“mainDB”) 가 아니라 @Qualifier(“mainDD”) 라고 작성해도 컴파일 오류가 발생하지 않는다.
이를 방지하기 위해 직접 애노테이션을 정의해 그 안에 @Qualifier(“mainDB”) 를 포함시켜주면 해당 문제를 방지할 수 있다.
직접 코드를 작성하여 이해해보자.
1
2
3
4
5
6
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Qualifier("SubDB")
public @interface SubDB {
}
New > java class > annotation 을 선택해서 SubDB 를 하나 만들어 주고, Qualifier 코드에 작성되어 있는 @Target, @Retention, @Documented 를 복사해온다.
그리고 @Qualifier(“SubDB”) 를 작성해준다.
이렇게 하면 필요한 곳에 @SubDB 를 붙여주면 @Qulifier(“SubDB”) 를 한 것과 동일하게 된다.
이 방법이 좋은 이유는 만약 @SubDD 라고 입력할 경우 @SubDD 가 없다는 컴파일 에러를 볼 수 있기 때문에 오류를 잡기 쉽다.
타입이 중복되는 모든 빈이 필요할 때
예를 들어 fixDiscountPolicy, rateDiscountPolicy 중 하나만 필요한 경우도 있지만 여러개가 필요한 경우가 있다.
1
2
@Autowired
public 생성자명(DiscountPolicy fixDiscountPolicy, DiscountPolicy rateDiscountPolicy) {}
이렇게 주입받는 방법도 있겠지만 만약 두 개가 아니라 10개가 필요하다면 일일히 작성하는 것이 비효율적일 것이다.
@Autowired 는 list 와 Map 에 대해서도 지원하기 때문에 다음과 같이 작성할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
private final Map<String, DiscountPolicy> policyMap;
@Autowired
public 생성자명(Map<String, DiscountPolicy> policyMap) {
this.policyMap = policyMap;
}
// 또는
private final List<DiscountPolicy> policyList;
@Autowired
public 생성자명(List<DiscountPolicy> policyList) {
this.policyList = policyList;
}
주입방법은 기존의 의존관계 주입하는 방법과 동일하다.
Map 과 List 는 java 의 기초문법이므로 생략한다.
이전 포스팅 : 컴포넌트 스캔
다음 포스팅 : 자동,수동 빈 등록 기준