본문 바로가기
Spring

[Spring] @Autowired 및 Bean 라이프 사이클

by dbjh 2019. 12. 15.
반응형

Spring에서 의존성주입을 하기 위해 사용하는 어노테이션이 바로 @Autowired 이다. 기본적으로 필드에 명시할 수도 있고 set메소드의 파라미터로 주입하고 싶은 클래스를 넣고 해당 메소드에 명시할 수 도있다. 

BookService

package com.example.demo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class BookService {

    @Autowired
    BookRepository bookRepository;

    // setter는 객체생성 이후에 호출하기 때문에 일단 BookService 객체가 생성될 거라고 생각하지만
    // @Autowired 어노테이션이 있으면 의존성을 주입하려고 시도하기 때문에, 해당 빈이 있어야한다.
    // 만약 의존성 주입을 optional로 설정해주려면 @Autowired(require = false)로 설정한다.
    public void setBookRepository(BookRepository bookRepository) {
        this.bookRepository = bookRepository;
    }
}

 

1. Bean의 의존성이 2개 이상인 경우

주입을 하고 싶은 클래스가 상속을 받았거나 인터페이스를 구현해서 같은 타입의 클래스가 2개 이상인 경우가 있다. 이런경우 어떻게 특정 클래스를 Bean으로 주입해줄수 있는지 알아보자.

BookRepository

package com.example.demo;

public interface BookRepository {
}

 

FirstBookRepository

package com.example.demo;

import org.springframework.stereotype.Repository;

@Repository
public class FirstBookRepository implements BookRepository{
}

 

SecondBookRepository

package com.example.demo;

import org.springframework.stereotype.Repository;

@Repository
public class SecondBookRepository implements BookRepository {
}

 

위와 같이 BookRepository 인터페이스를 구현한 FirstBookRepository와 SecondBookRepository가 있다. BookService클래스에 BookRepository 타입의 Bean을 주입하려고하면 아래와 같이 BookService에 오류가 발생할 것이다

IntelliJ가 이미 BookRepository 타입의 Bean이 두 개 이상인 것을 알고 의존성 주입을 할 수 없다고 알려준다. 그리고 바로 아래에 오류를 발생시킨 클래스를 알려준다.

이럴 경우, BookRepository를 구현한 클래스들 중 의존성 주입을 하고싶은 클래스 하나를 선택하거나 모든 자식 클래스들을 주입해주는 방법이 있다.

 


2. 의존성 주입을 하고싶은 클래스를 선택하는 방식

아래와 같이 @Primary 어노테이션이나 @Qualifier 어노테이션을 사용하는 방법이 있다

2.1 주입하고 싶은 클래스에 @Primary 어노테이션 명시

FirstBookRepository

package com.example.demo;

import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Repository;

@Repository @Primary
public class FirstBookRepository implements BookRepository{
}

 

2.2 주입을 받을 클래스 필드 및 setter등에 @Qualifier 어노테이션명시

BookService

package com.example.demo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

@Service
public class BookService {

    @Autowired @Qualifier("firstBookRepository")
    BookRepository bookRepository;
}

 

위의 두 방법 모두 실행하면 BookService의 bookRepository필드에 FirstBookRepository Bean을 주입하도록 되어있다.
그렇다면, 실제로 FirstBookRepository가 주입되는지 확인해보도록하자.

2.3 주입된 Bean 확인하기

주입되는 클래스를 볼 수 있도록 BookService 클래스에 bookRepository 필드에 어떤 타입의 클래스가 주입되는지 확인 할 수 있는 print 메소드를 추가하고, BookService의 객체를 사용할 수 있게 Runner 클래스를 만들어서 테스트해보자.

BookService

// 코드중략

    public void printBookRepository() {
        System.out.println(bookRepository.getClass());
    }

// 코드중략

 

BookServiceRunner

package com.example.demo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;

@Component
public class BookServiceRunner implements ApplicationRunner {

    @Autowired
    BookService bookService;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        bookService.printBookRepository();
    }
}

 

 코드 작성이 완료되었으면, @Primary 어노테이션 및 @Qualifier 어노테이션 방식을 둘다 사용하여 확인해보자.

FirstBookRepository 주입이 확인된 결과

