반응형

의존관계 자동주입


의존관계 주입은 크게 4가지 방법이 있다.

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

 

생성자 주입

말그대로 생성자를 통해서 의존 관계를 주입 받는 방법이다.

(위에서 했던 방식들)

생성자 호출 시점에 딱 1번만 호출되는 것이 보장된다. (그 다음부턴 호출 안되게 막을 수 있다.)

불변, 필수 의존관계에 사용한다. => OCP 지킴

    @Component//OrderServiceImpl가 스프링 빈에 등록됨 => 객체 인스턴스가 생성되니까 생성자도 호출!
    public class OrderServiceImpl implements OrderService {
        //private final로 선언됨 => 무조건 값을 세팅해줘!!! 라고 알리는 격임
        //따라서 무조건 값이 있어야댐!!! 아니면 null임
        //외부에서 값 변경이 불가! => setter, getter가 없음
        private final MemberRepository memberRepository;
        private final DiscountPolicy discountPolicy;
        
        
        //생성자가 여기 있음! 
        //Autowired이므로 스프링 컨테이너에서 스프링 빈을 꺼내서(리포지토리, 할인정책)을 주입시켜줌
        // 그리고 왠만하면 생성자에 있는 파라미터들을 다 넣어주는게 좋음! 아니 그래야댐
        @Autowired
        public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy
                discountPolicy) {
            this.memberRepository = memberRepository;
            this.discountPolicy = discountPolicy;
        }
    }

 

생성자 주입은 빈을 등록하면서 같이 일어난다.

-> 빈을 등록해야하니까!

빈을 등록?? => 객체 인스턴스를 생성해서 스프링 컨테이너가 들고 있음!!

객체 인스턴스 생성?? => 객체를 생성해야하니까 생성자 호출!!

 

 

 

생성자가 하나인 경우는 Autowired 생략 가능하다. 물론 스프링 빈에만 해당댐

(@Component 햇으니까 스프링 빈임)

    @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;
        }
    }

 

 

 

 

수정자 주입(setter 주입)

setter라 불리는 수정자 메서드로 의존관계를 주입함!

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

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

    @Component
    public class OrderServiceImpl implements OrderService {
        //final이 아님 => 값 변경 되어도 됨
        private MemberRepository memberRepository;
        private DiscountPolicy discountPolicy;
        
        //생성자로 주입 받는것이 아니라 setter 메서드로 주입받음
        @Autowired
        public void setMemberRepository(MemberRepository memberRepository) {
            this.memberRepository = memberRepository;
        }
        @Autowired
        public void setDiscountPolicy(DiscountPolicy discountPolicy) {
            this.discountPolicy = discountPolicy;
        }
    }

** @Autowired의 기본 동작은 주입할 대상이 없으면 오류가 발생한다. 주입할 대상이 없어도 동작하게 하려면 @Autowired(required = false) 로 지정하면 된다.

=> 예를 들어 MemberRepository 쓸지 안쓸지 잘 모름....

    @Component
    public class OrderServiceImpl implements OrderService {
        //final이 아님 => 값 변경 되어도 됨
        private MemberRepository memberRepository;
        private DiscountPolicy discountPolicy;
        
        //쓸지 안쓸지 몰라서 false로 해놓음, 따라서 주입할 대상이 없어도 오류가 안남
        @Autowired(required = false)
        public void setMemberRepository(MemberRepository memberRepository) {
            this.memberRepository = memberRepository;
        }
        @Autowired
        public void setDiscountPolicy(DiscountPolicy discountPolicy) {
            this.discountPolicy = discountPolicy;
        }
    }

 

 

** 자바빈 프로퍼티, 자바에서는 과거부터 필드의 값을 직접 변경하지 않고 setXxx, getXxx라는 메서드를 통해서 값을 읽거나 수정하는 규칙을 만들었는데, 그것이 자바빈 프로퍼티 규약이다.

 

필드 주입

말그대로 필드에 주입!

하지만 안티패턴이다...

코드가 간결하지만, 외부에서 변경이 불간으해서 테스트하기 힘들다는 단점이있다.

DI 프레임워크가 없으면 아무것도 할 수 없다. => 순수 자바코드로는 못한다.

즉, 사용하지 말자!!!!!!!!

