📝 스프링에서 DI (의존성 주입) 를 사용하는 이유
- 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을 모아둔 통
사용할 객체가 어떻게 만들어졌는지 알 필요가 없음