스프링 DI
앞서 봤던 DaoFactory를 다시 보자.
class DaoFactory{
public CarDAO carDAO(){
CarDAO carDao = new CarDAO();
carDao.setDbConnection(dBConnection());
return carDao;
}
public DBConnection getConnection(){
return new MySqlDBConnection_real(); // 변경되는 부분
}
}
간단하다. CarDAO, DBConnection 오브젝트를 관리하고 있다. 서로 의존관계도 주입해주고 있다.
이러한 클래스를 IOC컨테이너라고 하는데, 보다시피 기능이 빈약하고 매번 만들자니 번거롭다. 편리하게도 스프링에서 이런 IOC 컨테이너를 제공해주는 것이 있다. 한번 사용해보자.
일단 몇가지 용어를 숙지해야 한다.
DaoFactory는 CarDAO와 DBConnection 이라는 2가지 오브젝트를 관리하고 있다.
관리하고 있다는 말은 생성, 관계설정, 사용 등을 제어해줬다는 의미이다.
DaoFactory에서 각 오브젝트를 생성하고, 관계 설정하고 돌려줬었다.
스프링이 제공하는 IOC컨테이너도 DaoFactory와 동일하게 이런 오브젝트들을 관리한다. 그리고 스프링에서는 이렇게 관리가 되는 오브젝트들을 빈이라고 부른다.
그리고 이러한 빈들을 관리하는 IOC컨테이너를 빈 팩토리라고 부른다. 그리고 이 빈 팩토리를 좀 더 확장한 어플리케이션 컨텍스트 라는 것이 있고, 대부분 이를 더 자주 사용한다. 코드를 보자.
설정파일 작성 및 사용
빈 팩토리나 어플리케이션 컨텍스트 위의 DaoFactory처럼 자바 코드로 구현하지 않고 설정 파일 이라는 것을 사용한다.
설정파일을 작성하는 방법은 여러가지가 있는데, 그 중 대표적인 XML을 사용해서 작성해보았다.
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemalocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="carDAO" class="spring.dao.CarDAO">
<property name="dbConnection" ref="dbConncetion"></property>
</bean>
<bean id="dbConnection" class="spring.dao.MySqlDBConnection_real"></bean>
</beans>
<beans>라는 태그는 해당 파일이 설정 파일이라는 것을 의미한다.
이후 보이는 기나긴 글씨들은 네임스페이스를 의미하고 <beans>,<bean> 등의 태그를 사용하기 위해 추가한 것이다.
복사해서 사용해도 되지만, STS를 사용하면 설정파일 생성할 때, Spring Bean Configuration File로 생성하면 네임스페이스들을 고를 수 있으니 사용해보길 바란다.
<bean>은 어플리케이션 컨텍스트에 의해 관리되는 오브젝트들을 말한다.
아까 스프링에서 관리되는 오브젝트는 빈이라고 했다.
DaoFactory와 매칭해보면 id 속성은 메서드 명이 되겠고, class 속성은 리턴해주는 오브젝트 객체가 된다.(패키지 포함)
<bean> 태그 하나만으로 DaoFactory에서 new를 통해 직접 오브젝트를 생성하는 과정이 대체된다.
그렇다면 property의 속성은 무엇일까?
propery의 속성은 의존 오브젝트와의 관계를 정의해주는 부분이다.
위의 setter 호출 부분이 property 태그로 대체된 것이다.
carDao.setDbConnection(dBConnection());
이 부분이다.
set 부분이 <property> 태그로 대체된 것이므로, 반드시 setXXX형태로 시작하는 setter 메서드여야 한다.
name 속성은 set을 제외한 메서드의 이름이며, ref는 주입하게 될 오브젝트의 id를 넣는 속성이다.
결국 <property> 태그를 통한 의존관계 주입을 해주는 과정이다.
ref 처럼 오브젝트를 넣는 속성도 있지만, 값을 주입하는 value 속성도 있다.
가령 CarDAO에 setCarColor(String color)와 같은 setter가 있었다면
<property name="carColor" value="red"/> 와 같이 값을 주입하는 것 또한 가능하다.
value 속성에 넣는 값은 기본적으로 String 형태지만, 주입하는 setter의 파라미터 타입에 따라 자동 형 변환 해준다.
만약 setNumber(int number)와 같은 setter라면,
<property name="number" value="3"/> 해도 들어 간다는 것이다.
그리고 일반적인 형태의 파라미터말고도 컬렉션 형태의 파라미터도 태그로 전향 가능하다.
컬렉션 형태의 값은 솟겅으로 불가능하므로 직접 태그로 사용해줘야 한다.
-<list>
setXXX(List<?> list)형태의 setter에 값 넣을때 사용한다.
<property name="list">
<list>
<value>123</value>
<ref bean="testClass"></ref>
</list>
</property>
들어가는 값은 파라미터에 지정한 제네릭 값에 맞춰 넣어주면 된다.
위에서 value태그와 ref를 동시에 넣은 것은 value도 가능하고 ref도 가능할 수 있다는 것을 보여주기 위함이다. 평소에는 setter 파라미터에 선언된 제네릭에 맞춰 넣어주어야 한다.
(제네릭을 생략하면 위와 같이 넣을 수도 있다. setXXX(List list) 처럼 말이다.)
-<map>
SetXXX(Map<?,?> map) 형태의 setter에 값 넣을 때 사용한다.
<property name="map">
<map>
<entry>
<key><value>1</value></key>
<value>1Key</value>
</entry>
<entry>
<key><value>2</value></key>
<ref bean="class2"></ref>
</entry>
</map>
</property>
map 태그 내 entry 내에 key와 값이 들어가는 형태이다.
이 또한 entry 태그 내 값은 지정한 제네릭에 맞춰 넣어주면 된다. 이것도 <list> 와 동일하게 지정한 제네릭대로 자동 형 변환되어 들어간다.
여기서 value와 ref를 같이 넣은 이유도 위와 동일하다.
이로써 설정파일의 작성은 끝났다.
그렇다면 IOC컨테이너인 어플리케이션 컨텍스트는 이를 사용해야 한다.
용어로만 얘기했던 어플리케이션 컨텍스트, 빈 팩토리는 사실 별게 아니고 그냥 스프링 제공 인터페이스이다.
org.springframework.beans.factory.BeanFactory;
org.springframework.context.ApplicationContext;
ApplicationContext는 BeanFactory를 상속한 인터페이스이다. 아까 기능의 확장이라고 이야기했다.
그냥 클라이언트가 저 인터페이스를 사용하여 설정파일에 있는 빈 들을 가져오기만 하면 바로 사용 가능한 것이다.
DaoFactory의 메서드를 호출해서 빈을 가져오는 과정과 별 다를 것이 없다.
public static void main(String[] args) throws Exception{
ApplicationContext context = new GenericXmlApplicationContext("applicationContext.xml");
CarDAO carDao = context.getBean("carDao",CarDAO.class);
Sout(carDao.selectSUVData());
Sout(carDao.selectSedanData());
Sout(carDao.selectCoupeData());
}
GenericXmlApplicationContext는 클래스패스를 이용하여 설정파일의 위치를 지정해주면 해당 설정파일을 읽어오는 ApplicationContext 구현체이다.
설정 파일을 읽어온 뒤는 간단하다. ApplicationContext의 getBean 메서드를 사용해서 빈을 가져오면 된다.
getBean의 첫번째 인자로는 설정파일에 명시된 id를 주면되고, 두번째 인자로는 리턴타입을 주면 된다.
(원래 getBean의 리턴타입은 Object였으나, 자바 5이상의 제네릭을 사용하게 되는 두번째 인자를 넣어줌으로써 지저분한 캐스팅 코드가 없어졌다.)
작동하는 기능만 보면 DaoFactory와 별 차이 없지만, 그것은 단면적인 부분만 봐서 그런것이고, 스프링의 어플리케이션 컨텍스트는 DaoFactory와는 비교도 안되게 많은 장점을 제공해준다.
ApplicationContext의 장점
-
사용자가 굳이 컨테이너 클래스를 알 필요 없이 ApplicationContext 객체만 사용하면 되는 점이다.
위에서 DaoFactory가 있었고, 만약 서비스 관련 오브젝트들을 관리하려면 또 ServiceFactory와 같은 컨테이너가 필요했을 것이다. 하지만 어플리케이션컨텍스트를 사용함으로써 사용자는 굳이 컨테이너 클래스를 알 필요가 없어지게 되고, 각종 빈 들도 설정파일 하나에 관리할 수 있게되어 일관된 방식으로 오브젝트를 얻어올 수 있게 된다. -
빈을 검색하는 다양한 방법을 제공한다는 점이다. 현재는 getBean 메서드로 빈의 id를 검색하여 가져왔지만 타입으로도 가져올 수 있고, 어노테이션으로 가져올 수 있다.
-
어플리케이션 컨텍스트는 종합 IOC 서비스를 제공한다. 단순히 오브젝트 의존관계를 설정하고 그를 반환해주는 것 이상의 기능을 가진다.(싱글톤, 후처리 등등)
지금까지 스프링이 제공하는 IOC컨테이너에 대해 알아보았다.
개인적으로 전략패턴 + IOC + DI 의 조합은 굉장히 멋지다고 생각한다.
스프링 제공 IOC 컨테이너라고 표현했지만, IOC는 여러 분야에서 폭넓게 사용되는 용어라 스프링의 특징을 확실히 집어주진 못한다.
하지만 IOC 컨테이너의 기능에서 스프링의 대표적 기술인 DI를 사용하는 순간 달라진다.
스프링이 여타 프레임워크와 차별화해서 제공해주는 기술들은 DI라는 용어를 사용할 때 더 분명하게 드러난다.
그리하여 스프링을 DI컨테이너라고 부르는 것이다.