Hayden's Archive

[Spring] DI Container를 코드로 직접 구현해보기 본문

Study/Java & Kotlin

[Spring] DI Container를 코드로 직접 구현해보기

_hayden 2020. 8. 3. 18:05

* Summary

하나의 bean은 하나의 객체와 같음.
bean 태그에는 필수속성 2개가 있음. id와 class
classFQCN을 쓰고, id에는 인스턴스명을 씀.

주문서를 보고, 주문서대로 객체를 만들고 객체를 보관하고 있는 클래스가 DI Container인 것. 

 


* 폴더 구조

 


Dice.java (인터페이스)

package spring.service.dice;

public interface Dice {
	void selectedNumber();
	int getValue();
}

 


DiceAImpl.java (DiceBImpl.java, DiceCImpl.java도 거의 동일)

package spring.service.dice.impl;

import java.util.Random;

import spring.service.dice.Dice;

public class DiceAImpl implements Dice {

	//필드
	private int value;

	//생성자
	public DiceAImpl() {
		System.out.println("::"+getClass().getName()+" 생성자....");
	}

	//메소드 (getter) - selectedNumber()로 value 값 주입할 것이므로 setter는 생략.
	public int getValue() {
		return value;
	}
	
	//==> 주사위를 던저 선택되는 숫자를 생산하는 행위(무작위로 숫자 생산)
	public void selectedNumber(){
		value = new Random().nextInt(6) + 1;
	}
}

 


Player02.java

package spring.service.dice.play;

import spring.service.dice.Dice;

public class Player02 {

	//필드
	private Dice dice;
	private int totalValue;
	
	//생성자
	public Player02() {
	}
	public Player02(Dice dice) {
		this.dice = dice;
	}
	
	//메소드 (getter/setter)
	public Dice getDice() {
		return dice;
	}
	public void setDice(Dice diceA) {
		this.dice = diceA;
	}
	public int getTotalValue() {
		return totalValue;
	}
	public void setTotalValue(int totalValue) {
		this.totalValue = totalValue;
	}
	
	//==> count 만큼 주사위를 굴려서 합을 후하는 행위
	public void playDice(int count){
		System.out.println("==>"+getClass().getName()+".playDice() start....");
		for (int i = 0; i < count; i++) {
			dice.selectedNumber();
			System.out.println("::[ "+dice.getClass().getName()+" ] 의 선택된수 : "+dice.getValue());
			totalValue += dice.getValue(); 
		}
		System.out.println("==>"+getClass().getName()+".playDice() end....");
	}
}

 


diceService.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="diceA" class="spring.service.dice.impl.DiceAImpl"/>
	<bean id="diceB" class="spring.service.dice.impl.DiceAImpl"/>
	<bean id="diceC" class="spring.service.dice.impl.DiceAImpl"/>

	<!-- player01~02 : User Definition Bean : 생성자 주입 -->
	<!-- value 단순 파라미터 주입   |  ref 객체 주입(객체 참조) -->
	<bean id="player01" class="spring.service.dice.play.Player02">
		<constructor-arg ref="diceA"></constructor-arg>
	</bean>
	<bean id="player02" class="spring.service.dice.play.Player02">
		<constructor-arg ref="diceB"></constructor-arg>
	</bean>

	<!-- player01 : Setter로 주입 -->
	<!-- value 단순 파라미터 주입   |  ref 객체 주입(객체 참조) -->
	<bean id="player03" class="spring.service.dice.play.Player02">
		<property name="dice" ref="diceC"></property>
	</bean>
	<bean id="player04" class="spring.service.dice.play.Player02">
		<property name="dice" ref="diceA"></property>
	</bean>
</beans>

 


DiceTestAppUseSpring.java

package spring.service.dice.test;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.FileSystemResource;

