Skip to content

스프링 핵심편 Section7

SH-Seol edited this page May 14, 2024 · 2 revisions

다양한 의존관계 주입 방법

의존관계 주입 방법

  • 생성자 주입
  • 수정자 주입(setter 주입)
  • 필드 주입
  • 일반 메서드 주입

생성자 주입

  • 이름 그대로 생성자를 통해서 DI

  • 우리가 지금까지 진행한 방법

  • 특징

    • 생성자 호출시점에 딱 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;
        }
    }
    • 불변이라는 것이 개발할 때 매우 중요.

      불변이 아니면 분명 누군가가 건들게 됨

    • 필수값에 사용. 웬만하면 생성자에 집어 넣는다.

    생성자가 딱 1개만 있으면 @Autowired가 없어도 괜찮다.

수정자 주입(setter 주입)

@Autowired
public void setMemberRepository(MemberRepository memberRepository){
        this.memberRepository = memberRepository;
}
  • 여기서는 final이 들어가면 안된다.
  • 특징
    • 선택, 변경 가능성 있는 의존관계에 사용

자바빈 프로퍼티 규약

→ 필드 값을 직접 변경하는 것이 아닌 getXxx, setXxx를 이용하여 값을 읽거나 수정하자.

class Data {
	private int age;
	public void setAge(int age) {
		this.age = age;
	}
	public int getAge() {
		return age;
	}
}
  • age가 필드값이니 setAge, getAge

필드 주입

@Component
public class OrderServiceImpl implements OrderService {
	@Autowired private MemberRepository memberRepository;
	@Autowired private DiscountPolicy discountPolicy;
}
  • 필드에 바로 주입하는 방법
  • 특징
    • 코드가 간결하지만, 외부에서 변경이 불가능하여 테스트하기 어렵다.
      • 결국 테스트하려면 setter를 추가로 생성..그럴 바엔 setter주입이..
    • DI 프레임워크가 없으면 아무 것도 못한다.
    • 테스트 코드에서는 써도 된다.

일반 메서드 주입

@Component
public class OrderServiceImpl implements OrderService {
	private MemberRepository memberRepository;
	private DiscountPolicy discountPolicy;
	@Autowired
	public void init(MemberRepository memberRepository, DiscountPolicy
	discountPolicy) {
		this.memberRepository = memberRepository;
		this.discountPolicy = discountPolicy;
	}
}
  • 아무 메서드에 autowired주입 할 수 있다
    • 수정자 주입이랑 다른게 거의 없다
  • 특징
    • 한번에 여러 필드를 주입
    • 잘 사용 안됨

DI는 스프링 컨테이너가 관리하는 spring bean에서만 동작 빈이 아닌 곳에서 autowired코드 적용해도 아무 기능 안함

옵션 처리

주입할 스프링 빈이 없어도 동작해야할 때가 있다.

@Autowired(required = false)

@Nullable

Optional

생성자 주입 선택

과거에는 setter주입, 필드 주입을 많이 사용했지만 최근에는 생성자 주입을 권장

  1. “불변”
  • 대부분의 DI는 한번 일어나면 애플리케이션 종료시점까지 의존관계를 변경하지 않는다. → 오히려 애플리케이션 종료시점까지 변하면 안된다.
  • setter주입을 사용하면 setXxx메소드를 public으로 열어두기에 누군가 실수로 변경할 수 있다.
  • 생성자 주입은 객체를 생성할 때 딱 1번만 호출되므로 이후에 호출되는 일이 없다. → 불변
  1. “누락”
  • 프레임워크 없이 순수한 자바 코드를 단위 테스트 하는 경우
@Test
    void createOrder(){
        MemoryMemberRepository memberRepository = new MemoryMemberRepository();
        memberRepository.save(new Member(1L, "name", Grade.VIP));
        OrderServiceImpl orderService = new OrderServiceImpl(memberRepository, new FixDiscountPolicy());
        Order order = orderService.createOrder(1L, "itemA", 10000);
        assertThat(order.getDiscountPrice()).isEqualTo(1000);
    }

final

생성자 주입을 사용하면 final 사용 가능 → 값이 설정되지 않는 오류를 컴파일 시점에서 막아줌

컴파일 오류는 세상에서 가장 빠르고 좋은 오류

💡 항상 생성자 주입을 선택, 필요하면 가끔 setter 주입, 필드주입은 사용하지 말자

롬복과 최신 트랜드

개발할 때는 대부분 불변, 생성자에 final 사용

그런데 필드 주입처럼 편리하게 못 하나?

@Component
@RequiredArgsConstructor
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);
        //OrderService 입장에선 할인에 대해서는 난 몰라. discountPolicy 너가 알아서 해줘

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

    //test 용도
    public MemberRepository getMemberRepository(){
        return memberRepository;
    }
}
  • @RequiredArgsConstructor를 추가하면 class 내의 final인자에 대한 생성자를 자동으로 만들어줌
  • 컴파일 시점에서 생성자 코드를 자동으로 생성

