본문 바로가기
공부 및 활동/스프링 강의 정리

[스프링 입문] 4. 스프링 빈과 의존관계

by KChang 2021. 9. 26.

1. 컴포턴트 스캔과 자동 의존관계 설정

의존 관계 : Member Controller가 Member Serivce를 통해서 회원가입, 조회할 수 있어야 한다. (Controller가 Service 의존)

회원 컨트롤러에 의존관계 추가

package hello.hellospring.controller;

import hello.hellospring.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

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

@Controller를 사용해 MemberController 컨트롤러 생성

스프링 빈 관리 : @Controller 가 있다면 스프링은 스프링 컨테이너에 MemberController라는 객체를 생성, 이 컨트롤러를 컨테이너 안에서 관리한다.

 

 

private final MemberService memberService = new MemberService();

와 같이 new를 사용해 직접 MemberService 객체를 생성할 수도 있지만, 다른 컨트롤러가 회원 서비스를 사용할 때마다 회원 서비스 객체가 중복되어 생성될 것이고, 이때 컨트롤러는 굳이 다른 회원 서비스를 각각 생성하여 사용할 필요가 없다.

 

회원 서비스는 한 번만 생성되어 생성된 하나의 회원 서비스 인스턴스를 각각의 컨트롤러들이 공유하는 것이 좋다.

즉, 등록된 하나의 회원 서비스를 컨테이너로부터 받아 사용해야 한다.

이를 위해서 @Autowired를 사용한다.

 

 

스프링은 @Controller로 MemberService Controller를 스프링 컨테이너에 등록, 생성자를 호출하게 되고 이때 생성자에 @Autowired이 있다면 자동으로 스프링 컨테이너에 등록된 MemberService 객체를 찾아 매개변수로 넘겨준다.

객체 의존관계를 외부에서 넣어주는 것을 DI(Dependency Injection), 의존성 주입이라고 한다.

스프링이 @Autowired에 의해 자동으로 의존성을 주입한다.

 

 

main() 메서드 실행하면 오류 발생

@Controller
public class MemberController {
    private final MemberService memberService;

    @Autowired
    public MemberController(MemberService memberService) {
        this.memberService = memberService;
    }
}
오류발생
Parameter 0 of constructor in hello.hellospring.controller.MemberController required a bean of type 'hello.hellospring.service.MemberService' that could not be found.

 

오류가 난 이유 : 컨트롤러는 컨테이너에 등록되었지만 아직 회원 서비스는 컨테이너에 등록되지 않았기 때문이다.

사진2

MemberService는 아직 단순한 자바 코드이므로, 아직 스프링이 이를 인식하고 컨테이너에 스프링 빈으로 등록할 수 없다.

현 소스에서는 memberController는 @Controller를 통해 컨테이너에 등록되었지만 memberService는 스프링 빈으로 컨테이너에 등록되지 않았기 때문에 의존성 주입이 이루어지지 않는다.

 

 

memberService를 스프링 빈으로 등록하기 위해 컴포넌트 스캔 을 통해 자동 의존관계를 설정해야한다.

컴포넌트 스캔 : @Controller를 통해 컨트롤러가 컨테이너에 자동으로 등록되는 원리이다.

컴포넌트 스캔 원리

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

 

 

컴포넌트 스캔과 자동 의존관계 설정, 스프링 빈을 자동으로 등록하는 정형화된 패턴 애노테이션

@Controller

@Service

@Repository

세 가지 각각은 @Component로 대체될 수 있다.

(각각의 클래스는 @Component를 포함하기 때문이다.)

컴포넌트 관련 애노테이션이 존재한다면 스프링이 객체를 생성, 컨테이너에 스프링 빈으로 등록한다.

MemberService는 @Service

MemoryMemberRepository는 @Repository

로 스프링 컨테이너에 등록된다.

 

 

@Service
public class MemberService {

    private final MemberRepository memberRepository;

    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }
@Repository
public class MemoryMemberRepository implements MemberRepository {}

MemberService가 @Service를 통해 스프링 빈으로 등록될 때에도 생성자를 호출한다.

이때, @Autowired가 있으면 @Repository를 통해 컨테이너에 등록된 리포지토리 객체에 의존성을 주입한다.

스프링 컨테이너

@SpringBootApplication을 실행하면 속해있는 패키지 내의 @Controller, @Service, @Repository를 통해 각각이 컨테이너에 등록되고, @Autowired를 통해 의존관계를 가지는 모습이다.

