Spring 02 : 의존, 의존 주입

LeeWonjin·2022년 7월 31일

2022 백엔드스터디

목록 보기
12/20

환경
윈도우즈 10 / 이클립스 2022-06 (4.24.0) / Java SE 18 / 메이븐 3.8.6 / spring-context 5.3.22

교재
책 : 초보 웹 개발자를 위한 스프링5 프로그래밍 입문
영상 : 예제로 배우는 스프링 입문(개정판)

의존 (Dependency)

변경에 의해 영향이 전파되는 관계
e.g. 한 클래스가 다른 클래스의 메소드를 호출

아래 코드에서 GoraniGoraniAction에 의존
( = GoraniGoraniAction이 있어야만 동작 )

  • 근거 1. GoraniAction의 메소드를 호출
  • 근거 2. GoraniAction의 변경에 영향 받음

Gorani는 의존대상 객체를 클래스 내에서 직접 생성하여 의존 객체를 구함
이 방법은 프로그램의 유연성과 유지보수성을 낮추는 등의 문제를 가짐

class GoraniAction {
	public void cry(String s) { System.out.println("Gorani cry \"" + s + "\""); }
	public void sleep() { System.out.println("Gorani sleeps.."); }
}

class Gorani {
	private String crySound;
	private GoraniAction action = new GoraniAction();
	
	public Gorani(String crySound) { this.crySound = crySound; }
	public void cry() { action.cry(crySound); }
	public void sleep() { action.sleep(); }
}

public class Main {
	public static void main(String[] args) {
		Gorani gorani = new Gorani("quaaak!");
		gorani.cry(); // Gorani cry "quaaak!"
		gorani.sleep(); // Gorani sleeps..
	}
}

참고 의존성이란

의존 주입 (DI : Dependency Injection)

어떤 클래스 내에서 의존하는 클래스를 직접 생성하는 대신 외부로부터 의존객체를 주입받는 것

의존 주입이 없을 때

아래 코드는 Gorani가 의존객체인 GoraniAction객체를 직접 생성

class GoraniAction {
	public void cry(String s) { System.out.println(s); }
}

class Gorani {
	private GoraniAction action = new GoraniAction();
	public void cry() { action.cry("quaaak"); }
}

public class Main {
	public static void main(String[] args) {
		Gorani gorani = new Gorani();
		gorani.cry();
	}
}

의존 주입이 있을 때

아래 코드는 Gorani가 외부로부터 GoraniAction객체를 주입받음

  • 방법 1. 생성자를 통한 의존 주입
  • 방법 2. setter를 통한 의존 주입
class GoraniAction {
	public void cry(String s) { System.out.println(s); }
}

// ---------------------
// 방법 1. 생성자를 통한 의존 주입
class Gorani {
	private GoraniAction action;
	public Gorani(GoraniAction action) { this.action = action; }
	public void cry() { action.cry("quaaak"); }
}

public class Main {
	public static void main(String[] args) {
		Gorani gorani = new Gorani(new GoraniAction());
		gorani.cry();
	}
}

// ---------------------
// 방법 2. setter를 통한 의존 주입
class Gorani {
	private GoraniAction action;
	public void cry() { action.cry("quaaak"); }
	public void setAction(GoraniAction action) { this.action = action; } 
}

public class Main {
	public static void main(String[] args) {
		Gorani gorani = new Gorani();
		gorani.setAction(new GoraniAction());
		gorani.cry();
	}
}

DI 구현

구성요소

  • 프로그램을 구현한 코드
    • Gorani 클래스
    • GoraniDao 클래스
    • GoraniOwner 클래스
    • GoraniCaller 클래스
    • AllGoranisCaller 클래스
  • 조립기 : 객체 생성과 의존주입이 이루어지는 코드
    • Spring 없는 구현 Assembler.java
    • Spring을 사용한 구현 AppCtx.java
  • main함수 : 조립기를 불러와 실제 프로그램을 구동하는 코드

시나리오

  • 고라니gorani_{gorani}객체
    • 필드 : int id, String name
  • 고라니 주인GoraniOwner_{GoraniOwner}객체
    • 필드 : GoraniDao, GoraniCaller, AllGoranisCaller
    • 메소드 : sellGorani, buyGorani, callGorani, callAllGoranis
  • 고라니 관리 객체GoraniDao_{GoraniDao}객체
    • 필드 : HashMap<Integer, Gorani>
    • 메소드 : getGoraniById, getAllGoranis, add, remove

고라니 주인고라니 관리 객체를 통해 고라니를 구매/판매
구매한 고라니고라니 관리 객체의 해시맵에 put()
판매한 고라니고라니 관리 객체의 해시맵에서 remove()

의존 관계

  • GoraniDao에 의존하는 클래스
    • GoraniOwner
    • AllGoranisCaller
  • GoraniCaller에 의존하는 클래스
    • GoraniOwner
    • AllGoraniCaller

Spring없는 구현

모두 같은 패키지라고 가정

// Main.java
public class Main {
	public static void main(String[] args) {
		Assembler assembler = new Assembler();
		GoraniOwner owner = assembler.getGoraniOwner();
		
		owner.buyGorani(1, "wonjin");
		owner.buyGorani(2, "martindog");
		owner.buyGorani(3, "comgong");
		
		owner.callGorani(2); // ID : 2, NAME : martindog
		owner.callGorani(53); // Gorani Not Found
		owner.callAllGoranis();
		//	ID : 1, NAME : wonjin
		//	ID : 2, NAME : martindog
		//	ID : 3, NAME : comgong
		
		owner.sellGorani(3);
		owner.sellGorani(5353); // ID 5353 : Not Found
		owner.sellGorani(1);
		
		owner.callAllGoranis(); // ID : 2, NAME : martindog
	}
}

