[Spring Boot] 스프링 빈과 의존 관계

Updated:

1. 스프링 빈을 등록하고 의존관계 설정하기

멤버 서비스, 레퍼지토리, 멤버 객체 만듦, 서비스를 통해 멤버 가입할 수 있고, 레퍼지토리에 저장이 되고, 레퍼지토리에서 꺼내올 수 있고,, 테스트도 만들고 했다.

화면을 붙이려면 컨트롤러, 뷰 템플릿 필요

멤버 컨트롤러가 필요한데 멤버 컨드롤러가 멤버 서비스를 통해 가입, 데이터 조회를 할 수 있어야 한다. -> 이것을 서로 의존관계가 있다. 라고 한다(멤버 컨트롤러가 멤버 서비스를 의존한다.)

목표 : 회원 컨트롤러가 회원 서비스와 회원 레퍼지토리를 사용할 수 있게 의존 관계를 준비

실습

src/main/java/com.example.memberproject/controller 경로에 MemberController.java 생성

아래 코드는 스프링 컨테이너가 관리를 한다는 것

@Controller
public class MemberController {
    
}

스프링이 처음에 뜰 때, 스프링 컨테이너라는 통이 생긴다. @Controller 어노테이션이 있으면 MemberController 객체를 생성해 통에 넣어두고 스프링이 관리

스프링 컨테이너에서 스프링 빈이 관리된다 라고 표현

이제 MembeService를 가져다 써야한다.

private final MemberService memberService = new MemberService();

위에 처럼 new로 생성을 해서 쓸수도 있다. 그런데 스프링이 관리를 하게 되면, 다 스프링 컨테이너에 등록을 하고, 스프링 컨테이너로부터 받아서 쓰도록 다 바꿔야 한다.

new로 생성을 하면 MemberController 외에도 다른 여러 컨트롤러들이 MembeService를 가져다 쓸 수 있다. 그냥 하나만 생성해서 공용으로 쓰면 된다.

이렇게 쓰는 것보다 스프링 컨테이너한테 등록하고 쓰자. 그럼 딱 하나만 등록이 된다.

private final MemberService memberService;

@Autowired
public MemberController(MemberService memberService) {
    this.memberService = memberService;
}

MemberService를 스프링이 스프링 컨테이너에 있는 MemberService를 가져다가 연결 시켜준다.

  • 생성자에 @Autowired가 있으면 스프링이 연관된 객체를 스프링 컨테이너에서 찾아서 넣어준다. 이렇게 객체 의존 관계를 외부에서 넣어 주는 것을 DI(Dependency Injection), 의존성 주입이라고 한다.
  • 이전 테스트에서는 개발자가 직접 주입했고, 여기서는 @Autowired에 의해 스프링이 주입

여기까지 해놓고 실행시키면 오류 발생

Parameter 0 of constructor in com.example.memberproject.controller.MemberController required a bean of type 'com.example.memberproject.service.MemberService' that could not be found.

memberService가 스프링 빈으로 등록되어 있지 않다.

image

참고 : helloController는 스프링이 제공하는 컨트롤러여서 스프링 빈이 자동 등록(@Controller가 있으면 자동 등록)

MemberController는 @Controller를 적어 놓으니 스프링이 아 이건 내가 관리하는 얘니깐 생성을 해야 겠구나..! 라고 알 수 있다.

하지만 MemberService는 순수한 자바 클래스이다. 그래서 스프링이 MemberService를 알 수 있는 방법이 없다. 그래서 아무것도 안된다.

멤버 서비스 스프링 빈 등록

MemberService에 @Service를 넣어준다. 그럼 스프링이 올라 올 때, 어?! 서비스네? 하고 스프링 컨테이너에 MemberService를 등록해준다.

@Service
public class MemberService {

    private final MemberRepository memberRepository;

    @Autowired
    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

MemberService는 MemberRepository 필요하다. @Service가 있으면 스프링이 생성을 할 때, 어라 서비스네? 하고, 스프렝 컨테이너에 등록을 하면서 생성자를 호출한다. 그때 @Autowired가 있으면 아 너는 MemberRepository가 필요하구나 하고, 스프링 컨테이너에 있는 MemberRepository를 딱 넣어준다.

지금은 MemoryMemberRepository가 구현체로 있다. 그래서 MemoryMemberRepository를 딱 MemberService에 주입해준다.

회원 리포지토리 스프링 빈 등록

MemoryMemberRepository에 @Repository를 넣어준다. 그럼 스프링이 올라 올 때, 어?! 리퍼지토리네? 하고 스프링 컨테이너에 MemoryMemberRepository를 등록해준다.

@Repository
public class MemoryMemberRepository implements MemberRepository {
    ...
}

이렇게 해놓으면 스프링이 뜰 때, Controller - Service - Repository 를 쫙 가지고 올라온다.

✅ Controller - Service - Repository 는 정형화된 패턴이다. ✅ Controller를 통해서 외부 요청을 받고, Service에서 비즈니스 로직을 만들고, Repository에서 데이터를 저장


스프링 빈 등록 이미지

image

  • memberService와 memberRepository가 스프링 컨테이너에 스프링 빈으로 등록되었다.
  • 멤버 컨트롤러가 등록될 때, 스프링 빈에 있는 멤버 서비스 객체를 넣어줌(의존 관계 주입, DI)

