DI의 생성 배경
생성자에서 모든 의존성이 있는 클래스들을 초기화 하면
클래스들 간의 결합도가 높아진다.
하지만 비지니스 로직을 구현하다 보면, 다양한 컴포넌트들을 조합하게 되고,
이를 효율적으로 분리해야 협업이나 외주개발이 용이하게 되는데,
(컴포넌트가 실제로 구현될 때 까지 시간이 걸릴 수 있음)
이를 Dummy클래스로 대체하는 방법이 있다.
따라서 결합도를 낮추려면, 생성자에서 구현하는 것 보다,
생성자에서 인자로 받는것이 더 효율적일 것이다.
이렇게 하면 Interface에 명시된 메소드명만을 사용하게 되고,
실제 Impl의 구현체는 쉽게 수정할 수 있게 된다.
하지만 생성자에서 직접 주입을 받는 경우에도,
각 컴포넌트를 개발자가 직접 주입해주어야 하기 때문에,
로직적인 변경이 필요할 경우 큰 수정이 불가피하게 된다.
이를 위해 의존성 주입, Dependency Injection이라는 개념이 나오게 된다.
이렇게 의존성 주입을 자동으로 처리하는 기반을 DI 컨테이너라고 부른다.
Inversion of Control
IoC는 소프트웨어 디자인패턴 중 하나인데, 제어가 역전되었다는 의미이다.
제어권을 개발자가 가지는 것이 아니라, 프레임워크가 가지게 되는,
즉 프레임워크라면 누구나 가지고 있는 특성의 패턴이다.
스프링은 DI를 통해 사용자가 컴포넌트를 생성하고, 연결시켜주는 작업을
대신 해주기 때문에 제어가 역전 되었다고 보는 것이다.
DI 컨테이너
기존의 프로그래밍 방식 대로, 클래스간의 의존성이 생기면 다음과 같이 사용한다.
DI의 방식은 이를 관리해주는 DI 컨테이너가 존재하고,
의존성이 필요할 경우 다른 인스턴스를 DI 컨테이너를 통해 취득하게 된다.
위와 같은 방식으로 인스턴스의 생성 및 관리를 DI 컨테이너가 해주기 때문에
개발자는 비지니스 로직을 개발하는데에 집중할 수 있게 된다.
더 복잡한 의존성을 가지게 되는 경우에도 DI컨테이너가 관리를 해주게 되지만,
스프링 자체적으로도 상호 참조를 하게되는 의존성은 피해야 한다고 이야기하고 있다.
ApplicationContext와 Bean
스프링에서는 자바 객체 (POJO)를 Bean이라고 부른다.
그리고 ApplicationContext는 DI 컨테이너 역할을 하는데,
DI컨테이너가 바로 자바 객체들인 Bean을 들고있게 되는 것이다.
ApplicationContext는 Configuration클래스를 통해 Bean을 들고있게 되는데,
이 Configuration클래스가 ApplicationContext의 일종의 설정파일이 되는 것이다.
(이를 자바에 작성한 설정이라고 해서, Java Configuration이라고도 부른다.)
@Configuration
public class AppConfig{
@Bean
UserRepository userRepository(){
return new UserRepositoryImpl();
}
@Bean
PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Bean
UserService userService(){
return new UserServiceImpl(userRepository(), passowrdEncoder());
}
}
위의 코드 예제를 보면,
각 객체들은 @Bean 애노테이션을 통해 Bean이라는 것을 알리고,
각각의 메소드에서는 구현체를 리턴하게 된다.
위와 같이 DI 컨테이너(Application Context)에 등록되는 컴포넌트를 Bean이라고 하고,
이를 찾아오는 것을 룩업 (Lookup)이라고 부른다.
이렇게 Bean으로 등록할 객체들은 다음과 같이 ApplicationContext에 주입되고,
getBean메소드를 통해 싱글톤 인스턴스를 가져올 수 있게 된다.
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = context.getBean(UserService.class);
Bean을 설정하는 방식은 총 세가지가 있다:
- 자바 기반 설정
- XML 기반 설정
- Annotation 기반 설정
자바 기반 설정
자바 기반 설정은 위에서 언급한 Config클래스를 통해서 설정한다.
위와 같이 @Bean이 정의된 메서드를 호출하는 방식도 있지만,
이미 Bean으로 정의되었다면 메서드의 파라미터로 주입 받을 수도 있다.
...
@Bean
UserService userService(UserRepository userRepository, PasswordEncoder passwordEncoder){
return new UserServiceImpl(userRepository, passwordEncoder);
}
...
}
자바 파일 설정의 단점은, 의존성 주입으로 사용할 모든 컴포넌트를 Bean으로 정의해야한다는 점이다.
XML 기반 설정
XML을 통해서도 Bean을 설정할 수 있다.
다음 xml파일은 위의 세개의 Bean을 xml파일을 통해 정의하는 것이다.
<?xml version="1.0" encoding="UTF-8"?>
<beans
... 생략
>
<bean id="userRepository" class="com.example.demo.UserRepositoryImpl" />
<bean id="passwordEncoder" class="com.example.demo.BCryptEncoder" />
<bean id="userService" class="com.example.demo.UserServiceImpl">
<constructor-arg ref="userRepository" />
<constructor-arg ref="passwordEncoder" />
</bean>
</beans>
위의 파일을 보면 각각의 Bean을 등록하고, 의존성을 주입받는 클래스에서는
constructor-arg를 통해 다른 Bean을 주입 받는다.
이 constructor-arg는 Bean이 아닌 다른 값을 주입 받을 수도 있다.
Annotation 기반 설정
Annotation을 사용하면 설정 파일을 정의하는 대신, Bean으로 지정할 클래스 위에
간단한 Annotation으로 설정파일을 대신할 수 있다.
Annotation을 사용해 DI컨테이너에 자동 등록하는 행위를 "컴포넌트 스캔"이라고 부른다.
또한 의존성을 가진 컴포넌트를 자동으로 주입하는 행위를 "오토와이어링"이라고 한다.
먼저 위의 설정파일을 Annotation으로 대체하는 두가지 클래스이다.
@Component
public class UserRepositoryImpl implements UserRepository{
...
}
@Component
public class BCryptPasswordEncoder implements PasswordEncoder{
...
}
이를 주입받는 클래스에서 Autowired를 사용하는 예제이다.
@Component
public class UserServiceImpl implements UserService{
@Autowired
public UserServiceImpl(UserRepository userRepository, PasswordEncoder passwordEncoder){
...
}
}
의존성 주입
스프링에서 실제로 Dependency Injection을 하는 방식은 세가지가 있다.
- Constructor 기반 DI
- 필드 기반 DI
- Setter 기반 DI
스프링에서 의존성 주입을 하는 순서도 위의 순서와 같다.
생성자 기반 주입
생성자 (Constructor)기반 주입 방식은 위의 XML에서도 살펴본 바가 있다.
자바 코드에서의 생성자 기반 주입 방식은 다음과 같다.
@ConstructorProperties({"userRepository", "passwordEncoder"})
public UserServiceImpl(UserRepository userRepository, PasswordEncoder passwordEncoder){
...
}
ConstructorProperties내에 주입시킬 Bean이름들을 지정해 주는 것이다.
@Component 애노테이션을 사용하면 별도의 Bean명을 지정해 줄 수가 있는데,
별도로 지정하지 않았을 때는 자동적으로 지정된 이름(클래스명)을 사용한다.
필드 기반 주입
아마 스프링부트로 인해 간소화된 시스템에서 가장 많이 사용하는 주입 방식이 아닐까 한다.
소스를 줄이거나, 간결하게 표현하기에 가장 좋은 방법이다.
필드 기반 주입은 @Autowired 애노테이션을 사용해 주입 된다.
@Component
public class UserServiceImpl implements UserService {
@Autowired
UserRepository userRepository;
@Autowired
PasswordEncoder passwordEncoder;
...
}
위와 같은 방식을 오토와이어링이라고 부른다.
Setter 기반 주입
Setter방식에서 역시 @Autowired를 사용할 수 있다.
하지만 이 방식은 필드 기반 주입에 비해 코드가 길어지기 때문에, 많이 사용하지 않는 편이다.
@Component
public class UserServiceImpl implements UserService {
UserRepository userRepository;
@Autowired
public void setUserRepository(UserRepository userRepository){
this.userRepository = userRepository;
}
...
}
오토와이어링
Autowiring은 설정파일을 사용하지 않고 DI컨테이너에서 Bean을 주입을 받는 것을 말한다.
오토와이어링에는 두가지 방식이 있다.
- 타입 방식 (autowiring by type)
- 이름 방식 (autowiring by name)
위에서 살펴본 예제들은 모두 타입 방식인데, @Autowiring이 달린 곳의 타입, 즉 클래스 타입을 보고 찾는다.
하지만 타입 방식을 사용하면, 같은 타입의 Bean이 여러개가 있을때 식별할 수가 없게 된다.
다음은 같은 타입의 Interface를 상속받는 여러개의 클래스가 있는 경우의 예제이다.
@Configuration
@ComponentScan
public class AppConfig{
@Bean
PasswordEncoder sha256PasswordEncoder() {
return new Sha256PasswordEncoder();
}
@Bean
PasswordEncoder bcryptPasswordEncoder() {
return new bcryptPasswordEncoder();
}
...
}
이때 사용하는 것이 이름 방식이다.
컴포넌트를 주입받을 때 Qualifier를 지정함으로서 이름을 통해 주입받을 수 있게 된다.
@Component
public class UserServiceImpl implements UserService
@Autowired
@Qualifier("bcryptPasswordEncoder")
PasswordEncoder passwordEncoder;
...
}
만약 같은 타입의 여러개의 구현체 중 디폴트를 정하고 싶다면 Config파일에 @Primary 애노테이션을 달아놓으면 된다.
컴포넌트 스캔
컴포넌트 스캔이랑 클래스로더를 스캔하면서 애노테이션이 달린 클래스들을 찾고,
이들을 DI 컨테이너에 Bean으로 등록하는 방법이다.
스프링에서 컴포넌트 스캔을 기본으로 제공하는 클래스는 다음과 같다:
- @Component - 일반적인 기능을 수행하는 컴포넌트에 포괄적으로 적용
- @Controller - MVC의 C에 해당, 클라이언트의 요청을 처리(Service에 전달)하고 응답하는 컴포넌트
- @Service - 비지니스 로직을 실제 처리하는 컴포넌트, 영속적인 데이터가 필요하면 Repository와 통신
- @Repository - DB/저장소와 같은 영속적인 데이터를 관리, CRUD기능을 수행
- @Configuration
- @RestController
- @ControllerAdvice
- @ManagedBean
- @Named
참고자료
스프링 철저 입문, 위키북스
'서버 개발 > 스프링' 카테고리의 다른 글
[스프링 클라우드] 서비스 디스커버리 (0) | 2024.10.03 |
---|---|
스프링 프레임워크 - (4) 데이터 바인딩 (0) | 2019.09.10 |
스프링 프레임워크 - (3) AOP (0) | 2019.09.10 |
스프링 프레임워크 - (2) 스프링 빈 (Bean) (0) | 2019.09.02 |