[Spring] Spring에서 DI하는 3가지 방법
0. 들어가기 전에
스프링은 POJO(순수한 자바 객체) 형태의 객체 지향 설계를 만족하기 위해 탄생했습니다. 그렇다면 순수하게 객체 지향적으로 어떻게 코드를 작성할까요? 스프링은 DI와 IoC를 사용하는데, 이번 글에서는 DI에 대해서 설명을 하겠습니다.
1. DI 란?
DI는 Dependency Injection의 약자로, 의존관계 주입이라고 한다.
DI는 스프링의 핵심 기능 중 하나로 관심사 분리를 할 수 있게 도와준다.
'의존관계를 주입한다'라는 말을 살펴보자. 주입은 집어 넣는다는 것이라고 생각하면 되는데, 여기에서 말하는 의존관계는 무엇일까?
자바 예제 코드로 알아보자.
1.1 예제
public interface DiscountPolicy {
long discount(final long money);
}
public class FixDiscountPolicy implements DiscountPolicy {
private final long discountMoney;
public FixDiscountPolicy(final long discountMoney) {
this.discountMoney = discountMoney;
}
@Override
public long discount(final long money) {
return money - discountMoney;
}
}
public class RateDiscountPolicy implements DiscountPolicy {
private final double discountRate;
public RateDiscountPolicy(final double discountRate) {
this.discountRate = discountRate;
}
@Override
public long discount(final long money) {
return Math.round(money * discountRate);
}
}
public class Shop {
private DiscountPolicy discountPolicy;
public Shop() {
this.discountPolicy = new FixDiscountPolicy(1_000);
}
public long calculate(final long price) {
return discountPolicy.discount(price);
}
}
위의 예제 코드는 아래와 같이 연관 관계가 설정이 되어 있다.
이런 식으로 코드를 구성하면, Shop이라는 객체가 컴파일 타임에 FixDiscountPolicy(정액 할인 정책)와 연관관계가 생긴다. 이러면 setter 메서드가 없는한 Shop의 할인 정책은 정액 할인 정책으로 고정된다. Shop이 아무리 많이 생겨도 더 이상 RateDiscountPolicy(정률 할인 정책)으로 바꿀 수가 없다.
또한 FixDiscountPolicy가 변경이 되면 그 영향이 Shop에도 영향을 미치게 된다.
그렇다면 Shop이 FixDiscountPolicy를 의존한다고 말할 수 있고, 이 때 의존관계가 형성되었다고 말한다.
즉, 어떠한 한 객체가 변경되었을 때, 다른 객체에게 영향이 가면 의존관계가 있다고 말할 수 있다.
그렇다면 객체지향을 위해 어떻게 코드를 작성해야 할까? 팩토리 패턴으로 DiscountPolicy를 결정해주는 방법도 좋지만 팩토리를 호출해주는 책임이 Shop에 있기 때문에 완벽히 유연한 코드가 아니게 된다.
하지만 DI(의존관계 역전)를 사용하면, 상황에 맞게 할인정책을 선택할 수 있게 된다.
public class ShopWithDI {
private final DiscountPolicy discountPolicy;
public ShopWithDI(final DiscountPolicy discountPolicy) {
this.discountPolicy = discountPolicy;
}
public long calculate(final long price) {
return discountPolicy.discount(price);
}
}
ShopWithDI가 DiscountPolicy를 의존하는 관계로 바꾸어 보았다. 예제 코드를 보면, 생성자에서 DiscountPolicy 구현체를 파라미터로 넘겨 받게 된다. 이런 구조로 변경하면 ShopWithDI는 DiscoutPolicy의 구현체를 몰라도 되기 때문에 유연한 설계 된다. 이렇게 객체 밖에서 다른 객체를 넣어주는 것을 DI라고 한다.
1.2 특징
- 의존성이 줄어든다.
ShopWithDI
가 인터페이스에 의존하기 때문에, 구체클래스가 변경이 되더라도 기존 코드를 고칠 필요가 없어진다.
- 재사용성이 증가한다.
ShopWithDI
에DiscountPolicy
를 넣어줌으로써ShopWithDI
를 재사용할 수 있다.
- 테스트하기 좋은 코드가 된다.
DiscountPolicy
를 분리할 수 있기 때문에,ShopWithDI
를 테스하기가 쉬워진다.
2. Spring에서 DI 방법
빈으로 등록하고, @Autowired
를 사용하면 DI가 된다. 매우 간단하다! 추가로 DI하는 방법이 3가지가 있다.
2.1 필드(field) 주입
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public UserService() {
}
public void save(final User user) {
userRepository.save(user);
}
}
2.2 세터(setter) 주입
@Service
public class UserService {
private UserRepository userRepository;
public UserService() {
}
public void save(final User user) {
userRepository.save(user);
}
@Autowired
public void setUserRepository(final UserRepository userRepository) {
this.userRepository = userRepository;
}
}
2.3 생성자(Constructor) 주입
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(final UserRepository userRepository) {
this.userRepository = userRepository;
}
public void save(final User user) {
userRepository.save(user);
}
}
- 생성자 주입은 필드 주입, 세터 주입과 달리 생성자가 하나면
@AutoWired
를 생략해서 작성할 수 있다.3. 스프링에서 추천하는 DI 방법
3. 스프링에서 추천하는 DI 방법
스프링에서는 생성자 주입을 추천하는데, 그 이유는 다음과 같다.
- 생성자 호출 시점에 의존성을 주입해주기 때문에 한 번만 의존관계가 설정이 되고, 의존관계가 불변이 된다.
- 런타임시에 의존성이 변경이 안되기 때문에 안전하게 설계를 할 수 있다.
- 또한, 위에서 나온것처럼
@Autowired
를 생략할 수 있다. -> import문에 들어가지 않으므로 @Autowired와 연관관계가 없어진다.
- 순환 참조를 막는다.
- A -> B로 주입을 하면서 B -> A를 주입을 하는 코드를 만들면 애플리케이션 구동 시에 순환 참조를 잡아준다.
- 스프링 부트 2.6에서는 모든 주입 방법들이 순환참조를 막는 방법으로 변경이 되었다.
- A -> B로 주입을 하면서 B -> A를 주입을 하는 코드를 만들면 애플리케이션 구동 시에 순환 참조를 잡아준다.
- 테스트 코드를 작성이 간편하다.
- 위의 예를 들었던 ShopWithDI를 보면 생성자에서 외부 객체를 주입받기 때문에 스프링 없이도 해당 구현체를 만들어 넘겨주기만 하면 테스트가 가능하다.
- 원하는 구현체를 넣을 수 있으므로 테스트 할 때 Mock 객체를 사용해서 테스트 할 수 있다.
※ 출처
'FRAMEWORK > [SPRING]' 카테고리의 다른 글
[Spring] DispatcherServlet이란? (0) | 2023.04.27 |
---|---|
[Spring] Spring에서 Bean은 어떤 자료구조로 관리될까요? (0) | 2023.04.22 |
[Spring] RestAssured로 API 테스트 진행하기 (1) | 2023.04.13 |
[Spring] Spring ArgumentResolver란? (0) | 2022.08.10 |
[Spring] 스프링 필터(Filter) vs 인터셉터(Interceptor) (0) | 2022.08.09 |