- Spring Bean, IoC, DI
- Spring Bean이란?
- 싱글톤 패턴
- 제어의 역전(IoC, Inversion of Control)
- 의존성 주입(DI, Dependency Injection)
빈은 Spring IoC 컨테이너에 의해 관리되는 자바 객체이다. 빈의 라이프사이클은 스프링에 의해 관리되며, 사용자는 이에 관여하지 않는다.
스프링은 개발자가 생성한 설정(@Component 등)에 따라 빈을 읽어서 생성한다.
Bean의 이름은 컨테이너 안에서 유일성을 가져야 한다.
컨테이너가 빈을 찾지 못할 경우에는 @Qualifier를 통해 스프링 컨테이너가 주입할 빈을 알려줄 수 있다.
ApplicationContext을 통해 빈에 접근할 수 있다. 이러한 빈은 싱글톤 패턴으로 생성되는데, 이에 대해 알아보았다.
스프링에서 빈은 기본적으로 싱글톤 패턴으로 만들어진다. 빈은 IoC 컨테이너에서 단 한번 생성되며, 생성된 빈은 캐시에 저장되어 빈에 대한 요청 및 참조는 모두 캐시에서 반환한다.
빈을 싱글톤으로 생성하는 이유는, 웹 애플리케이션의 경우 보통 동시 요청이 다수 발생하게 되는데, 요청을 할 때마다 객체를 새로 생성하게 되면 리소스 낭비가 매우 심해지기 때문이다. 따라서 싱글톤 패턴으로 객체를 딱 하나만 생성하고, 이것을 공유하게 하는 것이다.
스프링은 기본적으로 멀티쓰레드 환경에서 동작하기 때문에, 여러 쓰레드에서 동시에 접근해서 사용하기 위해 Stateless하게 만들어져야 한다. 여러 쓰레드가 서로 값을 덮어쓰고 이것을 공유해 자신이 저장하지 않은 값도 읽어올 수 있도록 해야 하기 때문이다.
개발자가 직접 객체를 관리하지 않고 IoC 컨테이너에게 객체의 라이프사이클을 위임하는 이유는 제어의 역전을 위해서이다.
제어의 역전은 기존의 개발 방식에서 개발자가 직접 제어 흐름을 제어하는 것이 아니라, 외부의 프레임워크나 라이브러리가 제어 흐름을 대신하게 되는 것을 말한다.
이러한 제어의 역전을 통해 의존성 주입이 가능해진다.
단, 제어의 역전은 의존성 주입과 같은 개념이 아니다. 스프링에서 의존성 주입을 제어의 역전을 통해 하는 것일 뿐, 제어의 역전을 구현하는 방법도 여러가지가 존재하고, 의존성 주입도 꼭 제어의 역전을 통해서만 할 수 있는 것은 아니다.
스프링에서 의존성 주입을 어떻게 하는지 살펴보자.
객체를 빈으로 등록한 후, 의존성을 명시해 주면 의존성 주입을 IoC 컨테이너가 담당해서 하게 된다. 이러한 방법은 직접 의존성 주입을 하는 것보다 편리하게 관리되는데, 아래의 Controller-Service-Repository 의존관계 예시를 통해 살펴보자.
public class UserController {
@RequestMapping("/tst2")
@ResponseBody
public void tst2() {
UserRepository userRepository = new UserRepository();
UserService userService = new UserService(userRepository);
userService.viewMember();
}
}
public class UserService {
private UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
public class UserRepository {
}
UserRepository의 내용은 런타임 시 바뀔 일이 없다. 그런데 매번 새로운 객체를 UserController의 메서드에서 생성하고, Service의 생성자에 매번 의존성을 주입해야 한다. 이로 인해 성능이 저하되고 중복 코드가 생기게 된다.
이들을 빈으로 등록하면, 싱글톤 패턴으로 객체들을 관리하며, 의존성을 아주 쉽게 주입할 수 있다.
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@RequestMapping("/tst2")
@ResponseBody
public void tst2() {
userService.viewMember();
}
}
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
}
@Repository
public class UserRepository {
}
스프링에서 의존성 주입 방법으로는 필드 주입, 생성자 주입, 세터 주입 방법이 있는데, 공식 문서에서는 생성자 주입 방식을 가장 권장한다. 위 예시 코드에서는 롬복의 @RequiredArgsConstructor를 통해 의존성을 주입하고 있다.
생성자 주입 방식은 불변성, 필수 의존성 보장, 순환 의존성 방지, 테스트 용이성, 명확한 의존성 선언, Lombok과 함께 사용 가능 등의 여러 장점을 가진다.