웹 프로그래밍/공부, 연습

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

이 글은 인프런에서 김영한 님의 "스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술"을 수강 후 개인적으로 공부한 내용을 정리한 게시글입니다. 잘못된 점이나 부족한 부분이 있다면 언제든 지적 부탁드립니다.

 

해당 강의는 이곳에서 수강할 수 있습니다.

 

이전 강의에서는 회원 관리 예제를 작성하며 백엔드 개발을 실습해 보았습니다.

이번 강의에서는 스프링 빈과 의존관계에 대해 학습합니다.

 

 

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

 

이전 강의에서 작업한 로직을 화면으로 나타내기 위해서 컨트롤러를 작성해야 합니다.

클라이언트의 요청을 받은 컨트롤러는 서비스를 통해 view를 제공, 즉 회원 서비스를 사용하여 회원가입, 데이터 조회 등을 실시합니다.

이렇게 컨트롤러의 동작이 서비스에 의존하고, 우리는 이를 의존관계가 있다고 표현할 수 있습니다.

이러한 의존 관계를 어떻게 spring 스럽게 설정할지 알아봅니다.

 

컨트롤러 작성을 위해 프로젝트 아래에 이전에 HelloController를 작성한 것과 같이 MemberController 자바 파일을 작성, 다음과 같은 코드를 작성합니다.

 

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라는 객체를 생성, 이 컨트롤러를 컨테이너 안에서 관리합니다. 이를 스프링 빈이 관리된다고 합니다. 

 

컨트롤러는 회원 서비스를 의존하기 때문에 멤버변수로 MemberService를 memberService라는 이름으로 갖습니다. 이때 

private final MemberService memberService = new MemberService();

와 같이 new를 사용해 직접 MemberService 객체를 생성할 수도 있지만, 다른 컨트롤러가 회원 서비스를 사용할 때마다 회원 서비스 객체가 중복되어 생성될 것이고, 이때 컨트롤러는 굳이 다른 회원 서비스를 각각 생성하여 사용할 필요가 없습니다. 즉 회원 서비스는 한 번만 생성되어 생성된 하나의 회원 서비스 인스턴스를 각각의 컨트롤러들이 공유하는 것이 좋습니다. 다시 말해 등록된 하나의 회원 서비스를 컨테이너로부터 받아 사용해야 합니다.

 

이를 위해 @Autowired를 사용합니다.

스프링은 @Controller로 MemerServiceController를 스프링 컨테이너에 등록, 생성자를 호출하게 되고 이때 생성자에 @Autowired이 있다면 자동으로 스프링 컨테이너에 등록된 MemberService객체를 찾아 매개변수로 넘겨줍니다. 이렇게 객체 의존관계를 외부에서 넣어주는 것을 DI(Dependency Injection), 의존성 주입이라고 합니다.

이전 강의에서 개발자가 직접 DI를 주입했다면, 여기서는 스프링이 @Autowired에 의해 자동으로 의존성을 주입합니다.

 

위와 같이 코드를 작성 후 main() 메서드를 실행하면 오류가 발생합니다.

 

 

컨트롤러는 컨테이너에 등록되었지만 아직 회원 서비스는 컨테이너에 등록되지 않았기 때문입니다.

 

이미지 출처는 해당 강의 자료입니다

 

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

위의 자료를 살펴보면 memberController는 @Controller를 통해 컨테이너에 등록되었지만 memberService는 스프링 빈으로 컨테이너에 등록되지 않았기 때문에 의존성 주입이 이루어지지 않았습니다.

 

memberService를 스프링 빈으로 등록하는 방법으로 컴포넌트 스캔을 통해 자동 의존관계를 설정합니다.

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

컨트롤러를 생성할 때 @Controller로 스프링에 알려준 것과 같이, MemberService MemoryMemberRepository 또한 각각 @Service, @Repository로 스프링 컨테이너에 등록됩니다.

 

@Controller

@Service

@Repository

 

