오늘 IoC랑 DI 개념 정리했다.
사실 그동안 그냥 @Service 붙이면 되는 거 아닌가 하고 넘겼는데 제대로 짚고 가야 할 것 같아서 정리한다.
IoC (Inversion of Control) - 제어의 역전
직역하면 제어의 역전인데 처음엔 이게 무슨 말인지 몰랐다.
원래는 이렇게 쓴다.
public class UserService {
private UserRepository userRepository = new UserRepository();
}
내가 직접 new로 객체를 만드는 거다. 근데 Spring 쓰면 이걸 내가 안 해도 된다.
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
Spring이 알아서 객체 만들고 넣어준다. 이게 IoC다. 객체를 내가 제어하는 게 아니라 Spring이 제어하니까 "제어가 역전됐다"는 거였다. 이름이 어렵게 느껴지는데 개념 자체는 별거 없었다.
Bean (빈)
Spring이 관리하는 객체를 빈이라고 부른다. 그리고 그 빈들을 담아두는 공간이 IoC 컨테이너 (ApplicationContext).
빈 등록하는 방법
1. 어노테이션으로 등록
@Component
@Service
@Repository
@Controller
이거 붙이면 Spring이 알아서 빈으로 등록해준다.
@Service, @Repository, @Controller 다 내부적으로 @Component 포함하고 있음. 역할 구분하려고 나눠놓은 거라고 한다.
@Service
public class ParkingService {
// 그냥 이렇게만 해도 빈으로 등록됨
}
2. @Configuration + @Bean으로 직접 등록
외부 라이브러리처럼 내가 소스코드를 못 건드리는 경우에 쓴다.
@Configuration
public class SecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
BCryptPasswordEncoder는 외부 클래스라서 @Component 못 붙이니까 이렇게 수동으로 등록하는 거다.
프로젝트에서 시큐리티 설정할 때 이 방식 썼는데 그때는 왜 이렇게 하는지 몰랐는데 이제 이해된다.
DI (Dependency Injection) - 의존성 주입
IoC를 구현하는 방법이다. Spring이 빈끼리 연결해주는 방식.
주입 방법이 3가지가 있다.
1. 생성자 주입 (권장)
@Service
public class VehicleService {
private final VehicleRepository vehicleRepository;
public VehicleService(VehicleRepository vehicleRepository) {
this.vehicleRepository = vehicleRepository;
}
}
final 쓸 수 있어서 불변성 보장되고, 순환 참조도 컴파일 시점에 잡힌다. Spring 공식 권장 방식.
2. 필드 주입 (비권장)
@Service
public class VehicleService {
@Autowired
private VehicleRepository vehicleRepository;
}
코드가 짧아서 처음엔 이게 편해 보였는데 final 못 쓰고 테스트할 때 문제가 생긴다.
3. setter 주입
@Service
public class VehicleService {
private VehicleRepository vehicleRepository;
@Autowired
public void setVehicleRepository(VehicleRepository vehicleRepository) {
this.vehicleRepository = vehicleRepository;
}
}
처음엔 IoC, DI, 빈이 다 따로따로인 줄 알았는데 정리하고 보니까 결국 하나로 이어지는 개념이었다.
선택적 의존성 있을 때 쓰는 거라는데 솔직히 아직 이걸 쓸 상황이 잘 안 떠오른다.
셋의 관계 정리
IoC
└── IoC 컨테이너가 Bean 생성·관리
└── DI로 Bean 간 의존 관계 연결
느낀 점
솔직히 처음에 @Autowired 붙이면 되는 거 아닌가 하고 이해도 없이 쓰고 있었다.
근데 프로젝트하다가 순환 참조 오류 터지고 나서야 왜 생성자 주입 써야 하는지 몸으로 깨달았다.
필드 주입이 코드 짧아서 편해 보이는데 테스트 짤 때 의존성 못 넣어줘서 결국 다 생성자 주입으로 바꿨다.
그때부터 그냥 동작하니까 쓰는 거랑 이유 알고 쓰는 거 차이를 느꼈다.
Spring 처음 배울 때 제일 헷갈렸던 부분인데 이번에 제대로 정리하고 나니까 코드 읽는 게 좀 더 편해진 것 같다.