본문 바로가기

Development/Spring

빈(bean) 의존관계 설정.

빈 등록방법과 마찬가지로 빈 사이의 의존관계를 설정하는 방법에도 여러가지가 있다.


선정방법에 따른 분류는 아래와 같다.

1. 명시적으로 구체적인 빈을 지정.

2. 일정한 규칙에따라 자동으로 지정(Autowiring)


메타정보 작성방법에 따른 분류를 하면 아래와 같다.

1. XML <bean>태그 / 2. 전용태그 / 3. 애노테이션 / 4. 자바코드에 의한 직접적인 DI

이 4가지 방법이, 앞서 말한 선정방법에 따른 분류방식으로 각각 구분할 수 있기 때문에


총 8가지의 빈 의존관계 주입방식이 있다고 보면된다. (빈 등록방식과 의존관계 주입방식이 항상 같을 필요는  없다)

넓은 의미에서 DI는, 빈 외에도 단순 오브젝트나 값을 주입하는 것도 포함된다.


지금부터 DI 방식에 대해 알아보자~~~


1. XML : <property> , <constructor-arg>


<bean>을 이용해 빈을 등록했으면, 프로퍼티와, 생성자 2가지를 이용하여 DI를 지정할 수 있다.


<property> 수정자 주입

<bean id="hello" class="pojo.Hello">
<property name="name" value="Spring"/> 빈이 아닌 단순 오브젝트나 값을 입력할때 value를 사용한다.
<property name="printer" ref="printer"/> property를 이용해서 DI를 한다.
</bean>

<bean id="printer" class="pojo.StringPrinter"/>


<constructor-arg> 생성자 주입

