매일 조금씩

[IoC 컨테이너와 빈] 3. @Autowired 본문

Spring Framework/스프링 프레임워크 핵심 기술

[IoC 컨테이너와 빈] 3. @Autowired

mezo 2024. 11. 3. 18:53
728x90
반응형

 

 

 

 

[전체 목차]

  1. IoC 컨테이너와 빈
  2. Resource / Validation
  3. 데이터 바인딩
  4. SpEL
  5. 스프링 AOP
  6. Null-Safety

 

 

 

1. @Autowired

: 필요한 의존 객체의 "타입"에 해당하는 빈을 찾아 주입한다.

기본값은 true (못 찾으면 애플리케이션 구동 실패)

 

2. 사용할 수 있는 위치

1) 생성자 (스프링 4.3부터는 @Autowired 생략 가능)

@Service 클래스의 생성자에 @Autowired를 안붙여도 Spring 4.3 부터는 의존성 주입을 해준다.

@Repository 가 붙어야할 리파지터리 클래스에는 @Component가 아닌 @Repository를 붙여주는 것이 좋다.

그래야 @Repository가 붙은 모든 빈의 특정 기능을 실행시킬 수도 있고, AOP 에서 사용하기 더 좋다.

 

>> @Repository 가 @Component 보다 AOP 에서 사용하기 더 좋은 이유는?

  • @Repository 어노테이션이 붙은 클래스는 Spring AOP의 예외 변환 기능을 통해 데이터베이스 예외를 DataAccessException으로 일관되게 변환할 수 있기 때문이다.
  • AOP가 데이터 접근 계층에서 발생하는 예외를 변환해주기 때문에, 다른 계층에서 예외를 쉽게 처리할 수 있다.
  • 이는 예외가 데이터 접근 계층의 세부 구현에 종속되지 않고, 일관된 방식으로 처리되도록 도와준다.

물론, 위 사진의 코드처럼@Repository@Component가 붙은 인터페이스라서, @Component 와 같은 기능을 하는 하위 클래스 느낌이지만, @Repository에 특화된 데이터베이스 예외 기능 같은 걸 이용하기 위해서 @Repository 처럼 구체화된 걸 쓰는게 좋다.

 

 

2) 세터, 필드

의존성 주입은 생성자 말고, 세터필드에서도 할 수 있다.

필드로 넣어둔 다른 빈을 해당 필드를 set하는 메서드에 @Autowired를 달면 의존성 주입이 된다.

필드의 경우 필드 위에 달면 된다.

 

 

3) 정리

이렇게 Spring에서 의존성 주입은 생성자, 필드, 세터에 @Autowired를 붙이면 할수 있다.

그러나, Spring에서 가장 권장하는 방법생성자를 사용한 방법이다.

왜냐하면 생성자를 사용하는 방법이 ~

  1. final 필드를 사용할 수 있어, 의존성 주입 후 해당 필드를 변경할 수 없어 안정성이 높고,
  2. 생성자로 의존성을 전달 받아서, 테스트 시에 쉽게 mock 객체나 테스트용 객체를 주입할 수 있으며,
  3. 생성자를 통해 의존성을 주입하므로, 코드만 보고도 해당 클래스가 어떤 의존성을 갖는지 생성자만 보면 명확히 알수있고,
  4. Spring에 가장 덜 의존적이기 때문이다.

4번의 Spring에 의존적이라는 말은 ~

@Autowired스프링에서 제공하는 의존성 주입용 어노테이션이다.

따라서, Spring의 경우 @Autowired를 사용한 의존성 주입을 제공하기 때문에

필드 세터를 통한 의존성 주입처럼 @Autowired를 써야만 의존성 주입이 된다면, Spring에 의존하는 것이 된다.

 

그러나, 생성자를 이용하는 방법은 프레임워크(Spring) 없이 Java만으로 의존성을 주입할 수 있다.

이는 Java의 기본적인 객체 생성 방식이므로, 의존성 주입의 원형이기도 하다.

다만, Spring IoC 컨테이너는 이런 생성자를 통한 주입을 자동화하여 객체 생성과 의존성 관리를 더 편하게 해주는 역할을 한다.

또한, Spring4.3에서 부턴 생성자가 하나일때,@Autowired를 생략할 수 있기 때문에 더욱 편리해졌다.

 

생성자 방법의 유일한 단점인 코드의 길이가 길다는 것 또한, Lombok를 사용하여 커버 가능해졌다.

