본문 바로가기
Spring

스프링 핵심 원리 기본편 - 챕터 7. 의존관계 자동 주입

by mingutistory 2023. 3. 26.
728x90

Chapter 7. 의존 관계 자동 주입

다양한 의존관계 주입 방법

생성자 주입

생성자에 @Autowired가 붙어 생성자를 통해서 의존 관계를 주입하는 방법

생성자 호출 시점에 딱 1번만 호출되는 것이 보장된다. > 호출되는 시점에 한번 만 값을 세팅 할 수 있다.

불변, 필수, 의존 관계에서 사용한다.

 

생성자가 딱 1개 존재하면 생성자 주입이 자동으로 일어나서 의존 관계가 주입된다.

주입받아 사용 할 관계에 있는 객체를 final로 선언해서 외부에서 변경하지 못하도록 하는 것도 포인트.

- 의도를 알아 보기 쉽고, 벗어나지 않도록 하는 설계가 가장 좋은 설계다!

@Component
public class OrderServiceImpl implements OrderService {

    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;
    
    public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
        System.out.println("memberRepository = " + memberRepository);
        System.out.println("discountPolicy = " + discountPolicy);
        this.memberRepository = memberRepository;
        this.discountPolicy = discountPolicy;
    }
}

 

수정자 주입

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

선택, 변경 가능성이 있는 경우 사용한다.

- 선택적으로 하기 위해서는 @Autowried(required = false): 주입할 대상이 없으면 오류가 나기 때문에 동작하기 위해 설정.

 

@Autowired 어노테이션을 Setter에 붙이지 않으면 자동으로 주입되지 않아서 이 후 memberRepository.메소드() 사용 시에 bean 주입이 되지 않아서 NPE가 뜨게 된다. 

@Component
public class OrderServiceImpl implements OrderService {

    private MemberRepository memberRepository;
    private DiscountPolicy discountPolicy;

    @Autowired
    public void setMemberRepository(MemberRepository memberRepository) {
        System.out.println("memberRepository = " + memberRepository);
        this.memberRepository = memberRepository;
    }

    @Autowired
    public void setDiscountPolicy(DiscountPolicy discountPolicy) {
        System.out.println("discountPolicy = " + discountPolicy);
        this.discountPolicy = discountPolicy;
    }
    
    ...
}

 

1. 스프링 빈 등록

2. 연관 관계 주입

이 순서대로 스프링은 라이프 사이클을 가진다. 

그래서 생성자 주입과 수정자 주입의 순서를 따져보면 생성자 주입 이후 연관 관계 주입하면서 수정자 주입이 이루어진다.

왜냐면 결국 스프링도 자바 기반으로 돌아가기 때문에 OrderServiceImpl이라는 객체를 만들기 위해서 생성자를 호출하게 되고 그 과정에서 OrderServiceImpl 스프링 빈 등록하면서 해당 연관 관계들을 주입하게 됨.

 

필드 주입

말 그대로 필드에 주입 하는 것.

필드 주입은 추천하지 않음.

외부에서 변경이 불가능하기 때문에 스프링이 없는 환경에서 테스트가 불가능 함 = 순수한 자바 코드로 테스트 불가능.

> 테스트를 하기 위해서는 setter를 생성해야 함. 

 

@SpringBootTest를 붙여 사용하는 테스트 코드에서 사용 할 때 필드 주입을 통해 간단하게 테스트 가능.

Configuration 같은 경우(AutoAppConfig, AppConfig..) Bean 생성 시에 필요 할 때 간단하게 주입 가능. 왜냐면 스프링 컨테이너만 접근하기 때문.

 

일반 메서드 주입

@Component
public class OrderServiceImpl implements OrderService {

    private MemberRepository memberRepository;
    private DiscountPolicy discountPolicy;

    @Autowired
    public void init(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
        System.out.println("memberRepository = " + memberRepository);
        this.memberRepository = memberRepository;
    }
}

일반 메서드를 통해서 주입이 가능하지만, 일반적으로 잘 사용하지 않는다.

 

의존관계가 자동 주입되는 스프링 컨테이너가 관리하는 스프링 빈 일 때만 자동 주입이 이루어진다.

 

 

옵션 처리

public class AutowiredTest {
    
    @Test
    void AutowiredOption() {
        ApplicationContext ac = new AnnotationConfigApplicationContext(TestBean.class);
        
    }

    static class TestBean {
        @Autowired(required = false)
        public void setNoBean1(Member noBean1) {
            System.out.println("noBean1 = " + noBean1);
        }

        @Autowired
        public void setNoBean2(@Nullable Member noBean2) {
            System.out.println("noBean2 = " + noBean2);
        }