// Assembler.java
public class Assembler {
	private GoraniDao goraniDao = new GoraniDao();
	private GoraniOwner goraniOwner = new GoraniOwner();
	private GoraniCaller goraniCaller = new GoraniCaller();
	
	// 생성자 방식 DI
	private AllGoranisCaller allGoranisCaller = new AllGoranisCaller(goraniCaller, goraniDao);
	
	public Assembler() {
		// Setter 방식 DI
		goraniOwner.setGoraniDao(goraniDao);
		goraniOwner.setGoraniCaller(goraniCaller);
		goraniOwner.setAllGoranisCaller(allGoranisCaller);
	}
	
	public GoraniOwner getGoraniOwner() {
		return goraniOwner;
	}
}

// Gorani.java
public class Gorani {
	private int id;
	private String name;
	
	public Gorani(int id, String name) {
		this.id = id;
		this.name = name;
	}
	
	public int getId() { return id; }
	public String getName() { return name; }
	public void setName(String name) { this.name = name; }
}

// GoraniDao.java
import java.util.Collection;
import java.util.HashMap;

public class GoraniDao {
	private HashMap<Integer, Gorani> list = new HashMap<Integer, Gorani>(); // Database
	
	public Gorani getGoraniById(int id) {
		return list.get(id);
	}
	
	public Collection<Gorani> getAllGoranis() {
		return list.values();
	}
	
	public void add(Gorani gorani) {
		boolean isExist = getGoraniById(gorani.getId()) != null;
		if(isExist)
			throw new RuntimeException("ID : " + gorani.getId() + " : Already Exist");
		
		list.put(gorani.getId(), gorani);
	}
	
	public void remove(int id) {
		Gorani gorani = getGoraniById(id);
		if(gorani == null)
			throw new RuntimeException("ID " + id + " : Not Found");
		
		list.remove(id);
	}
}

// GoraniOwner.java
public class GoraniOwner {
	private GoraniDao goraniDao;
	private GoraniCaller goraniCaller;
	private AllGoranisCaller allGoranisCaller;
	
	// setter방식의 DI
	public void setGoraniDao(GoraniDao goraniDao) { this.goraniDao = goraniDao; }
	public void setGoraniCaller(GoraniCaller goraniCaller) { this.goraniCaller = goraniCaller; }
	public void setAllGoranisCaller(AllGoranisCaller allGoranisCaller) { this.allGoranisCaller = allGoranisCaller; }
	
	
	public void buyGorani(int id, String name) {
		Gorani obj = new Gorani(id, name);
		goraniDao.add(obj);
	}
	
	public void sellGorani(int id) {
		try { goraniDao.remove(id); }
		catch( Exception e ) { System.out.println(e.getMessage()); }
	}
	
	public void callGorani(int id) {
		Gorani obj = goraniDao.getGoraniById(id);
		try { goraniCaller.call(obj); }
		catch( Exception e ) { System.out.println(e.getMessage()); }
	}
	
	public void callAllGoranis() {
		allGoranisCaller.call();
	}
}

// GoraniCaller.java
public class GoraniCaller {
	public void call(Gorani gorani) {
		if(gorani == null)
			throw new RuntimeException("Gorani Not Found");
		
		System.out.println("ID : " + gorani.getId() + ", NAME : " + gorani.getName());
	}
}

// AllGoranisCaller.java
import java.util.Collection;
import java.util.Iterator;

public class AllGoranisCaller {
	private GoraniCaller caller;
	private GoraniDao goraniDao;
	
	// 생성자 방식의 DI
	public AllGoranisCaller(GoraniCaller caller, GoraniDao goraniDao) {
		this.caller = caller;
		this.goraniDao = goraniDao;
	}
	
	public void call() {
		Collection<Gorani> list = goraniDao.getAllGoranis();
		
		Iterator<Gorani> itr = list.iterator();
		while(itr.hasNext())
			caller.call(itr.next());
	}
}

Spring을 사용한 구현

위 코드에서

  • Main.java의 내용 변경
  • Assembler.java 대신 AppCtx.java 사용
// Main.jva
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main {
	public static ApplicationContext ctx;
	public static void main(String[] args) {
		ctx = new AnnotationConfigApplicationContext(AppCtx.class);
		GoraniOwner owner = ctx.getBean("goraniOwner", GoraniOwner.class);
		
		owner.buyGorani(1, "wonjin");
		owner.buyGorani(2, "martindog");
		owner.buyGorani(3, "comgong");

		owner.callGorani(2); // ID : 2, NAME : martindog
		owner.callGorani(53); // Gorani Not Found
		owner.callAllGoranis();
		//	ID : 1, NAME : wonjin
		//	ID : 2, NAME : martindog
		//	ID : 3, NAME : comgong

		owner.sellGorani(3);
		owner.sellGorani(5353); // ID 5353 : Not Found
		owner.sellGorani(1);

		owner.callAllGoranis(); // ID : 2, NAME : martindog
	}
}

// AppCtx.java
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Bean;

@Configuration
public class AppCtx {
	@Bean
	public GoraniDao goraniDao() {
		return new GoraniDao();
	}
	
	@Bean
	public GoraniCaller goraniCaller() {
		return new GoraniCaller();
	}
	
	@Bean
	public AllGoranisCaller allGoranisCaller() {
		return new AllGoranisCaller(goraniCaller(), goraniDao());
	}
	
	@Bean
	public GoraniOwner goraniOwner() {
		GoraniOwner owner = new GoraniOwner();
		owner.setGoraniDao(goraniDao());
		owner.setGoraniCaller(goraniCaller());
		owner.setAllGoranisCaller(allGoranisCaller());
		
		return owner;
	}
}

참고

profile
노는게 제일 좋습니다.

0개의 댓글