@RequiredArgsConstructor or @AllArgsConstructor를 쓰면 된다.

@RequiredArgsConstructor의 경우, 필드에 final 키워드를 쓰거나, @NonNull를 붙이면 적용 되고,

@AllArgsConstructor의 경우, 모든 필드에 적용 된다.

 

 

3. 주입할 빈에 따른 경우의 수

  1. 해당 타입의 빈이 없는 경우 (required = false 하면 에러 안남)
  2. 해당 타입의 빈이 한개인 경우
  3. 해당 타입의 빈이 여러개인 경우
    • 빈 이름으로 시도, ('@Primary', '@Qualifier', '빈 클래스 이름 직접 언급' 등의 방법 사용)
      • 같은 이름의 빈 찾으면 해당 빈 사용
      • 같은 이름의 빈 못 찾으면 실패

>> 생성자를 사용해서 의존성 주입을 할땐 주입할 빈이 없으면 현재 빈을 생성하는 것에도 에러가 나겠지만..세터를 사용하면 현재 빈을 생성하는거 자체엔 에러가 안나야하는 것 아닌가??

아니다. @Autowired라는 어노테이션이 있다면 해당 빈을 생성할 때, 이 어노테이션이 붙은 걸로 의존성 주입을 하려고 시도한다.

따라서, @Autowired 때문에 의존성 주입할 빈이 없으면 현재 빈생성도 실패한다.

만약 해당 의존성이 없어도 되면 @Autowired 옵션으로 required=false를 주면된다.

 

4. 같은 타입의 빈이 여러개 일때 (인터페이스 구현체 클래스들)

1) @Primary

인터페이스 그 자체를 @Autowired로 의존성을 주입 하는건 불가능하다.

만약 하나의 인터페이스에서 여러개의 구현체 빈들이 있다면, 어떤 빈이 우선적으로 다른 빈에 주입이 되게 할지,

구현체 중 하나의 어노테이션 '옆에' @Primary를 붙여줘야한다.

ex) @Repository @Primary

2) @Qualifier("")

혹은, 의존성 주입시,

@Autowired 옆에 @Qualifier("주입할 구현체의 소문자 시작 카멜케이스 이름") 를 붙이면 된다.

그러나 @Primary 가 좀더 타입 세이프해서 더 추천한다.

>> 왜 @Primary가 더 타입 세이프할까?

@Primary타입 일치만으로 빈을 선택하므로, 컴파일 시점에 타입 오류를 쉽게 발견할 수 있지만, @Qualifier문자열로 빈 이름을 개발자가 직접 지정하기 때문에 문자열 오타나 잘못된 빈 이름 지정 시 런타임 오류가 발생할 수 있다.

 

3) 구현체 이름을 직접 언급

잘 추천하진 않는 방법인데..

여러개의 빈 중 하나의 빈 클래스 이름을 넣어서 주입 받을 수도 있다. 아래와 같이..

@Autowired
BookRepository myBookRepository;

@Primary 를 가장 추천한다.

 

4) 여러개를 모두 주입 받아야하는 경우엔?

@Autowired 붙이는 필드에 List 타입으로 선언하면 된다.

 

5. 동작 원리

@Autowired의 동작 원리를 알려면 이 어노테이션을 감지하고, 의존성 주입을 수행하는

Spring 의 인터페이스(BeanPostProcessor)

그걸 실제로 실행하는 구현체 빈(AutowiredAnnotaionBeanPostProcessor)을 알아야한다.

 

1)  BeanPostProcessor 인터페이스

빈의 인스턴스를 만든 다음에, 빈의 initialization 라는 라이프사이클이 있는데,

그 라이프사이클 이전 또는 이후에 부가적인 작업을 할수 있는 라이프 사이클이 있다.

그게 바로 BeanPostProcessor다.

 

이건 BeanFactory 인터페이스와는 별개다.

둘 다 Spring의 IoC 컨테이너가 제공하는 기능의 일부인데.

BeanPostProcessorBeanFactory와는 직접적인 상속 관계가 없으며, BeanFactory가 관리하는 빈의 라이프사이클에 참여하는 독립적인 역할을 가집니다.

 

2) BeanPostProcessor 인터페이스 사용법

빈 생성 이후에 뭘 실행 시키려면,

  • 빈 클래스 안에 실행시킬 메서드에 @PostConstruct를 붙이거나,
  • 해당 클래스를 implements InitializingBean 하게 하고, afterProperiesSet 메서드를 클래스 안에 @Override 하면 된다.

