[Spring Framework] 스프링에서의 DI(의존성 주입) 기능
스프링은 Dependency Injection(의존성 주입)이라는 기능을 제공합니다.
흔히 DI라고 불리는 이 개념은 특정 애플리케이션에 외부에서 생성된 객체를 주입하는 개념입니다.
특정 애플리케이션에서 new를 이용하여 새로운 객체를 생성하는 방식과는 반대된다고 볼 수 있습니다.
저는 간단한 예제와 함께 위의 두 개념을 비교하여 스프링에서 DI를 사용하는 이유에 대해서 알아보겠습니다.
Profile.java
public class Profile {
//위치와 나이를 나타내는 필드 값 선언
public String myLocation;
public int myAge;
//프로필을 띄우는 메소드
public void myProfile() {
System.out.println("myProfile() 실행");
System.out.println("저는 " + myAge + "살이고, 현재 " + myLocation + "에 있습니다.");
}
//내 위치 setter
public void setMylocation(String myLocation) {
this.myLocation = myLocation;
}
//내 나이 setter
public void setMyage(int myAge) {
this.myAge = myAge;
}
}
main.java
public class main {
public static void main(String[] args) {
//Profile 객체 생성
Profile profile = new Profile();
//나이와 위치 정의
profile.setMyage(18);
profile.setMylocation("성남시");
//프로필을 띄움
profile.myProfile();
}
}
우선은 main 객체에서 새로운 객체를 생성하여 코드를 작성하였습니다.
보시다시피, main 객체에서 profile 객체를 생성하고 나이와 위치 필드 값을 정의한 다음, 정의한 프로필을 띄우는 코드입니다.
하지만, 한해가 지나 18세에서 19세가 되거나, 현재 위치가 성남시에서 다른 장소로 위치가 바뀌었을 경우에는
main 코드를 수정하여 필드 값을 바꾸어 줄 필요가 있어보입니다.
그러나, 실생활에서는 이처럼 기능이 수정되거나 추가될 경우 코드를 뜯어고치고 컴파일을 할 경우 많은 비용이 발생하기에
가급적 소스코드의 변경을 최소화하는 편이 좋습니다.
이와 같은 경우를 main 객체가 Profile 객체에 의존성을 가지고 있다고 표현합니다.
스프링에서는 DI를 통해 이러한 의존성을 외부로부터 주입함으로써 이와 같은 소스코드의 변경을 최소화할 수 있습니다.
app_context.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- Profile 객체를 가져와서 'profile' bean 객체를 생성 -->
<bean id="profile" class="Profile">
<!-- 각각의 setter 메소드를 불러와 위치와 나이 필드값을 정의 -->
<!-- 필드 값을 정하는 setter 메소드의 경우 name에 set을 뺀 나머지 메소드 이름을 적어준다. -->
<!-- value에는 해당 필드에 할당할 값을 입력한다. -->
<property name="mylocation" value="성남시"></property>
<property name="myage" value="18"></property>
</bean>
</beans>
main.java
import org.springframework.context.support.GenericXmlApplicationContext;
public class main {
public static void main(String[] args) {
//환경설정 파일(app_context.xml)을 불러와서 Bean 객체 설정 정보를 가져옴
GenericXmlApplicationContext cx = new GenericXmlApplicationContext("classpath:app_context.xml");
//id가 profile인 bean 객체를 호출하여 객체를 생성(외부에서 주입)
Profile profile = cx.getBean("profile", Profile.class);
profile.myProfile();
}
}
이번에는 Profile.java 파일은 그대로 둔 채, main.java 파일을 스프링의 DI 기능을 이용하는 쪽으로 수정해보았습니다.
기존의 main 객체에서 이루어졌던 내 위치와 나이 필드 값 정의는 app_context.xml에서 이루어지고 있습니다.
또한 main 객체에서는 기존의 나이, 위치 필드 값 정의 코드는 없으며, app_context.xml을 불러온 뒤, id가 "profile"인 bean 객체를 호출하여 해당 객체를 생성하는 작업과 프로필을 띄우는 매소드인 myProfile을 실행하는 작업을 하는 코드가 작성되어 있습니다.
여기서 알 수 있는 것은, 내 위치나 나이 값을 바꾸려면 main 객체의 코드 수정 없이 app_context.xml 파일만 수정하면 된다는 것입니다.
우리는 이 xml 파일을 '환경설정 파일'이라고 부르며, src/main/resources 폴더에 생성합니다.
환경설정 파일에서는 beans 태그 안에 bean 태그로 필요한 객체를 id를 지정하여 bean 객체로 생성할 수 있습니다.
<bean id="profile" class="Profile"></bean>
<!-- id에는 main 객체에서 불러올때 사용할 이름 -->
<!-- class에는 생성할 객체의 이름 -->
객체가 필드 값을 전달받을 때, setter 매소드를 통해서 전달받을 경우 <property> 태그를 이용합니다.
name에는 set을 제외한 프로퍼티 이름을 앞자리를 소문자로 하여 입력하고, value에는 넘겨줄 값을 입력합니다.
<property name="mylocation" value="성남시"></property>
<!-- name은 참조할 속성의 이름 -->
<!-- value는 객체로 넘겨줄 값 -->
객체가 필드 값을 전달받을 때, 생성자를 통해서 전달받을 경우 <constructor-arg> 태그를 이용합니다.
빈 객체를 전달받는 경우에는 ref를 사용하고, String 등의 기본 데이터 타입을 전달받을 경우 value를 사용합니다.
<constructor-arg ref="another-class"></constructor-arg>
<!-- Bean 객체를 주입받는 경우에는 ref 사용 -->
<constructor-arg value="성남시"></constructor-arg>
<constructor-arg>
<value>성남시</value>
</constructor-arg>
<!-- 기본 데이터 타입이면 value 사용 -->
<!-- 둘 중 어느 방법을 사용해도 무방하다. -->
main 객체에서는 GenericXmlApplicationContext 클래스를 이용하여 환경설정 파일을 불러올 수 있습니다.
여기서 GenericXmlApplicationContext은 스프링 컨테이너입니다.
스프링 컨테어너는 Bean 객체들을 생성 및 관리하는 역할을 합니다.
GenericXmlApplicationContext cx = new GenericXmlApplicationContext("classpath:app_context.xml");
//classpath:[불러 올 환경설정 파일.xml]
그 이후, 불러온 환경설정 파일에서 getBean 객체를 이용하여 원하는 bean 객체를 불러올 수 있습니다.
Profile profile = cx.getBean("profile", Profile.class);
//왼쪽에서부터 차례대로
//불러 올 Bean 객체의 id와 [실제 객체 이름].class를 입력
이렇게 객체 안에 새로운 객체를 생성하는 방식과는 달리 DI를 사용하여 객체를 외부로부터 주입할 경우 객체 간 결합이 느슨해지며, 소스코드가 아닌 환경설정 파일(app_context.xml)만을 수정함으로써 보다 유연한 프로그램을 만들 수 있습니다.