[Spring] DI와 IoC (상세)

최웅진·2023년 6월 30일
0

Spring

목록 보기
3/5

1. 소개 및 배경


1-1 DI와 IoC의 개념 소개:

  • DI는 Dependency Injection의 약자로, 객체간의 의존성을 외부에서 주입하는 개념이다.

  • IoC는 Iversion of Control의 약자로, 개발자가 객체의 생성과 관리를 제어하는 것이 아니라 프레임워크나 컨테이너에 위임하는 개념이다.

  • IoC
    피자가 있지만, 피자에는 제어권이 없어서 스스로 재료를 결정하지 못한다는 개념

  • DI
    피자가 스스로 재료를 결정하지 못하니, 피자에 맞는 피자의 재료를 외부에서 주입해주는 방법, (재료를 넣어준다는 행위의 초점)
    (ex.모짜렐라 치즈(혹은 그냥 치즈)를 넣어준다)

1-2 왜 DI와 IoC가 필요한지:

  • DI와 IoC는 코드의 유연성, 재사용성, 테스트 용이성을 높이고, 객체간의 결합도를 낮추기 위해 사용된다.

1-3 DI와 IoC의 장점과 이점:

  • 유연한 코드 구조, 테스트 용이성, 결합도 감소, 확장성과 유지 보수성을 높여준다.

2. IoC의 개념과 원리


서브웨이에 간것을 예시로 들어보기 🌯

  • 우선, 제어의 역전이 없다면(❌),
    우리는 직접 원하는 대로 재료를 선택한다면 알바생들은 이미 레시피(코드)에 지정되어 있는 재료의 샌드위치가 아니여서 당황을 겪게된다.
    각 재료들에 대한 제어권 이 객체 내부에 있기 때문이다.
    만약 요구를 반영하고자한다면 객체 내에 큰 변경이 생기게 된다.
  • But, 제어를 역전시킨다면(⭕️),
    각 재료들에 대한 제어권을 우리가 갖게 되어 우리가 직접 조합을 요청하여 자신만의 조합의 샌드위치를 주문할 수 있다.
    객체 내부에서 재료의 종류를 제어해 변경이 자유롭지 못하던 코드가 외벵서 제어를 받으면서 변경이 자유롭게 가능해진다.
    이를 통해 위에서 제어의 역전이 없을 때 생기는 큰 변경에 대한 문제를 해결할 수 있게 되었다.

2-1 IoC의 정의와 개념 설명:

  • 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;
}

2-2 IoC 동작 원리:

  • 스프링 컨테이너(IoC container)는 프로젝트에서 사용되는 객체들을 Bean으로 관리하고 있고 @Autowired를 통해 객체를 주입해준다.

  • 기존엔 사용자가 생성(new)해 파라미터로 다른 객체로 보내거나, 사용할 일이 없을 경우 객체를 소멸하는 등 객체에 대한 제어를 직접 진행했다.

  • 하지만 Spring에서는 위처럼 제어를 사용자가 아닌 Spring Framework가 진행하기 때문에 제어의 역전이라고 표현한다.

2-3 제어의 역전이 개발자의 코드 구조에 미치는 영향:

  • IoC는 개발자가 직접 객체 생성과 관리에 대한 책임을 가지지 않고, 프레임워크나 컨테이너에 위임하므로 코드의 결합도가 낮아지고 유연성이 증가한다.

  • 개발자는 의존성 주입을 통해 객체를 활용하며, 객체 간의 관계를 설정 하는 것에 집중한다.

2-4 IoC는 왜 필요할까?

  • IoC 프로그램 모델은 곧 역할과 책임의 분리 라는 내용과 관련이 있다.

  • 역할과 책임을 분리해 응집도를 높이고 결합도를 낮추며, 이에 따라 변경에 유연한 코드 를 작성할 수 있는 구조가 될 수 있다.

  • 결국 IoC를 사용하면 결과적으로 객체지향 원칙을 잘 지키는 코드를 만들 수 있다.

2-5 IoC 작동순서

    1. 객체 생성및 구성: IoC컨테이너나 프레임워크를 사용하여 객체를 생성하고 구성
      일반적으로 컨테이너는 XML 또는 Annotation과 같은 설정 메타데이터를 사용하여 객체를 생성하고 구성
    1. 객체의 생명주기 관리: IoC컨테이너는 객체의 생명주기를 관리
      객체의 생성, 초기화, 소멸과 같은 단계를 컨테이너가 관리하며, 필요에 따라 객체를 재사용하거나 새로 생성할 수 있다.
    1. 의존성 주입: IoC컨테이너는 객체에 필요한 의존성을 자동으로 주입한다.
      객체는 직접 의존성을 찾거나 생성하지 않고, 컨테이너로부터 필요한 의존성을 주입받는다.
      이를 통해 객체 간의 결합도를 낮출 수 있다.
    1. 제어의 역전: IoC컨테이너는 객체의 제어흐름을 개발자가 아닌 자체적으로 관리
      컨테이너는 필요한 객체를 생성하고 호출하며, 개발자는 컨테이너에 필요한 설정을 제공함으로써 컨테이너에게 제어 흐름을 위임한다.

