본문 바로가기

JAVA

스프링 빈과 자동 의존관계 설정

스프링 빈이란?

정의 : 스프링 컨테이너에 등록된 객체
사용 이유 : ① 싱글톤으로 객체 생성 / ② 의존성 자동 주입

싱글톤으로 객체 생성

스프링 컨테이너에 빈(객체)을 등록하면, 스프링은 이 빈을 싱글톤으로 만들어 관리한다.

if not…. )) 싱글톤으로 빈을 생성하지 않고, 요청 시마다 객체를 생성한다면
요청이 오는 만큼 객체를 생성해야 한다.
예를 들어, 요청 당 5개의 객체를 생성해야 하는데 초당 500개의 요청이 올 경우 2500개의 객체를 생성해야 한다.
이는 서버의 성능 저하로 이어질 수 있다.

 

반면 싱글톤으로 bean을 생성하게되면, 시작 시에만 객체가 생성이 된다.
이후 요청이 계속들어오더라도 이미 만들어진 bean이 리턴되기 때문에 성능 저하가 일어나지 않는다.

정리 → 싱글톤으로 객체를 생성하면 좋은 점 : 멀티 유저가 오더라도 성능 저하가 일어나지 않는다.
참고 : https://datajoy.tistory.com/112

의존성 자동 주입

의존의 의미
“A가 B를 의존한다.”는 표현은 ‘의존대상 B가 변하면, 그것이 A에 영향을 미친다.’라는 뜻이다.
예를들어, 햄버거 요리사는 햄버거 레시피를 의존한다.
(햄버거 레시피가 '치즈 버거 레시피'에서 '불고기 버거 레시피'로 바뀌면 만드는 버거가 달라지므로)

다형성을 이용한 의존 관계
다양한 레시피에 의존하기 위해 요리사는 햄버거 레시피 인터페이스 변수를 사용하고,
햄버거 레시피에 구현객체가 대입되어야 한다.

class BurgerChef {
    public BurgerChef(BurgerRecipe burgerRecipe) {
        this.burgerRecipe = burgerRecipe;
    }

    public void cook(){
        this.burgerRecipe.newBurger();
    }

    private BurgerRecipe burgerRecipe;
}

interface BurgerRecipe {
    public void newBurger();
}

class HamBurgerRecipe implements BurgerRecipe {
    @Override
    public void newBurger() {
        System.out.println("hamburger");
    }
}

class hamburger {
    public static void main(String[] args) {
        BurgerChef burgerChef = new BurgerChef(new HamBurgerRecipe());
        burgerChef.cook();
    }
}

참고 : 이일민, 토비의 스프링 3.1, 에이콘(2012), p113

의존성 주입이란? (Dependenct Injection)
어떤 햄버거 레시피를 따를지 외부에서 건네받는다면
(이를테면, 생성자의 매개변수의 타입을 인터페이스 타입으로 하고,
생성자를 호출할 때 외부에서 구현객체를 넘겨준다면) 다형성을 극대화할 수 있다.
이처럼 그 의존관계를 외부에서 결정하고 주입하는 것이 DI(의존관계 주입)이다.

DI의 장점
1. 의존성이 줄어든다 : 의존하는 것이 변한다 해서 코드를 바꿀 필요가 없다.
2. 재사용성이 높은 코드가 된다 : 주입을 하는 부분만 바꾸면 된다.
3. 테스트하기 좋은 코드가 된다 : 분리하여 테스트할 수 있다.
4. 가독성이 높아진다 : 기능을 분리하게 되어 가독성이 높아진다.
참고 : https://tecoble.techcourse.co.kr/post/2021-04-27-dependency-injection/

스프링의 다양한 의존성
스프링에서는 각각의 클래스들이 의존 관계를 가지고 있다.

MVC 구조를 복습해 보자.
MVC 구조에 따라 설계를 하면,
먼저 데이터를 전달하는 매개체인 Model 클래스(도메인)가 있고.
DB에 접근하여 데이터를 관리하는 Repository 클래스가 있다.
또한 생성된 Repository에 접근하는 비지니스 로직을 구현한 Service 클래스가 있고
Controller는 Service를 통해 Model이 담겨있는 Repository에 접근하게된다.
이들은 아래와 같은 의존관계를 갖는다.

※ 일반적으로 화살표를 통해 의존 관계를 나타낸다. (클래스 → 의존하는 대상)

이렇듯 복잡한 의존관계 때문에, 이를 관리하는 것이 중요하다.
(애초에 자바는 객체지향 언어기 때문에 객체들의 복잡한 관계를 다루는 것이 전혀 이상할게 아니다.)
의존성 주입은 개발자가 수동으로 할 수도 있지만, 스프링이 자동으로 하게 할 수도 있다.
일일이 객체를 생성하고, 집어넣는 코드를 바꾸지 않고
몇 가지 어노테이션만 적어주면 스프링이 알아서 객체(스프링빈)를 찾아서 주입해준다.

 

중간 정리