import spring.service.dice.play.Player02;
public class DiceTestAppUseSpring {
	public static void main(String[] args) {
		/* 1. BeanFactory 생성... 주문서는 공장에서 미리 받아서 읽어놔야 한다. */
		System.out.println("1. BeanFactory... 생성...");
		
		//BeanFactory 생성할 때 주문서와 함께 인자값으로 넣어준다.
		// 주문서 = Bean Configuration File(Bean 설정 문서)
		//BeanFactory가 추상 클래스이므로 자식 클래스로 객체 생성
		//XmlBeanFactory는 폐기될 구버전... 하지만 동작 원리를 알기 위해 쓰겠다.
				
		//FilePathSystem으로 불러와서 src 인식 못함
		BeanFactory factory = 
				new XmlBeanFactory(new FileSystemResource("./src/main/resources/config/diceService.xml"));
		
		//2. getBean()으로 빈을 받아와서 playDice(), getTotalValue() 호출
		//빈 공장을 먼저 만들어놓고 클라이언트가 요청(getBean())하면 그제서야 만들어짐.(Lazy Loading)
		System.out.println("2. getBean().... 클라이언트 호출...");
		Player02 player01 = (Player02)factory.getBean("player01");
		System.out.println("3. ↑ getBean() 호출 이후에 컨테이너가 객체 생성한 것 확인...");
		player01.playDice(3);
		System.out.println("======================");
		System.out.println("선택된 주사위 수의 총합은 :"+ player01.getTotalValue());
		System.out.println("=============\n\n");
		
		//2. getBean()으로 빈을 받아와서 playDice(), getTotalValue() 호출
		//빈 공장을 먼저 만들어놓고 클라이언트가 요청(getBean())하면 그제서야 만들어짐.(Lazy Loading)
		System.out.println("2. getBean().... 클라이언트 호출...");
		Player02 player02 = (Player02)factory.getBean("player02");
		System.out.println("3. ↑ getBean() 호출 이후에 컨테이너가 객체 생성한 것 확인...");
		player02.playDice(3);
		System.out.println("======================");
		System.out.println("선택된 주사위 수의 총합은 :"+ player02.getTotalValue());
		System.out.println("=============\n\n");
		
		
		//2. getBean()으로 빈을 받아와서 playDice(), getTotalValue() 호출
		//빈 공장을 먼저 만들어놓고 클라이언트가 요청(getBean())하면 그제서야 만들어짐.(Lazy Loading)
		System.out.println("2. getBean().... 클라이언트 호출...");
		Player02 player03 = (Player02)factory.getBean("player03");
		System.out.println("3. ↑ getBean() 호출 이후에 컨테이너가 객체 생성한 것 확인...");
		player03.playDice(3);
		System.out.println("======================");
		System.out.println("선택된 주사위 수의 총합은 :"+ player03.getTotalValue());
		System.out.println("=============\n\n");
		
		//2. getBean()으로 빈을 받아와서 playDice(), getTotalValue() 호출
		//빈 공장을 먼저 만들어놓고 클라이언트가 요청(getBean())하면 그제서야 만들어짐.(Lazy Loading)
		System.out.println("2. getBean().... 클라이언트 호출...");
		Player02 player04 = (Player02)factory.getBean("player04");
		System.out.println("3. ↑ getBean() 호출 이후에 컨테이너가 객체 생성한 것 확인...");
		player04.playDice(3);
		System.out.println("======================");
		System.out.println("선택된 주사위 수의 총합은 :"+ player04.getTotalValue());
		System.out.println("=============\n\n");
	}
}

출력 결과)

 


* 폴더 구조


User.java

 -  User 의 정보 id / pwd / age 를 갖는 Value Object
 -  User 의 정보 id / pwd / age 를 갖는 Domain / Command Object
 -  User 의 정보 id / pwd / age 를 갖는 POJO  

package spring.service.domain;

public class User {
	
    ///필드
    private String userId; 	/* 회원 ID */
    private String password; /* 비밀번호 */
    private int age; /* 나이 */ 
    
    //생성자
    public User() {
		System.out.println("\n::"+getClass().getName()+" 디폴트 생성자....");
	}
	public User(int age, String userId) {
		System.out.println("\n::"+getClass().getName()+" age,userId 인자 받는 생성자....");
		this.age = age;
		this.userId = userId;
	}
	public User(String password, String userId) {
		System.out.println("\n::"+getClass().getName()+"age,password,userId 인자 받는 생성자");
		this.password = password;
		this.userId = userId;
	}
	public User(int age, String password, String userId) {
		System.out.println("\n::"+getClass().getName()+"age,password,userId 인자 받는 생성자");
		this.age = age;
		this.password = password;
		this.userId = userId;
	}
	
	///메소드 (getter/setter)
	public String getUserId(){
		return this.userId;
	}
	public void setUserId( String userId ){
	   System.out.println("::"+getClass().getName()+".setUserId()");
	   this.userId= userId;
	}
	public String getPassword(){
	   return this.password;
	}
	public void setPassword( String password ){
	   System.out.println("::"+getClass().getName()+".setPassword()");		
	   this.password= password;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
        System.out.println("::"+getClass().getName()+".setAge()");
		this.age = age;
	}
	
	//toString()
	public String toString() {
		return "UserVO [userId=" + userId + ", password=" + password + ", age="+ age + "]";
	}	
}

 


