의존성 주입(DI)이 필요하게 된 배경
. 앤터프라이즈 애플리케이션 개발 시, 하나의 처리를 수행하기 위해 여러 개의 컴포넌트를 조합해서 개발하는 경우가 일반적이다.
. '공통으로 사용되는 기능을 따로 분리한 컴포넌트', 'DB 접근을 위한 컴포넌트', '외부 시스템에 접속하기 위한 컴포넌트' 등이 있다.
. 하나의 처리를 구현하기 위해 여러 컴포넌트를 통합할 때 의존성 주입이 필요하다.
. 예를 들어, UserService의 Save 기능을 구현하기 위해서는 UserRepository나 PasswordEncoder클래스를 이용해서 구현해야 한다.
클래스 간 결합도가 높다
. 위에서 말했듯 UserServiceImpl 클래스를 개발하는 단계에서는 이미 UserRepository나 PasswordEncoder와 같은 클래스가 이미 구현되어있어야 한다.
. 필요한 컴포넌트를 생성자에서 직접 생성하는 방식은 클래스가 한번 생성되고 나면 이미 구현된 클래스는 교체하기 힘들다.
. 아래와 같이 UserServiceImpl생성자 내부에서 생성된 클래스는 결합도가 높아 변경사항에 유연하게 대처하기 어렵다.
// 결합도가 높은 클래스 생성 방식
public UserServiceImpl(javax.sql.DataSource dataSource){
this.userRepository = new JdbcUserRepository(dataSource);
this.passwordEncoder = new BcryptPasswordEncoder();
}
결합도를 낮추기 위해서는 어떻게 해야 할까?
. 생성자 내부에서 new로 생성하는 것보다는 생성자의 인수로 받아 할당하는 방법을 이용하자.
. 이 경우 생성자 외부에서 구현체는 변경 가능한 객체가 된다.
// 결합도를 낮추는 클래스 생성 방식
public UserServiceImpl(UserRepository userRepository, PasswordEncoder passwordEncoder){
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
}
. 이런 식으로 처리하게 되면, 결국 외부에서 클래스를 new로 생성해주어야 하는데 해당 클래스는 개발이 조금 덜 되었더라도 더미 클래스를 만들어주어 영향도를 줄일 수 있게 된다.
// 결합도를 떨어뜨려 개발 영향 최소화
UserRepository userRepository = new JdbcUserRepository(dataSource);
PasswordEncoder passwordEncoder = new DummyPasswordEncoder();
UserService userService = new UserServiceImpl(userRepository, passwordEncoder);
의존성을 주입한다
. 이 경우에도 UserServiceImpl이 의존하는 각 컴포넌트는 개발자가 직접 생성해서 주입해야 하기 때문에 변경이 발생하는 경우 재작업이 필요하다.
. 어떤 클래스가 필요로 하는 컴포넌트를 외부에서 생성한 후, 내부에서 사용 가능하게 만들어주는 과정을 '의존성을 주입한다'라고 한다.
. 또, 의존성 주입을 자동으로 처리하는 기반을 'DI 컨테이너'라고 한다.
DI 컨테이너의 기능
. 스프링 컨테이너가 제공하는 가장 중요한 기능 중 하나이다.
. DI 컨테이너에 인터페이스와 구현 클래스를 알려주고 의존관계를 정의해주면 UserServiceImpl이 생성될 때 구현 클래스(UserRepository, PasswordEncoder)가 자동 생성되어 주입된다.
. AOP 기능을 돕는다.
. 인스턴스의 스코프(Scope) 관리를 돕는다.
. 인스턴스의 생명주기 관리를 돕는다.
. 의존하는 컴포넌트 간의 결합도를 낮춰주어 테스트가 용이하도록 한다.
DI 컨테이너가 UserService 코드를 꺼내는 방법
ApplicationContext context = ...;
UserService userService = context.getBean(UserService.class);
의존성 주입(DI) 개요
. IoC라고 하는 소프트웨어 디자인 패턴 중 하나이다.
. IoC : 인스턴스를 제어하는 주도권이 역전된다는 의미로 사용된다.
. 컴포넌트를 구성하는 인스턴스의 생성과 의존 관계의 연결 처리를 해당 소스코드가 아닌 'DI 컨테이너'에서 대신해주기 때문에 제어가 역전됐다고 한다.
. 결국 인스턴스를 앱에서 직접 생성해서 쓰는 방법 대신, DI 컨테이너가 만들어주는 인스턴스를 사용할 수 있게 된다.
ApplicationContext == DI 컨테이너
. DI 컨테이너에서 인스턴스를 꺼내는 방법
// 1. DI 컨테이너 생성
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
// 2. DI 컨테이너에서 인스턴스를 가져온다.
UserService userService = context.getBean(UserService.class);
AppConfig
. AppConfig 클래스는 DI 컨테이너에서 설정 파일 역할을 한다.
. 자바로 작성되어있기 때문에 Java Configuration Class라고 한다. 이렇게 설정하는 방식을 자바 기반 설정 방식이라고 한다.
. 아래의 클래스는 자바 기반 설정 방식으로 설정된 클래스 예시이다.
@Configuration
public class AppConfig {
@Bean
UserRepository userRepository(){
return new UserRepositoryImpl();
}
@Bean
PasswordEncoder passwordEncoder(){
return new BCyptPasswordEncoder();
}
@Bean
UserService userService(){
return new UserServiceImpl(userRepository(), passwordEncoder());
}
}
. @Configuration, @Bean 어노테이션을 이용해서 DI 컨테이너에 컴포넌트를 등록한다.
. 앱은 DI 컨테이너에서 있는 빈(Bean)을 ApplicationContext 인스턴스를 통해 가져올 수 있다.
. 빈(Bean) : DI 컨테이너에 등록하는 컴포넌트
. 빈 정의(Bean Definition) : 빈에 대한 설정 정보 자체
. 룩업(lookup) : DI 컨테이너에서 빈을 찾아오는 행위. 앱은 DI 컨테이너에서 있는 빈(Bean)을 ApplicationContext 인스턴스를 통해 가져올 수 있다.
DI 컨테이너에서 빈을 가져오는(룩업) 방법
. DI 컨테이너에서 빈을 가져오는 방법에는 3가지가 있다.
1. 빈의 타입을 지정하는 방법
. 지정 타입에 해당하는 빈이 DI 컨테이너에 오직 하나만 존재할 때 사용한다. 스프링 3.0.0 버전부터 사용 가능하다.
// 1. 빈의 타입을 지정하는 방식
UserService userSerivce = context.getBean(UserService.class);
2. 빈의 이름과 타입을 지정하는 방법
. 지정한 타입에 해당하는 빈이 DI 컨테이너에 여러개 있을 때 이름으로 구분하기 위해 사용된다.
// 2. 빈의 이름과 타입을 지정하는 방식
UserService userSerivce = context.getBean("userService", UserService.class);
3. 빈의 이름을 지정하는 방법
. 반환값이 Object 타입이어서 원하는 빈의 타입으로 형변환해야 한다.
// 3. 빈의 이름을 지정하는 방식
UserService userSerivce = (UserService) context.getBean("userService");
빈 설정하는 방법
. 위의 클래스 AppConfig처럼 빈을 설정하는 방법에도 3가지가 존재한다.
. 빈 설정 방식 3가지는 아래와 같다.
. ApplicationContext는 단독 앱에서 스프링 프레임워크를 사용하거나, JUnit으로 만든 테스트 케이스 안에서 스프링 프레임워크를 구동할 때 사용된다.
. 웹 앱 사용 시, 확장된 개념인 WebApplicationContext를 이용한다.
방법 | 설명 | 생성 방식 | |
자바 기반 설정 방식 | . 자바 클래스에 @Configuration 어노테이션을 사용하고, 메서드에 @Bean 어노테이션을 사용해서 빈을 정의한다. . 스프링 부트에서 많이 쓰는 방식이다. |
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); | |
XML 기반 설정 방식 | . XML 파일을 사용하는 방법이다. . <constructor-arg>나 <property> 요소를 사용해 의존성을 주입할 수 있다. |
ApplicationContext context = new AnnotationConfigApplicationContext("META-INF/spring/applicationContext-root.xml"); | |
어노테이션 기반 설정 방식 | . @Component 같은 클래스를 탐색해서 DI 컨테이너에 빈을 자동으로 등록하는 방법이다. | ApplicationContext context = new AnnotationConfigApplicationContext("com.example.app"); |
정리할 것, 공부할 자료들이 너무 방대한 관계로 다음편으로 넘어가야겠다;;
정리 자료 원안
스프링 철저 입문 - 처음부터 끝가지 철저하게 배우는 실무 중심의 스프링 프레임워크 완벽 입문서
'Dev' 카테고리의 다른 글
[Spring] 의존성 주입(DI)에 대해 알아보자 - 3 / 의존성 주입 (0) | 2022.03.03 |
---|---|
[Spring] 의존성 주입(DI) 에 대해 알아보자 - 2 / Bean 설정 (0) | 2022.03.02 |
13. 기술면접 전 보고가면 좋은 내용 공유 2 (0) | 2021.04.19 |
12. [면접질문 정리] 인터넷 네트워크 (0) | 2021.04.19 |
11. 기술면접 전 보고가면 좋은 내용 공유 (0) | 2021.04.06 |