Super Kawaii Cute Cat Kaoani [스프링 입문] 4. 스프링 빈과 의존관계

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

2024. 1. 20. 06:39
728x90
SMALL

📌 소개

스프링 빈 : 컨테이너가 관리하는 자바 객체를 빈(Bean) 이라고 함

자바에서 new를 통해 생성되는 객체를 의미하는 것이 아니라 컨테이너에서 생성하고 관리하는 객체를 의미한다.

 

스프링 빈을 등록하는 방법

  • 컴포넌트 스캔과 자동 의존 관계 설정
  • 자바 코드로 직접 스프링 빈 등록

 

 

📌 스프링 빈을 등록하는 방법

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

✔  컴포넌트 스캔

  • 스프링 컨테이너가 자동으로 클래스를 검색하고 등록하는 기능
  • spring Application에서 컴포넌트 스캔을 활용하면 개발자가 일일이 빈을 등록할 필요 없이, 클래스를 스캔하여 자동으로 빈으로 등록 가능
    • DI를 적용할 수 있음
  • @Component : 에노테이션이 있으면 스프링 빈으로 자동 등록됨
    • @Controller : 컨트롤러가 스프링 빈으로 자동 등록된 이유도 컴포넌트 스캔 때문이다
    • @Service
    • @Repository

 

 

✔ 자동 의존관계 설정

  • 스프링 컨테이너가 관리하는 빈 사이의 의존관계를 자동으로 설정하는 기능
  • 스프링 빈을 생성하면서 해당 빈이 필요로 하는 의존 객체를 자동으로 주입
  • @Autowired : 필드, 생성자, 메서드 등에 적용하여 해당 의존성을 주입받을 수 있도록 함
  • @Inject

 

cf)
스프링은 스프링 컨테이너에 스프링 빈을 등록할 때, 기본으로 싱글톤으로 등록(유일하게 하나만 등록해서 공유)
➡ 같은 스프링 빈이면 모두 같은 인스턴스임
➡ 설정으로 싱글톤이 아니게 할 수는 있지만, 특별한 경우가 아니면 대부분 싱글톤

 

 

 

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

 

 

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

🔶 main/.../hello.hellospring/controller/MemberController

@Controller
public class MemberController{
    private final MemberService memberService;

    @Autowired
    public MemberController(MemberService memberService){
        this.memberService = memberService;
    }
}

 

✔ @Controller

  • 스프링 컨테이너에 @Controller가 있으면 MemberController 객체를 생성해서 스프링 컨테이너에 넣어둠
  •  spring container가 관리(= spring been 관리)

 

✔ 생성자 생성

  • 단축키 : command + N => Constructor
  • private final MemberService memberService = new MemberService()를 여러 개 생성할 필요가 없음 (공용으로 사용)
  • 스프링 컨테이너에 등록을 하고 사용 (하나만 등록됨)
  • private final MemberService memberService;까지 작성 후 단축키 이용해 생성자 생성

 

@Autowired : 스프링이 연관된 객체를 스프링 컨테이너에서 찾아서 넣어줌

  • 스프링 컨테이너에 있는 memberService를 가져와 연결해줌
  • MemberController는 spring container가 뜰 떄 생성 -> 만들어진 생성자 호출
  • MemberController가 생성될 때 스프링빈에 등록되어있는 MemberService 객체를 넣어준다
  • 의존성 주입(Dependency Injection, DI) : 객체 의존관계를 외부에서 넣어주는 것

 

 

최종 MemberController.java

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

 

❗ 오류 발생

Consider defining a bean of type 'hello.hellospring.service.MemberService' in your configuration.
  • @Autowired는 스프링 컨테이너에서 MemberService를 가져움
  • MemberController는 Annotation이라도 걸어두어서 spring인걸 알 수 있지만, MemberService는 순수 java 코드이기 때문에 spring인지 알 수가 없다.

 

MemberService.java 코드에

 

@Service 추가

  • MemberService 클래스 위에 @Service 추가
    • spring이 스프링 컨테이너에 MemberService를 등록함

@Autowired 추가

  • MemberService 위에 @Autowired 추가
  • MemberService는 MemberRepository가 필요함
  • @Autowired가 spring이 MemberService를 생성할 때 스프링 컨테이너에 등록하면서 MemberService 생성자 호출
  • 스프링 컨테이너에 있는 MemberRepository에 구현체 MemoryMemberRepository를 넣어줌

 

 

최종 MemberService.java

package hello.hellospring.service;

