Spring/Springboot

📝 스프링에서 DI (의존성 주입) 를 사용하는 이유

늘이 2022. 11. 18. 01:10
  • DI(의존성 주입)을 하지 않을 때 문제점: 강한 결합

Controller는 Service의 객체를 생성하여 사용하고, Service는 Repository객체를 생성하여 사용함

→ Controller는 Service를 Service는 Repository를 의존하는 상황

 

// Controller1은 Service1 객체를 생성하여 사용
public class Controller1 {
	private final Service1 service1;

	public Controller1() {
		this.service1 = new Service1();
	}
}

// Service1은 Repository1 객체를 생성하여 사용
public class Service1 {
	private final Repository1 repository1;

	public Service1() {
		this.repository1 = new Repository1();
	}
}

// Repository1 객체 선언
public class Repository1 { ... }

 

Repository1의 생성자 변경 발생 시

 

public class Repository1 {

	public Repository1(String id, String pw) { // DB 연결을 위한 id, pw추가
    // DB 연결
    Connection connection = DriverManager.getConnection("jdbc:h2:mem:springcoredb", id, pw);
  }
}위 경우 Repository1의 생성자 변경으로 인해 Service1와 Controller1의 코드 변경 필요

 

 

Repository1의 생성자 변경으로 인한 Controlle와 Service 코드 변경 필요

→ 만약 Controlle와 Service가 각각 5개가 있었다면 모두 코드 변경이 필요하게 됨

→ 강한 결합 문제를 해결하기 위해 의존성 주입이 필요함

 

 

 

  • DI(의존성 주입)

Repository1 클래스 선언 및 객체 생성

 

public class Repository1 { ... }

// 객체 생성
Repository1 repository1 = new Repository1();

 

 

Service1 클래스 선언 및 객체 생성

 

Class Service1 {
	private final Repository1 repitory1;

	public Service1(Repository1 repository1) { // Service1은 Repository1 객체 사용
		this.repository1 = new Repository1(); // Repository1 객체 생성 삭제
		this.repository1 = repository1;
	}
}

Service1 service1 = new Service1(repository1); // repository1을 사용하여 service1 객체 생성

 

 

Controller1 선언

 

Class Controller1 {
	private final Service1 service1;
	
	public Controller1(Service1 service1) { // service1 객체 사용
		this.service1 = new Service1();  // Service1 객체 생성 삭제
		this.service1 = service1;
	}
}

 

  • DI(의존성 주입)의 장점
  • 의존성 감소: 강한 결합은 의존한다는 것으로 그 의존대상의 변화에 취약하다.(대상이 변경되었을 때, 이에 맞게 수정 필요) DI로 구현하면 주입받는 대상이 변하더라도 그 구현 자체를 수정할 일이 없거나 줄어듦

 

  • 코드의 재사용성 증가: 기존에 Controller1 내부에서만 사용되었던 Service1을 별도로 구분하여 구현하면, 다른 클래스(ex. Controlle2, Controller3)에서 재사용할 수가 있음

 

  • 가독성이 높아짐: Service1의 기능들을 별도로 분리하게 되어 자연스레 가동성이 높아짐

 

 

 

제어의 역전 (IoC: Inversion of Control): 프로그램의 제어 흐름이 뒤바뀜

일반적으로는 사용자가 자신이 필요한 객체를 생성해서 사용하는것에 반해 용도에 맞게 필요한 객체를 가져다 사용하는 것

일반적 루틴: 객체 생성(A) -> 내부에서 의존성 객체 생성(B) -> 의존성 객체(B)의 메소드 호출
IoC적용 루틴: 객체 생성(A, B) -> 의존성 객체 주입(B) -> 의존성 객체(B)의 메소드 호출

스프링에서는 모든 의존성 객체(Bean)를 스프링 컨테이너에서 직접 관리하고 필요한 곳에 주입해줌

 

DI(Dependency Injection) 의존성 주입: 하나의 객체 A가 다른 객체 B를 의존(사용)할 때 A코드 내부에서 B를 만드는것이 아니라 외부(IoC컨테이너)에서 B를 만들고 생성자 혹은 Setter메소드를 이용하여 A내부로 주입하는 것

 

Bean: 스프링(IoC컨트롤러)이 생성/관리하여 자동으로 DI를 제공하는 객체

1) 클래스를 Bean으로 등록하는 방법

- Component Scanning

 

@ComponentScan 어노테이션은 하위 패키지를 탐색하며 @Component 어노테이션이 붙어있는 클래스를 찾아 Bean으로 등록하라고 지시하는 역할을 한다.스프링 어플리케이션의 메인 함수 위에 붙어있는 @SpringBootApplication 어노테이션에는 @ComponentScan 어노테이션이 포함되어 있다.

@Component 어노테이션 포함된 어노테이션은 대표적으로 @Controller, @Repository, @Service, @Configuration등이 있다.

 

- 직접 등록

xml 혹은 자바 설정파일을 사용하여 직접 Bean을 등록할 수 있다. 동일한 인터페이스 상에서 구현체가 종종 바뀌는 경우, 자바 설정 파일에 직접 등록하는 방식을 사용하면 변경지점을 최소화할 수 있어서 매우 유용하다.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class BeanConfiguration {

    @Bean
    public ProductRepository productRepository() {
        String dbUrl = "jdbc:h2:mem:springcoredb";
        String dbId = "sa";
        String dbPassword = "";

        return new ProductRepository(dbUrl, dbId, dbPassword);
    }
}

 

2) 스프링 Bean 이름: 클래스의 앞 글자만 소문자로 변경

public class ProductServcie  productServcie

3) 스프링 Bean 사용 방법

  • @Autowired : 멤버변수 선언 위에 @Autowired → 스프링에 의해 DI(의존성 주입) 됨
@Component
public class ProductService {
		
    @Autowired
    private ProductRepository productRepository;
		
		// ...
}

'빈' 을 사용할 함수 위에 @Autowired → 스프링에 의해 DI(의존성 주입) 됨

@Component
public class ProductService {

    private final ProductRepository productRepository;

    @Autowired
    public ProductService(ProductRepository productRepository) {
        this.productRepository = productRepository;
    }
		
		// ...
}

@Autowired 적용 조건

@Autowired 생략 조건

Spring 4.3 버젼 부터 @Autowired 생략가능

생성자 선언이 1개 일 때만 생략 가능

 

ApplicationContext

스프링 IoC 컨테이너에서 빈을 수동으로 가져오는 방법

@Component
public class ProductService {
    private final ProductRepository productRepository;

    @Autowired
    public ProductService(ApplicationContext context) {
        // 1.'빈' 이름으로 가져오기
        ProductRepository productRepository = (ProductRepository) context.getBean("productRepository");
        // 2.'빈' 클래스 형식으로 가져오기
        // ProductRepository productRepository = context.getBean(ProductRepository.class);
        this.productRepository = productRepository;
    }

		// ...		
}

 

 

스프링 IoC컨테이너: Bean을 모아둔 통

 

 

 

 

사용할 객체가 어떻게 만들어졌는지 알 필요가 없음