DI와 스프링 Bean

ttomy·2022년 2월 19일
0

---김영한님의 스프링입문 강의의 내용을 정리한 글입니다---

DI(의존성 주입)이란?

우리는 자바에서 객체지향적인 프로그래밍을 위해 의존하는 객체,인터페이스의 구현체를 직접 부르지 않고 주입 받는다.

public class MemberService {
 private final MemberRepository memberRepository = 
 new MemoryMemberRepository();
}

이렇게 memberservice 안에서 memberRepositroy 의 생성자를 직접 호출하며 구현체에 의존하는 상황은 좋지않다.
서비스의 사용자는 서비스의 구체적 과정에 개입하는 일이 없어야 한다. 손님은 치즈버거를 시키든 불고기버거를 시키든 원하는 결과물을 받기만 하면 그만이지 버거가 만들어지는 과정까지 알 필요가 없다. 버거를 만드는 곳까지 손님이 들어가 내용물을 수정하는 일은 없어야 하는 것이다.
마찬가지로 MemberService의 사용자가 service의 구현부까지 들어와 수정하는 일이 없도록 의존하는 것을 주입받는 DI가 가능하게 변경해야 한다.
버거집을 계속 예로 들자면 서비스에 치즈버거 레시피,불고기버거 레시피 등을 주입받는 방법으로 바꿔야 한다는 것이다.

public class MemberService {
 private final MemberRepository memberRepository;
 public MemberService(MemberRepository memberRepository) {
 this.memberRepository = memberRepository;
 }
 
}

이렇게 수정한다면 아래와 같이 Memberservice를 수정하지 않고
다른 종속성을 사용할 수 있어 이전보다는 덜 service에 종속적이 된다.
그리고 아래와 같이 memberservice를 사용한다.

public class MemberController {
 memberRepository = new MemoryMemberRepository();
 memberService = new MemberService(memberRepository);
 
 //종속성 바꿀 경우 
 // ex)
 // memberRepositoryVer2 = new MemoryMemberRepositoryVer2();
 // memberService=new MemberService(memberRepositroyVer2)
 }

하지만 막상 DI를 가능하게 바꿨는데도 MemberController라는 memberService를 사용하는 입장의 함수가 new memoryMemberRepository() 를 선언하며 구현체를 알고 있는 상황이 된다. 이러면 DI의 메리트를 크게 못 느낄 수가 있다.
여기서 스프링이 bean을 이용해 DI를 지원하는 방법을 알 필요가 있다.

스프링 bean의 등록방법

스프링에서는 DI에 관련된 객체들을 Bean이라는 이름으로 스프링 컨테이너에서 생성하고 관리한다. 객체들이 Bean으로 관리되면 싱글톤으로 생성되어 효율적으로 사용이 가능하고, 의존성 주입이 더 편리해지고, 객체의 생명주기 관리도 유리해지는 등의 장점들이 있는데 여기서는 DI을 편하게하는 방식만 살펴보도록 하자.
결론부터 애기하자면 bean으로 등록된 객체는 의존성이 필요한 경우 bean으로 등록된 객체 중 적합한 것을 알아서 매핑해준다.

1) bean으로 등록된 클래스 중 의존성을 구현한 클래스가 있다면 자동으로 주입하는 방식
2) 설정클래스를 만들어 주입될 객체들을 지정하는 방식

을 지원하는데 아래가 그 방법이다.

  • 스프링 빈을 등록하는 2가지 방법
    -컴포넌트 스캔과 자동 의존관계 설정
    -자바 코드로 직접 스프링 빈 등록하기(java configuration)

컴포넌트 스캔

@Component 애노테이션을 붙이면 스프링 빈으로 등록이 되고,
@Component를 포함하는 @Controller,@Service,@Repository등의 애노테이션이 붙은 것도 스프링 빈으로 자동등록이 된다.

@Service
public class MemberService {
 private final MemberRepository memberRepository;
 @Autowired
 public MemberService(MemberRepository memberRepository) {
 this.memberRepository = memberRepository;
 }
}
@Repository
public class MemoryMemberRepository implements MemberRepository {}
@Controller
public class MemberController {
 private final MemberService memberService;
 @Autowired
 public MemberController(MemberService memberService) {
 this.memberService = memberService;
 }

}

생성자에 @Autowired를 사용하면 객체 생성 시점에 해당 스프링 빈을 찾아서 주입한다. 이때 생성자가 1개만 있으면 @Autowired는 생략할 수 있다.