import hello.hellospring.domain.Member;
import hello.hellospring.repository.MemberRepository;
import hello.hellospring.repository.MemoryMemberRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;

@Service
public class MemberService {
    private final MemberRepository memberRepository;

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

    /**
     * 회원가입
     */
    public Long join(Member member) {
        // 같은 이름이 있는 중복 회원 검증
        validateDuplicateMember(member);
        // 회원 가입
        memberRepository.save(member);
        return member.getId();
    }

    private void validateDuplicateMember(Member member) {
        memberRepository.findByName(member.getName())
                .ifPresent(m -> {
                    throw new IllegalStateException("이미 존재하는 회원입니다");
                });
    }

    /**
     * 전체 회원 조회
     */
    public List<Member> findMembers()   {
        return memberRepository.findAll();
    }

    public Optional<Member>  findOne(Long memberId) {
        return memberRepository.findById(memberId);
    }
}

 

반응형

 

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

  • @Service, @Autowired, @Repsitory 등을 사용하지 않고 직접 코드로 작성하여 스프링 빈 등록
먼저 Annotation을 제거한다
➡ MemberController의 @Controller, @Autowired만 남겨두고 다른 파일의 annotation들을 제거함

 

 

SpringConfig.java

🔶 main/.../hello.hellospring/SpringConfig.java

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

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

 

@Configurtion
SpringConfig class 위에 @Configuration 작성

 

@Bean
memberService를 스프링빈에 등록

 

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

 

두 방법의 차이

1. DI(Dependency Injection)

 

세가지 방법 존재

  • 필드 주입
  • setter 주입
  • 생성자 주입 ( 주로 생성자 주입 사용, 의존관계가 실행중에 동적으로 변하는 경우는 거의 없기때문)

 

필드 주입 : 안좋음, 변경할 수 없음

@Autowired private MemberService memberService;

 

setter 주입 : setter를 통해 들어옴(commane + N)

private MemberService memberService;

@Autowired
public void setMemberService(MemberService memberService){
    this.memberService = memberService;
}
  •  setMemberService 위에 Autowired를 넣어주면 된다.
    • 하지만,  setMemberService를 설정하면 바꿀 일은 없지만, 호출할 때 public으로 열려있어야 하므로 public하게 노출된다. (변경하면 문제가 발생함)

 

생성자 주입 : 생성자를 통해 들어옴 가장 추천

private final MemberService memberService;

@Autowired
public MemberController(MemberService memberService){
    this.memberService = memberService;
}

 

 

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

해당 상황 : 상황에 따라 구현클래스를 변경해야 하는 상황

  • 현재 데이터 저장소가 선정되지 않아 interface로 설계를 하고, 구현체로 MemoryMemberRepository를 사용하고 있다.
    • 나중에 MemoryMemberRepository를 다른 리포지토리로 교체하기로 했다. (기존의 코드를 하나도 변경하지 않고 교체할 것
    • 바꾸려는 리포지토리를 데이터베이스에 실제 연결하는 리포지토리로 바꿀 것이다.
    • 스프링 빈에 등록 -> 코드 변경 간단

 

기존 구현체 : MemoryMemberRepository()

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

 

바꾼 구현체 : DbMemberRepository()

public MemberRepository memberRepository(){
    return new DbMemberRepository();
}

 

 

❗ 주의

@Autowired를 통한 DI는 HelloController, MemberService 등과 같이 스프링이 관리하는 객체(spring container에 올라가있는 것들)에서만 동작함
➡ 스프링 빈으로 등록하지 않고 내가 직접 생성한 객체에서는 동작하지 않음

 

 

 

 

이 글은 <김영한_스프링 입문>을 수강하고 참고하여 작성한 글입니다

 

 

 

 


PREV

 

[스프링 입문] 3. 회원 관리 예제 - 백엔드 개발

📌 비지니스 요구사항 정리 데이터 : 회원ID, 이름 기능 : 회원 등록, 조회 아직 데이터 저장소가 선정되지 않았다는 가상의 시나리오 1. 일반적인 웹 애플리케이션 계층 구조 🔷 컨트롤러 : 웹 MV

nyeroni.tistory.com

 

NEXT

 

[스프링 입문] 5. 회원 관리 예제 - 웹MVC 개발

📌 회원 웹 기능 - 홈 화면 추가 HomeController.java 🔶 main/.../controller/HomeController.java package hello.hellospring.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; @

nyeroni.tistory.com

 

728x90
LIST

BELATED ARTICLES

more