Spring DI와 IoC 개념정리

Spring

Spring IOC와 DI 정리

Spring에서 DI와 IoC에 대해 개념을 정리하고자 한다. 크게 3가지의 시나리오를 가지고 시작한다.

현재 사용하고 있는 IDE는 Spring Tool Suite 4이고 JAVA17 버전으로 사용하고 있습니다.

해당 부분은 기본적으로 Spring을 알고있는 사람을 기준으로 작성했습니다.

우선 파일 구조는 이렇습니다.

파일 구조

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
ProjectName/
    │
    ├── gradle/wrapper
    ├── src/
    │   ├── main/
    │   │   ├── java/com/example/ProjectName/
    │   │   │   ├── beans/	# 생성된 파일 (3개)
    │   │   │   ├── Chocolate.java
    │   │   │   ├── Strawberry.java
    │   │   │   ├── Vanllia.java
    │   │   │   │
    │   │   │   ├── controller/		# 생성된 파일 (1개)
    │   │   │   │		└── control.java
    │   │   │   │		
    │   │   │   └── ProjectnameApplication.java
    │   │   │   
    │   │   └── resources/	
    │   │       ├── templates   
    │   │       ├── static/     
    │   │       │		└── order.html	# 생성된 파일
    │   │       │		     
    │   │       └── application.properties    
    │   │       
    │   └── test/java/
		│
    ├── .gitignore
    ├── build.gradle
    ├── gradlew
    ├── gradlew.bat
    └── settings.gradle

그리고 아래는 build.gradle 구성요소 입니다.

build.gradle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
plugins {
	id 'java'
	id 'org.springframework.boot' version '3.3.3'
	id 'io.spring.dependency-management' version '1.1.6'
}

group = 'com.product'
version = '0.0.1-SNAPSHOT'

java {
	toolchain {
		languageVersion = JavaLanguageVersion.of(17)
	}
}

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

tasks.named('test') {
	useJUnitPlatform()
}

이제 시작해봅시다.

S#1 : 일반적으로 웹 생성, DI와 IOC 사용 X, Controller만 존재

첫번째 시나리오는 만약 당신이 상품을 구매한다면 특정 메뉴를 통해서 구매하게 됩니다. 메뉴가 일단 주문처리, 상품제조, 전달까지만 생성합니다.

이를 위해선 웹에서 보여줄 페이지를 구성할 html 파일과 뒤에 있는 요청과 응답을 처리해줄 Controller 만 있으면 됩니다. 코드는 아래와 같습니다.

log는 그냥 테스트용 주석입니다.

order.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html>
<html>
<head>
<meta charset="EUC-KR">
<title>Product Info</title>
</head>
<body>

	<h2> - item(Product) list </h2>
	<p> order something...</p>
	
	<form action="/choosed" method="post">
		<input type="radio" name="item" value="1" checked="checked"><label>Chocolate</label><br>
		<input type="radio" name="item" value="2"><label>Vanilla</label><br>
		<input type="radio" name="item" value="3"><label>Strawberry</label><br>
		<p></p>
		<input type="submit" value="choose one">
	</form>
</body>
</html>

Control.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@RestController
public class Control {
	// 1번째 시나리오 : 일반적으로 웹 생성, DI와 IOC 사용 X, Controller만 존재
	// private final static Logger log = LoggerFactory.getLogger(Control.class);
	
	@RequestMapping("/choosed")
	public String doOrder(HttpServletRequest request) {
		
		String strTaste = "";
		String strPrice = "";
		String strMenu = request.getParameter("item");
		
		//		log.info(strMenu);
		//		log.info("--------------------");
		
		if (strMenu.equals("1")) {
			strTaste = "Chocolate - sweet";
			strPrice = "1000";
		} else if (strMenu.equals("2")) {
			strTaste = "Vanilla - original";
			strPrice = "1500";
		} else if (strMenu.equals("3")) {
			strTaste = "Strawberry - refresh";
			strPrice = "2000";
		}
		
		return strTaste+" : "+strPrice;
	}
}

코드 작성이후 Boot Dashboard에서 실행시키게 될 경우

  • order.html 버튼 누르기 전 화면

order.html 화면

  • order.html 버튼 누른 후 화면

choosed 화면

이런 식으로 웹이 돌아갈 겁니다. 이제부터는 좀 업그레이드 할 겁니다.

S#2 : Repository, Service 사용, 의존관계 생성 (Injection은 없다)

이번에는 각각의 상품을 구분해서 바로 사용가능하케 만들겁니다. 각각의 상품을 Repository라는 저장소에 넣고 이들을 불러올 겁니다. 의존관계(Dependency)만 만들고 주입(Injection)은 없습니다.

Beans라는 파일에 각각의 상품 객체를 따로 생성해 줄겁니다.

Control.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@RestController
public class Control {
	// 2번째 시나리오 : 미리 구분해서 바로 사용 (Repository, Service 사용)
	// 의존관계 - item을 받아오기 위해 외부 클래스인 Chocolate, Vanilla, Strawberry에 의존
	