  • 스프링이 올라올 때, 컴포넌트에 관련된 어노테이션()이 있으면 객체를 생성해 스프링 컨테이너에 등록을 한다.
  • @Autowired : 선을 연결해 준다. 연간관계, 그래서 멤버 컨트롤러가 멤버 서비스를 쓸 쑤 있게 해준다. 멤버 서비스가 멤버 리포지토리를 쓸 수 있게 되는 것이다.

참고 : 스프링은 스프링 컨테이너에 스프링 빈을 등록할때, 기본으로 싱글톤으로 등록한다. (유일하게 하나만 등록해서 공유) 따라서 같은 스프링 빈이면 모두 같은 인스턴스다. 설정으로 싱글톤이 아니게 설정할 수 있지만, 특별한 경우를 제외하면 대부분 싱글톤을 사용한다.

다시 실행을 해보자

Screen Shot 2022-04-08 at 6 24 49 PM

잘 실행 된다. 스프링이 컨테이너를 만들때, 문제 없이 잘 만들었다는 것 이다.

하지만 지금 회원 컨트롤러에 관련된 어떤 기능도 없다. 우선 연결 하는 것만 설명한 것!

정리

스프링 빈을 등록하는 2가지 방법

  • 컴포넌트 스캔과 자동 의존관계 설정 (이번에 한것 @Controller, @Service, @Repositroy)
  • 자바 코드로 직접 스프링 빈 등록하기 (둘다 알아야 함)

컴포넌트 스캔과 자동 의존관계 설정

  • @Component 가 있으면 스프링 빈에 자동 등록 된다.
  • @Controller 컨트롤러가 스프링 빈으로 자동 등록된 이유도 컴포넌트 스캔 때문

  • @Component를 포함하는 다음 어노테이션도 스프링 빈으로 자동 등록된다. (@Component 를 포함했기 때문에)
    • @Controller
    • @Service
    • @Repository

아무 곳에나 @Component가 있어도 되는가? -> 안된다. main 애플리케이션 부터 패키지를 포함해서 하위의 파일들은 스프링이 자동으로 다 뒤져서 스프링 빈으로 등록을 한다. 하지만 이 하위 패키지랑 동일하거나 하위 패키지가 아닌 애들은 스프링 빈으로 컴포넌트 스캔을 하지 않는다. 설정해주면 되긴 하지만 기본적으로 컴포넌트 스캔의 대상이 아님


2. 자바 코드로 직접 스프링 빈 등록하기

하나하나 직접 스프링에 등록하는 방법, 설정 파일에 등록

실습

회원 서비스와 회원 리포지토리의 @Service, @Repository, @Autowired 애노테이션을 제거하고 진행

애노테이션을 제거하고 실행해보면 당연히 실행이 안된다.

Screen Shot 2022-04-08 at 6 57 00 PM

-> 스프링 올라올 때, 컴포넌트 스캔이 안된다. MemberService가 스프링 빈에 등록이 안되어 있어 오류가 난다.

src/java/com.example.memberproject 경로에 SpringConfig.java 생성하고 아래 코드 작성

@Configuration
public class SpringConfig {

    @Bean
    public MemberService memberService() {
        return new MemberService(memberRepository());
    }

    @Bean
    public MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }
}

-> 스프링이 뜰 떄, @Configuration을 읽고, 어? 이거는 스프링 빈에 등록하라는 뜻이네, 하고 인식을 한다. 그러면서 memberService를 return new MemberService(); 라는 로직을 호출을 해서 스프링 빈에 등록을 해준다.

그럼 memberService는 스프링 빈에 등록이 된다.

-> MemberService생성자에서 memberRepository를 넣어줘야 한다. memberService는 아래의 memberRepository() 스프링 빈에 있는 것을 엮어 줘야 한다.

-> 스프링이 뜰 때, memberService와 memberRepository 둘 다 스프링 빈에 등록을 한다. 스프링 빈에 등록되어 있는 memberRepository를 memberService에 넣어준다.

-> 컨트롤러는 어차피 스프링이 관리하는 것 이기 때문에, 컴포넌트 스캔에 올라가고, 컴포넌트 스캔이기 때문에 @Autowired로 해주면 된다.

결국 아래 그림과 같은 관계가 완성된다.

image

실행해 보면 정상적으로 실행이 된다.

Screen Shot 2022-04-08 at 7 15 49 PM

참고

❗️ (참고)
XML로 설정하는 방식도 있지만 최근에는 잘 사용하지 않으므로 생략
❗️(참고)
DI에는 필드 주입, setter 주입, 생성자 주입 이렇게 3가지 방법이 있음. 의존 관계가 실행 중에 동적으로 변하는 경우는 거의 없으므로 생성자 주입을 권장. (실제로는 아예 없음)

setter은 public 하게 노출 된다. 생성자 주입 추천!!

❗️ (참고)
실무에서는 주로 정형화된 컨트롤러, 서비스, 레퍼지토리 같은 코드는 컴포넌트 스캔을 사용. 그리고 정형화 되지 않거나, 상황에 따라 구현 클래스를 변경해야 하면 설정을 통해 스프링 빈으로 등록.

(데이터 저장소가 선정되지 않았다는 가상의 시나리오를 설정했다. 그래서 지금 MemberRepository 인터페이스를 설계를 하고, 구현체로 MemoryMemberRepository를 쓰는 그림이 됐다. 그런데 나중에 MemoryMemberRepository를 다른 Repository로(실제 DB 연결하는) 바꿔치기 할 거다. 기존의 운영되던 코드를 하나도 수정하지 않고 저장소를 변경할 것임. -> Config 파일에서 하나만 수정하면 된다. 직접 설정 파일을 운영할 때의 장점!

❗️ (주의)
@Autowired를 통한 DI는 helloController, memberService 등과 같이 스프링이 관리하는 객체에서만 동작. 스프링 빈으로 등록하지 않고 내가 직접 생성한 객체에서는 동작 x

❗️ 스프링 컨테이너, DI 관련된 자세한 내용은 스프링 핵심 원리 강의에서 설명

참고

스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술

YoonkyungH.log

Leave a comment