스프링을 배우면서 가장 처음 접하는 개념이 IOC와 DI를 접하게 된다.
IOC : Inversion Of Control, 즉 제어의 역전이라고 직역을 한다.
DI : Dependency Injection, 즉 의존성 주입이라고 직역을 한다.
그러면 이 말들이 대체 뭘까? 왜 제어의 역전이라고 하고 왜 의존성 주입이 스프링의 특징일까?
우선 스프링은 하나의 프레임워크이다.
프레임워크 : 어떤 결과물을 구현하기 위해 클래스, 코드들이 합쳐진 형태. 즉 프레임워크가 뼈대라고 볼 수 있다.
예를 들면 ppt에서 제공하는 기본 템플릿 느낌?
즉 스프링은 어떤 결과물을 제작하기 위한 하나의 뼈대를 제공하는 도구이며, 우리는 이러한 도구를 이용해서 무언가를 만들어낼 수 있는 것.
그렇기에 스프링은 다양한 편의 기능을 제공하는데 가장 대표적인 특징이 IOC, DI, AOP이다. 여기서는 IOC와 DI에 대해서만 알아보자.
IOC(제어의 역전)
말 그대로 제어의 역전이 일어난다. 즉 흐름을 제어하는 주체가 개발자 -> 프레임워크로 이동이 된 것.
여기서 흐름은 객체의 생성, 생명주기 관리를 의미한다.
이게 무슨 소리인지 이해가 되지 않을 것이다. 나 또한 그랬으니
public class BookService {
private BookRepository bookRepository;
public void save(){
bookRepository.save();
}
}
위 코드는 BookService에서 save를 시키는 간단한 메서드이다.
우선 BookService는 BookRepository를 의존 객체로 가지고 있다. 즉 BookService를 사용하기 위해서는 BookRepository가 필요하다는 의미이다.
그러면 BookRepository를 생성해줘야 하는 것 아닌가? 지금 저 상태면 null 아닌가?
public class BookService {
private BookRepository bookRepository = new BookRepository();
public void save(){
bookRepository.save();
}
}
BookService에서 직접 new를 이용해서 생성해 줘도 물론 직접적인 문제가 생기지는 않는다.
그러나 위 코드가 추천되지 않는 이유는 아래와 같다.
BookService와 BookRepository의 강한 결합도
만약에 BookRepository 코드 내부 구현이 변경되거나, 다른 걸로 교체해야 한다고 하면? BookService도 같이 건드려야 한다.
하지만 BookService는 BookRepository의 구체적인 구현에 대해서는 알 필요가 없다.
그러면 어떻게 BookService에서 BookRepository를 할당할 수 있는데?
-> 바로 스프링 컨테이너를 이용하면 된다.(ioc 컨테이너, di 컨테이너라고도 불린다.)
스프링에서는 스프링 컨테이너라는 공간을 만들어내고, 이 공간에서 Bean이라는 객체들을 생성해 놓고 관리한다.(Bean을 생성할 때 싱글톤으로 관리를 한다.)
Bean : 스프링 컨테이너에 저장되는 객체.(즉 하나의 클래스라고 보면 된다.)
@Repository
public class BookRepository {
public void save(){
return;
}
}
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
@Service
public class BookService {
private BookRepository bookRepository = new BookRepository();
public void save(){
bookRepository.save();
}
}
이렇게 @Service, @Repository 어노테이션을 이용해서 해당 클래스를 빈으로 등록하겠다!라고 선언을 해놓는다.
(@Service, @Repository 어노테이션이 어떻게 빈으로 등록되는지는 @Component 어노테이션에 대한 정보를 찾아보면 된다.)
@SpringBootApplication
public class CsApplication implements CommandLineRunner {
@Autowired
private ApplicationContext context;
public static void main(String[] args) {
SpringApplication.run(CsApplication.class, args);
}
@Override
public void run(String... args) {
String[] beanDefinitionNames = context.getBeanDefinitionNames();
Arrays.sort(beanDefinitionNames);
StringBuilder sb = new StringBuilder();
for(String beanName : beanDefinitionNames) {
sb.append(beanName).append("\n");
}
System.out.print(sb);
}
}
그리고 main에서 위 코드를 작성해서 실행시켜보면
이렇게 등록된 빈의 이름이 주르르르륵 뜬다.
bookRepository, bookService가 등록된 것 확인이 가능하다.
(빈의 이름은 첫 글자는 소문자로 바뀌어서 등록됨.)
이제 Bean으로 등록을 시켰으니, Service에서 Repository를 직접 생성하는 것이 아닌, 생성된 Repository를 주입시켜주기만 하면 된다.
여기서는 생성자 주입 방법을 선택했다.
@Service
public class BookService {
private BookRepository bookRepository;
@Autowired
public BookService(BookRepository bookRepository){
this.bookRepository = bookRepository;
}
public void save(){
bookRepository.save();
}
}
위 코드를 보면 BookService를 만드는 생성자에서 bookRepository를 받아서 집어넣어 주는 것을 확인할 수 있다.
동작 순서는 다음과 같다.
1. BookService를 생성하려니 BookRepository가 필요하네?
2. 스프링 컨테이너에서 BookRepository 빈을 찾는다.(이미 생성된 객체, 없으면 런타임 에러 발생)
3. 끌고 와서 BookService의 BookRepository에 집어넣는다.
이 과정을 스프링이 대신해 주는 것.
그래서 기존의 객체 생성을 개발자가 직접 해줬다면
이제는 스프링이 빈으로 객체를 생성하고 주입까지 다 해준다. 즉 제어권이 프레임워크로 넘어갔다고 해서 IOC(제어의 역전)라고 불린다.
그럼 다시 처음으로 돌아가서, DI는 의존성 주입이라고 했다. 즉 의존성을 외부에서 주입해 주는 것.
그런데 우리는 의존 객체를 스프링 빈으로 등록하고, 해당 빈을 통해 의존 객체를 할당했으니, 이게 DI 아닌가?
맞다. 정확하다. 여기서 말하는 외부는 스프링 컨테이너가 되고, 컨테이너의 Bean을 통해 DI를 진행한 것.
DI를 하기 위해 Bean을 생성하고 관리하는 과정이 IOC를 통해 일어난다. (IOC 컨테이너는 DI를 가능하게 해 준다.)
즉 두 개는 복합적으로 엮여있다고 생각한다.
스프링을 처음 시작하면 제일 먼저 마주치는 개념이고, 제일 이해가 안 되는 시작점이었다.
나도 1년 전에는 이게 무슨 의미인가 싶었는데, 시간이 지나고 개념을 자주 접하고 사용하다 보니 이제 의미를 깨달아가고 있다.
'CS지식' 카테고리의 다른 글
@Transactional 어노테이션의 역할 (0) | 2024.05.09 |
---|---|
Interceptor와 Filter는 뭐가 다를까? (0) | 2024.05.06 |
final 키워드를 사용하면 어떤 이점이 존재할까?(JAVA) (0) | 2024.04.29 |
HTTPS 개념을 잡고 가자..(feat. SSL/TLS을 곁들인) (0) | 2024.03.16 |