강좌/Spring 3.0

004. 스프링을 통한 의존성 주입 - @Autowired 를 통한 속성 주입

여름나라겨울이야기 2012. 11. 27. 17:55
728x90

000 ~ 005 STS 프로젝트 파일:ExpertSpring30.zip

의사 코드

운전자가 종합 쇼핑몰에서 자동차를 구매 요청한다.
종합 쇼핑몰은 자동차를 생산한다.
종합 쇼핑몰은 타이어를 생산한다.
종합 쇼핑몰은 자동차에 타이어를 장착한다.
종합 쇼핑몰은 운전자에게 자동차를 전달한다.

흠흠 의사 코드를 보죠.  이전 강좌와 동일합니다.

여기서 질문입니다.  여러분은 프로그래머의 3대 스킬을 아시나요?

1. C&P : Copy & Paste / 복사 & 붙이기
2. D&C : Divide & Conquer / 분할 & 정복
3. C&i : Creative idleness / 창조적 게으름

특히 3번째 스킬은 진정한 고수들의 방법론이죠.  일례로 더 게으르고 싶어진 그루들께서 while 문으로 충분한 걸 for / for each / do ~ while 등등의 반복문을 더 만들어 더 빠르게 일을 마칠 수 있는 방법들을 만드셨답니다.  가장 대표적인 창조적 게으름의 산물은 i++ 이 아닐까 싶습니다.
자 그럼 Spring 을 만든 분들은 어떤 게으름을 위한 창조력을 발휘하셨을까요?

Car 라고 하는 클래스에 tire 라고 하는 속성을 만든다고 하죠.  그럼 대부분 아래와 같은 코드를 만들게 됩니다.

Tire tire;

public Tire getTire() {
    return tire;
}

public void setTire(Tire tire) {
    this.tire = tire;
}

흠흠... 그럼 여기서 창조적 게으름을 발휘해 보죠.  get/set 메서드를 꼭 만들어야 할까요?  Eclipse 의 Source > Generate Getters and Setters...메뉴를 사용하면 get/set 메서드를 아주 쉽게 만들어 주지만 Spring 개발팀은 더 창조적으로 게을러지기로 한 것 같습니다.

위의 코드를 아래 코드처럼 바꾸어 버렸습니다.

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

@Autowired
Tire tire;

import 문장 하나와 @Autowired 애노테이션을 사용하면 get/set 접근 메서드를 더 이상 만들지 않아도 종합쇼핑몰 SpringFramework 이 설정 파일을 통해서 알아서 get/set 접근 메서드 대신 일을 해주도록 말이죠.

변경된 Spring 설정파일(여기서는 expert.xml)입니다.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:context="http://www.springframework.org/schema/context"
 xsi:schemaLocation="http://www.springframework.org/schema/beans
  http://www.springframework.org/schema/beans/spring-beans.xsd
  http://www.springframework.org/schema/context
  
http://www.springframework.org/schema/context/spring-context-3.1.xsd">

 <context:annotation-config />

 <bean id="tire" class="expert004.KoreaTire"></bean>

 <bean id="americaTire" class="expert004.AmericaTire"></bean>

 <bean id="car" class="expert004.Car"></bean>

</beans>

빨간색 부분이 기존 Spring 설정 파일 대비 추가되는 부분입니다.  오오 저 많은 걸 외워서 타이핑.. 에이 절대 아니죠.  expert.xml 의 context menu 에서 Open With > Spring Config Editor 를 선택합니다.  편집기가 열리면 하단의 Namespaces 탭을 클릭해 줍니다.  그러면 여러 개의 체크 박스가 아래 그림처럼 보이실 겁니다.  그 중에 context 를 체크해 주시면 위의 빨간줄 중에 위쪽의 3 줄이 자동으로 삽입됩니다.  그 후에 Source 탭으로 돌아오셔서 <context:annotaion-config /> 만 입력하고 저장해 주시면 됩니다. 

@Autowird 의 의미는 이해가 되시죠.  Spring 설정 파일을 보고 자동으로 속성의 set 메서드에 해당하는 역할을 해주겠다는 의미이죠.  헛 그러고 보니 xml 파일에서 뭔가가 사라졌네요.

기존 소스

<bean id="car" class="expert003.Car">
    <property name="tire" ref="koreaTire"></property>
</bean>

새로은 소스

 <bean id="car" class="expert004.Car"></bean>

어디갔나요?  네 @Autowird 를 통해서 자동으로 car 의 property 를 찾아줄 수 있음으로 생략이 가능해 졌습니다.

소스의 다른 부분은 이전 강좌와 달라진 점이 없습니다.
그래도 복습 차원에서 한 번 쭉 살펴볼까요.