여기까지의 내용을 정리하자면,
객체를 스프링 빈으로 등록하는 것이 좋다.
스프링 빈으로 등록해야하는 이유? 싱글톤으로 객체를 생성하고, 자동 의존성 주입을 할 수 있다.
싱글톤으로 객체를 생성하면 뭐가 좋지? 많은 응답이 와도 이미 만들어준 객체를 반환하므로, 멀티 유저로 인한 서버 성능 저하가 없다.
의존이란 뭐지? 의존 대상이 바뀔 때 영향을 받으면, 그것을 의존한다고 말한다.
그렇다면 의존성 주입은? 다형성을 위해서, 외부에서 의존관계를 결정하는 것을 말한다.
자동 의존성 주입은 왜 필요하지? MVC 모델은 복잡한 의존관계를 갖기 때문에 의존성 주입이 중요하다.
객체를 스프링빈으로 등록하면 스프링은 이 객체를 찾아서 자동으로 의존성을 주입해준다.
자동 의존성 주입을 활용하면, 주입할 타입만 바꿔줘도 스프링에서 알아서 객체를 넣어주기 때문에 매우 편리하다.

 

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

1. 컴포넌트 스캔과 자동 의존관계 설정(자동) : @Component 로 자동 빈 등록, @Autowired 사용
2. 자바 코드로 직접 스프링 빈 등록하기(수동) : @Configuration이 있는 설정 파일에서 @Bean을 이용해서 수동 빈 등록

본 글에서는 두가지 방법을 모두 해보려 한다.
참고 : https://devwithpug.github.io/spring/spring-1/

 

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

컴포넌트 스캔
클래스 선언부 앞에 @Component 어노테이션이 있으면, 스프링이 자동으로 객체를 생성하여 스프링 빈에 등록해준다.
@Component가 내제된 다음 어노테이션을 이용해도 스프링 빈으로 자동 등록된다.

  • @Controller, @Service, @Repository

아무 위치에나 어노테이션을 사용한다고 해서 스프링 빈으로 등록되는건 아니다.
main 함수가 포함된 패키지 or 그 하위 패키지에 어노테이션이 있는 경우에만 스프링 빈으로 등록된다.

자동 의존관계 설정
의존성 주입이 일어나는 클래스 앞에 @Autowired가 있으면
스프링이 주입에 해당되는 객체(스프링빈)를 스프링 컨테이너에서 찾아서 넣어준다.

 

1.2 컨트롤러 개발 - @Autowired 이용

이제 @Autowired를 이용해 의존성 주입을 하는 컨트롤러를 만들어보자.
먼저, 의존성 관계를 떠올려보면
컨트롤러는 뷰를 통해 입력을 받고, 서비스를 통해서 도메인을 레포지토리에 전달해야한다.
따라서 memberController → memberService → memberRepository 라는 의존관계임을 알 수 있다.
따라서 컨트롤러는 서비스를 주입받아야 하고, 서비스는 레포지토리를 주입받아야 한다.

[구현]
1️⃣ 레포지토리 스프링 빈 등록 - @Repository
@Repository 어노테이션은 @Component 어노테이션이 내제된 어노테이션이므로 
레포지토리 클래스 앞에 @Repository를 작성하면 레포지토리가 스프링 빈으로 등록된다.

2️⃣ 서비스 스프링 빈 등록 - @Service & @Autowired로 멤버 레포지토리 주입받기
@Service 어노테이션에 의해 서비스 클래스도 1번과 같은 원리로 스프링 빈에 등록된다.
서비스가 레포지토리를 주입받을 때 @Autowired 어노테이션에 의해 자동으로 레포지토리가 주입된다.

@Service
public class MemberService {
	private final MemberRepository memberRepository;

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

 

3️⃣ 컨트롤러 스프링 빈 등록 - @Controller & @Autowired로 서비스 주입받기

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

 

2.1 자바 코드로 직접 스프링 빈 등록하는 방법

1번에서 설정한 @Service, @Repository, @Autowired 어노테이션을 제거하고 진행한다.
루트 폴더 밑에 Configuration 폴더를 생성한다.
코드 전체에 @Configuration 어노테이션을 달아주고, 스프링 빈으로 등록할 객체에는 @Bean 어노테이션을 달아준다.
이렇게 '의존관계 설정 파일'을 만들어 의존관계를 관리하면,
어떤 클래스가 어디에 의존하는지를 한눈에 알아볼 수 있다는 장점이 있다.

수동 의존성 주입 vs. 자동 의존성 주입
그렇다면, 언제 수동의 방법을, 언제 자동의 방법을 사용해야할까?
정형화된 컨트롤러, 서비스, 리포지토리 같은 코드는
의존 관계가 변할 여지가 없으므로 자동 의존성 주입(컴포넌트 스캔)을 사용한다.
그리고 정형화되지 않거나, 상황에 따라 구현 클래스를 변경해야 하면
설정 파일을 보며 관리하는 것이 좋으므로 수동 의존성 주입(@Bean이용)을 사용한다.
여기서는 향후 메모리 리포지토리를 다른 리포지토리로 변경할 예정이므로,
컴포넌트 스캔 방식 대신에 자바 코드로 스프링 빈을 설정하겠다.

참고: DI에는 필드 주입, setter 주입, 생성자 주입 이렇게 3가지 방법이 있다.
의존관계가 실행중에 동적으로 변하는 경우는 거의 없으므로 생성자 주입을 권장한다.