	@RequestMapping("/choosed")
	public String doOrder(HttpServletRequest request) {
		
		String strMenu = request.getParameter("item");
		String strRet = "";
		
		if (strMenu.equals("1")) {
			Chocolate choco = new Chocolate();
			strRet = choco.toString();
		} else if (strMenu.equals("2")) {
			Vanilla vanilla = new Vanilla();
			strRet = vanilla.toString();
		} else if (strMenu.equals("3")) {
			Strawberry straw = new Strawberry();
			strRet = straw.toString();
		}
		
		return strRet;
	}
}

아래 코드는 만들고자한 상품 객체들입니다.

Chocolate.java

1
2
3
4
5
6
7
8
9
public class Chocolate {
	String strTaste = "Chocolate - sweet";
	String strPrice = "1000";
	
	@Override
	public String toString() {
		return "Chocolate : [strTaste="+strTaste+", strPrice="+strPrice+"]";
	}
}

Vanilla.java

1
2
3
4
5
6
7
8
9
public class Vanilla {
	String strTaste = "Vanilla - original";
	String strPrice = "1500";
	
	@Override
	public String toString() {
		return "Vanilla : [strTaste="+strTaste+", strPrice="+strPrice+"]";
	}
}

Strawberry.java

1
2
3
4
5
6
7
8
9
public class Strawberry {
	String strTaste = "Strawberry - refresh";
	String strPrice = "2000";
	
	@Override
	public String toString() {
		return "Strawberry : [strTaste="+strTaste+", strPrice="+strPrice+"]";
	}
}

실행 결과 자체는 다르지만 본질은 틀리지 않았습니다. 상품을 선택하고 선택한 상품 객체가 포함하고 있는 데이터를 불러옵니다.

기존에는 컨트롤러에서 모든 것을 다 했다면 이제는 이를 나눈후에 불러옵니다.

S#3 : IoC(Injection of Control)

2번째 시나리오에서 이번에 변경한 것은 진짜 별 게 없습니다. 바로 어노테이션을 추가한 것이죠.

아 물론 Controller에서 객체자체를 요소로 불러와줘야 합니다.

Control.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
	// 의존성 주입(DI) 방법 1
//	@Autowired
//	Chocolate choco;	// 이게 Injection 이다.
//	
//	@Autowired
//	Vanilla vanilla;
//	
//	@Autowired
//	Strawberry straw;
	
	// 의존성 주입 방법 2
	Chocolate choco;
	Vanilla vanilla;
	Strawberry straw;
	
	// 3번째 시나리오 : 2번째 시나리오에서 IoC추가, 스프링 빈(컨테이너에 보관, BeanFactory, 어노테이션을 사용)
	@Autowired	// injection을 하기 위해서 생성
	public Control(Chocolate cho, Vanilla van, Strawberry straw) {
		this.choco = cho;
		this.vanilla = van;
		this.straw = straw;
	}
	
	// bean을 만들어서 주입을 받게 됨.
	// 이전에는 불러왔다면, 이제는 Bean 컨테이너에서 받아온다.
	
	@RequestMapping("/choosed")
	public String doOrder(HttpServletRequest request) {
		
		String strMenu = request.getParameter("item");
		String strRet = "";
		
		if (strMenu.equals("1")) {
			strRet = choco.toString();
		} else if (strMenu.equals("2")) {
			strRet = vanilla.toString();
		} else if (strMenu.equals("3")) {
			strRet = straw.toString();
		}
		
		return strRet;
	}

그리고 각각의 객체에 @Component 를 추가해줘야 합니다. 코드에서 변경사항이 없기 때문에 ... 이라고 축약하겠습니다.

Chocolate.java

1
2
3
4
@Component	// 3번째 시나리오일 때 추가, beanFactory의 bean 만들기
public class Chocolate {
	...
}

Vanilla.java

1
2
3
4
@Component
public class Vanilla {
	...
}

Strawberry.java

1
2
3
4
@Component
public class Strawberry {
	...
}

이렇게 되면 결과는 2번째 시나리오와도 같을 것입니다. 배포까지 한 번 해보도록하죠.

내장 Tomcat, Jar로 배포하기

사실 배포는 Jar말고 War도 되는데 Jar로 한번 실행해볼 예정입니다.

우선 STS(Spring Tool Suite)에서 상단 네비바에서 Window > Show View > Other... 를 눌러서 Gradle Tasks 를 눌러서 하단에 띄우고 안에 프로젝트 이름이 적힌 파일을 한번 더 눌러 프로젝트 이름(product_DI) > build > bootjar 를 열어서 마우스 우클릭해 Run Gradle Tasks 를 눌러주면 됩니다.

choosed 화면

그러면 STS에서는 안 보이지만 Workspace에 직접 들어가 build > libs 로 들어가서 보면 프로젝트 이름으로 된 파일을 볼 수 있다. 이 파일을 cmd 창을 열어서 java 명령어로 실행해주면 됩니다.

1
2
3
> cd c:\SpringDI_IOC\product_DI\build\libs # 1. 우선적으로 jar 파일이 있는 위치로 이동을 해야합니다.
> java -jar product_DI-0.0.1-SNAPSHOT.jar
# 파일의 최종경로 = c:\SpringDI_IOC\product_DI\build\libs\product_DI-0.0.1-SNAPSHOT.jar

choosed 화면

choosed 화면

choosed 화면