두 방법 모두 FirstBookRepository Bean이 주입된 것을 확인할 수 있다.
@Qualifier는 클래스를 직접 타이핑해야하고 @Primary는 주입하고 싶은 클래스에 명시만 해주면 되기때문에 @Primary 어노테이션 방식이 조금더 Type Safe하다. 

 

 


3. BookRepository 타입의 클래스 모두 주입하기

BookRepository 인터페이스를 구현한 모든 클래스를 주입하는 방법은 List를 사용하면된다.
아래와 같이 List 타입으로 Bean 주입을 받도록하고 주입받은 모든 Bean들의 이름을 출력하도록 해보자.

BookService

package com.example.demo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class BookService {

    @Autowired
    List<BookRepository> bookRepositories;

    public void printBookRepository() {
        this.bookRepositories.forEach(System.out::println);
    }
}

 

아래와 같이 모든 클래스 이름이 출력되는 것을 확인할 수 있다.

BookRepository 인터페이스를 구현한 Bean들

 

여기까지 디렉토리 구조

스텝3 까지 디렉토리 구조

 


4. Bean 라이프 사이클

2. 의존성 주입을 하고싶은 클래스를 선택하는 방식에서 나왔던 방식 말고 또 클래스를 선택하여 주입하는 방식이 하나 더있는데 추천하지는 않는 방식이다. 하지만 Bean 라이프 사이클에 대한 설명을 위해 간단하게 알아보도록 하자.

BookService의 BookRepository를 주입받고 싶은 필드의 이름을 주입하고 싶은 클래스명과 동일하게 해주면된다.

BookService

package com.example.demo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class BookService {

    @Autowired
    BookRepository firstBookRepository; // FirstBookRepository 클래스와 이름을 같게 수정한다.

}

위처럼, 같은 이름의 클래스를 찾아 의존성 주입을 해주는 원리는 BeanPostProcessor라는 인터페이스를 구현한 클래스에 의해서 동작한다.
BeanPostProcessor는 Bean의 초기화 라이프사이클(Bean 생성 당시, 전, 후)에 부가적으로 작업 할 수 있는 콜백을 관리하는 인터페이스라고 생각하자.

이것을 이용하면 Runner 클래스를 사용하지 않고 BookService 객체의 라이프 사이클 콜백메소드만으로 생성된 Bean들의 이름을 출력할 수 있다.

4.1 @PostConstruct 어노테이션을 이용한 방법

BookService

// 코드중략

@Service
public class BookService {

    @Autowired
    BookRepository bookRepository;

    // 동작하도록 어노테이션을 명시하여 Bean 주입 직후에 동작시키는 방법
    @PostConstruct
    public void afterCreateBean(){
        System.out.println(bookRepository.getClass());
    }
}

 

4.2  InitializingBean 인터페이스를 구현한 방법

BookService

@Service
public class BookService implements InitializingBean {

    @Autowired
    BookRepository bookRepository;
    
    // 아래 메소드를 오버라이딩 하여 Bean 주입 직후에 동작시키는 방법
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println(bookRepository.getClass());
    }
}

 

위의 두 방법을 사용하면 프로젝트가 시작되면 Bean 생성 및 주입을 하기 때문에 아래와 같이 프로젝트 시작 로그 중간에 클래스 이름이 출력되는 것을 확인할 수 있다.

프로젝트가 시작되면서 출력된 클래스이름

 

위에서 정리한 Bean 주입 직후에 동작하도록 하는 라이프 사이클 콜백함수는 아래링크에 들어가서 확인해보면 표시된 순서에 해당된다.

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/beans/factory/BeanFactory.html

 

@Autowired에 의해 의존성이 주입되는 순서는,
IOC의 최상단에 있는 BeanFactory가 BeanPostProcessor 타입의 Bean을 찾는데, Bean중에 하나로 등록되어있는 AutowiredAnnotationBeanPostProcessor(BeanPostProcessor를 상속받음)가 @Autowired를 동작시켜서 Bean을 찾아 주입 해주는 것이다.

AutowiredAnnotationBeanPostProcessor가 Bean으로 등록되어 있는것을 확인하고 싶으면 Step 2.3처럼 다시 ApplicationRunner를 만들어서 확인해보면 알 수 있다.

 

내용 출처

반응형

댓글