MemberController에서 이전과의 차이가 보이는가?
Membercontroller에서 MemberService가 의존하는MemoryMemberRepository가 보이지 않게 되었다. @Repository로 인해 빈으로 등록되있기에 MemberRepository를 구현한 MemoryMemberrepository를 알아보고 자동으로 주입해 주었다.

*여기서 만약 MemberRepository를 구현하고 @Repository가 붙은 구현체가 2개 이상이 된다면 스프링이 어떻게 주입을 해주지? 하는 의문이 들었다면 훌륭하게 학습하고 있는 사람일 것이다. 이런 경우 @Qualifier라는 애노테이션을 이용해 의존할 객체를 선택한다.

자바코드로 스프링 빈 등록

인터페이스라는 역할에 맞는 실제 구현체를 지정해주는 설정클래스를 만드는 방법이다. 이 방법은 마치 역할에 맞는 배우들을 캐스팅하는 기획자를 하나 더 생성하는 것과 같이 비유된다.

@Configuration
public class SpringConfig {
 @Bean
 public MemberService memberService() {
 return new MemberService(memberRepository());
 }
 @Bean
 public MemberRepository memberRepository() {
return new MemoryMemberRepository();
 }
}
public class MemberService {
 private final MemberRepository memberRepository;
 
  public MemberService(MemberRepository memberRepository) {
 this.memberRepository = memberRepository;
 }
 
}

public class MemoryMemberRepository implements MemberRepository {}
@Controller
public class MemberController {
 private final MemberService memberService;
 
 @Autowired
 public MemberController(MemberService memberService) {
 this.memberService = memberService;
 }

}

위와 같이 @Configuration이 붙은 클래스에서 생성자를 정의하고 빈 등록을 함으로써 구현체를 지정하게 된다. 그 결과 controller에서 memberservice를 사용할 떄 구현체에 대한 관심없이 사용가능하다.
구현체를 바꾸고 싶다면 SpringConfig의 return하는 생성자를 교체하면 된다.

--- 위의 configuration을 이용한 방법과 @Autowired를 이용한 방법은 함께 사용될 수 없다. 스프링 빈의 중복등록이 있다면 오류가 발생한다. 위의 코드에서도 controller의 생성자는 @Autowired가 붙었지만 SpringConfig에서 Bean으로 등록된 service와 repositroy 생성자는 @Autowired를 또 붙여선 안된다.

DI의 방법들

DI를 하는 방법은 필드주입,setter주입,생성자 주입 이렇게 3가지가 있다. 그런데 우리는 위에서 컴포넌트 스캔을 이용한 방법,자바 코드로 스프링 빈을 등록하는 방법으로 빈을 등록하고 의존성을 주입에 도움을 받았다. 이 2방법은 DI의 3방법과 어떤 관련이 있는걸까.
우선 위에서 자바 코드로 빈을 등록한 방법은 생성자를 이용한 DI방식이었다. SpringConfig코드로 돌아가 보면 알겠지만 생성자를 정의한 후 @Bean으로 빈 등록을 한 방법이다.

위에서 예시를 보여주진 않았지만 필드 주입 방법도 위의 컴포넌트 스캔방법,@Autowired를 이용해 가능하다.

@Controller public class Controller { 
	@Autowired 
    private Service service; 
}

위와 같이 Service의 구현체가 빈으로 등록이 되어 있다면 필드에 @Autowired를 붙이기만 해도 DI가 가능하다.

+참조:https://dev-coco.tistory.com/70

하지만 스프링에서는 순환참조를 방지하고,불변성을 보장하고, test코드를 만들기 좋은 등의 이유로 생성자를 이용한 방법을 가장 권장한다.

Lombok을 사용한다면 @NoArgsConstructor,@RequiredArgsConstruct의
어노테이션을 이용해 DI를 받는 경우도 많은데, 이 또한 생성자를 이용한 방식이고 거기에 위에서 언급한

어떠한 빈(Bean)에 생성자가 오직 하나만 있고, 생성자의 파라미터 타입이 빈으로 등록 가능한 존재라면 이 빈은 @Autowired 어노테이션 없이도 의존성 주입이 가능하다.

이 스프링의 특성을 이용한 방식이다.
@Service,@Repository,@Controller 와 같은 애노테이션으로 빈 등록을 해놓으면 위 특성으로 인해 Lombok을 이용해 생성자를 생성해놓기만 해도 의존성 주입을 해준다.

0개의 댓글