위의 애노테이션은 스프링 빈을 자동으로 등록하는 정형화된 패턴입니다. 세 가지 각각은 @Component로 또한 대체될 수 있으며(각각의 클래스는 @Component를 포함하기 때문입니다.), 스프링이 올라올 때 위와 같은 컴포넌트 관련 애노테이션이 존재한다면 스프링이 객체를 생성, 컨테이너에 스프링 빈으로 등록합니다.

 

@Service
public class MemberService { 
	private final MemberRepository memberRepository;

	@Autowired
 	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에서 데이터를 저장하는 것이 위에서 언급한 정형화된 패턴이라고 할 수 있습니다.

 

참고로 스프링은 스프링 컨테이너에 스프링 빈 등록 시 중복되지 않게 유일하게 하나의 객체만 싱글톤으로 등록합니다. 예를 들어 따로 설정을 하는 경우가 아니라면 다른 컨트롤러(또는 서비스)에서 사용하는 리포지토리는 같은 리포지토리 인스턴스를 사용합니다.

 

 

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

 

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

 

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

 

프로젝트 아래에 SpringConfig라는 이름의 자바 파일을 생성합니다.

 

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을 통해 컨테이너에 등록된 레포지토리가 매개변수로 회원 서비스 객체와 연결(@Autowired와 비슷), 회원 서비스 객체가 반환됩니다(스프링 빈으로 등록됩니다).

 

컨트롤러컴포넌트 스캔과 동일하게 작동합니다. 즉, @Controller로 스프링 컨테이너에 등록, @Autowired로 스프링 빈에 등록된 객체와 연결됩니다.

 

위와 같이 설정 파일(자바 코드)을 직접 등록하여 스프링 빈을 등록할 수 있습니다. 

 

 

DI에는 크게 필드 주입, setter 주입, 생성자 주입의 3가지 방법이 있습니다.

필드 주입은 생성자 없이 @Autowired로 DI를 주입하는 방법, setter 주입은 setter에 @Autowired로 연결하는 방법, 생성자 주입은 위에서 살펴본 것처럼 new를 사용하여 생성자를 호출할 때 DI를 호출하는 방법으로, 일반적인 프로젝트에서는 조립(생성자 호출) 시점에 생성자를 한 번만 호출하여 컨테이너에 스프링 빈을 등록하고, 이후에는 의존관계가 동적으로 변경되는 경우가 거의 없기 때문에 생성자 주입이 주로 권장됩니다.

 

또한 실무에서는 주로 정형화된 컨트롤러, 서비스, 리포지토리 같은 코드컴포넌트 스캔을 사용하고, 정형화되지 않거나 상황에 따라 구현 클래스를 변경해야 할 경우 설정(@Configuration)을 통해 스프링 빈으로 등록합니다. 해당 강의에서는 임시로 사용하고 있는 MemoryMemberRepository를 나중에 실제 DB로 변경할 예정이기 때문에 컴포넌트 스캔 방식 대신 자바 코드로 스프링 빈을 설정하였습니다. (실제로 SpringConfig 파일의 new MemoryMemberRepository();를 new DBMemberRepository();로 변경하여 간단히 리포지토리를 변경할 수 있습니다.)

 

주의해야 할 점은 @Autowired를 사용한 DI는 MemberController, MemberService와 같이 스프링이 관리하는 객체에서만 동작합니다. 즉, 스프링 빈으로 등록되어 스프링 컨테이너에 올라가야만 @Autowired가 동작합니다. 예를 들어 @Configuration으로 설정 파일은 생성되어 있지만 @Bean으로 서비스, 리포지토리가 등록되지 않은 경우이거나, 혹은 main() 등에서 직접 new를 사용하여 객체를 생성하더라도 이는 스프링 컨테이너에 올라간 객체가 아니므로(스프링이 관리하는 객체가 아니기 때문에) @Autowired는 동작하지 않습니다.

 

 

이번 강의에서는 스프링 빈과 의존관계에 대해 학습하였습니다.

다음 강의에서는 회원 관리 예제를 통해 웹 MVC 개발을 학습합니다.