본문 바로가기

Spring/스프링 입문

03. 스프링 빈과 의존관계 - (1) 컴포넌트 스캔과 자동 의존관계 설정

💡 본 게시글은 김영한님의 인프런(Inflearn) 강의 스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술에 대해 공부하고, 정리한 내용입니다.

1) 이전에 구현했던 내용들과 이번에 구현할 내용

지금까지는 MemberRepository와 MemberService를 만들었습니다. 이를 통해 다음 로직을 구현하고 테스트 코드를 작성했습니다.

  1. 멤버 객체 만들기
  2. 서비스를 통한 멤버 가입
  3. 리포지토리를 통한 멤버 정보 저장 및 조회

이제 이 로직을 웹 페이지에 적용하려고 합니다. 이를 위해서는 회원 가입 결과를 HTML 페이지로 표시해야 하므로, Controller와 View template가 필요합니다.

 

MemberController를 만들어 사용하려고 합니다. MemberController는 MemberService를 통해 회원 가입을 하고, 데이터를 조회해야 합니다.

 

이런 관계를 "MemberController가 MemberService를 의존한다"라고 표현합니다. 이번 시간에는 이 의존관계를 설정하는 작업을 진행해보겠습니다.


2) MemberController 생성하기

 먼저, Java - Controller 패키지 내에 MemberController 클래스를 생성하고, @Controller 어노테이션을 붙여줍니다.

@Controller
public class MemberController {
    // ...
}

 

@Controller 어노테이션을 붙이면 어떤 일이 일어나는 지 알아보겠습니다.

 

Spring 애플리케이션이 실행될 때, Spring 컨테이너가 생성됩니다. 이때, @Controller 어노테이션을 달아둔 클래스의 객체가 Spring 컨테이너에 저장되고, Spring이 이를 관리하게 됩니다.

 

이전에 만들었던 HelloController 클래스도 @Controller 어노테이션을 붙여서 Spring이 이를 관리하게 했습니다.

 

Spring이 시작될 때, @Controller 어노테이션을 보고 HelloController 객체를 생성한 후 이를 관리합니다. 필요할 때마다 Spring 컨테이너에서 해당 컨트롤러를 가져와 사용합니다.

 

이런 방식으로 동작하는 것을 "Spring 컨테이너에서 Spring Bean이 관리된다"라고 표현합니다.

 

이처럼 MemberController에도 @Controller 어노테이션을 붙임으로써, 이를 Spring 컨테이너에 저장하고, Spring이 관리하도록 만들 수 있습니다.


3) Autowired를 이용하여 MemberController와 MemberService 연결하기

 이제 MemberController가 MemberService를 사용해야 합니다.

 

이전과 같이 new 키워드를 이용하여 MemberService 객체를 생성하여 사용할 수도 있지만, MemberService를 Spring 컨테이너에 등록해서 Spring이 이를 관리하도록 하는 것이 바람직합니다. Spring 컨테이너에 등록하면 하나의 객체만 등록되며, 이를 싱글턴 패턴이라고 합니다.

 

이렇게 하면 다른 Controller에서도 MemberService를 가져와 사용할 수 있습니다. (회원 관련 함수들은 여러 곳에서 사용되기 때문에 이는 중요합니다.)

 

MemberService를 Spring 컨테이너에 연결하는 방법은 다음과 같습니다.

(1) MemberController 클래스에서 memberService를 클릭하고 Alt+Enter를 누른 뒤, "Add Constructor Parameter"를 선택합니다.

 

(2) 생성자에 @Autowired 어노테이션을 붙여줍니다.

@Controller
public class MemberController {
    private final MemberService memberService;

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

 

이렇게 하면 Spring이 MemberService를 Spring 컨테이너에 연결해줍니다. 그런데 이 코드를 실행하면 오류가 발생합니다.

"Practice1.practicespring.service.MemberService' that could not be found."

 

이는 MemberService를 찾을 수 없다는 오류 메시지입니다.

 

@Autowired 어노테이션이 있다면 Spring 컨테이너에서 해당 객체를 찾아 연결시켜줍니다. 그러나 이렇게 하려면 MemberService가 Spring 컨테이너에 스프링 빈으로 등록되어 있어야 합니다.

 

그런데 MemberService는 순수한 Java 클래스이므로 스프링 빈으로 등록되어 있지 않습니다. 따라서, Spring이 MemberService를 인식할 방법이 없어 오류가 발생한 것입니다.


4) 어노테이션을 통해 Spring Container에 등록하기

 어노테이션을 이용하면 MemberService와 MemoryMemberRepository를 Spring 컨테이너에 등록할 수 있습니다. @Service와 @Repository 어노테이션을 사용하면 됩니다.

 

