Spring(1) - Ioc와 DI컨테이너 작성
이번에는 직접 Spring프로젝트를 구현해볼려고 한다.
Spring프로젝트는 다른 프레임워크와 다르게 무슨 파일이 어떻게 작동하는지에 대한 원리를 이해하기도 많이 어렵다.
부족한 부분이 있을 수도 있으면 댓글을 달아주길 바란다.
첫 번째로 Ioc(Inversion of control)에 대한 개념을 설명하겠다.
해석을 하면 "제어의 역전"이다. 이 뜻은 무슨 뜻이냐면 원래는 개발자가 주도로 객체를 생성하고 해야하는데 그것을
프레임워크인 "Spring"이 담당하고 있다라고 이해하면 된다. 자세히는 Container부분이 담당한다.
그러면 IoC컨테이너에 대해서 알아보자.
IoC컨테이너는 주로 객체 생성 및 Bean객체 라이프 사이클을 관리한다. Bean객체란 IoC컨테이너에 의해 생성되고 관리되는 객체를 일컫는 말이다.
IoC컨테이너에서 사용하는 핵심적인 Class 2가지가 있는데 ApplicationContext, BeanFactory이다.
1. BeanFactory
자바의 팩토리 디자인 패턴을 구현한 것. Bean을 생성 및 분배. getBean()이용.
2. ApplicationContext
BeanFactory를 상속 받은 인터페이스. BeanFactory보다 훨씬 많은 기능을 가짐.
AOP, 메세지처리, 이벤트 처리 등의 기능을 제공.
@ContextConfiguration(locations="classpath:config/beans.xml")
........
ApplicationContext context;
Hello hello = context.getBean("hello");
위의 예시는 beans.xml파일에 있는 빈 객체들을 생성하고 그것을 getBean을 통해서 빈 들을 가져와서 활용한다.
결과적으로 AppicationContext를 사용하면 그 곳에 Bean객체들이 다 만들어진다는 뜻이다.
그리고 IoC컨테이너를 구현하기 위한 방법으로는 Dependency Lookup, Dependency Injection이 있다.
Dependency Lookup은 컨테이너가 제공하는 API를 이용해 Bean을 찾는 것이다. 특성은 종속성이 높다.
Dependency Injection은 컨테이너가 빈 설정을 바탕으로 자동으로 연결해준다. 종속성이 낮아진다.
DI같은 경우는 XML파일 및 Annotation을 이용하여 Bean을 등록해준다.
위의 Setter Injection, Constructor Injection을 살펴보자.
1. Setter Injection : Property태그를 이용.
<bean id="hello" class="myspring.di.xml.Hello">
<property name="name" value="Spring"/>
<property name="printer" ref="printer"/>
</bean>
위와 같이 value와 ref로 값을 지정해준다. value는 단순 값을 지정. ref는 객체 주입 시 사용한다.
2. Constructor Injection : Constructor-arg태그를 이용.
<bean id="hello2" class="myspring.di.xml.Hello">
<constructor-arg index="0" value="${myname}"></constructor-arg>
<constructor-arg index="1" ref="${myprinter}"></constructor-arg>
</bean>
Constructor은 주로 많은 인자 수를 받을 때 이용한다. index로 value,ref를 제어한다.
위의 개념들을 익히고 이제 실제로 DI컨테이너를 구현해보자.
순서는 PoJo Class작성 -> XML 설정 -> DI 클래스 작성이다 .
1. PoJo(Plain Java Old Project)만들기
진짜 용어들이 다양한 것 같다. PoJo란 Java Beans를 의미한다. 그 중에서도 "순수한 자바 객체"를 의미한다.
즉, 어떠한 인터페이스에도 상속받지 않는 것. 예시를 살펴보자.
public class Hello {
private String name;
private Printer printer;
public Hello(String name, Printer printer) {
super();
this.name = name;
this.printer = printer;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Printer getPrinter() {
return printer;
}
public void setPrinter(Printer printer) {
this.printer = printer;
}
}
위와 같이 set,get method가 분명하게 있어야 한다.
이렇게 PoJo 클래스를 생성하였다. 이제 XML파일을 설정하는 방법을 알아보자.
2. 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-3.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<context:property-placeholder location="classpath:config/value.properties"/>
<bean id="hello2" class="myspring.di.xml.Hello">
<constructor-arg index="0" value="${myname}"></constructor-arg>
<constructor-arg index="1" ref="${myprinter}"></constructor-arg>
<property name="names">
<list>
<value>${value1}</value>
<value>${value2}</value>
<value>${value3}</value>
</list>
</property>
</bean>
<bean id="hello" class="myspring.di.xml.Hello">
<!-- setName(message) -->
<property name="name" value="Spring"/>
<!-- setPrinter(Printer) -->
<property name="printer" ref="printer"/>
</bean>
<bean id="printer" class="myspring.di.xml.StringPrinter"/>
<bean id="consolePrinter" class="myspring.di.xml.ConsolePrint"/>
</beans>
위의 코드는 XML파일에 Beans을 등록한 것이다. 하나하나 살펴보자.
일단 Beans밖에 있는 코드들은 파일을 생성할 경우 자동으로 생성되기 때문에 스킵하겠다.
첫 번째 <context:property-placeholder>태그이다. 이것의 역할은 Beans들의 Properties를 외부에서 따로 관리해준다.
왜냐하면 자주 바뀔 수 있는 데이터들이 있기 때문이다. 첫 번째 bean태그를 보면 "${}"표시가 있다.
이것은 value.properties파일에서 가져온 값들로 대체된다.
두 번째로는 bean에 대한 것을 살펴보자. 위의 개념 중 setter injection과 constructor injection을 둘 다 구현해봤다.
그리고 list태그는 따로 다음에 설명하겠다. 간단하게 컬렉션 타입으로 값들을 모아놓은 변수이다.
이렇게 Beans를 설정했다. 그리고 최종적으로 Beans을 생성해 줄 수 있는 BeanFactory를 만들어보자.
3. DI/IoC컨테이너 생성
컨테이너를 생성하기 위한 두 가지 방법을 설명하겠다.
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:config/beans.xml")
public class HelloBeanSpring {
@Autowired
ApplicationContext context;
}
첫 번째는 어노테이션을 이용하여 클래스에서 직접적으로 Beans을 얻어오는 것이다.
위와 같이 RunWith와 ContextConfiguration Annotation을 사용한다.
그리고 Autowired로 의존성 주입을 한다.
public static void main(String[] args) {
ApplicationContext context = new GenericXmlApplicationContext("config/beans.xml");
Hello hello = (Hello) context.getBean("hello");
System.out.println(hello.sayHello());
}
두 번째로는 GenericXmlApplicationContext로 Beans이 설정되어있는 Xml파일을 읽어 Beans 생성하는 것이다.
두 번째 방법은 첫 번째보다 불편하고 코드가 길어지기 때문에 첫 번째 방법을 사용하겠다.