스프링 빈과 의존관계

이민지·2021년 9월 26일
0

스프링입문

목록 보기
4/7

스프링은 웹 어플리케이션 실행 시 스프링 컨테이너에 스프링 빈을 등록해서 의존 관계를 자동으로 설정해준다. 스프링을 사용하면 객체를 스프링 빈으로 등록해서 써야 얻는 이점이 많다.

스프링 Bean 등록 방법에는 2가지가 있다.

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

두가지 방법 모두 알아야 한다.

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

컴포넌트 스캔

  • 컴포넌트 스캔은 @SpringBootApplictaion이 있는 패키지와 하위 패키지들을 전부 조사해서 @Component가 있는 객체를 모두 스프링 컨테이너의 스프링 Bean으로 등록한다.
  • hello.hellospring 패키지와 하위 패키지의 @Component가 있는 클래스만 스프링 컨테이너의 스프링 Bean으로 생성

  • @SpringBootApplication에 들어가보면 @ComponentScan이 있다.

  • demo의 Demo 클래스는 hello.hellospring의 하위 패키지가 아니므로 스프링 빈으로 생성되지 않는다.

  • @Controller, @Service, @Repository는 내부에 @Component가 있다. 따라서 스프링 빈으로 자동 등록된다.



회원 컨트롤러에 의존관계 추가

  • 회원 컨트롤러가 회원 서비스와 회원 리포지토리를 사용할 수 있게 의존관계를 준비하자
  • 생성자에 @Autowired가 있으면 객체 생성 시점에 스프링이 스프링 컨테이너에서 연관된 객체를 찾아서 넣어준다. 생성자 1개만 있으면 @Autowired 생략 가능하다.
  • 이렇게 객체 의존관계를 외부에서 넣어주는 것을 DI (Dependency Injection), 의존성 조입이라고 한다.
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;

    //DI의 생성자 주입 방식.
    @Autowired
    public MemberController(MemberService memberService) {
        this.memberService = memberService;
    }
}
  • 이대로 Controller만 추가한 후 실행시키면 아래와 같은 에러가 뜬다.

  • memberService가 스프링 빈으로 등록되어 있지 않기 때문이다.

  • memberService에 @Service와 생성자에 @Autowired를 붙여준다.
@Service
public class MemberService {

    private final MemberRepository memberRepository;

    @Autowired
    public MemberService(MemberRepository memberRepository) {
        
        this.memberRepository = memberRepository;
    }
  • MemberRespository의 구현체인 MemoryMemberRepository 에 @Repository를 붙여준다.
@Repository
public class MemoryMemberRepository implements MemberRepository { 
  • @Service와 @Repository로 memberService와 memberRepository가 스프링이 컨테이너에 스프링 빈으로 등록되었다.

  • @Autowired로 각 개체의 의존성이 주입되어 연결되었다.

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

  • 스프링 빈에 등록하지 않고, new 로 객체를 직접 생성한 경우 @Autowired가 작동하지 않는다. @Autowired는 스프링 컨테이너에서 관리하는 스프링 빈에만 작동한다.

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

  • 회원 서비스와 회원 리포지토리의 @Service, @Repository, @Autowired 애노테이션을 제거하고 진행한다.

  • SpringConfig 클래스 파일을 만든다.

  • @Configuration은 현재 클래스가 Spring의 설정 파일임을 알려주는 어노테이션이다.

  • @Bean: 스프링 컨테이너의 스프링 빈으로 등록

  • @Autowired로 스프링 컨테이너가 의존성 주입을 해주었던 MemberSerivce의 MemberRepository는 자바 소스에서 직접 넣어준다.

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() {
    
    //@Autowired로 스프링 컨테이너가 의존성 주입을 해주는 대신, JAVA 소스로 직접 넣어준다. 
        return new MemberService(memberRepository());
    }

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

}
  • 자바 코드로 직접 스프링 빈을 등록하는 것의 이점은 나중에 사용하는 구현체 클래스를 바꿔야 할 때, 설정 파일(Ex., SpringConfig)에서 해당 부분만 변경하면 된다는 것이다.

  • 여기서는 향후 MemoryMemberRepository를 다른 리포지토리로 변경할 예정이므로, 나중에 아래와 같이 변경해주면 된다.

    @Bean
    public MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }
    @Bean
    public MemberRepository memberRepository() {
        return new DbMemberRepository();
    }
  • 참고로 XML로 설정하는 방식도 있지만 최근에는 잘 사용되지 않는다.

Dependecy Injection (DI) 주입 방식

DI에는 3가지 주입 방식이 있다.

  • 필드 주입
  • Setter 주입
  • 생성자 주입

3가지 방법 중 생성자 주입 방식을 제일 권장한다.

필드 주입

  • 필드에 @Autowired 어노테이션을 붙인다.
  • 테스트 코드에서 단위 테스트 시 생성자 주입 방식과 다르게, 의존관계를 가지는 객체를 생성해서 주입할 수가 없다. 할 수 있는 방법이 없다. 스프링 컨테이너가 다 생성해서 주입해주는 방식이고 외부로 노출되어 있는 것이 하나도 없기 때문이다. 그래서 의존관계를 가지고 있는 메소드의 단위테스트를 작성하면 NullPointException이 발생한다.
@Controller
public class MemberController {

    @Autowired
    private final MemberService memberService;
    
}

Setter 주입

  • set메소드에 @Autowired 어노테이션을 붙인다.
  • 외부에서 호출 할 수 있게 set 메소드를 Public으로 만들어야 한다. Application 조립 시점 이후 set한 객체가 바뀔 일이 아예 없다. 오히려 중간에 잘못 바뀌면 위험하므로 막아야 한다.
    따라서, 조립 시점 이후 바뀌지 않도록 하는 생성자 방식이 좋다.
  • set방식은 주입되어야 할 스프링 빈이 스프링 컨테이너에 없더라도 객체가 만들어질 수 있다.
    즉, 주입이 필요한 객체가 주입이 되지 않아도 얼마든지 객체를 생성할 수 있다.
    NullPointException이 발생한다.
@Controller
public class MemberController {

    
    private MemberService memberService;

    @Autowired
    public void setMemberService(MemberService memberService) {
        this.memberService = memberService
    }
    

생성자 주입

  • 생성자에 @Autowired 어노테이션을 붙인다
  • Application 조립 이후 변경할 수 없다.
  • 테스트 코드 작성 시, 의존 관계를 가지는 객체를 생성해서 주입할 수 있다.
@Controller
public class MemberController {


    private final MemberService memberService;

    @Autowired
    public MemberController(MemberService memberService) {
        this.memberService = memberService;
    }
}
  • 롬복의 @RequiredArgsConstructor를 사용하면 클래스의 final이 선언된 모든 필드를 인자값으로 하는 생성자를 생성해준다. @Autowired 대신 생성자 주입 방식으로 의존 관계를 주입해준다.
@Controller
public class MemberController {


    private final MemberService memberService;

    @Autowired
    public MemberController(MemberService memberService) {
        this.memberService = memberService;
    }
}
  • @RequiredArgsConstructor를 사용하면 위 소스를 아래와 같이 변경할 수 있다.
@RequiredArgsConstructor
@Controller
public class MemberController {

    private final MemberService memberService;

}

0개의 댓글