CS log

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

Spring & Spring Boot

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

sj.cath 2024. 11. 14. 00:33

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

더보기

controller vs service

Spring Application Layered Pattern
Domain Driven Design Layered Pattern


- Spring Application Layered Pattern은 Spring에서 흔히 사용하는 계층 구조로 Web Layer에서 사용자의 요청을 받고 Service Layer에서 실제 요청을 처리하고, Repository Layer에서 통해 Data를 조회/변경한다.

- Domain Driven Design Layered Pattern에서는 User Interface Layer에서 사용자의 요청을 받고 응답을 만들며 Application Layer에서는 기능을 제공한다. Domain Layer에서는 기능을 제공하기 위한 실제 변경 작업을 진행하며 Infrastructure Layer에서는 Database, 다른 Application, 웹 프레임워크등 인프라에 접근하도록 인터페이스를 제공하는 레이어이다.

Controller는 Web Layer와 User Interface Layer에,
Service는 Service Layer와 Application Layer 혹은 Domain Layer에 해당한다.

 

Controller에서는 사용자의 입력처리와 응답에만 집중하고, Service에서는 실제 기능을 어떤식으로 제공하는지에 대해서만 집중하여야 나중에 다른 팀원이 코드를 수정할 때도 어떤 기능이 어디에 있는지 쉽게 알 수 있고, 변경하기 유리하다.
이는 단일 책임 원칙에 해당한다.

 

컨트롤러에서 요청을 받고 -> 서비스에서 비즈니스 로직을 만들고 -> 리포지토리에서 데이터 저장하기

 

스프링 빈과 의존관계

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

멤버 컨트롤러가 멤버 서비스를 통해 회원가입 및 조회한다.

 

스프링 컨테이너가 생기면서 hellocontroller(bean)을 관리함

 

memberservice는 사실상 기능이 별로 없기 때문에 굳이 객체를 매번 새로 생성하지 않고 하나만 생성해서 공용으로 쓰는게 낫다.

Autowired : spring이 memberserive를 컨테이너에 자동으로 연결시켜줌(넣어줌)

member는 자바 클래스 (에노테이션도 없어서 스프링이 알 수 있는 방법도 없음)

 

그래서 다음과 같이 에노테이션을 달면 스프링이 '앗 멤버 서비스네' 하고 스프링 컨테이너에 memberservice를 등록해줌

@Service // 요기!
public class MemberService {
    private final MemberRepository memberRepository;
    public MemberService(MemberRepository memberRepository) { // 내가 직접 생성하지 않고 외부에서 넣어주는 방식
        this.memberRepository = memberRepository;
    }

 

MemberRepository, controller도 마찬가지로 달아준다.

멤버 컨트롤러가 생성이 될 때, 스프링 컨테이너에서 해당 스프링 빈(멤버 서비스 객체)을 등록해준다. 의존 관계를 주입.

의존관계도

 

@Service
public class MemberService {
    private final MemberRepository memberRepository;

    @Autowired
    public MemberService(MemberRepository memberRepository) { // 내가 직접 생성하지 않고 외부에서 넣어주는 방식
        this.memberRepository = memberRepository;
    }

예를들어 그림을 보면, memberservice는 memberrepository가 필요하다. (회원가입 로직 작동 시 데이터베이스가 필요)

그럼 memberservice가 생성될 시(@service), @autowired를 보고 생성자를 실행시켜 memorymemberrepository를 컨테이너에 넣어준다. 

 

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

1) 컴포넌트 스캔과 자동 의존관계 설정 : @모양 어노테이션 = @component

스프링은 스프링 컨테이너에 스프링 빈을 등록할 , 기본으로 싱글톤으로 등록한다(유일하게 하나만

등록해서 공유한다) 따라서 같은 스프링 빈이면 모두 같은 인스턴스다. 설정으로 싱글톤이 아니게 설정할 있지

, 특별한 경우를 제외하면 대부분 싱글톤을 사용한다.

 

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

더보기

 

MemoryMemberRepository와 MemberRepository차이?

일반적으로 두 가지 다른 목적을 위한 클래스 혹은 인터페이스로 구현되는데, 주로 메모리 내 데이터 저장 영속성 계층을 구분하기 위한 용도로 사용됩니다.

  1. MemberRepository:
    • 보통 데이터 저장소에 접근하는 추상화 계층으로 인터페이스나 상위 클래스입니다.
    • 다양한 저장 방식 (예: DB, 파일, 메모리 등)을 지원하기 위해 공통적인 메서드만을 정의합니다.
    • 실제 데이터가 어디에 저장될지 구체적으로 알지 못하고, 그저 데이터를 읽고 쓰는 규약만 제공합니다.
  2. MemoryMemberRepository:
    • MemberRepository를 구현한 구현체 중 하나로, 데이터를 메모리에 저장합니다.
    • 테스트 용도로 많이 사용됩니다. 예를 들어, 개발 초기에 DB 설정이 아직 완료되지 않았거나, 단위 테스트에서 DB 접속 없이 빠르게 테스트할 때 유용합니다.
    • 데이터는 메모리에만 저장되므로, 애플리케이션이 재시작되면 데이터가 모두 사라집니다.
package hello.hello_spring.service;

import hello.hello_spring.repository.MemberRepository;
import hello.hello_spring.repository.MemoryMemberRepository;
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();
    }

}

위 코드는 아래 그림을 완성해준다.

즉 memberservice와 memberrepository라는 빈을 만들어주고, memberserivce에 memberrepository를 주입해주는 것이다.

참고로 controller는 (@Controller가 붙은 클래스)는 Spring Boot의 @SpringBootApplication 또는 @ComponentScan에 의해 자동으로 빈으로 등록된다. 따라서 SpringConfig에 컨트롤러를 명시적으로 추가할 필요가 없다.

 

package hello.hello_spring.controller;

import hello.hello_spring.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) { // 생성자를 통해서 memberservice가 멤버 컨트롤러에 주입
        this.memberService = memberService;
    }
}

위와 같이 생성자를 쓰는 것이 가장 권장되는 스타일이다!

 

실무에서는 주로 정형화된 컨트롤러, 서비스, 리포지토리 같은 코드는 컴포넌트 스캔을 사용한다. 그리고 정형화 되지 않거나, 상황에 따라 구현 클래스를 변경해야 하면 설정을 통해 스프링 빈으로 등록한다.

: memorymemberrepository를 데이터베이스로 바꿔치기할 때, 코드에 하나도 손을 안대고 바꿀 수 있다.

DBmemberrepository라고만 하면 된다. (매우 간단, 간편)