주로 실제코드와 관계없는 테스트코드나, 스프링 설정을 목적으로 하는 @Configuration 같은 곳에서만 특별한 용도로 쓴다.

@Component
public class OrderServiceImpl implements OrderService {
    
 //=> 값을 집어 넣을 수 없다... 어케넣어 setter도 없고 일반 메서드도 없고, 생성자로도 못넣고..
 @Autowired
 private MemberRepository memberRepository;
 @Autowired
 private DiscountPolicy discountPolicy;
    
 //값을 못 집어넣어버림 
 public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
        this.memberRepository = memberRepository;
        this.discountPolicy = discountPolicy;
    }
  
    
}
@Test
void fieldInjectionTest(){
    //이렇게 new 임의로 생성한 객체는 Autowired 되지 않음
    //Autowired는 스프링 빈에 등록된 객체를 연결하는것이므로
    OrderServiceImpl orderService = new OrderServiceImpl();
    orderService.createOrder( ~~~ )
    
    //할인 정책과 멤버리포지토리를 못 넘겨줌....
    //set 메소드를 만들어서 넣어 주거나 해야댐
    //orderService.setRepository(new MemoryMemberRepositroy) 처럼
}

==> 필드 인젝션은 결국 값을 넣으려면 setter가 필요하다.

즉, @Autowired로 땡겨온다곤 햇는데 넣어 주질 않으니 어케 그 값에 넣으란 말이냐....... 결국 setter와 같은게 필요한거지..

그럼 왜 필드 인젝션 씀? 그러므로 쓰지말자..

 

** 순수한 자바 테스트 코드에는 당연히 @Autowired가 동작하지 않는다. @SpringBootTest처럼 스프링 컨테이너를 테스트에 통합한 경우에만 가능하다 => @Autowired는 스프링 빈에 등록된 걸 가져오는건데 스프링 컨테이너가 없는데 어케 가져와?!?! , 스프링 컨테이너 = ApplicationContext

예시)

@Test
    @DisplayName("컴포넌트 스캔으로 빈이 등록되었는지 확인, 의존관계도 확인")
    void basicScan(){
        //스프링 컨테이너에 빈들이 등록되었는지 확인 해보면된다.
        //설정정보로 스프링컨테이너 불러오고
        ApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class);
//        MemberService memberService = ac.getBean("memberService", MemberService.class);

        MemberService memberService = ac.getBean(MemberService.class);
        Assertions.assertThat(memberService).isInstanceOf(MemberService.class);
    }

 

 

** @Bean을 사용하는 경우에, 파라미터에 의존관계는 자동 주입된다.

수동 등록시 자동 등록된 빈의 의존관계가 필요할 때 문제를 해결할 수 있다.

    @Bean
    OrderService orderService(MemberRepository memberRepoisitory, DiscountPolicy
            discountPolicy) {
        new OrderServiceImpl(memberRepository, discountPolicy)
    }

 

 

 

일반 메서드 주입

일반 메서드를 통해서 주입받을 수 있다. (사실상 수정자 주입이랑 같다..)

한번에 여러 필드를 주입 받을 수 있고 일반적으로 잘 사용하지 않는다.

 

    @Component
    public class OrderServiceImpl implements OrderService {
        private MemberRepository memberRepository;
        private DiscountPolicy discountPolicy;
        
        //일반 메서드 init를 통해서 주입 받음
        @Autowired
        public void init(MemberRepository memberRepository, DiscountPolicy
                discountPolicy) {
            this.memberRepository = memberRepository;
            this.discountPolicy = discountPolicy;
        }
    }

** 의존 관계 자동 주입은 스프링 컨테이너가 관리하는 스프링 빈이어야 동작한다.

스프링 빈이 아닌 Member 클래스에서 @Autowired 코드를 적용해도 아무 기능도 동작하지 않는다.

(스프링 컨테이너에 빈으로 등록되어 있어야지 가져오지, 등록이 안되었는데 어케 가져와?!)

 

 

 

순수한 자바로 테스트한다는 말을 잘 생각해보시면 좋습니다. 스프링 컨테이너를 실행하지 않은 상태(필드 주입을 위한 @Autowired가 동작하지 않음)에서는 생성자나 수정자가 없으면 필드에 구현체를 넣을 방법이 없습니다.

