본문 바로가기
Spring

[Spring] 의존성 주입(Dependency Injection) - (1) XML 기반 설정

by Amy IT 2022. 8. 14.

 

목차

     

    의존성 주입(Dependency Injection)이란?

    의존성 주입(DI)은 스프링 프레임워크가 지원하는 핵심 기능 중 하나로서, 객체 사이의 의존관계가 객체 자신이 아닌 외부에 의해 결정되는 디자인 패턴입니다. 스프링 IoC 컨테이너는 어떤 객체(A)가 필요로 하는, 의존관계에 있는 다른 객체(B)를 직접 생성하여 A 객체로 주입(설정)하는 역할을 담당합니다. 컴포넌트를 구성하는 객체의 생성과 의존관계의 연결 처리를 해당 객체가 아닌 컨테이너가 대신하기 때문에 제어의 역전(Inversion of Control)이라고 합니다. 의존성 주입을 사용하면 종속성과 결합도(coupling)가 낮아져 테스트가 용이해지고 재사용성, 유연성, 확장성을 향상시킬 수 있게 됩니다.

     

     

    의존성 주입 방법

    의존성을 주입하는 방법에는 대표적으로 두 가지가 있습니다.

     

    방법 설명
    생성자 주입
    (Constructor Injection)
    생성자를 통해 의존관계를 주입하는 방법. 생성자 호출 시점에 1회 호출되는 것이 보장된다. 주입받은 객체가 변하지 않거나, 반드시 객체의 주입이 필요한 경우에 강제하기 위해 사용할 수 있다.
    수정자 주입
    (Setter Injection)
    필드 값을 변경하는 setter 메소드를 통해서 의존 관계를 주입하는 방법. 주입받는 객체가 변경될 가능성이 있는 경우에 사용할 수 있다.

     

     

    빈 설정 방법

    IoC 컨테이너는 빈 설정 정보를 바탕으로 의존성을 주입하는데, 빈을 설정하는 방법에는 다음과 같이 몇 가지 유형이 있습니다.

     

    방법 설명
    XML 기반 설정 방식
    (XML-based configuration)
    XML 파일을 사용하는 방법으로 <bean> 요소의 class 속성에 FQCN(Fully-Qualified Class Name)을 기술하면 빈이 정의된다. <constructor-arg>나 <property> 요소를 사용해 의존성을 주입한다.
    자바 기반 설정 방식
    (Java-based configuration)
    자바 클래스에 @Configuration 애너테이션을, 메서드에 @Bean 애너테이션을 사용해 빈을 정의하는 방법. 최근에는 스프링 기반 애플리케이션 개발에 자주 사용되고 특히 스프링 부트에서 이 방식을 많이 활용한다.
    애너테이션 기반 설정 방식
    (Annotation-based configuration)
    @Component 같은 마커 애너테이션(Marker Annotation)이 부여된 클래스를 탐색해서(Component Scan) DI 컨테이너에 빈을 자동으로 등록하는 방법이다.

     

     

    XML 기반 설정

    이번 글에서는 XML 기반으로 빈을 설정하는 방법을 알아보겠습니다.

     

    빈 설정 예시

    <bean id="id" class="com.dto.BeanTest"></bean>

    <bean> 태그를 사용해 빈을 등록합니다. <bean> 태그의 속성으로 다음과 같은 속성을 설정할 수 있습니다.

     

    • id : 빈 이름(id) 설정 
    • class : 빈 타입 설정
    • scope : 빈의 scope 설정 (singleton/prototype)
    • primary : true를 지정하여 같은 타입의 빈이 여러개 일때 우선적으로 사용할 빈 설정
    • lazy-init : true를 지정하여 빈을 사용할 때 객체가 생성되도록 설정
    • init-method : 빈 객체가 생성될때 호출할 메소드 설정
    • destroy-method : 빈 객체가 소멸될때 호출할 메소드 설정
    • autowire : 자동주입 설정 (no, byName, byType, constructor)

     

    기본형/문자열 주입

     

    Store.java

    public class Store {
    	private String storeName;
    	public Store() {
    		super();
    	}
    	public Store(String storeName) {
    		super();
    		this.storeName = storeName;
    	}
    	public String getStoreName() {
    		return storeName;
    	}
    	public void setStoreName(String storeName) {
    		this.storeName = storeName;
    	}
    	@Override
    	public String toString() {
    		return "Store [storeName=" + storeName + "]";
    	}
    }

    Store 클래스의 인스턴스 변수로 String 타입의 스토어이름을 선언하고, 기본생성자와 매개변수 1개 생성자, get/set 메소드를 정의하였습니다.

     

    applicationContext.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">
    
    <!-- 1. 생성자 주입 - 기본생성자 호출 -->
    <!-- id : 빈 이름(id) 설정 -->
    <!-- class : 빈 타입 설정 -->
    <bean id="store1" class="com.dto.Store"></bean>
    
    <!-- 1. 생성자 주입 - 매개변수 1개 생성자 호출 -->
    <!-- name : 생성자의 매개변수 이름 설정 -->
    <!-- value : 매개변수에 넘겨줄 값 -->
    <bean id="store2" class="com.dto.Store">
    	<constructor-arg name="storeName" value="Emart"></constructor-arg>
    </bean>
    
    <!-- 2. setter 주입 -->
    <bean id="store3" class="com.dto.Store">
    	<property name="storeName" value="Bmart"></property>
    </bean>
    
    <!-- value 태그 사용 두 줄 작성 가능 -->
    <bean id="store4" class="com.dto.Store">
    	<constructor-arg name="storeName">
    		<value>Cmart</value>
    	</constructor-arg>
    </bean>
    
    </beans>

    빈 설정 정보를 등록할 XML 파일입니다. Spring Bean Configuration File을 New하여 XML 파일을 생성합니다. 생성자 주입의 경우 <contructor-arg> 태그를 사용하고, setter 주입의 경우 <property> 태그를 사용합니다. 기본형 데이터 혹은 문자열을 주입할 경우 value 속성 또는 태그를 사용합니다.

     

    TestMain.java

    public class TestMain {
    	public static void main(String[] args) {		
    		//스프링 컨테이너 생성
    		ApplicationContext ctx = new GenericXmlApplicationContext("classpath:applicationContext.xml");
    		
    		//생성되어 있는 빈 가져오기
    		//(1) getBean(String name, Class<Store> requiredType)
    		Store store1 = ctx.getBean("store1", Store.class);
    		System.out.println(store1); //Store [storeName=null]
    		
    		Store store2 = ctx.getBean("store2", Store.class);
    		System.out.println(store2); //Store [storeName=Emart]
    		
    		//(2) getBean(String name)
    		Store store3 = (Store) ctx.getBean("store3");
    		System.out.println(store3); //Store [storeName=Bmart]
    		
    		Store store4 = (Store) ctx.getBean("store4");
    		System.out.println(store4); //Store [storeName=Cmart]
    	}
    }

    작성한 XML파일의 경로를 지정하여 ApplicationContext 컨테이너를 생성합니다. ApplicationContext 컨테이너는 구동되는 시점에 등록된 Bean 객체들을 스캔하여 객체화합니다. getBean 메소드를 이용해 컨테이너에 생성된 빈을 가져옵니다. 빈의 id와 함께 타입을 지정할 수도 있고, 빈의 id만 지정한 후 형변환을 할 수도 있습니다.

     

     

    객체 주입

     

    Product.java

    public interface Product {
    
    }

     

    Pencil.java

    public class Pencil implements Product {
    	private String prodName;
    	private int price;
    	public Pencil(String prodName, int price) {
    		super();
    		this.prodName = prodName;
    		this.price = price;
    	}
    	@Override
    	public String toString() {
    		return "Pencil [prodName=" + prodName + ", price=" + price + "]";
    	}
    }

     

    Store.java

    public class Store {
    	private String storeName;
    	private Product product;
    	public Store() {
    		super();
    	}
    	public Store(String storeName, Product product) {
    		super();
    		this.storeName = storeName;
    		this.product = product;
    	}
    	public String getStoreName() {
    		return storeName;
    	}
    	public void setStoreName(String storeName) {
    		this.storeName = storeName;
    	}
    	public Product getProduct() {
    		return product;
    	}
    	public void setProduct(Product product) {
    		this.product = product;
    	}
    	@Override
    	public String toString() {
    		return "Store [storeName=" + storeName + ", product=" + product + "]";
    	}
    }

    이번에는 스토어에 제품 한 개를 넣기 위해 Product 인터페이스를 구현하고 있는 Pencil 클래스를 정의하고 Product를 스토어의 인스턴스 변수로 선언하였습니다. 

     

    applicationContext.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">
    
    <!-- 의존관계에 있는 빈 등록 -->
    <bean id="pencil" class="com.dto.Pencil">
    	<constructor-arg name="prodName" value="A01"></constructor-arg>
    	<constructor-arg name="price" value="100"></constructor-arg>
    </bean>
    
    <!-- 1. 생성자 주입 -->
    <!-- ref : 매개변수에 넘겨줄 객체의 참조값 -->
    <bean id="store1" class="com.dto.Store">
    	<constructor-arg name="storeName" value="Emart"></constructor-arg>
    	<constructor-arg name="product" ref="pencil"></constructor-arg>
    </bean>
    
    <!-- 2. setter 주입 -->
    <bean id="store2" class="com.dto.Store">
    	<property name="storeName" value="Bmart"></property>
    	<property name="product" ref="pencil"></property>
    </bean>
    
    <!-- ref 태그 사용 두 줄 작성 가능 -->
    <bean id="store3" class="com.dto.Store">
    	<constructor-arg name="storeName">
    		<value>Cmart</value>
    	</constructor-arg>
    	<constructor-arg name="product">
    		<ref bean="pencil"/>
    	</constructor-arg>
    </bean>
    
    </beans>

    기본형 데이터 혹은 문자열이 아닌 객체를 주입할 때는 ref 속성 또는 태그를 사용합니다. ref 속성값 또는 <ref> 태그의 bean 속성값으로 빈의 id를 설정합니다.

     

    TestMain.java

    public class TestMain {
    	public static void main(String[] args) {
    		//스프링 컨테이너 생성
    		ApplicationContext ctx = new GenericXmlApplicationContext("classpath:applicationContext.xml");
    		
    		//생성되어 있는 빈 가져오기
    		Store store1 = ctx.getBean("store1", Store.class);
    		System.out.println(store1); //Store [storeName=Emart, product=Pencil [prodName=A01, price=100]]
    		
    		Store store2 = (Store) ctx.getBean("store2");
    		System.out.println(store2); //Store [storeName=Bmart, product=Pencil [prodName=A01, price=100]]
    		
    		Store store3 = (Store) ctx.getBean("store3");
    		System.out.println(store3); //Store [storeName=Cmart, product=Pencil [prodName=A01, price=100]]
    	}
    }

    주입된 Pencil 객체의 데이터까지 모두 잘 출력되는 것을 확인할 수 있습니다.

     

     

    컬렉션 주입 - List

     

    위의 예제에서 Product와 Pencil은 동일하게 사용하고 Desk 클래스를 추가하여 Store 클래스의 인스턴스 변수로 List를 선언해 보겠습니다.

     

    Desk.java

    public class Desk implements Product {
    	private double size;
    	public Desk(double size) {
    		super();
    		this.size = size;
    	}
    	@Override
    	public String toString() {
    		return "Desk [size=" + size + "]";
    	}
    }

     

    Store.java

    public class Store {
    	private String storeName;
    	private List<Product> prodList;
    	public String getStoreName() {
    		return storeName;
    	}
    	public void setStoreName(String storeName) {
    		this.storeName = storeName;
    	}
    	public List<Product> getProdList() {
    		return prodList;
    	}
    	public void setProdList(List<Product> prodList) {
    		this.prodList = prodList;
    	}
    	@Override
    	public String toString() {
    		return "Store [storeName=" + storeName + ", prodList=" + prodList + "]";
    	}
    }

     

    applicationContext.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">
    
    <bean id="pencil" class="com.dto.Pencil">
    	<constructor-arg name="prodName" value="A01"></constructor-arg>
    	<constructor-arg name="price" value="100"></constructor-arg>
    </bean>
    <bean id="desk" class="com.dto.Desk">
    	<constructor-arg name="size" value="30"></constructor-arg>
    </bean>
    
    <bean id="store1" class="com.dto.Store">
    	<property name="storeName" value="Emart"></property>
    	<property name="prodList">
    		<list>
    			<ref bean="pencil"/>
    			<ref bean="desk"/>
    			<bean class="com.dto.Desk">
    				<constructor-arg name="size" value="10"></constructor-arg>
    			</bean>
    		</list>
    	</property>
    </bean>
    
    </beans>

    <list> 태그를 사용하여 데이터를 주입합니다. 이때 <ref> 태그로 기존에 등록한 빈을 참조하도록 할 수도 있고, <bean> 태그로 빈을 직접 설정할 수도 있습니다. 컬렉션에 속한 데이터의 타입이 기본형 혹은 문자열인 경우 <value> 태그를 사용하여 값을 설정합니다. 

     

    TestMain.java

    public class TestMain {
    	public static void main(String[] args) {
    		//스프링 컨테이너 생성
    		ApplicationContext ctx = new GenericXmlApplicationContext("classpath:applicationContext.xml");
    		
    		//생성되어 있는 빈 가져오기
    		Store store1 = ctx.getBean("store1", Store.class);
    		System.out.println(store1); 
    		//Store [storeName=Emart, prodList=[Pencil [prodName=A01, price=100], Desk [size=30.0], Desk [size=10.0]]]
    	}
    }

     

     

    컬렉션 주입 - Set

    위의 예제와 동일하게 하되, Store의 인스턴스 변수를 Set으로 설정해 보겠습니다.

     

    Store.java

    public class Store {
    	private String storeName;
    	private Set<Product> prodList;
    	public String getStoreName() {
    		return storeName;
    	}
    	public void setStoreName(String storeName) {
    		this.storeName = storeName;
    	}
    	public Set<Product> getProdList() {
    		return prodList;
    	}
    	public void setProdList(Set<Product> prodList) {
    		this.prodList = prodList;
    	}
    	@Override
    	public String toString() {
    		return "Store [storeName=" + storeName + ", prodList=" + prodList + "]";
    	}
    }

     

    applicationContext.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">
    
    <bean id="pencil" class="com.dto.Pencil">
    	<constructor-arg name="prodName" value="A01"></constructor-arg>
    	<constructor-arg name="price" value="100"></constructor-arg>
    </bean>
    <bean id="desk" class="com.dto.Desk">
    	<constructor-arg name="size" value="30"></constructor-arg>
    </bean>
    
    <bean id="store1" class="com.dto.Store">
    	<property name="storeName" value="Emart"></property>
    	<property name="prodList">
    		<set>
    			<ref bean="pencil"/>
    			<ref bean="desk"/>
    			<bean class="com.dto.Desk">
    				<constructor-arg name="size" value="10"></constructor-arg>
    			</bean>
    			<ref bean="desk"/><!-- 주입되지 않음 -->
    		</set>
    	</property>
    </bean>
    
    </beans>

    마찬가지로 <set> 태그를 사용하여 데이터를 주입합니다. Set 타입의 특성상 중복된 값을 넣을 수 없기 때문에 value값이 같거나 참조하는 bean id가 동일한 경우 주입되지 않습니다. 위의 예제와 동일한 결과가 출력됩니다.

     

     

    컬렉션 주입 - Map

    위의 예제에서 Store의 인스턴스 변수를 Map으로 설정해 보겠습니다. 이번에는 Map의 value 타입을 Object로 지정하여 다양한 경우를 생각해 보겠습니다.

     

    Store.java

    public class Store {
    	private Map<String, Object> map;
    	public Map<String, Object> getMap() {
    		return map;
    	}
    	public void setMap(Map<String, Object> map) {
    		this.map = map;
    	}
    	@Override
    	public String toString() {
    		return "Store [map=" + map + "]";
    	}
    }

     

    applicationContext.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">
    
    <bean id="pencil" class="com.dto.Pencil">
    	<constructor-arg name="prodName" value="A01"></constructor-arg>
    	<constructor-arg name="price" value="100"></constructor-arg>
    </bean>
    <bean id="desk" class="com.dto.Desk">
    	<constructor-arg name="size" value="30"></constructor-arg>
    </bean>
    
    <bean id="store1" class="com.dto.Store">
    	<property name="map">
    		<map>
    			<entry>
    				<key><value>storeName</value></key>
    				<value>Emart</value>
    			</entry>
    			<entry key="num" value="123" value-type="int"></entry>
    			<entry>
    				<key><value>pencil</value></key>
    				<ref bean="pencil"/>
    			</entry>
    			<entry key="desk" value-ref="desk"></entry>
    			<entry key="desk2">
    				<bean class="com.dto.Desk">
    					<constructor-arg name="size" value="10"></constructor-arg>
    				</bean>
    			</entry>
    			<entry key="list">
    				<list>
    					<ref bean="pencil"/>
    					<ref bean="desk"/>
    				</list>
    			</entry>
    		</map>
    	</property>
    </bean>
    
    </beans>

    <map> 태그로 데이터를 주입합니다. 이때 <entry> 태그 안에 <key>와 <value> 태그 또는 참조형인 경우 <key>와 <ref>, <bean> 태그로 값을 설정할 수도 있고, <entry> 태그의 key와 value 속성 또는 value-ref 속성으로 값을 설정할 수도 있습니다. value-type 속성을 사용하여 데이터의 타입을 지정할 수도 있습니다. 

     

    TestMain.java

    public class TestMain {
    	public static void main(String[] args) {
    		//스프링 컨테이너 생성
    		ApplicationContext ctx = new GenericXmlApplicationContext("classpath:applicationContext.xml");
    		
    		//생성되어 있는 빈 가져오기
    		Store store1 = ctx.getBean("store1", Store.class);
    		System.out.println(store1); 
    		//Store [map={storeName=Emart, 
    		//		num=123, 
    		//		pencil=Pencil [prodName=A01, price=100], 
    		//		desk=Desk [size=30.0], 
    		//		desk2=Desk [size=10.0], 
    		//		list=[Pencil [prodName=A01, price=100], Desk [size=30.0]]}]
    	}
    }

     

     

    컬렉션 주입 - Properties

    이번에는 Store의 인스턴스 변수로 Properties를 설정해 보겠습니다.

     

    Store.java

    public class Store {
    	private Properties prop;
    	public Properties getProp() {
    		return prop;
    	}
    	public void setProp(Properties prop) {
    		this.prop = prop;
    	}
    	@Override
    	public String toString() {
    		return "Store [prop=" + prop + "]";
    	}
    }

     

    applicationContext.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">
    
    <bean id="store1" class="com.dto.Store">
    	<property name="prop">
    		<props>
    			<prop key="storename">Emart</prop>
    			<prop key="address">Seoul</prop>
    		</props>
    	</property>
    </bean>
    
    </beans>

     

    TestMain.java

    public class TestMain {
    	public static void main(String[] args) {
    		//스프링 컨테이너 생성
    		ApplicationContext ctx = new GenericXmlApplicationContext("classpath:applicationContext.xml");
    		
    		//생성되어 있는 빈 가져오기
    		Store store1 = ctx.getBean("store1", Store.class);
    		System.out.println(store1); 
    		//Store [prop={address=Seoul, storename=Emart}]
    	}
    }

     

     

    컬렉션 주입 - namespace util 사용

    XML 파일의 namespace 탭에서 util을 체크하면 util 태그를 이용하여 컬렉션을 따로 등록한 후 사용할 수 있습니다.

     

    map을 예시로 들어보자면 다음과 같은 형태가 됩니다.

    <util:map id="map">
    	<entry key="one" value-ref="pencil"></entry>
    	<entry key="two">
    		<ref bean="desk"/>
    	</entry>
    </util:map>
    
    <bean id="store" class="com.dto.Store">
    	<property name="map" ref="map"></property>
    </bean>

     

    이렇게 하면 등록한 컬렉션을 다른 빈에서도 재사용하여 참조할 수 있게 됩니다.

     

     

     

    이상으로 XML을 기반으로 빈을 설정하여 의존성을 주입하는 방법을 여러 가지 예시를 통해 살펴보았습니다. 

     

     

    참고

    https://kgvovc.tistory.com/m/43

    https://mangkyu.tistory.com/150

    https://mangkyu.tistory.com/125

    https://atoz-develop.tistory.com/entry/Spring-%EC%8A%A4%ED%94%84%EB%A7%81-XML-%EC%84%A4%EC%A0%95-%ED%8C%8C%EC%9D%BC-%EC%9E%91%EC%84%B1-%EB%B0%A9%EB%B2%95-%EC%A0%95%EB%A6%AC

    https://devlog-wjdrbs96.tistory.com/165

     

     

    댓글