Spring boot runner는 애플리케이션 구동 후에 실행이 돼서 주입된 빈을 출력하면 마지막에 찍히는데,

@PostConstruct 는 다음 spring 애플리케이션 구동 단계 중에서 ..

...

11. postProcessorBeforeInitialization methods of BeanPostProcessors

12. InitializingBean's afterPropertiesSet

13. a custom init-method definition

14. postProcessorAfterInitialization methods of BeanPostProcessors

...

 

12번 단계에서 일을 한다.

11번에서 @Autowired로 빈 주입을 다 한다.

12번에선 빈 주입이 다 됐다고 생각하고, 초기화 단계에서 수행된다.

13번과정은 초기화과정에서 커스텀된게 있다면 실행하는데, 12번이랑 비슷하지만 12번이 먼저 동작한다.

 

여기서 말해주는 라이프사이클 정도만 알아도 크게 지장없다.

BeanPostProcessor,

AutowiredAnnotaionBeanPostProcessor extends BeanPostProcessor

이렇게 두 인터페이스가 핵심.

 

3)  정리하자면,,,

빈 라이프 사이클에

BeanPostProcessor라는 게 있구나!

그리고 그 구현체 중 하나인
AutowiredAnnotaionBeanPostProcessor가 동작을 하는구나!

 

4) 이 구현체가 어떻게 동작을 하느냐! 동작 순서를 보자!

  1. ApplicationContextBeanFactory 역할을 수행하며, 모든 빈을 생성하고 관리하는 IoC 컨테이너 역할을 하는데.
  2. 빈을 생성할 때마다 내부에 등록된 BeanPostProcessor 빈들을 찾는다.
  3. 그중에 하나로 @Autowired 와 같은 어노테이션을 처리하는 AutowiredAnnotaionBeanPostProcessor라는 빈 후처리기
    또한 으로 포함 되어있다. 
  4. ApplicationContext는 이렇게 초기화 과정에 AutowiredAnnotationBeanPostProcessor를 개입시켜 구동시킨다.
  5. AutowiredAnnotationBeanPostProcessor는 모든 빈에 등록된 @Autowired를 감지하여 의존성을 주입한다.
  6. 그리고 다른 일반적인 빈들한테 이걸 적용하는 거다.
  7. 예를 들어, UserService 빈의 필드에 @Autowired 가 붙어 있으면, AutowiredAnnotationBeanPostProcessor 는 같은 타입의 빈을 찾아 UserService 빈에 주입한다.

그러니까 기본적으로 AutowiredAnnotaionBeanPostProcessor 얘도 빈으로 등록이 되어있다는 것!

 

5) claud.ai가 그려준 Spring Framework의 핵심 아키텍처

그림 그리는건 chatGPT 보다 훨~~~씬 나은거 같다.

점선은 의존성 주입을 표한한 것이다.

 

  • Configuration 메타데이터: 빈 설정 정보를 제공 (어노테이션, XML, 자바 설정).
  • ApplicationContext: Configuration 메타데이터를 읽고, 빈을 생성하고 등록.
  • AutowiredAnnotationBeanPostProcessor: 빈 초기화 단계에서 @Autowired를 처리하여 의존성을 주입.
  • 빈 생명주기 관리: ApplicationContext가 빈의 생명주기를 관리하고, 초기화와 종료 작업을 수행.

 

 

>> 테스트 중에 spring boot runner를 썼던 이유는?

@SpringBootApplication 을 쓰다보니 의존성 주입 된 빈 출력하려면 빈등록 과정에서 빼와서 찍었는데 그걸 저 어노테이션이 다 해줘서 spring boot runner 써서 구동 이후에 ApplicationContext 에서 빈을 꺼내와서 찍도록 했음.

ApplicationContext도 Spring 애플리케이션에서 빈으로 등록된다.

사실 ApplicationContext는 자체적으로 빈을 관리하고 제공하는 컨테이너이지만,

그 자체도 하나의 빈(BeanFactory 의 하위 클래스)으로 등록되어 다른 빈들이 필요할 때 주입받아 사용할 수 있다.

 

>> 모든 빈들은 @Autowired로 다 꺼내와서 의존성 주입을 받을 수 있다.

 

 

 

 

 

 

 

728x90
반응형