        @Autowired
        public void setNoBean3(Optional<Member> noBean3) {
            System.out.println("noBean3 = " + noBean3);
        }
    }
}

 

생성자 주입을 선택해라!

대부분의 의존관계 주입은 한 번 일어나면 애플리케이션 종료 지점까지 변경하지 않음으로 불변해야 한다.

수정자 주입같은 경우 setter가 public하여 누군가 변경이 가능함.

 

롬복과 최신 트랜드

1. 생성자가 하나면 @Autowired를 생략 할 수 있다. - 하지만 생성자 코드가 남아 있음.

2. 롬복 라이브러리 추가를 통해서 추가 코드도 생략 할 수 있다. 

 

@Getter

@Setter

 

@RequiredArgsConstructor

final이 붙은 객체 안의 변수를 변수로 받는 생성자를 만들어 줌.

 

조회 빈이 2개 이상 - 문제

@Autowired는 기본으로 타입으로 조회함으로 선택된 빈이 2개 이상일 때 문제가 발생함.

> NoUniqueBeanDefinitionException 오류 발생

 

항상 테스트 통과 후에 코드를 짜는 습관을 들이자. 

 

@Autowired 필드 명, @Qualifier, @Primary

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

 

@Autowried 필드 명 매칭

타입 매칭을 시도하고, 이 때 여러 빈이 있는 경우 필드명을 통해서 가져옴

 

@Qualifier -> @Qualifier끼리 매칭 (빈 이름 매칭)

직접 빈 등록 시에도 @Qulifier 사용 가능

빈 이름 매칭

@Component
@Qualifier("mainDiscountPolicy")
public class RateDiscountPolicy implements DiscountPolicy {
....
}

@Component
public class OrderServiceImpl implements OrderService {

    private MemberRepository memberRepository;
    private DiscountPolicy discountPolicy;

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

@Primary 사용

@Primary가 붙은 빈이 우선권을 가진다.

기본값처럼 동작하는 것.

 

@Primary와 @Qualifier를 섞어서 사용해서 메인으로 사용 할 빈과 서브로 사용할 빈을 지정해서 사용하는 방밥을 사용 할 수 있고 두 가지가 섞여서 사용되는 경우 상세하게 설정한 @Qualifier가 우선권이 높다.

 

애노테이션 직접 만들기

@Qualifier를 사용하면 문자를 사용하기 때문에 컴파일 에러를 미리 발생시키지 못하기 때문에 어노테이션을 만들어서 사용한다.

어노테이션에는 상속이라는 개념이 없음. 

다른 어노테이션들도 함께 조합해서 사용할 수 있음.

 

 

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

의도적으로 해당 타입의 스프링 빈이 모두 필요한 경우 => 전략 패턴을 스프링을 통해 구현 가능 

Autowired를 통해서 DiscountPolicy 타입의 컴포넌트가 모두 저장 됨.

    static class DiscountService {
    
    	// Map과 List로 주입 받는 방법
        private final Map<String, DiscountPolicy> policyMap;
        private final List<DiscountPolicy> policies;

        @Autowired
        public DiscountService(Map<String, DiscountPolicy> policyMap, List<DiscountPolicy> policies) {
            this.policyMap = policyMap;
            this.policies = policies;
            System.out.println("policyMap = " + policyMap);
            System.out.println("policies = " + policies);
        }

        public int discount(Member member, int price, String discountCode) {
            DiscountPolicy discountPolicy = policyMap.get(discountCode);
            return discountPolicy.discount(member, price);
        }
    }

 

 

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

편리한 자동 기능을 기본으로 사용하자 - 컴포넌트 스캔

관리할 빈이 많아서 설정 정보가 커지면 설정 정보를 관리하는 것도 부담 됨.

 

업무 로직 - 컨트롤러, 핵심 비즈니스 서비스, 데이터 계층 로직 처리 리포지토리... = 어디가 문제인지 명확하게 보임

수동 빈으로 등록하거나 혹은 구현체들은 특정 패키지에 같이 묶어두는 것이 옳다. 딱 보고 이해 할 수 있어야 함. 

 

기술 지원 - 기술 문제, 공통 관심사(AOP)처리 시 사용. 데이터베이스 연결, 공통 로그 처리... = 문제가 잘 안 보이기 때문에 수동 등록 빈을 통해서 명확하게 빈을 지정해주는 것이 좋다. 애플리케이션에 광범위하게 영향을 미치는 경우에는 설정 정보를 통해서 관리하는 것이 편리하다. 

 

 

 

300x250

댓글