public class Hello {
private String name;
private Printer printer;

public Hello(String name, Printer printer) {
this.name = name;
this.printer = printer;
}
....

<bean id="hello" class="pojo.Hello">
<constructor-arg index="0" value="Spring"/>
<constructor-arg index="1" ref="printer"/> 생성자 파라미터를 이용해서 DI를 한다. index 대신 name="name"처럼 파라미터이름도 가능하다.
</bean>

<bean id="printer" class="pojo.StringPrinter"/>


2. XML: 자동와이어링


자동와이어링은 명시적으로 프로퍼티나 생성자 파라미터를 지정하지 않고, 정해진 규칙을 이용해 자동으로 DI 설정을 컨테이너가 추가하도록 한다.

그 규칙에 따라 "이름"과 "타입"을 이용한 자동와이어링 방식이 있다.


프로퍼티의 이름과 프로퍼티에 DI할 이름이 비슷하거나 같은 경우가 많다.(보통 빈 이름은 클래스 이름이나 구현한 인터페이스의 이름을 따름)

또, 프로퍼티 이름도 프로퍼티 타입의 이름을 사용함. (1번 방식의 xml 예에서 "printer" 처럼 ! )


이름을 이용한 자동와이어링 방식은 이런 관례를 이용한다.

<bean id="hello" class="pojo.Hello" autowire="byName"> 이름을 이용한 자동와이어링 옵션 추가
<property name="name" value="Spring"/>
</bean>

<bean id="printer" class="pojo.StringPrinter"/>

이와 같이 autowire="byName" 옵션을 주면, 

Hello 클래스의 프로퍼티 이름과 동일한 빈을 찾아서 자동으로 프로퍼티로 등록해 준다. 


Hello 클래스에는 setPrinter() 메소드가 있고, 아래 printer라는 빈이 정의 되어 있으므로, printer 프로퍼티는 printer 빈을 DI 한다.

루트 태그인 <beans>에 default-autowire="byName" 을 통해 하위 모든 빈에 적용시킬 수 있다.


타입을 이용한 자동와이어링 방식은 autowire="byType"옵션으로 사용한다.

<bean id="hello" class="pojo.Hello" autowire="byType">
<property name="name" value="Spring"/>
</bean>

<bean id="myPrinter" class="pojo.StringPrinter"/>


위와 같이, hello는 setPrinter(Printer printer) 처럼 Printer 타입의 파라미터를 받는 수정자 메소드가 있고, myPrinter는 Printer 인터페이스를 구현한 빈이다. 

byType을 통하여, 같은 인터페이스의 빈을 DI 한다.

그러나 이 방식은, 동일한 인터페이스 구현체 빈이 여러개가 존재할 경우 사용하기 어렵고, 내부적으로 이름에 의한 자동와이어링 보다 속도가 느리다.


3. 애노테이션: @Resource


수정자 메소드에 @Resource애노테이션을 사용하여 DI를 할 수 있다.

@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Hello {
private String name;
private Printer printer;

@Resource(name = "printer") //XML의 <property name="printer" .... /> 와 대응
public void setPrinter(Printer printer) {
this.printer = printer;
}

public String sayHello() {
return "Hello " + name;
}

public void print() {
this.printer.print(sayHello());
}

}

애노테이션으로 의존관계를 설정하기 위해서는, 아래 세가지중 하나를 적용해야한다.

XML : <context:annotation-config/> 빈 후처리기를 등록해주는 전용 태그

XML : <context:component-scan/> 컴포넌트 스캔을 위한 태그인데, 위의 빈후처리기 등록시 만들어 지는 빈을 함께 등록한다.

JAVA 컨텍스트 : AnnotationConfigApplicationContext, AanotationConfigWebApplicationContext 빈 후처리기를 내장한 애플리케이션 컨텍스트 



또, 필드 자체에 이 애노테이션을 적용할 수 있다.

@Setter
@NoArgsConstructor
@AllArgsConstructor
@Component
public class Hello {
private String name;

@Resource //(name="printer")를 생략해도 좋다
private Printer printer;

public String sayHello() {
return "Hello " + name;
}

public void print() {
this.printer.print(sayHello());
}

}

XML의 이름을 이용한 자동와이어링과 @Resource와의 차이 -> XML 자동와이어링은 주입할 후보 빈이 없을 경우 무시하지만, @Resource는 예외를 발생시킨다.

또한 @Resource는 이름을 이용해서 빈을 먼저 찾는다, 그러나 name 엘리먼트를 지정하지 않고, 디폴트 이름으로는 찾을수 없다면 타입으로 한번 더 찾는다.


4. 애노테이션: @Autowired / @Inject


이 두가지 애노테이션은 기본적으로 타입에 의한 자동와이어링 방식이며, 의미나 사용법은 거의 동일하다.

@Autowired : 스프링2.5부터 적용된 스프링 전용 애노테이션

@Inject : JavaEE 6의 표준 스펙에 정의 되어 있는 것, 스프링 외에도 JavaEE 6 스펙을 따르는 다른 프레임워크에서도 동일한 의미로 사용됨.


@Setter
@NoArgsConstructor
@AllArgsConstructor
@Component
public class Hello {
private String name;

@Autowired //Printer 타입으로 등록된 빈을 찾아 DI 한다. 마찬가지로 수정자 메소드에도 적용가능하다 setPrinter(Printer printer);
private Printer printer;

public String sayHello() {
return "Hello " + name;
}

public void print() {
this.printer.print(sayHello());
}

}

- 생성자에도 @autowired 를 적용할 수 있는데, 해당 생성자의 모든 파라미터에 타입에 의한 자동와이어링이 적용 된다. 하나의 생성자에만 적용 가능


또한, 

@Autowired
public void config(Printer printer) {
this.printer = printer;
}

와 같이 일반 메소드에서 적용이 가능하다. 파라미터를 가진 메소드를 만들고 이 메소드를 통해 DI 한다.


@Autowired는 타입으로 대상을 찾기 때문에, 같은 타입의 빈이 2개 이상 존재할 수 있다. 이때는 여러개를 DI할 수 있다.

@Component
public class Hello {
private String name;

@Autowired
private Printer[] printer; //컬렉션, 맵 으로도 받을 수 있다. 맵은 <beanId, bean>을 키밸류로 갖는다

public String sayHello() {
return "Hello " + name;
}

public void print() {

}

}

2개 이상의 빈이 존재하지만, 그 중 특정 하나에 대해 DI를 하고 싶다면 @Qualifier 를 이용한다.


@Component
@Qualifier("stringPrinter")//식별용 한정자를 정해준다.(XML로 등록할때는 <qualifier value="stringPrinter" /> 태그를 이용
public class StringPrinter implements Printer { ...

@Setter
@NoArgsConstructor
@AllArgsConstructor
@Component
public class Hello {
private String name;

@Autowired //(required = false) 를 지정하여, 빈을 못찾아도 예외처리를 하지 않고 그냥 진행하게 할 수 있다.

@Qualifier("stringPrinter") // Printer 타입 빈들 중, 해당 한정자의 빈을 주입한다. 못찾으면, 이름과 한정자를 비교해본다.
private Printer printer;

public String sayHello() {
return "Hello " + name;
}

public void print() {
this.printer.print(sayHello());
}
}


@Inject : @autowired와 동작방식이 거의 똑같음. 다만 required 옵션을 지정할 수 없음. (Java EE 6표준인 JSR-330)에 @Inject, @Qualifier 가 있다.

@Setter
@NoArgsConstructor
@AllArgsConstructor
@Component
public class Hello {
private String name;

@Inject
private Printer printer;

public String sayHello() {
return "Hello " + name;
}

public void print() {
this.printer.print(sayHello());
}
}