개발놀이터

@Autowired 본문

Spring/Spring

@Autowired

마늘냄새폴폴 2021. 12. 28. 13:42

이 포스팅은 인프런 김영한 님의 스프링 핵심 원리 기본 편을 보고 각색한 포스팅입니다. 자세한 내용은 강의를 확인해주세요

 

의존관계 주입에는 다양한 방법이 있다.-생성자 주입-수정자 주입 (setter 주입)-필드 주입

 

이제부터 하나씩 살펴보며 어떤 방법을 지향해야 하는지 알아보자

 

1. 생성자 주입

말 그대로 생성자를 통해서 의존 관계를 주입 받는 방법이다. 여태까지 우리가 진행했던 방법이 바로 생성자 주입이다.

 

*특징

-생성자 호출시점에 딱 한번만 호출되는 것이 보장된다.

-불변, 필수 의존관계에 사용

 

불변, 필수 의존관계에 사용한다는 것은 어떤 의미를 가지고 있을까? 

 

불변이라는 의미는 생성자는 클래스가 생성되는 순간에 바로 결정되기 때문에 수정자가 없는한 값을 변경할 수 없는 의미이다. 또한 필수라는 의미는 마치 공연이 시작되기 전에 모든 배우를 섭외하고 시작하는 것과 같은 효과를 줄 수 있다.

 

@Component
public class OrderServiceImpl implements OrderService{

    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;

    @Autowired
    public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
        this.memberRepository = memberRepository;
        this.discountPolicy = discountPolicy;
    }

    @Override
    public Order createOrder(Long memberId, String itemName, int itemPrice) {
        Member member = memberRepository.findById(memberId);
        int discountPrice = discountPolicy.discount(member, itemPrice);

        return new Order(memberId, itemName, itemPrice, discountPrice);
    }

    public MemberRepository getMemberRepository() {
        return memberRepository;
    }
}

 

2. 수정자 주입

setter라 불리는 필드의 값을 변경하는 수정자 메서드를 통해서 의존관계를 주입하는 방법이다.

 

*특징

-선택, 변경 가능성이 있는 의존관계에 사용

-자바빈 프로퍼티 규약의 수정자 메서드를 사용하는 방법이다.

 

@Component
public class OrderServiceImpl implements OrderService{

    private MemberRepository memberRepository;
    private DiscountPolicy discountPolicy;

    @Autowired
    public void setDiscountPolicy(DiscountPolicy discountPolicy) {
        this.discountPolicy = discountPolicy;
    }

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

    @Override
    public Order createOrder(Long memberId, String itemName, int itemPrice) {
        Member member = memberRepository.findById(memberId);
        int discountPrice = discountPolicy.discount(member, itemPrice);

        return new Order(memberId, itemName, itemPrice, discountPrice);
    }

    public MemberRepository getMemberRepository() {
        return memberRepository;
    }
}

 

3. 필드 주입

이름 그대로 필드에 바로 주입하는 방법이다.

 

*특징

-코드가 간결해서 많은 개발자들을 유혹하지만 외부에서 변경이 불가능해서 테스트하기 힘들다는 치명적인 단점이 있다.

-DI프레임워크가 없으면 아무것도 할 수 없다.

-사용하면 안되는 패턴이다!

 

@Component
public class OrderServiceImpl implements OrderService{

    @Autowired 
    private MemberRepository memberRepository;
    @Autowired 
    private DiscountPolicy discountPolicy;

    @Override
    public Order createOrder(Long memberId, String itemName, int itemPrice) {
        Member member = memberRepository.findById(memberId);
        int discountPrice = discountPolicy.discount(member, itemPrice);

        return new Order(memberId, itemName, itemPrice, discountPrice);
    }

    public MemberRepository getMemberRepository() {
        return memberRepository;
    }
}

 

 

생성자 주입을 선택해라!

