Hayden's Archive
[Spring] DI Container를 코드로 직접 구현해보기 본문
* Summary
하나의 bean은 하나의 객체와 같음.
bean 태그에는 필수속성 2개가 있음. id와 class
class에 FQCN을 쓰고, 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
반면, ApplicationContext는 Pre-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);
}
}
출력 결과)