<스프링입문>04. 스프링 빈과 의존관계

박서연·2023년 3월 24일
0

Spring

목록 보기
4/10

📌 단축키

art+insert 단축키로 생성자 생성
ctrl+p 단축키 작성해 함수 안에 들어갈 매개변수 찾기

📌 이전까지 요약

🔻 member, service, repository 생성
🔻 service를 통해 member 가입, repository에 저장 및 가져오기
🔻 test

📌 요약

🔻 화면 붙이고 싶음 => controller와 view template 필요

🔻 memberController 필요
memberController는 memberService를 통해 회원가입 및 데이터 조회 가능
=> "memberController가 memberService를 의존한다"

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

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

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

1. 컴포넌트 스캔

anotation 넣기 @Contoller, @Service, @Repository

1) MemberController.java

main/.../controller/MemberController.java
controller 아래에 MemberController 이름의 class 생성

🔸 @Controller
spring container에 MemberController 객체 생성 및 관리 (=스프링 빈 관리)
💡 MemberService 가져와서 써야함 => 스프링 컨테이너에 등록
🔸 private final MemberService memberService; 작성 후 alt+insert 단축키로 생성자 생성
🔸 @Autowired
생성자 위에 @Autowired 작성
💡 spring container에 있는 memberService를 가져와 연결
MemberController는 spring container가 뜰 때 생성-> 만들어진 생성자 호출

💎 MemberController는 spring container가 뜰 때 생성 후 만들어진 생성자 호출. MemberController가 생성될 때 springbean에 등록되어있는 MemberService 객체를 넣어줌 : dependency injection

🏅 최종 코드_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;
    }
}

🚫 오류발생

MemberService는 순수 java 코드이기에 spring container가 알 수 없어 MemberService가 없다는 error 발생.

2) MemberService.java

main/.../service/MemberService.java
🔸 @Service
MemberService.java의 MemberService class 위에 @Service 코드 추가 => spring이 올라올때 spring이 spring container에 MemberService 등록
🔸 @Autowired
MemberService 위에 @Autowired 추가 => MemberService를 생성하여 container에 등록.
🔹 즉, spring container에 있는 memberRepository에 구현체인MemoryMemberRepository 넣어줌

🏅 최종 코드_MemberService

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

3) MemoryMemberRepository.java

main/.../repository/MemoryMemberRepository.java
🔸 @Repository
MemoryMemberRepository.java에 @Repository 코드 추가

🏅 최종 코드_최종코드_MemoryMemberRepository

package hello.hellospring.repository;

import hello.hellospring.domain.Member;
import org.springframework.stereotype.Repository;

import java.util.*;

@Repository
public class MemoryMemberRepository implements MemberRepository{

    private static Map<Long, Member> store = new HashMap<>();
    private static long sequence = 0L;
    @Override
    public Member save(Member member) {
        member.setId(++sequence);
        store.put(member.getId(), member);
        return member;
    }

    @Override
    public Optional<Member> findById(Long id) {
        return Optional.ofNullable(store.get(id));
    }

    @Override
    public Optional<Member> findByName(String name) {
        return store.values().stream()
                .filter(member -> member.getName().equals(name))
                .findAny();
    }

    @Override
    public List<Member> findAll() {
        return new ArrayList<>(store.values());
    }

    public void clearStore()    {
        store.clear();
    }
}

🌟 컴포넌트는 원래 @Component 을 사용해 스프링빈으로 자동 등록
@Component를 포함하는 다음 annotation도 스프링 빈으로 자동 등록
@Controller, @Service, @Repository

2. 자동 의존관계 설정

💎 @Autowired

💡 MemberController와 MemberService 안에 작성

💡 Controller를 통해 외부 요청 받고, Service를 통해 business logic 만들고, Repository에 data 저장해야하므로 의존관계가 존재해야함

🔸 spring이 올라올 때, Component와관련된 annotation이 있을 경우 모두 스프링 객체를 생성한 후 스프링 컨테이너에 등록. Autowired는 연관관계를 나타냄. MemberController가 MemberService를 쓸 수 있고 MemberService가 MemberRepository를 쓸 수 있음
🔻 연관관계 표

🌟 실행 파일인 HelloSpringApplication.java 코드에서 package hello.hellospring을 가져오는데, 이 패키지를 포함한 그 하위 파일들만 컴포넌트 스캔의 대상이 됨. 다른 위치(ex.main/java)에 패키지를만들고 코드에 @Component 있어도 실행 파일 밖에 존재하기에 컴포넌트 스캔의 대상이 되지않고, 따라서 스프링빈에 등록되지않음

🔸 참고. 스프링은 스프링 컨테이너에 스프링 빈을 등록할 때 기본으로 싱글톤으로 기록(유일하게 하나만 등록해서 공유). 따라서 같은 스프링 빈이면 모두 같은 인스턴스. ex.memberService와 orderService에서 memberRepository를 @autowired하면 같은 인스턴스를 넣어 메모리 절약

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

💎 @Service, @Repository, @Autowired 와 @Autowired 사용하지 않고 코드로 직접 스프링 빈 등록

1. anotation 제거

🔸 MemberController 파일의 @Controller, @Autowired만 남겨두고 다른 파일의 annotation 제거

2. SpringConfig.java

main/.../hello/hellospring/SpringConfig.java
main/.../hello.hellospring 아래에 SpringConfig.java 파일 생성

🔸 @Configuration 작성
SpringConfig의 class 위에 @Configuration 작성

🔸 memberService
memberService bean에 등록
아래 코드 작성 후

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

단축키 ctrl+p 작성해 MemberService() 안에 들어갈 매개변수(memberRepository()) 찾기 => memberRepository가 필요하므로 얘도 bean에 존재해야함

🔸 MemoryMemberRepository

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

🏅 최종 코드_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();
    }
}

💡 참고) DI(Dependency Injection)에는 필드 주입, setter 주입, 생성자 주입 3가지 방법 존재 => 주로 생성자 주입 사용

  • 필드주입: 안좋음. 변경할 수 없음
@Autowired private MemberService memberService;
  • setter 주입: setter를 통해 들어옴
    setMemberService를 설정하면 바꿀 일이 없는데 호출을 할 때 public으로 열려있어야하므로 public하게 노출됨. 이때 setMemberService 변경하면 문제 발생
private MemberService memberService;

@Autowired
public void setMemberService(MemberService memberService)	{this.memberService memberService;}
  • 생성자 주입: 생성자를 통해 들어옴. 생성자 주입 쓰는것이 좋음
private final MemberService memberService;

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

💡 참고
실무에서는 주로 정형화된 컨트롤러, 서비스, 리포지토리 같은 코드는 컴포넌트 스캔을 사용. 정형화되지 않거나 상황에 따라 구현 클래스를 변경해야하면 코드 작성을 통해 스프링빈으로 등록
=> 현재 데이터 저장소가 선정되지 않아 구현체로 MemoryMemberRepository 만든 후 교체하기로했음. 기존의 코드 변경없이 MemoryMemberRepository를 데이터베이스에 실제 연결하는 repository로 바꿀 것 => 스프링빈 등록할 경우 코드 변경 간단

1) 기존 구현체인 MemoryMemberRepository

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

2) 바꿀 구현체인 DbMemberRepository

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

💡 스프링빈으로 등록되어있지않읅경우 또는 내가 직접 생성한 객체의 경우 @Autowired 동작X. 스프링 컨테이너에 올라가있는 것들만 동작

오류 발생

Q. Test만 실행되고 main 실행 안 됨
A. Run -> edit configurations 선택 -> 아래 그림과 같이 Application 추가

0개의 댓글