의존성 주입을 하는 여러 방식
Spring에서 의존성을 주입(DI : Dependency Injection)하는 방법은 생성자 주입(lombok의 @RequiredArgsConstructor이용), 필드주입(필드 주입(@Autowired를 단 필드), 수정자주입(setter메소드에 @Autowired) 3가지 방식이 있다.
우선 의존성 주입이란 무엇인지 알아보자.
new연산자를 이용하여 객체를 생성하는 것이 아니고, 외부에서 객체를 주입받아 생성하는 것이다.
객체 내부에 다른 객체를 생성하면 수정 시 새로 생성된 클래스도 함께 수정해야 하므로 강한 결합도를 가지고, 코드의 재활용성이 떨어지는 문제가 생긴다. ( 결합도는 낮추고 응집도는 높여야 한다. )
그러나 스프링은 @Autowired을 붙이는 필드 주입 방식을 선호하지 않는다.
Spring Team recommends: “Always use constructor based dependency injection in your beans. Always use assertions for mandatory dependencies”.
맞는지 모르겠지만 대충 해석해보면 “너의 bean에는 항상 생성자 기반 의존성 주입을 사용해라. 필수 종속성에 항상 선언하라.”
그 전에, 바로 위에서 말한 spring bean은 무엇일까?
스프링은 경량 컨테이너로서 객체 생성, 소멸과 같은 life cycle을 관리하며 스프링 컨테이너로부터 필요한 객체를 얻을 수 있다. 스프링 컨테이너에 의해 자바 객체가 만들어지면 이 객체를 스프링은 스프링 빈이라고 한다. (스프링 빈과 자바 일반 객체와의 차이점은 없고, 스프링 컨테이너로 만들어진 객체를 스프링 빈이라고 하는 것.) 쉽게 말해 스프링에서 관리하는 객체이다.
그렇다면 스프링에서의 의존성 주입은?
스프링 프레임워크에는 객체의 생성, 소멸과 같은 생명주기를 관리하기 위해 다양한 어노테이션이 제공되어 쉽게 스프링에서 관리하는 객체를 생성하고, 의존성 주입을 쉽게 할 수 있다.
스프링 빈 등록 어노테이션 : 의존성 등록 대상
l @Configuration : 해당 클래스 내부에 Bean을 여러 개 등록할 때 사용
l @Bean : 개발자가 컨트롤할 수 없는 외부 라이브러리를 Bean으로 등록할 때 사용
l @Component : 개발자가 직접 컨트롤 할 수 클래스(직접 만든)를 Bean으로 등록할 때 사용
l @Controller : 프레젠테이션 구성 요소에서 Bean으로 등록할 때 사용
l @Service : 비즈니스 서비스 레이어에서 Bean으로 등록할 때 사용
l @Respository : 데이터 액세스 개체(DAO)에서 Bean으로 등록할 때 사용
의존성 주입 어노테이션 : 의존성 주입 대상 (스프링빈 등록된 것을 '대상'에 주입 시)
l @Autowired : 자동으로 스프링 빈 객체를 특정 참조 변수에 매핑해주는 어노테이션
l @Primary : 특정 의존성을 주입할 수 있는 후보가 둘 이상인 경우 객체 생성의 우선권을 부여(의존성 등록 대상에 사용)
l @Qualifier : 이름을 통해 의존 객체를 선택할 수 있도록 매핑해주는 어노테이션
빈으로 등록해야 할 어노테이션을 찾기 : @Component, @Controller, @Service,@Repository,.. 등 어노테이션 찾아 Bean 등록
// 해당 클래스가 존재하는 패키지 이하(동일 혹은 아래)에 있는 빈으로 등록해야 할 어노테이션을 찾아 전부 빈으로 등록
l @ComponentScan(basePackageClasses="클래스. class")
스프링 빈 등록 방법 : @Configuration 어노테이션을 이용한 방법
/* @Configuration 어노테이션 : XML - Bean 태그 이용 방법과 동일 */
// Bean 설정 파일을 명시(스프링 설정 파일)
@Configuration
public class 클래스명 {
@Bean("빈의 이름")
public HelloRepository helloRepository() {
return new HelloRepository();
}
@Bean("빈의 이름")
public HelloService helloService() {
HelloService helloService = new HelloService();
// 의존성 주입 setter 이용
// HelloService에 setter 메소드가 존재해야 가능
helloService.setHelloRepository(helloRepository)
return new HelloRepository();
}
}
스프링 부트 어노테이션 @SpringBootApplication
스프링 부트 프로젝트를 생성하면 기본적으로 애플리케이션을 실행할 수 있는 클래스가 생성되고 @SpringBootApplication이 붙어있다. 해당 어노테이션 안에 @ComponentScan과 @Configuration 이 내포되어 있어, 해당 클래스 이하에 존재하는 @Component, @Controller, … 등의 어노테이션을 찾아 빈으로 등록한다.
-> 그래서 해당 클래스 이하에 패키지들을 생성하고, 각 클래스에 예를 든 어노테이션들을 쓰면 따로 빈으로 등록해주지 않아도 됐던 것이다.
스프링 IoC ( Inversion Of Control ) 컨테이너 구조를 보고 spring에서 DI가 어떻게 이뤄지는지 보자.
Application context가 Bean Factory(스프링 빈 컨테이너에 접근하기 위한 최상위 인터페이스)를 의존하고 있고,
bean Factory가 객체에 대한 생성을 담당하는 것을 볼 수 있다.
spring에서 loc Container라는 것은 결국 Application Context와 동일하며, 바로 이 IoC container가 주축이 되어 DI가 이루어지게 되는 것이다.
IoC container가 객체(Bean)에 대한 설정 파일(xml)혹은 설정 객체(Configuration)들을 읽어서 해당 객체의 의존성 주입이 이루어지게 된다.
즉, IoC Container는 객체에 대한 생명주기를 담당하고 있다고 할 수 있다.
객체의 생명주기: Container started -> Bean Installed -> dependencies injected -> Custom init() method -> Custom utility method -> Cutsom destroy() method
어쨌든 핵심 내용은 다른 주입방식 말고 생성자 주입 방식을 권장한다는 의미다.
스프링팀이 spring4.3 이후부터 이와 같이 말하는 이유는 필드 주입방식과 수정자 주입 방식에 문제가 있기 때문인데, 순환 참조로 인한 스택오버플로우 발생 가능성과 final로 선언할 수 없어 객체가 변할 가능성, null처리 불가 등의 이유가 있다.
이 문제들을 생성자 주입방식으로 전환하면 방지할 수 있다.
- 단일 책임의 원칙(Single Responsibility Principle)을 지키기 쉽다. -> 한눈에 해당 객체에 대한 의존관계와 복잡도를 알 수 있다.
- 테스트가 용이하다 -> DI컨테이너를 사용하지 않고도 인스턴스화 할 수 있고, 다른 DI프레임워크로 쉽게 바꿀 수 있다.
- Immutability -> 생성자로 주입했기 때문에, 객체의 불변성을 보장할 수 있다. Final로 선언 가능
- 순환 의존성 -> 생성자 주입에서는 객체가 서로 순환 의존성을 가질 경우 BeanCurrentlyInCreationException이 발생한다.
위와 관련하여 코드 예시와 함께 설명하는 글이 있다. 참고하면 좋을 듯..
https://madplay.github.io/post/why-constructor-injection-is-better-than-field-injection
또 참고한 여러 글들..
https://huisam.tistory.com/entry/springDI
'spring | spring boot' 카테고리의 다른 글
SpringBoot - Jasypt를 이용한 개인정보 암호화 (0) | 2022.03.30 |
---|---|
springboot-개발환경 분리하기 application.yaml (0) | 2022.03.24 |
JSON object, array parsing (0) | 2022.02.15 |
SpringBoot yaml, naver API와 연결하기 (0) | 2022.02.03 |
spring boot 에서 swagger 사용하기 +)버전오류 (0) | 2022.02.03 |
댓글