3. DI의 개념과 원리


3-1 DI의 정의와 의존성의 개념 설명:

  • 제 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 하나의 클래스 만으로 모든 글을 관리할 수 있다.

3-2 의존성 주입의 원리와 종류:

  • 클래스 간 의존 관계(의존성)가 있다면, 한 클래스가 바뀌면 다른 클래스도 영향을 받는다.

  • 결국 의존은 영향을 받는 관계라는 것을 의미한다.

  • 주로 생성자 주입(Constructor Injection), Setter 주입(Setter Injection), 필드 주입(Field Injection) 등의 방식을 사용

3-3 의존관계 주입이란?

  • 의존성 주입 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();
	}
}
  • DI 적용 후
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-4 의존성은 어떻게 주입하면 될까?

3가지 방법이 존재한다.
1.생성자 주입 , 2.Setter 주입 , 3.Interface 주입

3-5 DI작동 순서

  1. 의존성 정의: 먼저, 각각의 클래스나 컴포넌트에서 필요로하는 의존성을 정의

  2. 의존성 제공: DI 컨테이너나 프레임워크를 사용하여 의존성을 제공
    DI컨테이너는 의존성을 인스턴스화하고 관리하는 책임을 갖는다.

  3. 의존성 주입: 의존성을 주입받는 객체에서 의존성을 전달
    일반적으로 주입은 생성자를 통해 이루어진다.
    DI 컨테이너는 의존성을 자동으로 주입하거나 필요한 경우에 수동으로 주입

  4. 객체사용: 의존성이 주입된 객체를 사용하여 원하는 작업을 수행
    의존성을 사용하는 객체는 직접 의존성을 인스턴스화하거나 관리하지 않고, DI 컨테이너로부터 주입 받은 의존성을 사용

  • Spring에서의 DI?
    @Controller, @Service 를 들 수 있다.
    Controller, Service 는 의존관계를 지니고 있다. Controller 에서 Service 를 호출할때, Service 를 받는 생성자가 어딘가에서 호출되어야하지만 instance를 주입하는 코드를 쓰지 않아도 가능하다.

4. Spring에서 DI를 구현하는 방법

4-1 생성자 주입(Constructor Injection):

  • 클래스의 생성자를 통해 의존성을 주입하는 방식

  • 주입받을 의존성을 인자로 받는 생성자를 정의하고, 해당 인스턴스 변수에 주입

  • Ex)

public class UserController{
	private UserService userService;

	public UserController(UserService userService){
    	this.userService = userService;
    }
}

4-2 속성 주입(Property Injection):

  • 의존성을 클래스의 속성으로 주입하는 방식

  • Setter 메서드를 통해 의존성을 설정

  • Ex)

public class UserController {
    private UserService userService;

    public void setUserService(UserService userService) {
        this.userService = userService;
    }
}

4-3 필드 주입(Field Injection):

  • 의존성을 필드를 통 주입하는 방식

  • 메서드 호출 시 의존성이 주입되는 방식

  • Ex)

public class UserService {
    @Inject
    private UserRepository userRepository;

    // UserService의 기능들...
}

public class UserRepository {
    // UserRepository의 기능들...
}
  • 위의 예시에서 UserService 클래스는 UserRepository에 의존성을 가지고 있습니다. UserService 클래스의 userRepository 필드에 @Inject 어노테이션을 사용하여 의존성 주입을 표시한다.

4-4 Spring의 DI 설정:

  • XML 설정 파일을 사용하는 방법:
<bean id = "userService" class= "com.example.UserService">
	<property name = "userRepository" ref= "userRepository" />
</bean>

<bean id = "userRepository" class = "com.example.UserRepository"/>
  • Java Config를 사용하는 방법:
@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();
    }
}

4-5 @Autowired 어노테이션:

  • @Autowired 어노테이션을 사용하여 의존성 주입을 자동화

  • Spring은 해당 타입의 빈을 찾아 자동으로 주입

-Ex)

public class UserController{
	@Autowired
    private UserService userService
}

5. Spring에서의 IoC(제어의 역전)

5-1. Spring에서의 IoC 컨테이너

  • Spring은 ApplicationContext라는 IoC컨테이너를 제공

  • ApplicationContext는 Bean을 생성하고, 의존성을 관리하며, 객체의 생명주기를 관리

5-2. Spring IoC 설정

  • XML설정 파일을 사용하는 방법:
<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>
  • Java Config를 사용하는 방법:
@Configuration
public class AppConfig {
    @Bean
    public UserService userService() {
        return new UserService();
    }
}
  • Spring의 DI와 IoC를 통해 객체간의 결합도를 낮출 수 있으며, 유연하고 확장 가능한 애플리케이션을 개발할 수 있다.

  • Spring은 많은 개발자들에게 선호되는 프레임워크로, DI와 IoC를 지원하여 개발생산성을 향상시키고 유지보수를 용이하게 한다.

profile
PlayData

0개의 댓글