본문 바로가기

Back-End/토비의 스프링3

1.2-IoC 컨테이너 : 빈 설정하기

1.2-IoC 컨테이너 : 빈 설정하기

스프링에서 IoC 컨테이너의 가장 기본적인 역할은 코드를 대신해서 애플리케이션을 구성하는 오브젝트를 생성하고 관리하는 것이다.
( 이전 내용은 http://happyer16.tistory.com/183?category=692836 을 참고 )

그렇다면 IoC 컨테이너가 자신이 만들 오브젝트가 무엇인지 어떻게 알 수 있을까?

빈을 만들기 위한 설정 메타정보가 담긴 BeanDefinition을 IoC 컨테이너가 활용한다.

  • 빈 생성을 위한 메타정보가 무엇이 있는지?
  • 메타 정보를 표현하는 다양한 방법
이 2가지에 대해 살펴보자.

IoC 컨테이너 : 빈 설정 메타정보

스프링에서는 오브젝트를 IoC 컨테이너가 생성하고 관리하는 과정에서 필요한 세밀한 방법을 제공한다. 하지만, 우리는 클래스 이름을 통해 빈이 등록되고 다른 값들은 디폴트 값을 사용하기 때문에 이러한 설정이 있는 줄 모른다.

  • scope : 빈 오브젝트의 생명주기를 결정 ( 싱글톤 / 비싱글톤 )
  • beanClassName : 빈 오브젝트의 클래스 이름 ( 디폴트 값 없음, 필수항목 )
  • parentName : 빈 메타정보를 상속받을 부모 BeanDefinition의 이름
  • factoryBeanName : 팩토리 역할을 하는 빈을 이용해 빈 오브젝트를 생성하는 경우
  • factoryMethodName : 다른 빈 또는 클래스의 메소드를 통해 빈 오브젝트를 생성하는 경우 
  • lazyInit : 빈 오브젝트의 생성을 최대한 지연할 것인지 ( false )
  • dependsOn : 먼저 만들어져야 하는 빈을 지정할 수 잇다. 빈 오브젝트의 생성순서가 보장되어야 하는 경우 사용
  • autowireCandidate : 
  • primary
  • abstract
  • autowireMode
  • dependencyCheck
  • initMethod : 빈이 생성되고 DI를 마친 뒤에 실행할 초기화 메소드의 이름
  • destoryMethod : 빈의 생명주기가 다 돼서 제거하지 전에 호출할 메소드의 이름
  • propertysValues 
  • constructorArgumentValues
  • annotationMetadata

IoC 컨테이너 : 빈 등록 방법

이제 빈 설정 메타정보가 어떤것인지 살펴보았고, 이 메타정보를 컨테이너에게 어떻게 건네주는지 살펴보자. 보통 XML, 프로퍼티 파일, 소스코드 애노테이션과 같은 외부 리소스로 빈 메타정보를 작성하고 이를 적절한 리더나 변환기를 통해 IoC 컨테이너가 사용할 수 있는 정보로 변환해주는 방법을 사용한다.

IoC 컨테이너에 빈 등록하기 1 - XML : <bean> 태그

<bean> 태그를 사용하는 것은 가장 단순하면서도 가장 강력한 설정 방법이다. 빈 메타정보의 모든 항목을 지정할 수 있어 세밀한 제어가 가능하다.

이 방법부터 충분히 익힌 뒤 다른 방법을 익히라 하는데 왜 그런지는 모르겠다.... (?)

<bean id="hello" class="happyer16.bean.Hello">
...
</bean>



< 연관글 >

1.1-IoC 컨테이너 : 빈 팩토리와 애플리케이션 컨텍스트

 



IoC 컨테이너에 빈 등록하기 2 - 빈 스캐너와 스테레오타입 애노테이션 

위와 같이 XML에 일일이 선언하는 게 귀찮을 수도 있다. 빈의 개수가 많아지면 XML에서 여러 개발자가 수정하면 충돌이 일어나는 경우도 많을 것이다.

빈으로 사용될 클래스에 특별한 애노테이션을 부여해주면 이런 클래스를 자동으로 찾아서 빈으로 등록해주게 할 수 있다. 이 방법을 빈 스캐닝이라 한다.

@Component를 포함해 디폴트 필터에 적용되는 애노테이션을 스프링에서는 스테레오타입 애노테이션이라고 부른다.

@Component
public class AnnotatedHello {

/**
* @Component("myAnnotatedHello")와 같이 빈의 이름을 등록해줄 수 있다.
* 기존에 있는 클래스의 이름과 중복을 피하거나 의미를 부여할 때 사용한다.
*
* === XML 파일 설정과 비교 ===
* 장점
* 1) 개발 속도 향상
*
* 2) 컴파일러나 IDE를 통한 타입 검증이 가능함.
* 3) 자동완성과 같은 IDE 지원 기능 최대한 이용
* 단점
* 1) 빈의 개수가 많아지면 한눈에 파악하기 힘들다.
* 2) XML처럼 상세한 메타정보 항목을 지정할 수 없음 (?)
* 3) 클래스당 한개 이상의 빈을 등록할 수 없다는 제한 ( 대부분 클래스당 하나 이상의 빈을 등록하는 경우가 없다? )
* => 빈 이름, 스코프, 지연된 생성 방법 같은 것은 Stereotype anntotation으로도 변경 가능
*
* === 무엇을 사용할까?===
*
* 초반 개발 => Bean Scanning
* 개발 마무리가 되면 => XML 형태로 하는 것도 좋은 전
*
* 단계마다 독립된 설정정보 두는 것은 필수
*
}


빈 클래스 자동인식에는 @Component 외에도 다른 애노테이션을 사용할 수 있다. 이는 계층별로 빈의 특성이나 종류를 나타내려는 목적이 있고, AOP의 적용 대상 그룹을 만들기 위해서다. 


  • @Repository : 데이터 액세스 계층의 DAO 또는 리포지토리 클래스에 사용된다. DataAccessException 자동변환과 같은 AOP의 적용 대상을 선정하기 위해 사용
  • @Service : 서비스 계층의 클래스에 사용
  • @Controller : MVC 컨트롤러에 사용. 스프링 웹 서블릿에 의해 웹 요청을 처리하는 컨트롤러 빈으로 선정

 IoC 컨테이너에 빈 등록하기 3 - 자바 코드에 의한 빈 등록 @Configuration 클래스, @Bean 메소드

스프링 컨테이너는 의존관계 내에 있지 않은 제 3의 오브젝트를 만들어 의존관계를 가진 오브젝트를 생성하고 메소드 주입을 통해 의존관계를 만들어준다. 이처럼 오브젝트의 생성과 의존관계 주입을 담당하는 오브젝트를 오브젝트 팩토리라고 부른다. 오브젝트 팩토리의 기능을 일반화해서 컨테이너로 만든 것이 스프링 컨테이너이다.

아래와 같이 빈을 등록할 수 있다. 

@Configuration
public class AnnotatedHelloConfig {

@Bean
public AnnotatedHello annotatedHello2() { // 메소드 이름이 등록되는 빈의 이름이 된다.
return new AnnotatedHello(); // 컨테이너는 이 리턴 오브젝트를 빈으로 활용
}
}

아래는 @Component가 붙은 AnnotationHello 클래스가 빈 스캐너를 통해 등록되는 것을 테스트와 @Configuration에 대한 테스트 코드이다. 

@Test
public void config_bean_scanning() {

ApplicationContext ctx = new AnnotationConfigApplicationContext(
"com.ioccontainer.ioccontainer.stereotypeAnnotation");

AnnotatedHello hello = ctx.getBean("annotatedHello2", AnnotatedHello.class);

assertThat(hello, is(notNullValue()));

// AnnotatedHelloConfig도 하나의 빈으로 등록이 됨
AnnotatedHelloConfig config = ctx.getBean("annotatedHelloConfig", AnnotatedHelloConfig.class);
assertThat(config, is(notNullValue()));

/**
* config를 통해 annotatedHello2()를 직접 호출할 수도 있다.
* 아래의 결과는 어떻게 될까?
*/
assertThat(config.annotatedHello2(), is(hello));

/**
* new AnnotatedHello()가 각각 호출되었는데 왜 같다고 하는걸까?
* 테스트 결과를 통해, 평범한 자바 코드처럼 동작하지 않는다는 사실을 알 수 있다.
*
* === 왜?? ===
* @Bean이 붙은 annotatedHello2() 메소드에서 얻을 수 있는 정보는 1.빈 클래스, 2.빈의 이름 뿐이다.
* 다른 메타정보는 모두 디폴트 값이다. 디플트 스코프는 싱글톤이다.
* => getBean(), annotatedHello() 등 어떻게 호출되더라도 한개의 오브젝트만 생성되는 것이다.
*/
}


IoC 컨테이너 : 빈 의존관계 설정 방법 ( 빈 사용하기 )

DI 할 대상을 선정하는 방법에는 두가지가 있다.
  1. DI 할 빈의 아이디를 직접 지정하는 방식
  2. 타입 비교를 통해서 호환되는 타입의 빈을 DI 후보로 삼는 방식 ( 자동와이어링 - autowiring )
빈 의존관계 설정 방법은 4가지가 있다.
  1. XML <bean> 태그
  2. 스키마를 가진 전용 태그
  3. 애노테이션
  4. 자바 코드

2. 빈 의존관계 설정 방법 - 자바 코드

@Autowired는 Spring 2.5부터 지원하는 의존성 주입(Dependency Injection) 방법이다. 의존성 주입에 대해 알아보기 전에 먼저 Autowired의 사용법에 대해 먼저 살펴보자.

@Autowired를 이용한 의존성 주입 ( 생성자 주입 vs Setter 함수 주입 )

public class Store {

private Hello hello;

/**
* 생성자를 통한 의존성 주입
* 장점 : 생성자를 통해 객체주입을 함으로 실수로 객체주입 안하는 경우 발생 X
* 단점 : 생성자 매게변수가 많아지면 어떤 것을 주입하는지 보기
* 힘듦
*/
@Autowired
public Store(Hello hello) {
this.hello = hello;
}

/**
* Setter 함수를 이용한 의존성 주입
* 장점 : 어떤 객체를 주입하는지 확실함
* 단점 : 실수로 객체주입을 안할 수도 있음.
*/
@Autowired
public void setHello(Hello hello) {
this.hello = hello;
}


/**
* 위의 장점을 수용한 일반 메소드를 사용하는 DI 방법
* 생성자 주입과 달리 오브젝트 생성 후에 차례로 호출이 가능하므로 여러개를 만들어도 됨
* => 코드도 상대적으로 깔끔해짐
*
* @param hello
*/
@Autowired
public void config(Hello hello) {
this.hello = hello;
}

@Autowired가 설정된 프로퍼티를 반드시 설정한 필요가 없는 경우 처리 방법 3가지 

/**
* Spring 5 부터 지원하는 방법
*/
@Autowired
public void setHello_optional(Optional<Hello> hello) {
if (hello.isPresent()) {
System.out.println("null인 경우 처리");
} else {
this.hello = hello.get();
}
}

/**
* hello Bean 객체가 없는 경우인데도 hello는 null값이 아니라
* 아래 코드 동작하지 않음
*/
@Autowired(required = false)
public void setHello_required(Hello hello) {
if (hello == null) {
System.out.println("null인 경우 처리");
} else {
this.hello = hello;
}
}

@Autowired
public void setHello_nullable(@Nullable Hello hello) {
if (hello == null) {
System.out.println("null인 경우 처리");
} else {
this.hello = hello;
}
}


중복된 타입의 빈이 존재하는 경우 - 컬렉션

@Autowired를 이용하면 같은 타입의 빈이 하나 이상 존재할 때 그 빈들은 모두 DI 받도록 할 수 있다.


@Autowired
Collection<Printer> printers;

@Autowired
Map<String,Printer> printerMap; // Key : Bean Id

위와 같은 코드를 단지 같은 타입의 빈이 여러개 등록되는 경우에 충돌을 피하려는 목적으로 사용해서는 안된다. 의도적으로 타입이 같은 여러개의 빈을 등록하고 이를 모두 참조하거나 그중에서 선별적으로 필요한 빈을 찾을 때 사용하는 것이 좋다.


@Qualifier

타입만으로 원하는 빈을 지정하기 어려운 경우 아래와 같이 지정할 수 있다.
@Autowired
@Qualifier("oracleDatasource")
DataSource dataSource;