userservice01.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">

	<!-- 개발자 정의 인스턴스 :: setter 단순파라미터 주입 :: 01유저(id), 01(age) -->
	<bean id="user01" class="spring.service.domain.User">
		<property name="userId" value="01유저"></property>
		<property name="age" value="01"></property>
	</bean>
	
	<!-- API 인스턴스 정의 :: 생성자를 통한 단순 파라미터 값 주입 -->
	<bean id="password" class="java.lang.String">
		<constructor-arg value="7777"/>
	</bean>
	
	<!-- 개발자 정의 인스턴스 :: setter 단순파라미터 주입 :: 02유저(id), 02(age), 7777(password) -->
	<bean id="user02" class="spring.service.domain.User">
		<property name="userId" value="02유저"></property>
		<property name="age" value="02"></property>
		<property name="password" ref="password"></property>
		<!-- 
		<property name="password" value="password"></property> 
		이렇게 입력하면 비밀번호가 password가 됨.
		-->
	</bean>
	
	<!-- 개발자 정의 인스턴스 user03 :: DI가 없음 = 주입이 하나도 없다. 그냥 객체만 만든다. -->
	<bean id="user03" class="spring.service.domain.User"/>
	
	<!-- 개발자 정의 인스턴스 user04 :: 생성자 주입 :: 04, 04유저 -->
	<!-- 생성자로 값을 여러개 주입할 때 index, type, name 이 세 가지 옵션을 유념해서 쓴다. -->
	<bean id="user04" class="spring.service.domain.User">
		<constructor-arg value="04유저" index="1"/>
		<constructor-arg value="04" index="0"/>	
	</bean> 
	
	<!-- 개발자 정의 인스턴스 user05 -->
	<bean id="user05" class="spring.service.domain.User">
		
		<!-- 방법 1) index 활용
		<constructor-arg value="05유저" index="2"/>
		<constructor-arg value="05" index="0"/>
		<constructor-arg value="0505" index="1"/> 
		-->	
		
		<!-- 방법 2) type 활용(그런데 type이 string으로 겹치니까 type, index 섞어서 씀)
		<constructor-arg value="05유저" type="String"/>
		<constructor-arg value="05" type="int"/>
		<constructor-arg value="0505" index="1"/>
		-->	
		
		<!-- 방법 3) Setter 활용 - 많이 쓰는 이유가 있다. -->
		<property name="age" value="05"/>
		<property name="password" value="0505"/>
		<property name="userId" value="05유저"/>
	</bean>
</beans>

 


UserTestApp01.java

 


* 파일 읽어올 때


FilePathSystem으로 읽어올 때 - 파일은 src 인식을 못하므로 src부터 다시 인식시켜야 한다. 
ClassPathSystem으로 읽어올 때 - src를 이미 먹고 들어간다.
 

 

* BeanFactory

BeanFactory는 객체를 getBean() 하는 순간에 생성한다.

package spring.service.test;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.FileSystemResource;

import spring.service.domain.User;

public class UserTestApp01 {
	public static void main(String[] args) {
		BeanFactory factory = new XmlBeanFactory(new
		FileSystemResource("./src/main/resources/config/userservice01.xml"));
        
		System.out.println("\n=============================================================================");
		User user01 = (User)factory.getBean("user01");
		System.out.println(user01);
		
		System.out.println("\n=============================================================================");
		User user02 = (User)factory.getBean("user02");
		System.out.println(user02);
		
		System.out.println("\n=============================================================================");
		User user03 = (User)factory.getBean("user03");
		System.out.println(user03);
		
		System.out.println("\n=============================================================================");
		User user04 = (User)factory.getBean("user04");
		System.out.println(user04);
		
		System.out.println("\n=============================================================================");
		User user05 = (User)factory.getBean("user05");
		System.out.println(user05);
	}
}

출력 결과)

 

* ApplicationContext

반면, ApplicationContextPre-loading을 가능하게 한다. ( 관련 포스팅 : https://hayden-archive.tistory.com/206#preloading )

메타데이타를 읽어올떄 미리 인스턴스를 생성해서 읽어온다. 
다시 말해서 PreLoading하는데 이게 더 나은 방식이다.(현업에서는 BeanFactory 안쓴다)

WAS(서버 구동할때 DD 파일 읽어서 한번에 서블릿을 다 미리 만들어 놓음.)와 같은 방식이다.

ApplicationContext는 BeanFactory의 자식으로 더 나은 기능들이 있다.
특히나 파일 시스템이 아니라 바로 클래스 패스 잡혀진 곳으로 파일이 들어오기에 바로 src 밑부터 찾으면 된다. 

package spring.service.test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import spring.service.domain.User;

public class UserTestApp01 {
	public static void main(String[] args) {
		ApplicationContext factory = new ClassPathXmlApplicationContext("/config/userservice01.xml");
		
		System.out.println("\n=============================================================================");
		User user01 = (User)factory.getBean("user01");
		System.out.println(user01);
		
		System.out.println("\n=============================================================================");
		User user02 = (User)factory.getBean("user02");
		System.out.println(user02);
		
		System.out.println("\n=============================================================================");
		User user03 = (User)factory.getBean("user03");
		System.out.println(user03);
		
		System.out.println("\n=============================================================================");
		User user04 = (User)factory.getBean("user04");
		System.out.println(user04);
		
		System.out.println("\n=============================================================================");
		User user05 = (User)factory.getBean("user05");
		System.out.println(user05);
	}
}

출력 결과)