1. 💻 Spring의 IoC와 DI, 그리고 그 동작 방식
Spring에서 중요한 개념 중 하나는 IoC(제어의 역전)과 DI(의존성 주입)입니다. 이 두 가지는 결국 객체의 생성과 관리를 누가 담당하는지에 관한 이야기입니다.
원래는 개발자가 프로그램 내에서 필요한 객체를 직접 생성하고, 의존 관계가 있으면 그 관계를 직접 설정해야 했습니다. 하지만 Spring은 IoC를 통해 이런 책임을 프레임워크가 맡습니다. 쉽게 말하면, 객체 생성과 의존성 관리의 제어권을 Spring이 가져가고, 개발자는 비즈니스 로직에만 집중할 수 있게 되는 것이죠.
IoC 개념을 구현하는 방법이 바로 DI(Dependency Injection)입니다. DI는 말 그대로 객체가 필요한 의존성을 외부에서 주입받는 방식입니다. 예를 들어, 서비스 클래스가 Repository
를 필요로 한다면, 개발자가 직접 Repository
객체를 생성하지 않고, Spring이 이를 자동으로 주입해줍니다. 코드를 한번 보겠습니다.
@Component
public class UserService {
private final UserRepository userRepository;
// 생성자 주입 방식
@Autowired
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public void createUser(User user) {
userRepository.save(user);
}
}
여기서 UserService
는 UserRepository
를 필요로 하고, 우리는 생성자 주입 방식으로 이를 받아들입니다.
@Autowired
어노테이션 덕분에 Spring이 UserRepository
빈을 자동으로 찾아 주입해주는 것이죠. 이것이 바로 DI(의존성 주입)입니다. 이렇게 Spring이 객체 간의 의존성을 해결해주면, 개발자는 각 객체가 어떤 구체적인 클래스에 의존하는지 알 필요가 없고, 결과적으로 객체 간의 결합도가 낮아지며 코드가 더 유연해집니다.
2. 🛠 의존성 주입의 효과와 적용 시점
DI는 객체 간 결합도를 낮추는 데 큰 역할을 합니다. 만약 UserService
가 UserRepository
를 직접 생성하는 코드라면 다음과 같이 작성될 겁니다.
public class UserService {
private final UserRepository userRepository = new UserRepository();
public void createUser(User user) {
userRepository.save(user);
}
}
이 코드에서는 UserService
가 UserRepository
의 구체적인 구현에 의존하고 있습니다. 만약 UserRepository
의 구현이 변경되거나 새로운 타입의 Repository
가 필요해진다면, UserService
도 함께 수정해야 합니다. 이렇게 직접 객체를 생성하는 방식은 클래스 간 결합도를 높이고, 변경이 발생할 때 유연성이 떨어지게 됩니다.
하지만 DI를 사용하면 클래스 내부에서 구체적인 구현을 몰라도 됩니다. Spring이 외부에서 의존성을 주입해주기 때문에, UserService
는 UserRepository
의 구체적인 동작을 몰라도 됩니다. 결과적으로 클래스는 인터페이스나 추상 클래스에만 의존하게 되어, 이후에 변경이 생기더라도 쉽게 처리할 수 있습니다.
그리고 Spring에서는 의존성 주입이 애플리케이션 실행 시 자동으로 이루어집니다. Spring이 애플리케이션을 구동하면서 필요한 빈들을 미리 생성하고, 주입을 완료하기 때문에 개발자가 직접 신경 쓰지 않아도 됩니다.
3. 🔄 DI를 활용한 다양한 주입 방식
Spring에서는 주입 방식으로 크게 생성자 주입, 필드 주입, 세터 주입이 있습니다. 각각의 방식에 대해 간단히 살펴보겠습니다.
- 생성자 주입: 의존성을 객체가 생성될 때 주입받는 방식입니다. 생성자에 의존성을 명시하고, Spring이 해당 의존성을 주입해줍니다.
@Component
public class UserService {
private final UserRepository userRepository;
@Autowired
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
- 필드 주입: 클래스의 필드에 직접
@Autowired
를 붙여서 주입하는 방식입니다. 코드가 간단하지만, 테스트하기 어렵고 순환 참조 문제를 발견하기 어렵다는 단점이 있습니다.
@Component
public class UserService {
@Autowired
private UserRepository userRepository;
}
- 세터 주입: 의존성을 세터 메서드를 통해 주입받는 방식입니다. 의존성이 필수가 아닐 때 유용하게 사용할 수 있지만, 생성자 주입만큼 많이 사용되진 않습니다.
@Component
public class UserService {
private UserRepository userRepository;
@Autowired
public void setUserRepository(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
4. 🧩 DI에서 구체적인 객체 선택과 주입
Spring에서 여러 구현체가 있을 때, 어떤 객체가 주입될지는 Spring이 타입을 기준으로 결정합니다. 만약 여러 구현체가 같은 타입으로 존재한다면, Spring은 @Primary
나 @Qualifier
어노테이션을 통해 어느 빈을 주입할지 결정할 수 있습니다.
@Primary
: 여러 빈 중에서 기본적으로 주입할 빈을 지정하는 방법입니다. 여러 개의 빈이 있을 때 우선적으로 선택됩니다.
@Primary
@Component
public class PrimaryUserRepository implements UserRepository {
// ...
}
@Qualifier
: 특정한 이름의 빈을 명시적으로 주입하고 싶을 때 사용합니다. 이를 통해 여러 구현체 중 하나를 선택할 수 있습니다.
@Component
public class UserService {
private final UserRepository userRepository;
@Autowired
public UserService(@Qualifier("secondaryUserRepository") UserRepository userRepository) {
this.userRepository = userRepository;
}
}
5. 🔄 싱글톤 스코프의 컨트롤러와 의존성 관리
Spring에서 빈의 기본 스코프는 싱글톤입니다. 즉, 애플리케이션이 시작될 때 한 번만 생성되고 이후에 재사용됩니다. 하지만, 싱글톤 빈을 사용하더라도 각 요청마다 다른 데이터를 처리할 수 있습니다. 왜냐하면, 컨트롤러 같은 경우는 상태를 가지지 않고, 매 요청마다 새로운 파라미터나 데이터를 처리하기 때문입니다.
또한, 만약 요청마다 새로운 빈이 필요하다면, @RequestScope
를 사용해서 요청마다 다른 빈을 생성할 수도 있습니다.
@Component
@RequestScope
public class RequestScopedBean {
// ...
}
이렇게 하면, 각 요청마다 새로운 빈이 생성되어 서로 다른 데이터를 처리할 수 있습니다.
정리:
Spring에서 IoC와 DI는 객체의 생성과 의존성 관리를 프레임워크가 대신 해주는 방식입니다. 의존성 주입 덕분에 코드 간의 결합도가 낮아지고, 유지보수성이 높아집니다. 주입 방식으로는 생성자 주입이 가장 많이 사용되며, 여러 구현체가 있을 경우@Primary
나@Qualifier
로 주입할 객체를 선택할 수 있습니다. 또한, 싱글톤 스코프의 빈을 사용하면서도 각 요청마다 다른 데이터를 처리할 수 있는 다양한 방법이 제공됩니다.
'Java' 카테고리의 다른 글
[JAVA] Spring Boot Redis 캐시 사용법 (0) | 2024.09.20 |
---|---|
[JAVA] Spring 돌아보기 Part.2 #AOP (0) | 2024.09.12 |
[JAVA] 객체 비교와 Integer 클래스 (0) | 2024.09.09 |
[JAVA] #Deque VS Queue #ArrayDeque VS LinkedList #Map (1) | 2024.09.06 |
[JAVA] Java 메서드 참조 (0) | 2024.09.04 |