Controller를 통해 외부 요청을 받고, Service에서 비즈니스 로직을 처리, Repository에서 데이터를 저장하는 것이 위에서 언급한 정형화 패턴이라고 할 수 있다.

 

참고로 스프링은 스프링 컨테이너에 스프링 빈 등록시 중복되지 않게 유일하게 하나의 객체만 싱글톤으로 등록한다.

예를 들어 따로 설정을 하는 경우가 아니라면 다른 컨트롤러(또는 서비스)에서 사용하는 리포지토리는 같은 리포지토리 인스턴스를 사용한다.

 

마지막으로, @SpringBootApplication하위에 있는 소스 파일에는 스프링 빈으로 등록된다.(이외는 안된다고 생각한다.)

컴포넌트 관련 애노테이션 : 스프링 객체를 각각 생성하여, 스프링 컨테이너에 등록한다. @Autowired : 연관 관계, 스프링 객체를 연결해준다.

 

 

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

이번에는 직접 설정 파일을 등록하여 컨테이너에 스프링 빈을 등록하는 방법이다.

이전에 회원 서비스와 회원 리포지토리에 작성했던 @Service, @Repository, @Autowired 애노테이션을 제거하고 진행한다.

1) SpringConfig.java 생성

package hello.hellospring;

import hello.hellospring.repository.MemberRepository;
import hello.hellospring.repository.MemoryMemberRepository;
import hello.hellospring.service.MemberService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SpringConfig {

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


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

클래스명 앞에 @Configuration을 등록하면 스프링이 설정 파일임을 인식, 컨테이너에 스프링 빈을 등록할 준비를 한다.

@Bean을 사용하여 다음의 메서드를 호출해 스프링 빈이 등록된다.

 

memberService메서드가 호출되면 MemberService 객체가 생성되고, 이때 @Bean을 통해 컨테이너에 등록된 레파지토리가 매개변수로 회원 서비스 객체와 연결, 회원 서비스 객체가 반환된다. 이때 스프링 빈으로 등록된다.

 

컨트롤러컨포넌트 스캔과 동일하게 작동한다.

@Controller로 스프링 컨테이너에 등록되며

@Autowired로 스프링 빈에 등록된 객체와 연결된다.

이와 같이, 설정 파일을 직접 등록하여 스프링 빈을 등록할 수 있다.

 

 

DI(Dependency Injection)란?

1) 필드 주입

생성자 없이 @Autowired로 DI를 주입하는 방법이다.

    // 필드 주입
    @Autowired private MemberService memberService;

2) setter 주입

setter에 @Autowired로 연결하는 방법이다.

(누구나 들어올 수 있어 위험하다.)

    // setter 주입 (누구나 들어올 수 있어 위험하다)
    @Autowired
    public void setMemberService(MemberService memberService){
        this.memberService = memberService;
    }

3) 생성자 주입

new를 사용하여 생성자를 호출할 때 DI를 호출하는 방법으로, 일반적인 프로젝트에서는 조립(생성자 호출) 시점에 생성자를 한 번만 호출하여 컨테이너에 스프링 빈을 등록하고, 이후에는 의존관계가 동적으로 변경되는 경우가 거의 없기 때문에 생성자 주입이 주로 권장된다.

 

 

실무에서는?

1) 정형화된 컨트롤러, 서비스, 리포지토리 같은 코드는 컴포넌트 스캔을 사용

2) 정형화되지 않거나 상황에 따라 구현 클래스를 변경해야 할 경우 설정(@Configuration)을 통해 스프링 빈으로 등록

 

 

DB?

현재 임시로 사용하고 있는 MemoryMemberRepository를 나중에 실제 DB로 변경할 예정이기 때문에 컴포넌트 스캔 방식 대신 자바 코드로 스프링 빈을 설정

  • 실제로 SpringConfig 파일의 new MemoryMemberRepository();를 new DBMemberRepository(); 만 변경하여 간단히 레파지토리를 변경할 수 있다.

 

 

주의해야할 점

@Autowired를 사용하는 DI는 MemberController, MemberService와 같이 스프링이 관리하는 객체에서만 동작한다.

즉, 스프링 빈으로 등록되어 스프링 컨테이너에 올라가야만 @Autowired가 동작한다.

ex)

  • @Configuration으로 설정 파일은 생성되어 있지만 @Bean으로 서비스, 레파지토리가 등록되지 않은 경우
  • main() 등에서 직접 new를 사용하여 객체를 생성

이는 스프링 컨테이너에 올라간 객체가 아니므로(스프링 관리하는 객체가 아니기 때문에) @Autowired는 동작하지 않는다.

 

 


강의 주소

참고 자료1

댓글