Inversion of Control(IoC)는 소프트웨어 엔지니어링에서 매우 중요한 원칙 중 하나이다.
이 원칙은 프로그램의 흐름 제어가 사용자가 아닌 프레임워크에 의해 수행되는 것을 의미한다.
일반적인 프로그래밍에서, 사용자가 작성한 코드(또는 메인 함수)는 프로그램의 흐름을 제어하며 다양한 함수나 객체를 호출한다. 그러나 IoC 원칙에서는 이 흐름이 역전되어, 사용자 코드를 프레임워크가 호출하게 된다. 이것이 바로 "제어의 역전"이라는 표현이 의미하는 것이다.
이러한 원칙은 보통 프레임워크나 라이브러리를 설계할 때 많이 사용되며, 사용자의 코드를 더 모듈화하고 재사용 가능하며 유연하게 만드는데 도움이 된다.
스프링 프레임워크에서 IoC는 주로 "IoC 컨테이너"라는 형태로 구현된다. IoC 컨테이너는 객체의 생명주기를 관리하고, 의존성 주입(Dependency Injection)을 수행하는 등의 역할을 담당한다.
스프링의 IOC 컨테이너란
톰캣과 IoC 컨테이너는 다른 목적을 가진 컴포넌트이다.
IoC 컨테이너 (ApplicationContext)
스프링 어플리케이션에서 객체의 생명주기와 의존성을 관리한다. @Component, @Service, @Repository, @Controller 등의 애노테이션으로 표시된 클래스는 IoC 컨테이너에 의해 자동으로 빈(@Bean)으로 등록되고 관리된다.
IoC 컨테이너는 @Autowired나 생성자 주입과 같은 방법을 통해 의존성 주입을 수행한다.
톰캣(tomcat)
톰캣(tomcat)은 자바 서블릿 컨테이너 및 웹서버이다. 웹 애플리케이션을 호스팅하고, 클라이언트의 요청을 처리한다.
스프링 부트는 내장 톰캣(tomcat)을 포함하고 있어서 별도의 웹 서버 설치 없이 웹 애플리케이션을 실행할 수 있다.
정리
코드 예시 - 서비스 인터페이스와 구현체 정의
public interface GreetingService {
void greet();
}
@Component
public class GreetingServiceImpl implements GreetingService {
@Override
public void greet() {
System.out.println("Hello, Spring IoC!");
}
}
컴포넌트에서 서비스 사용
@Component
public class MyComponent {
private final GreetingService greetingService;
@Autowired
public MyComponent(GreetingService greetingService) {
this.greetingService = greetingService;
}
public void execute() {
greetingService.greet(); // Output: Hello, Spring IoC!
}
}
위의 예시에서 GreetingService는 인터페이스로 정의되어 있으며, GreetingServiceImpl은 그 구현체이다.
MyComponent는 GreetingService에 의존하며(필드에 선언되어 있다.), 이 의존성은 스프링 IoC 컨테이너에 의해 주입된다.
개발자는 GreetingService의 인스턴스를 직접 생성(new를 사용)하거나 관리할 필요가 없으며, 단순히 필요한 의존성을 선언하기만 하면 된다. 이렇게 하면 코드의 결합도가 낮아지고, 다른 구현체로 쉽게 교체할 수 있게 된다.
스프링의 IoC는 객체의 생성과 관리를 프레임워크가 담당하도록 제어의 역전을 실현한다. 이로 인해 개발자는 비즈니스 로직에 집중할 수 있으며, 코드의 유연성과 재사용성이 향상된다.
스프링의 IoC 원칙 중에서도 "의존성 주입(Dependency Injection, DI)"은 중요한 개념이다.
예를 들어, 데이터베이스 연결을 담당하는 DatabaseConnection 클래스가 있다고 가정해 보자.
public interface DatabaseConnection {
void connect();
}
@Component
public class MySQLDatabaseConnection implements DatabaseConnection {
@Override
public void connect() {
// MySQL에 연결하는 로직
System.out.println("Connected to MySQL database");
}
}
@Service
public class UserService {
private final DatabaseConnection databaseConnection;
@Autowired
public UserService(DatabaseConnection databaseConnection) {
this.databaseConnection = databaseConnection;
}
public void performAction() {
databaseConnection.connect();
// 사용자 관련 작업 수행
}
}
위 코드에서 UserService는 DatabaseConnection에 의존한다. MySQLDatabaseConnection이라는 구현체는 스프링 IoC 컨테이너에 의해 주입된다.
나중에 다른 데이터베이스로 이전해야 할 경우 DatabaseConnection의 다른 구현체만 작성하고 스프링 설정을 조금 변경하면 된다.
IoC의 또 다른 장점은 테스트 용이성이다.
// 테스트 코드
public class UserServiceTest {
@Test
public void testPerformAction() {
DatabaseConnection mockConnection = mock(DatabaseConnection.class);
UserService userService = new UserService(mockConnection);
userService.performAction();
verify(mockConnection).connect();
}
}