조회 빈이 2개 이상 - 문제

아래의 방법으로 해결할 수 있다.

@Autowired 필드 명, @Qualifier, @Primary

조회 대상 빈이 2개 이상일 때 해결 방법

  • @Autowired 필드 명 매징
  • @Qualifier → @Qualifier끼리 매칭 → 빈 이름 매칭
  • @Primary 사용

@Autowired 필드 명 매칭

Autowired는 type 매칭 시도하고 여러 빈이 있으면 필드, 파라미터 이름으로 빈 이름을 추가 매칭

@Autowired 매칭 정리

  • 타입 매칭
  • 타입 매칭의 결과가 2개 이상일 때 필드 명, 파라미터 명으로 빈 이름 매칭

@Qualifier 사용

주입시 추가적인 방법을 제공하는 것, 빈 이름 변경은 아님

@Qualifier 로 주입할 때 @Qualifier("mainDiscountPolicy") 를 못찾으면?

→ 그러면 mainDiscountPolicy라는 이름의 스프링 빈을 추가로 찾는다. 하지만 경험상 @Qualifier@Qualifier 를 찾는 용도로만 사용하는게 명확하고 좋다.

@Primary 사용

여러개가 있을 때 Primary가 있으면 우선 순위 지정

Primary와 Qualifier의 우선순위 → Qualifier가 더 높다.

애노테이션 직접 만들기

@Qualifier(”mainDiscountPolicy) → 여기서 문자는 컴파일 타임에서 체크가 되지 않는다.

Primary로 해결이 되면 Primary를 사용하면 되지만 그것이 아니라면 직접 만드는 것도 좋다.

  • 애노테이션은 상속이라는 개념 x
  • 여러 애노테이션 모아서 사용하는 기능 → 스프링
  • @Qualifier 뿐만 아니라 다른 애노테이션들도 함께 조합해서 사용
    • Autowired도 재정의 가능하나 뚜렷한 목적 없이 스프링이 제공하는 기능을 무분별하게 재정의하는 것은 유지 보수에 더 혼란을 준다.

조회한 빈이 모두 필요할 때, List, Map

의도적으로 해당 타입의 bean이 다 필요한 경우도 있음

소비자가 rate, fix를 선택할 수 있다면 spring의 전략패턴을 사용

자동, 수동의 올바른 실무 운영 기준

편리한 자동 기능을 기본으로 사용하자

  • 자동을 선호하는 추세이다.
  • Component만 넣으면 되는 것을 Cofiguration설정 정보에 가서 Bean을 적는 과정은 번거롭다.
  • 자동 빈 등록을 사용해도 OCP, DIP 지킬 수 있다.

수동 빈 등록은 언제?

  • 업무 로직 빈

    • 웹 지원 컨트롤러, 핵심 비즈니스 로직이 있는 서비스, 리포지토리 등
    • 보통 비즈니스 요구사항을 개발할 때 추가되거나 변경
  • 기술 지원 빈

    • 기술적인 문제나 공통 관심사(AOP)처리에 주로 사용
  • 업무로직은 숫자가 많고, controller, service, repository 등 유사한 패턴이 있어 자동 기능 사용이 좋다.

  • 기술 지원 로직은 업무 로직과 비교해 수가 적고, app 전반에 걸쳐 광범위한 영향

    • 적용이 잘 되는지도 파악하기 어렵기에 이 경우 수동 빈 등록 사용이 좋다.

app 광범위하게 영향을 미치는 기술 지원 객체 → 수동 빈

설정 정보에 바로 나타나게 하는 것이 유지보수에 좋다.

비즈니스 로직 중 다형성을 적극 사용하는 경우

DiscountPolicy의 경우 여러 파일로 나뉘어 있어 하나 하나 찾아봐야 하지만, DiscountPolicyConfig로 묶여 있으면 바로 파악할 수 있다 → 특정 패키지에 같이 묶어 두는 것이 좋다. 딱 보고 이해가 되어야 한다!

@Configuration
public class DiscountPolicyConfig {
@Bean
public DiscountPolicy rateDiscountPolicy() {
return new RateDiscountPolicy();
}
@Bean
public DiscountPolicy fixDiscountPolicy() {
return new FixDiscountPolicy();
}
}
  • 한 눈에 빈의 이름, 어떤 빈들이 주입될지 파악 가능

스프링, 스프링 부트가 자동 등록하는 빈들은 예외

스프링 부트가 아닌 내가 직접 기술 지원 객체를 bean으로 등록한다면 수동으로 등록해 명확하게 들어내는 것이 좋다.