개발/스프링 입문

(스프링 입문) 4. 스프링 빈과 의존관계

용꿀 2022. 9. 24. 02:10

 글은 김영한 님의 스프링 입문 강좌 수강 후에 정리한 글입니다.

(https://www.inflearn.com/course/스프링-입문-스프링부트/dashboard)

스프링 빈과 의존관계

회원 컨트롤러가 회원 서비스와 회원 리포지토리를 사용할 수 있게 의존관계를 추가하여야 한다.

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;
    }
}

 

MemberController에 의존관계를 추가한 것이다.

생성자에 @Autowired가 있으면 스프링이 연관된 객체를 스프링 컨테이너에서 찾아서 넣어주는데 이것을 의존성 주입, 즉 DI이라고 한다.

저번 회원 서비스 테스트 시에도 DI를 했었는데 그때는 개발자가 직접 주입한 형식이었고, 지금은 @Autowired를 사용하여 스프링이 직접 주입해준다.

 

Consider defining a bean of type 'hello.hellospring.service.MemberService' in your configuration.

 

하지만 실행 시에 다음과 같은 오류가 생긴다. 이는 memberService가 스프링 빈으로 등록되지 않았기 때문에 일어난 오류이다.

 

스프링 빈을 등록하는 2가지 방법이 있다. 첫 번째 방법은 컴포넌트 스캔과 자동 의존관계 설정이고, 두 번째 방법은 자바 코드로 직접 스프링 빈을 등록하는 방법이다.

 

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

 

1. 컴포넌트 스캔 원리

@Component가 있으면 스프링 빈으로 자동 등록된다. 그리고 @Component를 포함하는 @Controller, @Service, @Repository도 스프링 빈으로 자동 등록된다.

 

2. 회원 서비스 스프링 빈 등록

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

MemberService 클래스에 @Service를 사용하여 스프링 빈으로 자동 등록한다.

MemberService 생성자에 @Autowired를 사용하여 객체 생성 시점에 스프링 컨테이너에서 해당 스프링 빈을 찾아서 주입한다.

생성자가 하나인 경우에는 @Autowired를 생략할 수 있다.

 

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

@Repository
public class MemoryMemberRepository implements MemberRepository {}

위와 마찬가지로 MemoryMemberRepository 클래스에 @Repository를 사용하여 스프링 빈으로 자동 등록한다.

 

4. 스프링 빈 등록 이미지

 

1~3의 과정을 통해 memberService와 memberRepository도 memberController와 마찬가지로 스프링 컨테이너에 스프링 빈으로 등록이 완료되었다.

참고로 스프링 빈이 등록될 때 기본적으로 싱글톤(유일하게 하나만 등록해서 공유)으로 등록한다. 그래서 같은 스프링 빈이면 모두 같은 인스턴스다.

 

● 자바 코드로 직접 스프링 빈을 등록하기

 

우선 memberService와 memoryMemberRepository의 @Service, @Repository, @Autowired를 제거하고 진행한다.

 

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();
	}
}

 

현재 진행 중인 프로젝트는 DB를 어떤 것으로 할지 정해져 있지 않으므로 향후에 MemoryRepository를 다른 Repository로 대체할 예정이다. 그래서 컴포넌트 스캔 대신에 자바 코드로 스프링 빈을 설정하여 나중에 간단하게 수정하는 방법이 더 적당하다.

 

1. @Configuration

설정 파일을 만들거나, 스프링 빈을 등록할 때 사용하는 어노테이션이다.

 

2. @Bean

스프링 컨테이너에 스프링 빈을 등록할 때 사용하는 어노테이션이다.

특히 @Bean은 개발자가 직접 제어가 불가능하거나 정형화 되어있지 않거나, 상황에 따라 구현 클래스를 변경해야 할 때사용한다.

반면에 정형화된 Controller, Service, Repository 같은 코드는 컴포넌트 스캔을 사용한다.

 

위에서는 DI를 할 때 생성자 주입을 사용했는데 그 외에도 필드 주입과 setter 주입이 존재한다.

 

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

//setter 주입
@Autowired
public void setMemberService(MemberService memberService){
	this.memberService = memberService;
}

//생성자 주입
@Autowired
public MemberController(MemberService memberService){
	this.memberService = memberService;
}

 

필드 주입은 외부에서의 변경이 불가능해지므로 테스트 케이스 등을 작성할 때 객체 수정이 불가능하기 때문에 어려움을 겪을 수 있다.

setter 주입은 setter가 public으로 공개되기 때문에 잘못 수정되어 문제가 발생할 가능성이 있다.

생성자 주입은 생성자의 호출 시점에 1회 호출이 보장되고, 주입받은 객체는 변하지 않고, DI를 강제할 수 있다는 장점이 있다.

(출처: https://velog.io/@zihs0822/DI의존성-주입-방식별-장단점)

 

마지막으로 @Autowired 어노테이션을 통한 DI는 helloConroller , memberService 등과 같이 스프링이 관리하는
객체에서만 동작한다. 스프링 빈으로 등록하지 않고 내가 직접 생성한 객체에서는 동작하지 않는다.