Tire.java - 전혀 변한 게 없습니다.(물론 기존 코드 보존을 위해 새 패키지인 expert004 를 만들고 그 안에 프로그래머 스킬을 이용해 C&P 했다는 거 외에는..)

package expert004;

public interface Tire {
String getBrand();
}

KoreaTire.java / AmericaTire.java - 역시 변화 없습니다.

package expert004;

public class KoreaTire implements Tire {
public String getBrand() {
return "코리아 타이어";
}
}

package expert004;

public class AmericaTire implements Tire {
public String getBrand() {
return "미쿡 타이어";
}
}

Car.java - @Autorwird 사용을 위해 변경 되었습니다.

package expert004;

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

public class Car {
@Autowired
Tire tire;

public String getTireBrand() {
return "장착된 타이어: " + tire.getBrand();
}
}

Driver.java - 역시 변경된 부분이 없습니다.  음 필요하시면 Car 도 속성으로 뽑아내신 후 @Autowird 하게 바꾸셔도 됩니다.  여기선 @Autowird 만 살펴 볼 것임으로 Car.java 외의 다른 소스 변경은 최소화 했습니다.

package expert004;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

public class Driver {
 public static void main(String[] args) {
  ApplicationContext context = new FileSystemXmlApplicationContext("/src/main/java/expert004/expert.xml");
  Car car = (Car)context.getBean("car");

  System.out.println(car.getTireBrand());
 }
}

expert.xml - 이건 위에 있습니다.  스크롤의 수고 정도는 해주셔야 기억에 오래 남습니다. ^^;

그럼 여기서도 선 긋기 놀이.. 이번에 선은 좀 억지스럽긴 하지만.. ㅡㅡ; 

이제 Dirver.java 를 실행해 보도록 하겠습니다.

Spring 이 내부적으로 준비하는 INFO 정보가 더 늘어났네요.

JUnit Test 는 이전 강좌와 동일합니다.  이전 강좌의 테스트 코드에서 @Autowird 를 설명하지 않고 넘어왔는데 이제는 이해가 되셨나요?

package expert004;

import static org.junit.Assert.assertEquals;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("expert.xml")
public class CarTest {
 @Autowired
 Car car; 

 @Test
 public void 자동차_장착_타이어브랜드_테스트() {
  assertEquals("코리아 타이어", car.tire.getBrand());
 }
}

여기서 부터는 약간의 번외 경기를 펼쳐 보도록 하겠습니다. 


번외 경기 1. AmericaTire 로 변경된 Driver.java 를 실행하려고 하면 어디를 고치면 될까요?
재컴파일할 필요 없이 expert.xml 에서 bean 의 id 속성만 변경해 주시면 됩니다.

<bean id="tire02" class="expert004.KoreaTire"></bean>
<bean id="tire" class="expert004.AmericaTire"></bean>

Driver.java 를 실행해 보시면 바뀐 결과를 확인하실 수 있습니다.

번외 경기 2. 위 번외에서 KoreaTire 부분을 완전 삭제하고 AmericaTire 에서 id 속성 부분을 삭제해 보도록 하겠습니다.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:context="http://www.springframework.org/schema/context"
 xsi:schemaLocation="http://www.springframework.org/schema/beans
  http://www.springframework.org/schema/beans/spring-beans.xsd
  http://www.springframework.org/schema/context
  http://www.springframework.org/schema/context/spring-context-3.1.xsd">

 <context:annotation-config />

 <bean class="expert004.AmericaTire"></bean>

 <bean id="car" class="expert004.Car"></bean>

</beans>

그리고 Driver.java 를 다시 실행해 보세요.  정상 구동되는 것을 확인하실 수 있을 겁니다.
헐 왜 정상 구동할까요?  구동이 안 돼야 할 것 같은데 구동되는 게 더 신기하지 않으세요?

자 기존에는 Car.java 에서 @Autowired 가 붙여진 tire 속성과 expert.xml 파일에서 bean 의 id 속성과 일치하는 것을 찾아 매핑 시킨 것 같은데 말이죠.  여기는 bean 의 id 속성이 없는데 어떻게 매핑 시켰을까요?

기존 설정은 아래와 같았습니다.

 Car.java

expert.xml 

 @Autowired
 Tire tire;

 <bean id="tire" class="expert004.AmericaTire"></bean>

그런데 아래와 같이 작성되어도 제대로 매핑이 됐다는 건데요.  어떤 마법이 숨어 있는 것일까요?

Car.java

expert.xml

@Autowired
Tire tire;

<bean class="expert004.AmericaTire"></bean>

고민 좀 해보시고 아래를 보시기 바랍니다.

Spring 의 마법은 아래 코드에 있습니다. 

 Car.java  @Autowired Tire tire;
 expert.xml  <bean class="expert004.AmericaTire"></bean>

 AmericaTire.java

 public class AmericaTire implements Tire

