DI란 무엇인가?
DI란 말 그대로 Dependency Injection으로 의존성을 주입시킨다는 뜻이다.
의존성 주입을 설명할 가장 간단한 예를 들 수 있다. 아래와 같이 객체에서 new 연산자를 사용하여 포함시킬 수 있다.
//A객체
B b = new B();
C c = new C();
하지만 스프링에서 객체를 포함시키는 방법은 코드 내부에서가 아닌 외부에서 객체를 얻어오는 방식이다.
AbstractApplicationContext ctx = new GenericXmlApplicationContext("classpath:applicationCTX.xml");
PersonService personService = ctx.getBean("personService", PersonService.class);
추후 설명하겠지만 외부 파일(xml)에서 생성된 객체를 가져오는 방식이다.
DI 방식의 이점은 무엇인가?
간단하게 말하겠다. 코드 상에서 변경이 아니므로 쉽게 설정파일만을 수정하여 부품(각 기능을 담당하는 클래스)들을 생성 및 조립이 가능하다.
DI 적용 방법
우선 세가지의 방식이 있다.
어노테이션 이용한 방식
Bean 파일을 이용한 방식
JavaConfig 파일을 이용한 방식
가장 많이 사용되는 2번 방식만을 이번 포스팅에서 다루려고 한다. 각 방법마다 장점과 단점이 있으니 추후 포스팅할 기회가 있다면 하겠다.
먼저 BeanFactory를 알자
DI 컨테이너의 핵심은 BeanFactory이다. BeanFactory는 실행 시 건네지는 Bean 정의 파일(기본적으로 applicationCTX.xml로 명명)을 바탕으로 인스턴스를 생성하고 인스턴스의 인젝션을 처리한다.
DI 컨테이너로부터 인스턴스를 얻는 다는 말은 BeanFactory 로부터 인스턴스를 얻는 다는 것을 의미한다.
전체 구조
예제를 위한 전체 구조는 아래 사진과 같다.
중간에 구현클래스와 인터페이스를 구분하였는데 DI를 이용할 때는 원칙적으로 인터페이스에 의존하고 구현 클래스에서는 의존하면 안된다. 이를 인터페이스 기반의 컴포넌트화라고 하는데 PersonService와 PersonDao는 인터페이스화 시키고 그 구현 클래스들은 인터페이스 클래스 이름에 Impl를 덧붙였다.
PersonDaoImpl은 PersonServiceImpl을 인젝션함을 알 수 있다. 이는 내부적으로 PersonServiceImpl가 PersonDao를 포함하고 있고, 이를 bean 파일에 정의함으로써 설정을 해야 한다.(곧 배운다.)
Bean 파일 정의
src/main/resources에서 오른쪽 마우스를 클릭하여 New -> Other 에서 Spring Bean Configuration File을 클릭하여 생성한다.(applicationCTX.xml로 생성)
bean 태그에는 여러가지가 있지만 이번 포스팅에서 사용할 주요 태그는 아래와 같다.
속성 | 의미 |
---|
id | 오브젝트를 유일하게 구분하는 값 |
name | 오브젝트명을 의미 |
class | id의 실체로 패키지명 + 클래스명 으로 구성 |
autowire | no, byName, byType, constructor 값을 통해 인젝션 방법 설정 |
personService 인스턴스는 내부적으로 personDao를 포함하는데 autowire 의 byType이라는 속성을 통해 프로퍼티형과 일치하는 Bean이 인젝션된다.
// applicationCTX.xml
<bean id="personDao" class="com.bbo.example.PersonDaoImpl"/>
<bean id="personService" class="com.bbo.example.PersonServiceImpl"
autowire="byType"/>
단, 인젝션을 위해서는 personService 내에서 personDao에 대한 setter 함수가 꼭!! 필요하다.
// PersonServiceImpl.java
private PersonDao personDao;
public void setPersonDao(PersonDao personDao)
{
this.personDao = personDao;
}
생성된 객체를 Bean 으로부터 가져오기
bean 파일에 대한 path를 통해 생성된 bean 파일에서 생성한 id를 통해 personService를 가져온다.
// PersonSampleRun.java
AbstractApplicationContext ctx = new GenericXmlApplicationContext("classpath:applicationCTX.xml");
PersonService personService = ctx.getBean("personService", PersonService.class);
personService는 DI 컨테이너에 의해 만들어진 PersonServiceImpl 클래스이기에 멤버함수를 호출하여 사용할 수 있다.
// PersonSampleRun.java
Person person = new Person("bbo", 22);
personService.addPerson(person);
System.out.println(person.getName());
Person loaded_person = personService.findByPerson("bbo");
System.out.println(loaded_person.getName());
전체 소스
// Person.java
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
// PersonDao.java
public interface PersonDao {
public void addPerson(Person person);
public Person findByPerson(String name);
}
// PersonDaoImpl.java
package com.bbo.example;
import java.util.HashMap;
import java.util.Map;
public class PersonDaoImpl implements PersonDao{
private Map<String, Person> storage = new HashMap<String, Person>();
@Override
public void addPerson(Person person) {
// TODO Auto-generated method stub
storage.put(person.getName(), person);
}
@Override
public Person findByPerson(String name) {
// TODO Auto-generated method stub
return storage.get(name);
}
}
// PersonService.java
public interface PersonService {
void addPerson(Person person);
Person findByPerson(String name);
}
// PersonServiceImpl.java
public class PersonServiceImpl implements PersonService{
private PersonDao personDao;
public void setPersonDao(PersonDao personDao) {
this.personDao = personDao;
}
@Override
public void addPerson(Person person) {
// TODO Auto-generated method stub
personDao.addPerson(person);
}
@Override
public Person findByPerson(String name) {
// TODO Auto-generated method stub
return personDao.findByPerson(name);
}
}
// PersonSampleRun.java
package com.bbo.example;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;
import ex00.MyCalculator;
public class PersonSampleRun {
public static void main(String[] args) {
AbstractApplicationContext ctx = new GenericXmlApplicationContext("classpath:applicationCTX.xml");
PersonService personService = ctx.getBean("personService", PersonService.class);
Person person = new Person("bbo", 22);
personService.addPerson(person);
System.out.println(person.getName());
Person loaded_person = personService.findByPerson("bbo");
System.out.println(loaded_person.getName());
ctx.close();
}
}
정리
Bean파일을 이용한 DI 방법에 대해 알아보았다. bean 파일을 이용하여 의존성 주입하는 방법에는 setter 방식외에도 생성자를 이용한 방식도 있다.
setter 이용시에는 <property> 태그를 이용하지만 생성자를 이용한 방식에는 <constructor-arg> 태그를 이용한다. 이 방식에 대해서는 자료가 많으니 찾아보면 좋을 것 같다.
DI 가 정말 중요한 개념인만큼 프로젝트 진행시 여러 방법에 대해 탐구해보며 공부해봐야 할 것 같고 느낀점에 대해 계속 포스팅해야겠다.
참고서적
Spring 4 입문(한빛미디어)
인프런 강좌(신입 프로그래머를 위한 자바 스프링 프레임워크 강좌)