DI는 소프트웨어 개발에서 의존성 주입이라는 개념을 말한다.
여기서 '의존성'이란 한 객체가 다른 객체를 필요로 하는 것을 의미한다.
ex) 자동차가 엔진이 필요하듯이 어떤 프로그램도 특정 기능을 위해 다른 코드나 객체를 필요로 한다.
프로그램의 큰 틀(추상화)에 의존하고, 작은 세부사항(구체화)에 의존하지 말라는 의미
이 원칙을 따르면 다음과 같은 장점을 얻을 수 있다.
ex) 만약 자동차의 엔진을 교체해야 할 때, 차체에 대한 구조가 바뀌지 않으면 쉽게 교체할 수 있는 것과 같다.
전통적인 방법으로, 어떤 객체가 다른 객체를 직접 생성하는 경우가 존재한다.
ex) UserService라는 객체가 UserRepository라는 객체를 직접 만드는 경우
class UserService {
private UserRepository userRepository;
public UserService() {
this.userRepository = new UserRepository(); // 의존성 직접 생성
}
}
⇒ 이렇게 되면 UserRepository를 바꿀 때마다 UserService도 수정해야 해서 불편함이 존재한다. 그래서 DI를 사용하면 UserService가 필요한 UserRepository를 외부에서 주입받도록 할 수 있다.
class UserService {
private UserRepository userRepository;
public UserService(UserRepository userRepository) { // 의존성 주입
this.userRepository = userRepository;
}
}
⇒ 이렇게 하면 UserService는 UserRepository의 여러 종류에 얽매이지 않아서 훨씬 유연해진다.
타입 의존성은 특정 클래스에 의존하는 것을 의미한다.
만약 UserService가 특정 UserRepository 클래스에 의존하면, 그 클래스를 바꿀 때마다 UserService도 바꿔야 한다.
interface UserRepository {
void save(User user);
}
class MySQLUserRepository implements UserRepository {
public void save(User user) {
// MySQL에 저장하는 로직
}
}
class UserService {
private UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository; // 외부에서 주입받음
}
}
⇒ 이렇게 하면 UserService는 특정 클래스에 의존하지 않게 되어 필요할 때 쉽게 변경할 수 있다.
DI는 여러 방법으로 구현할 수 있다.
가장 많이 쓰이는 방법은 생성자 주입 ! → 테스트하기 쉽고 코드가 깔끔하기 때문
Spring Framework는 DI를 쉽게 사용할 수 있게 도와주는 도구를 제공하고 있다.
Spring Container 안에는 여러 가지 객체(필요한 도구들)가 들어 있다. 이 객체들은 서로 도움을 주고받으며, 프로그램이 잘 작동하도록 한다.
+-----------------------+
| Spring | <- Spring 상자
| Container |
+-----------------------+
|
|
| +------------------+
| | 객체들 | <- 우리가 필요한 도구들
| +------------------+
| |
| |
| +------------------+
| | 의존성 관리 | <- 도구들이 서로 도와주는 관계
| +------------------+
|
| +------------------+
| | 생명 주기 | <- 도구가 언제 만들어지고 사라지는지 관리
| +------------------+
|
+-----------------------+
| 빈 팩토리 | <- 도구들을 만들고 관리하는 일
+-----------------------+
[ Java 파일 ]
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="userService" class="com.example.UserService"> <!-- 전체 패키지명 -->
<constructor-arg ref="userRepository"/> <!-- 생성자 주입 -->
</bean>
<bean id="userRepository" class="com.example.MySQLUserRepository"/>
</beans>
UserService와 UserRepository의 객체를 정의하고 있다.userService는 UserService 클래스를 나타내고, userRepository는 UserRepository의 구현체인 MySQLUserRepository를 참조한다.[ Java 파일 ]
@Component
public class UserService {
@Autowired // 의존성 주입
private UserRepository userRepository;
}
UserService가 사용할 UserRepository라는 도구(객체)를 Spring이 자동으로 찾아서 넣어준다는 의미이다.UserService가 UserRepository를 필요로 할 때, Spring이 자동으로 userRepository 변수에 UserRepository의 인스턴스를 할당해준다.[ XML 파일 ]
<context:component-scan base-package="com.example"></context:component-scan>
base-package="com.example"은 Spring이 com.example 경로 아래의 모든 클래스에서 @Component가 붙은 것을 찾아 자동으로 객체를 생성하겠다는 의미이다.@Configuration 어노테이션으로 설정 클래스를 만들고, 메서드에 @Bean 어노테이션을 붙여 객체를 생성한다.
[ Java 파일 ]
@Configuration
public class AppConfig {
@Bean
public UserService userService() {
return new UserService(userRepository()); // UserRepository의 생성자 주입
}
@Bean
public UserRepository userRepository() {
return new MySQLUserRepository(); // MySQLUserRepository의 인스턴스 생성
}
}
userService() 메서드는 UserService의 인스턴스를 생성하고, userRepository() 메서드는 UserRepository의 구현체인 MySQLUserRepository의 인스턴스를 생성하여 반환한다.DI는 프로그램의 유연성을 높이고, 코드의 품질을 개선하는 중요한 개념이다.
Spring Framework는 DI를 쉽게 구현할 수 있도록 다양한 방법을 제공하고 있으며, 상황에 맞는 방법을 선택하는 것이 중요하다 !