바로 interface 의 구현 여부 입니다.  Spring 의 마법은 type 기준 매핑이 먼저이고, 같은 type을 구현한 여러 개의 클래스가 있다면 그 때 bean id 로 구분해서 매핑하게 됩니다.

그럼 여기서 하나의 실험을 더해 보죠.  아래는 정상 구동할까요?

Car.java

expert.xml

@Autowired
Tire tire;

<bean id="usaTire" class="expert004.AmericaTire"></bean>

답은 안 알려드립니다.  직접 구동해 보세요. ^^; 

이것은 암호문 입니다. 읽는다면 당신은 능력자... 위와 같은 경우에도 정상 구동합니다.

그리고 아래와 같은 경우라면 어떨까요?

Car.java

expert.xml

@Autowired
Tire tire;

<bean class="expert004.KoreaTire"></bean>
<bean class="expert004.AmericaTire"></bean>

너무 어려워하지 마시고 상식선에서 생각해 보도록 하겠습니다.  Spring 을 설계하고 만든 분들은 외계인이 아닙니다.  인간의 상식으로 접근하면 됩니다.
둘 다 똑같은 타입(Tire.java)를 구현하고 있는데 id 로 구분할 수도 없다면 여러분이라면 어떻게 하시겠습니까?
먼저 선언된 KoreaTire 를 매핑 시켜준다 하시는 분 손 들어주세요.
나중에 선언된 AmericaTire 를 매핑 시켜준다 하시는 분 앞 발 들어주세요.
음 손 드신 분들 비율이 반반씩 갈리시는군요.  안 드신 분들도 있고...
의견이 분분해지죠.  네 Spring 설계자들도 의견이 분분했을 것이고 위와 같은 설정이라면 아마도 개발자가 뭔가 실수를 했을 가능성 높습니다.  그래서 Spring 의 마법이 이 경우에는 통하지 않습니다.  실행 에러를 토해냅니다.

Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No unique bean of type [expert004.Tire] is defined: expected single matching bean but found 2: [expert004.KoreaTire#0, expert004.AmericaTire#0]

unique: 독특한, 유일한
no: 개콘 신보라의 no~~~~

네 유일한 선택을 할 수 없다면 Spring 마저 포기한 설정이 되겠습니다.  실전에서 위와 같이 설정하실 일은 없겠죠.  그리고 개인적인 생각으로는 항상 bean id 를 작성하시라고 말씀 드리고 싶네요.  사실 @Autowired 를 쓰는 경우, 먼 훗날 소스를 다시 보게된다면 Car 의 tire 속성이 어떻게 매핑 되는지 소스를 열어보지 않으면 알 수가 없겠죠.  물론 툴의 도움을 받으면 조금 쉽게 찾아내겠지만요.
그래서 저는 xml 파일의 property 태그를 통해 직접 명시적으로 표기하는 것을 추천합니다(이전 강좌 참고 요망).  물론 이 생각을 완강히 주장하는 건 아니고 현재까지의 사견입니다.

아래 경우에 대한 실험은 여러분 각자의 몫으로 남기겠습니다.

Car.java

expert.xml

@Autowired
Tire tire;

<bean id="tire" class="expert004.KoreaTire"></bean>
<bean class="expert004.AmericaTire"></bean>

KoreaTire 로 잘 작동하는 걸 확인할 수 있습니다.

아래의 경우라면

Car.java

expert.xml

@Autowired
Tire tire;

<bean id="wheel" class="expert004.KoreaTire"></bean>

역시 정상 구동합니다.  Spring 은 id 기준 매핑보다 type 매핑이 우선순위가 있기 때문에 정상 구동합니다.

마지막으로 아래의 경우는 어떨까요?

 Door.java

 package expert004;

 public class Door {
 }

 Car.java  @Autowired Tire tire;

 expert.xml

 <bean class="expert004.KoreaTire"></bean>
 <bean id="tire" class="expert004.Door"></bean>

여기서 Spring 개발진이 생각한 상식은 어떤 것일까요?  직접 구동해 보시길 권합니다.
그리고 위의 경우 실행이 되던, 안 되던 실무에서 위와 같이 설정하셨다면 사수에게 끌려가서 소리 소문 없이 맞으실 수도 있습니다.

정답은 정상 구동한다! 입니다.  KoreaTire 가 잘 매핑 됩니다.
따라서 id 와 type 중에 type 구현이 우선순위가 있다는 것을 알 수 있습니다.

모든 힌트는 위에 그려놓은 순서도에 있습니다.

그럼 다음 강좌에서 뵈어요.  하나님의 자비와 은총이 이 글을 읽는 모든 분들과 함께 하시길...

반응형