[Spring] 입문3
Java & Spring

[Spring] 입문3

스프링 빈과 의존관계

스프링 빈에 대해 알기 전에 IOC와 DI에 대해 먼저 알아야한다.

아직은 명확히 와닫지 않아서 쉽게 설명해놓은 링크만 첨부한다. 좀더 명확하게 알게되면 새로 포스팅해야지!

 

간단하게 말하면 이제 controller에서 view에 화면을 뿌려주는 작업을 할건데, controller에서는 service를 통해서 회원가입을 하고 데이터를 조회할 수 있다. 이렇게 되는 걸 서로 의존관계가 있다고 하고(controller가 service를 의존하고 있다.) 이제 이 과정을 스프링으로 구현해볼 것이다.

 

오늘 개발 후 폴더 구조 (보이지 않는 부분은 변경 사항 없음)

 


스프링 빈을 생성하는 방법

  • 스프링 빈이란?
    • Spring IoC 컨테이너가 관리하는 자바 객체를 (Bean)이라는 용어로 부른다.
    • 라고 하는데 이렇게만 봤을 땐 무슨말인지 잘 이해가 가지 않았다.
    • 이 용어는 아래 예시를 보면 좀 더 잘 이해가 된다. 예시를 보고 이해한 내용은 다음과 같다.
@Service, @Controller, @Repository 모두 @Component인데 @Component annotation이 있는 것들은 처음 Spring이 생길 때 생기는 Spring container에 등록이 되어 Spring container에 의해 관리된다. 이렇게 관리되는 자바 객체들을 빈이라고 하고 아래 예시에서, "MemberService가 스프링 컨테이너에 스프링 빈으로 등록되었다." 라고 한다.
  • DI에는 필드 주입, setter 주입, 생성자 주입 이렇게 3가지 방법이 있다. 의존관계가 실행중에 동적으로 변하는 경우는 거의 없으므로 생성자 주입을 권장한다. 아래 예시에서도 생성자 주입을 통해 구현한다.
  • controller <-> service <-> repository 이렇게 의존관계를 생성해보자.
  • 아래에서 두가지 방법에 대해 설명하는데 이 두가지 방법 모두를 알고 있어야 한다!
    • xml을 사용하는 방식도 있지만 최근에는 사용하지 않는다.
    • 실무에서는 주로 정형화된 컨트롤러, 서비스, 레포지토리 같은 코드는 컴포넌트 스캔을 사용한다. 그리고 정형화 되지 않거나, 상황에 따라 구현 클래스를 변경해야 하면 설정을 통해 스프링 빈으로 등록한다. -> 정형화 되지 않거나? ex) 메모리를 db로 사용하다가 다른 db로 변경해줘야 하는 경우 코드를 수정해야하는데 스프링 빈으로 등록할 경우 이전 코드를 수정하지 않고 바꿀 수 있다!

 

1. 컴포넌트 스캔과 자동 의존관계 생성

컴포넌트 스캔이라고 하는 이유: @Controller, @Service, @Repository는 @Component의 특수한 케이스라고 할 수 있다. 즉, 모두 결국 @Component라는 것. 근데 Spring이 처음 생성될 때 만들어지는 Spring Container가 이러한 @Component들을 전부 스캔해서 관리하게 되므로 컴포넌트 스캔이라고 한다.

// MemberController.java

package hello.hellospring.controller;

import hello.hellospring.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

// Spring이 처음 생길 때, Spring container라는 큰 통이 생기는데
// 이렇게 controller annotation을 적어두면 MemberController라는 객체를 생성해서
// Spring에 넣어두게 된다. 그리고 Spring에서 관리를 하게 된다.
// == Spring Container에서 Spring Bean이 관리된다.
@Controller
public class MemberController {
     /* 아래와 같이 생성할 수 있지만
     다른 controller들에서도 MemberService를 생성해서 쓸 수 있는데
     이건 여러개 생성해서 쓸 필요 없고 하나만 생성해서 공용으로 쓰면 더 좋음
     따라서 Spring Container에 등록하고 쓰면 하나만 쓸 수 있음 + 부가적 효과(뒤에서 설명) */

    // private final MemberService memberService = new MemberService();

    /*
    1. 필드 주입
    @Autowired private MemberService memberService;
    */

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

    /*
    3. 생성자 주입(권장)
     */
    private final MemberService memberService;



    // 이렇게 Autowired라고 annotation이 생성자에 적혀져 있으면
    // 아래의 생성자를 실행할 때 Spring container에서 MemberService를 해당 생성자의 인자에 넣어준다.
    // 이렇게 주입하는 것을 DI(Dependency Injection)이라고 함
    @Autowired
    public MemberController(MemberService memberService) {
        this.memberService = memberService;
    }
}
// MemberService.java

package hello.hellospring.service;

import hello.hellospring.domain.Member;
import hello.hellospring.repository.MemberRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Optional;

// service -> 비즈니스 로직을 작성하는 부분
@Service
public class MemberService {

    // final = 처음 초기화 할 때와 생성자에서만 값을 할당할 수 있음. 그 외의 경우 값 수정 불가
    private final MemberRepository memberRepository;

    /*
    final 변수 초기화를 생성자로 외부에서 넣어주도록 해줌.
    여기서는 spring Container에서 MemberRepository를 주입해줌
    이런걸 Dependency Injection이라고 함
    */
    @Autowired
    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    ...
}
  • 위 두개의 class를 보자. 우선 MemberContoller부분에서는 @Autowired를 통해 컨테이너가 주입한 MemberService를 쓸 수 있게 되고, MemberService부분에서는 @Autowired를 통해 컨테이너가 주입한 MemberRepository를 쓸 수 있게 된다.

참고1

package hello.hellospring;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

// 프로젝트가 시작하는 부분
@SpringBootApplication
public class HelloSpringApplication {

	public static void main(String[] args) {
		SpringApplication.run(HelloSpringApplication.class, args);
	}

}

package hello.hellospring을 포함한 하위 컴포넌트들에서만 컴포넌트 스캔이 일어난다. 상위 폴더에서도 할 수 있긴 하지만 기본적으로는 안되고, 추가 설정도 해줘야 한다.

 

참고2

스프링은 스프링 컨테이너에 스프링 빈을 등록할 때, 기본적으로 싱글톤으로 등록한다. (유일하게 하나만 등록해서 공유한다.) 따라서 같은 스프링 빈이면 모두 같은 인스턴스다. 설정으로 싱글톤이 아니게 설정할 수 있지만, 특별한 경우를 제외하면 대부분 싱글톤을 사용한다.

 

참고3

@Autowired를 통한 DI는 helloController, MemberService등과 같이 스프링이 관리하는 객체에서만 동작한다. 스프링 빈으로 등록하지 않고 내가 직접 생성한 객체에서는 동작하지 않는다.

 

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

@Service, @Repository + @Autowired annotation을 지우고 직접 설정파일에 빈을 등록할 수 있다.

(이때 Controller쪽의 annotation은 남겨둔다.)

// HelloSpringApplication.java라는 프로젝트 시작점 파일과 같은 위치에 
// 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();
    }
    /*
    장점: Memory -> db로 변경하고 싶으면 return new DbMemberRepository()로만 바꿔주면 된다!
    */
}

 

728x90
반응형

'Java & Spring' 카테고리의 다른 글

[Spring] 입문6 (끝!)  (0) 2021.11.03
[Spring] 입문5  (0) 2021.11.01
[Spring] 입문4  (0) 2021.10.09
[Spring] 입문2  (0) 2021.08.16
[Spring] 입문1(feat. 인프런 김영한님 강의)  (0) 2021.08.14