과거에는 수정자 주입과 필드 주입을 ㅁ낳이 사용했찌만, 최근에는 스프링을 포함한 DI 프레임워크 대부분이 생성자 주입을 권장한다. 이유는 다음과 같다.

 

불변

-대부분의 의존관계 주입은 한번 일어나면 애플리케이션 종료시점까지 의존관계를 변경할 일이 없다. 오히려 대부분의 의존관계는 애플리케이션 종료 전까지 변하면 안된다.

-수정자 주입을 사용하면 setXxx메서드를 public으로 열어두어야 한다.

-누군가 실수로 변경할 수도 있고, 변경하면 안되는 메서드를 열어두는 것은 좋은 설계 방법이 아니다.

-생성자 주입은 객체를 생성할 때 딱 한번만 호출되므로 이후에 호출되는 일이 없다. 따라서 불변하게 설계할 수 있다.

 

누락

프레임워크 없이 순수한 자바 코드로 단위테스트를 하는 경우에 다음과 같이 수정자 의존관계인 경우

@Component
public class OrderServiceImpl implements OrderService{

    private MemberRepository memberRepository;
    private DiscountPolicy discountPolicy;

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

    @Autowired
    public void setDiscountPolicy(DiscountPolicy discountPolicy) {
        this.discountPolicy = discountPolicy;
    }

    ....
}

@Autowired가 프레임워크 안에서 동작할 때는 의존관계가 없으면 오류가 발생하지만, 지금은 프레임워크 없이 순수한 자바 코드로만 단위 테스트를 수행하고 있다.

 

이렇게 테스트를 수행하면 실행은 된다.

@Test
void createOrder() {
	OrderServiceImpl orderService = new OrderServiceImpl();
	orderService.createOrder(1L, "itemA", 10000);
}

그런데 막상 실행 결과는 NullPointerException이 발생하는데 memberRepository, discountPolicy 모두 의존관계 주입이 누락되었기 때문이다.

 

final 키워드

생성자 주입을 사용하면 필드에 final 키워드를 사용할 수 있다. 그래서 생성자에서 혹시라도 값이 설정되지 않는 오류를 컴파일 시점에서 막아준다. 다음 코드를 보자

@Component
public class OrderServiceImpl implements OrderService{

    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;

    @Autowired
    public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
        this.memberRepository = memberRepository;
    }

   	....
}

잘 보면 필수 필드인 discountPolicy 에 값을 설정해야 하는데, 이 부분이 누락되었다. 자바는 컴파일시점에 다음 오류를 발생시킨다.

 

java: variable discountPolicy might not have been initialized

 

기억하자! 컴파일 오류는 세상에서 가장 빠르고, 좋은 오류다! 

 

막상 개발을 해보면, 대부분이 다 불변이고 그래서 다음과 같이 생성자에 final 키워드를 사용하게 되는데 생성자도 만들어야하고 주입받은 값을 대입하는 코드도 만들어야하고... 여간 귀찮은게 아니다. 

 

이때 등장하는 라이브러리가 바로 롬복이다. 

 

@RequiredArgsConstructor를 달아주기만 하면 코드가 깔끔하게 바뀐다.

@Component
@RequiredArgsConstructor
public class OrderServiceImpl implements OrderService{

    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;

    ....
}

 

롬복을 사용하는 방법은 다음과 같다.

1. start.spring.io 에서 dependency 추가에 lombok을 입력 후 추가

2. build.gradle에 추가

dependencies {
	compileOnly 'org.projectlombok:lombok'
	annotationProcessor 'org.projectlombok:lombok'
}

 

'Spring > Spring' 카테고리의 다른 글

스프링 MVC 구조 파악하기  (0) 2022.01.07
MVC 프레임워크 만들기  (0) 2022.01.06
컴포넌트 스캔과 자동 의존 관계 주입  (0) 2021.12.27
싱글톤과 @Configuration  (0) 2021.12.27
스프링을 사용해서 DI 구현하기  (0) 2021.12.26