Inversion Of Control (IoC, 제어의 역행)
보통 프로그램에서 객체를 사용해야 할 때의 실행 과정은 아래와 같다.
public class GameController() {
public static void main(String[] args) {
// 객체가 필요하다. 필요한 객체를 생성한다.
Game game = new Game();
// 생성한 객체를 사용한다.
game.start();
}
}
GameController에서 Game 객체가 필요하므로, GameController에서 Game 객체를 생성해서 사용한다.
그러나 Spring에서는 Game 객체를 먼저 생성하고, GameController에 주입해준다.
public class GameController() {
public static Game game;
// GameController가 생성될 때, 미리 생성된 game 객체가 자동으로 주입된다.
public GameController(Game game){
this.game = game;
}
public static void main(String[] args) {
// GameController는 생성 시부터 바로 객체를 사용할 수 있다.
game.start();
}
}
그 결과, GameController는 따로 Game 객체를 생성하지 않고, 생성 할때부터 바로 Game 객체를 사용할 수 있게 된다.
클래스에서 필요한 객체를 생성해서 사용한다가 아닌, 필요한 객체를 미리 만들어서 사용할 클래스에 주입해준다로 과정이 역으로 바뀐다. 이를 제어의 역행이라고 한다.
Inversion Of Control (IoC, 제어의 역행)
- 객체 생성을 Container에게 위임하여 처리한다.
- 객체 지향 언어에서 Object간의 연결 관계를 런타임에 결정한다.
- IoC를 사용하면 객체 간의 관계가 느슨하게 연결된다. (loose coupling)
- IoC의 구현 방법 중 하나가 DI(Dependency Injection)이다.
느슨하게 연결된다는게 뭐지?
class B {
public A a = new A();
public void hello() {
a.printHello();
}
}
만약 위 코드에서 클래스 A에 있던 printHello 메서드가 수정된다면(리턴타입/함수명 등) 클래스 A뿐만 아니라 클래스 B또한 수정해야 한다. 이걸 tight한 결합이라고 한다. 즉 A가 수정되면 B도 수정되어야 할 때, tight한 결합이라고 한다. 객체간 결합도가 높으면 클래스가 유지보수될 때 그 클래스와 결합된 다른 클래스도 같이 유지보수 되어야 할 가능성이 높기 때문에 좋지 않다.
위를 방지하기 위해 interface를 사용한다. interface를 사용하면 함수의 리턴타입, 함수명, 파라미터가 고정되므로 수정되어도 다른 클래스를 수정할 필요가 없게 된다. interface를 사용하면 느슨한 결합이 된다. (한 쪽이 수정되어도, 다른 쪽에서 수정할 필요없음)
IoC 유형
Dependency Lookup
- 메모리 안에 미리 생성된 객체를 필요할 때 찾아서 쓰는 방식 (객체는 컨테이너가 생성)
- 컨테이너가 lookup context를 통해서 필요한 Resource나 Object를 얻는 방식
- JNDI 이외의 방법을 사용한다면 JNDI 관련 코드를 오브젝트 내에서 일일이 변경해 주어야 한다.
- Lookup한 Object를 필요한 타입으로 Casting 해 주어야 한다.
- Naming Exception을 처리하기 위한 로직이 필요하다.
JNDI(Java Naming and Directory Interface) Lookup
메모리 안에 객체가 여러개 만들어진다. 만들어진 여러개의 객체를 윈도우의 파일 시스템과 같이 디렉토리화 시켜서 저장한다.
객체를 저장해놨다가 필요할 때마다 디렉토리와 이름을 가지고 찾아서 사용한다.필요할 때 객체를 만드는 것이 아니라, 미리 객체를 생성하고 필요할 때 찾아서(Lookup) 사용하는 방식이다.(ex) java/comp/env/jdbc/myobject 와 같이 찾아서 사용한다.)
Dependency Injection
- 메모리 안에 미리 생성된 객체 중 필요한 객체를 처음 생성할 때부터 주입받는 방식
- Object에 lookup 코드를 사용하지 않고 컨테이너가 직접 의존 구조를 Object에 설정 할 수 있도록 지정해주는 방식
- Object가 컨테이너의 존재 여부를 알 필요가 없다.
- Lookup 관련 코드들이 Object내에서 사라진다.
- Setter 주입, 생성자 주입, 메서드 주입 방법이 있다.
public class UserController {
private static UserService userService;
public UserController() {
this.userService = UserService.getUserService();
}
}
위의 코드에서 UserController는 UserService를 의존하고 있다. 즉, Controller는 Service가 없으면 동작하지 못한다.
-> Controller는 Service를 100%로 사용하므로, 아예 Controller를 생성하는 시점에서 필요한 Service를 넣어준다.
너가 쓸 거(=의존하는 것) 미리 넣어줄게! (주입) 라고 생각하면 될 것 같다.
public class UserController {
private static UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
}
UserController가 생성될 때, 외부에서 자동으로 userService에 미리 만들어진 UserService 객체의 주소값을 넣어준다. 이것을 의존관계 주입이라고 한다. 즉, 내가 필요한 객체를 외부에서 생성해서 자동으로 넣어주는 방식이다!
의존 관계를 주입 받는 방법은 Setter 주입, 생성자 주입, 메서드 주입이 있는데 Spring에서 권장하는 방식은 생성자 주입이다.
Container
객체의 생성, 사용, 소멸에 해당하는 라이프사이클을 담당한다.
라이프사이클을 기본으로 애플리케이션 사용에 필요한 주요 기능을 제공한다.
Container 기능
- 라이프사이클 관리
- Dependency 객체 제공
- Thread 관리
- 기타 애플리케이션 실행에 필요한 환경
Container 필요성
- 비즈니스 로직 외에 부가적인 기능들에 대해서 독립적으로 관리되도록 하기 위함
- 서비스 Lookup이나 Configuration에 대한 일관성을 갖기 위함
- 서비스 객체를 사용하기 위해 각각 Factory 또는 Singleton 패턴을 직접 구현하지 않아도 된다.
IOC Container
- 오브젝트의 생성과 관계 설정, 사용, 제거 등의 작업을 애플리케이션 코드 대신 독립된 컨테이너가 담당
- 컨테이너가 코드 대신 오브젝트에 대한 제어권을 갖고 있어서 IoC라고 부른다
- 이런 이유로, 스프링 컨테이너를 IoC 컨테이너라고 부르기도 한다
- 스프링에서 IoC를 담당하는 컨테이너에는 BeanFactory, ApplicationContext가 있다.
Spring DI Container
- Spring DI Container가 관리하는 객체를 Bean이라 하고, 이 빈들의 생명주기(Life-Cycle)를 관리하는 의미로 BeanFactory라 한다.
- Bean Factory에 여러 가지 컨테이너 기능을 추가하여 ApplicationContext라 한다.
public interface BeanFactory
- Bean을 등록, 생성, 조회, 반환 관리
- 일반적으로 BeanFactory 보다는 확장한 ApplicationContext를 사용한다
- getBean() 메서드가 정의되어 있다
public interface ApplicationContext
- Bean을 등록, 생성, 조회, 반환 관리
- Spring의 각종 부가 서비스를 추가로 제공한다
- Spring이 제공하는 ApplicationContext 구현 클래스는 여러가지가 있다 (참고 : 공식문서)
'Study > Spring' 카테고리의 다른 글
[Spring Boot] XSS 방어 설정하기 (1) | 2023.07.16 |
---|