DI는 Dependency Injection의 약자로, 객체간의 의존성을 외부에서 주입하는 개념이다.
IoC는 Iversion of Control의 약자로, 개발자가 객체의 생성과 관리를 제어하는 것이 아니라 프레임워크나 컨테이너에 위임하는 개념이다.
IoC
피자가 있지만, 피자에는 제어권이 없어서 스스로 재료를 결정하지 못한다는 개념
DI
피자가 스스로 재료를 결정하지 못하니, 피자에 맞는 피자의 재료를 외부에서 주입해주는 방법, (재료를 넣어준다는 행위의 초점)
(ex.모짜렐라 치즈(혹은 그냥 치즈)를 넣어준다)
서브웨이에 간것을 예시로 들어보기 🌯
- 우선, 제어의 역전이 없다면(❌),
우리는 직접 원하는 대로 재료를 선택한다면 알바생들은 이미 레시피(코드)에 지정되어 있는 재료의 샌드위치가 아니여서 당황을 겪게된다.
각 재료들에 대한 제어권 이 객체 내부에 있기 때문이다.
만약 요구를 반영하고자한다면 객체 내에 큰 변경이 생기게 된다.- But, 제어를 역전시킨다면(⭕️),
각 재료들에 대한 제어권을 우리가 갖게 되어 우리가 직접 조합을 요청하여 자신만의 조합의 샌드위치를 주문할 수 있다.
객체 내부에서 재료의 종류를 제어해 변경이 자유롭지 못하던 코드가 외벵서 제어를 받으면서 변경이 자유롭게 가능해진다.
이를 통해 위에서 제어의 역전이 없을 때 생기는 큰 변경에 대한 문제를 해결할 수 있게 되었다.
IoC를 그대로 해석하면 제어의 역전이다.
무엇인가 제어하는 주체가 바뀐다는 의미인데 어떤 제어가 어떻게 바뀌는 것일까?
Spring을 사용해 본 사람이면 알듯이 Service, DAO같은 객체를 사용자가 직접 생성(new) 하지 않는다.
@Autowired를 통해 받아 사용하는데 이 @Autowired가 IoC(제어의 역전)이다.
제어의 역전이라는 용어는 개발자가 직접 객체를 생성하고 관리하는 것에서 벗어나, 객체의 생성과 관리를 프레임워크 또는 컨테이너가 담당하는것을 의미
//기존 자바 프로젝트
public class order {
private Customer customer;
public order() {
this.customer = new Customer();
}
}
// Spring 프로젝트
public class order {
@Autowired
private Customer customer;
}
스프링 컨테이너(IoC container)는 프로젝트에서 사용되는 객체들을 Bean으로 관리하고 있고 @Autowired를 통해 객체를 주입해준다.
기존엔 사용자가 생성(new)해 파라미터로 다른 객체로 보내거나, 사용할 일이 없을 경우 객체를 소멸하는 등 객체에 대한 제어를 직접 진행했다.
하지만 Spring에서는 위처럼 제어를 사용자가 아닌 Spring Framework가 진행하기 때문에 제어의 역전이라고 표현한다.
IoC는 개발자가 직접 객체 생성과 관리에 대한 책임을 가지지 않고, 프레임워크나 컨테이너에 위임하므로 코드의 결합도가 낮아지고 유연성이 증가한다.
개발자는 의존성 주입을 통해 객체를 활용하며, 객체 간의 관계를 설정 하는 것에 집중한다.
IoC 프로그램 모델은 곧 역할과 책임의 분리 라는 내용과 관련이 있다.
역할과 책임을 분리해 응집도를 높이고 결합도를 낮추며, 이에 따라 변경에 유연한 코드 를 작성할 수 있는 구조가 될 수 있다.
결국 IoC를 사용하면 결과적으로 객체지향 원칙을 잘 지키는 코드를 만들 수 있다.
제 3자가 제어를 관리하는 순간 제어의 역전이고, 스프링이 생기기 전부터 있던 개념이었기에 IoC는 다른 프레임워크와 다른 스프링만의 차별점을 설명하기 부족했다.
그래서 스프링만의 차별점을 설명하기 위해 만들어진 개념이 DI이다.
DI는 Spring에서 IoC 구조를 만드는 방식이다.
DI를 그대로 해석하면 의존성 주입이다.
의존성은 무엇이고 왜 주입할까?
프로그래밍에서 뜻하는 의존성은 객체간의 관계를 먼저 알아야 이해하기 쉽다.
의존성
클래스 간 의존 관계(의존성)가 있다면, 한 클래스가 바뀌면 다른 클래스도 영향을 받는다.
결국 의존은 영향을 받는 관계라는 것을 의미한다.
DI를 사용하는 이유는 개체간의 의존성을 줄이기 위함이다.
밖에서 객체를 생성해 넣어주기 떄문에 재사용성이 늘어나고 수정에 용이해진다.
//기존 자바 프로젝트
class concept_api implements Post(){}
class concept_Spring implements Post(){}
class Blog_log() {
private Post post; // 블로그 글 클래스
public Blog_log() {
this.post = new concept_api();
this.post = new concept_Spring();
}
}
// DI
private Post post; // 블로그 글 클래스
public Blog_log(Post post) {
this.post = new post();
}
첫번째 방법은 A객체가 B와 C객체를 New 생성자를 통해서 직접 생성하는 방법이고,
두번째 방법은 외부에서 생성 된 객체를 setter()를 통해 사용하는 방법이다.
이러한 두번째 방식이 의존성 주입의 예시인데,
A 객체에서 B, C객체를 사용(의존)할 때 A 객체에서 직접 생성 하는 것이 아니라 외부(IOC컨테이너)에서 생성된 B, C객체를 조립(주입)시켜 setter 혹은 생성자를 통해 사용하는 방식이다.
만약 기존 프로젝트 처럼 interface를 직접 만든다면 글마다 CRUD 함수가 필요하지만, DI처럼 의존성을 주입해 사용한다면 Blog_log 하나의 클래스 만으로 모든 글을 관리할 수 있다.
클래스 간 의존 관계(의존성)가 있다면, 한 클래스가 바뀌면 다른 클래스도 영향을 받는다.
결국 의존은 영향을 받는 관계라는 것을 의미한다.
주로 생성자 주입(Constructor Injection), Setter 주입(Setter Injection), 필드 주입(Field Injection) 등의 방식을 사용
의존성 주입 DI(Dependency Inversion) 는
제어의 역전이 일어나는것을 전제 로 스프링 내부의 객체들 간의 관계를 관리할 때 사용한다.
애플리케이션 실행 시점(런타임)에 (이전에는 인터페이스) 객체 외부에서 실제 구현 객체를 생성해서 그 참조값을 전달하므로써, 클라이언트와 서버의 실제 의존관계가 연결 되는 것이다.
의존관계 주입의 장점은 애플리케이션 코드를 변경하지 않고도 이제 의존관계를 변경할 수 있다는 점이다.
DI 적용 전
public class Pizza {
Bread bread;
Cheese cheese;
Sauce sauce;
public Pizza(){
this.white = new WhiteBread();
this.mozzarella = new MozzarellaCheese();
this.chili = new ChiliSauce();
}
}
public class Pizza {
Bread bread;
Cheese cheese;
Sauce sauce;
public Sandwitch(Bread bread, Cheese cheese, Sauce sauce){
this.bread = bread;
this.cheese = cheese;
this.sauce = sauce;
}
}
3가지 방법이 존재한다.
1.생성자 주입 , 2.Setter 주입 , 3.Interface 주입
의존성 정의: 먼저, 각각의 클래스나 컴포넌트에서 필요로하는 의존성을 정의
의존성 제공: DI 컨테이너나 프레임워크를 사용하여 의존성을 제공
DI컨테이너는 의존성을 인스턴스화하고 관리하는 책임을 갖는다.
의존성 주입: 의존성을 주입받는 객체에서 의존성을 전달
일반적으로 주입은 생성자를 통해 이루어진다.
DI 컨테이너는 의존성을 자동으로 주입하거나 필요한 경우에 수동으로 주입
객체사용: 의존성이 주입된 객체를 사용하여 원하는 작업을 수행
의존성을 사용하는 객체는 직접 의존성을 인스턴스화하거나 관리하지 않고, DI 컨테이너로부터 주입 받은 의존성을 사용
클래스의 생성자를 통해 의존성을 주입하는 방식
주입받을 의존성을 인자로 받는 생성자를 정의하고, 해당 인스턴스 변수에 주입
Ex)
public class UserController{
private UserService userService;
public UserController(UserService userService){
this.userService = userService;
}
}
의존성을 클래스의 속성으로 주입하는 방식
Setter 메서드를 통해 의존성을 설정
Ex)
public class UserController {
private UserService userService;
public void setUserService(UserService userService) {
this.userService = userService;
}
}
의존성을 필드를 통 주입하는 방식
메서드 호출 시 의존성이 주입되는 방식
Ex)
public class UserService {
@Inject
private UserRepository userRepository;
// UserService의 기능들...
}
public class UserRepository {
// UserRepository의 기능들...
}
<bean id = "userService" class= "com.example.UserService">
<property name = "userRepository" ref= "userRepository" />
</bean>
<bean id = "userRepository" class = "com.example.UserRepository"/>
@configuration
public class AppConfig {
@Bean
public UserService userService(UserRepository userRepositoty){
UserService userService = new UserService();
userService.setUserRepository(userRepository);
return userService;
}
@Bean
public UserRepository userRepository() {
return new UserRepository();
}
}
@Autowired 어노테이션을 사용하여 의존성 주입을 자동화
Spring은 해당 타입의 빈을 찾아 자동으로 주입
-Ex)
public class UserController{
@Autowired
private UserService userService
}
Spring은 ApplicationContext라는 IoC컨테이너를 제공
ApplicationContext는 Bean을 생성하고, 의존성을 관리하며, 객체의 생명주기를 관리
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="userService" class="com.example.UserService" />
</beans>
@Configuration
public class AppConfig {
@Bean
public UserService userService() {
return new UserService();
}
}
Spring의 DI와 IoC를 통해 객체간의 결합도를 낮출 수 있으며, 유연하고 확장 가능한 애플리케이션을 개발할 수 있다.
Spring은 많은 개발자들에게 선호되는 프레임워크로, DI와 IoC를 지원하여 개발생산성을 향상시키고 유지보수를 용이하게 한다.