(1) 우선, MemberService 클래스 위에 @Service 어노테이션을 붙여줍니다.

(2) MemoryMemberRepository 클래스 위에는 @Repository 어노테이션을 붙여줍니다.

 

이렇게 하면 MemberController, MemberService, MemoryMemberRepository가 Spring 컨테이너에 등록됩니다.

 

MemberService의 생성자를 보면, MemberRepository를 할당받습니다. 이때 @Autowired 어노테이션을 이용하여 Spring 컨테이너에서 등록된 MemoryMemberRepository 객체를 MemberService에 넣어줍니다.

 

이렇게 하면 MemberController, MemberService, MemoryMemberRepository가 모두 연결되며, Spring이 이들을 컨테이너에 등록하고, 필요할 때 @Autowired 어노테이션을 통해 객체를 가져옵니다.

 

이제 코드를 실행해보면 오류 없이 잘 동작하는 것을 확인할 수 있습니다.

 

Controller, Service, Repository는 정형화된 패턴입니다. Controller는 외부 요청을 받고, Service는 비즈니스 로직을 만들며, Repository는 데이터를 저장하는 역할을 합니다.

// MemberController
@Controller
public class MemberController {
    private final MemberService memberService;

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

// MemberService
@Service
public class MemberService {
    private final MemberRepository memberRepository;

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

// MemoryMemberRepository
@Repository
public class MemoryMemberRepository implements MemberRepository{
    private static Map<Long, Member> store = new HashMap<>();
    private static long sequence = 0L;
    // ...
}

 

이와 같이, @Service, @Repository, @Autowired 어노테이션을 이용하면 Spring 컨테이너가 객체의 생명주기를 관리하며, 필요할 때 적절한 객체를 주입해줍니다. 이는 코드의 유연성과 재사용성을 높여줍니다.


4) 컴포넌트 스캔

 Spring Bean을 등록하는 방법 중 하나는 컴포넌트 스캔입니다. 컴포넌트 스캔은 @Controller, @Service, @Repository, @Autowired와 같은 어노테이션을 사용하여 Spring Bean을 자동으로 등록하고, 필요한 곳에 자동으로 주입하는 방식입니다.

 

@Controller, @Service 등의 어노테이션 내부 코드를 보면 @Component 어노테이션이 있는 것을 확인할 수 있습니다. 이는 Spring이 시작될 때 해당 어노테이션이 붙은 클래스를 찾아 해당 클래스의 인스턴스를 생성하고, 이를 Spring 컨테이너에 등록한다는 것을 의미합니다. 그리고 @Autowired 어노테이션을 통해 필요한 곳에 자동으로 주입합니다.

 

이런 방식으로 MemberController, MemberService, MemoryMemberRepository 같은 클래스를 Spring Bean으로 등록하고, 필요한 곳에 주입하여 사용할 수 있습니다.

 

컴포넌트 스캔의 원리

(1) @Component 어노테이션이 붙은 클래스는 Spring Bean으로 자동 등록됩니다.

 

(2) @Controller, @Service, @Repository 등의 어노테이션도 내부적으로 @Component 어노테이션을 포함하고 있으므로, 이들도 Spring Bean으로 자동 등록됩니다.

 

(3) 컴포넌트 스캔 시 생성되는 객체는 기본적으로 싱글톤으로 등록되며, 생성된 객체는 하나만 존재하고 이를 공유하여 사용합니다.

 

(4) 컴포넌트 스캔은 기본적으로 메인 메서드가 위치한 패키지와 그 하위 패키지들을 대상으로 합니다.

 

 따라서 컴포넌트 스캔을 통해 Spring Bean을 간편하게 등록하고 관리할 수 있으며, 이를 통해 애플리케이션의 구조를 효과적으로 관리할 수 있습니다.