본문 바로가기
Dev

[Spring] 의존성 주입(DI) 에 대해 알아보자 - 1 / 개요, Bean 정의

by VIPeveloper 2022. 2. 28.
반응형

의존성 주입(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");

정리할 것, 공부할 자료들이 너무 방대한 관계로 다음편으로 넘어가야겠다;;

정리 자료 원안

스프링 철저 입문 - 처음부터 끝가지 철저하게 배우는 실무 중심의 스프링 프레임워크 완벽 입문서

반응형