즉, 필드 주입을 사용하고 있는 클래스를 테스트하려면 스프링 컨테이너를 통해 의존관계가 설정되어야 합니다. 만약, 필드 주입을 사용하는데 스프링 컨테이너를 사용하지 않는다면 Null Pointer Exception이 발생할 것 입니다. 해당 필드에 구현체가 존재하지 않기 때문입니다.

따라서 생성자 주입을 사용하게 되면 스프링과 결합된 코드(테스트를 위해 스프링 컨테이너를 실행해야 하는 코드)가 아닌 순수한 자바 코드(스프링 컨테이너를 실행하는 게 없는 코드)로 의존관계를 주입하고 테스트를 할 수 있습니다.

 

순수한 자바로 테스트한다는 말은 "스프링 컨텍스트"를 띄우지 않은 상태에서 하는 테스트를 말하는 것입니다.

그렇기 때문에 순수한 자바로 테스트를 진행하게 되면 필드주입은 발생하지 않습니다. 필드 주입은 스프링 컨텍스트에서 @Autowired를 감지하여 해당 필드에 들어갈 빈을 컨테이너로부터 가져와야 합니다. 그러나 스프링 컨텍스트를 사용하지 않기 때문에 필드주입이 발생하지 않습니다. 마찬가지로 생성자 주입도 "자동"으로 일어나지 않습니다.

다만, 생성자 주입의 경우 스프링 컨텍스트를 사용하여 자동으로 주입되지 않지만 수동으로 의존관계를 주입할 순 있습니다. 직접 구현체를 생성하고 생성된 구현체를 생성자에 전달하면 되니깐요.

순수 자바코드 테스트 에서는 ApplicationContext를 사용하지 않는다.

-> 스프링 컨테이너가 없다.

-> Bean을 관리하지 않는다.

-> @Autowired 포함 @Bean, @Configuration도 동작하지 않는다.

 

 

결국 필드주입은 스프링 컨텍스트를 의존해야 주입받을수있어서 순수자바로 테스트를 못하지만, 생성자 주입의 경우 현재 강의의 초기내용처럼 AppConfig를 통해 주입을 받아 테스트를 할수있기때문에 순수자바로 테스트를 했다고 볼수있다.

라고 이해했습니다. 맞을까요?

꼭 AppConfig가 아니더라도 됩니다. 의존관계에 필요한 객체를 생성해서 생성자의 파라미터로 넘겨주기만 하면 됩니다. 나머진 이해하신 부분이 맞습니다.

 

필드주입의 경우 필드에 @Autowird 어노테이션만 붙이면 된다고 하셨는데

OrderServiceImpl 이라는 클래스에서

@Autowird private MemberRepository memberRepository 이렇게 가지고 있을 경우

Spring에서 MemberRepository 형태로 등록되어 있는 빈을 꺼내서 자동으로 저기에 주입시켜주는 것이 맞나요? 강의의 예제의 경우 MemoryMemberRepository 클래스 정의 상단에 @Component 어노테이션이 있기에 처음에 component scan 방식으로 해당 컴포넌트를 인식하여 MemoryMemberRepository를 빈에 등록을 한 후 OrderServiceImpl에서 @Autowird private MemberRepository memberRepository 해당 부분을 본 후 MemoryMemberRepository도 MemberRepository 인터페이스를 구현한 것이기에 MemberRepository 형태로 빈에서 찾을경우 찾은 결과에 속할 수 있기에

그렇게해서 찾은 MemoryMemberRepository를 OrderServiceImpl의 memberRepository로 의존관계를 주입해주는 것이 맞나요??

만약 맞다면 MemberRepository를 구현하는 메모리멤버리파짓토리와 디비멤버리파지토리가 모두 bean 에 등록되어있을 경우에는 어떻게 작동하게 되는건가요?

 

MemberRepository 인터페이스를 구현한 모든 것이 주입의 대상이 됩니다. 따라서 이 경우 충돌이 발생합니다.

바로 조금 뒤에 이런 문제를 어떻게 해결하는지 자세히 설명드립니다^^

감사합니다.

728x90
반응형
블로그 이미지

아상관없어

,