<< 학습 목표 >>

1. XX

2. XX


이번에는 코드를 더 발전시켜보자

프로젝트 -> com.study.chapter03 -> Ex05 소스 파일을 추가하고 아래 코드를 추가하자

package com.study.chapter03;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Ex05 {
	
	public static void main(String[] args) {
		ApplicationContext ctx = new AnnotationConfigApplicationContext(Config.class);
		
		GalaxyS23 phone = (GalaxyS23) ctx.getBean("galaxyS23");
		
		System.out.println("<< phone >>");
		phone.printBatteryInfo();
	}

}

 

그리고 실행시켜보면 문구가 이상하다는걸 알 수 있음

스마트폰의 배터리를 다 썼다고해서 교체하는 사람은 없을 것

스마트폰의 배터리를 다 쓰면 충전하는게 일반적

 

우리가 만든 스마트폰은 베터리가 건전지였을 때는 베터리의 정보를 출력하면 "교체하세요." 문구가 적절했지만 이제 리튬 베터리로 바뀌어서 "교체하세요." 대신 "충전하세요." 로 문구를 바꿔야함

 

스마트폰 베터리는 나중에라도 더 효율적인 베터리로 업그레이드가 될 가능성이 있으므로 가장 적은 노력으로 문구를 바꾸는 방법은 자바에서 배운 클래스, 인터페이스, 상속 등을 활용하는 것

다형성, 클래스, 인터페이스에 대한건 기초인 자바에서 배우는 것들이므로 여기서는 설명하진 않겠음

 

프로젝트 -> com.study.chapter03 -> BatteryType 인터페이스를 추가하고 아래 코드를 추가하자

package com.study.chapter03;

public interface BatteryType {
	void printBatteryInfo();
}

 

프로젝트 -> com.study.chapter03 -> NormalBattery 클래스를 추가하고 아래 코드를 추가하자

package com.study.chapter03;

public class NormalBattery implements BatteryType {

	@Override
	public void printBatteryInfo() {
		System.out.println("배터리는 건전지입니다.");
		System.out.println("배터리를 모두 사용했다면 건전지를 교체하세요.");
	}

}

 

프로젝트 -> com.study.chapter03 -> LithiumBattery 클래스를 추가하고 아래 코드를 추가하자

package com.study.chapter03;

public class LithiumBattery implements BatteryType {

	@Override
	public void printBatteryInfo() {
		System.out.println("배터리는 리튬 배터리입니다.");
		System.out.println("배터리를 모두 사용했다면 배터리를 충전하세요.");
	}

}

 

BatteryType 인터페이스는 베터리가 되기 위한 클래스는 반드시 구현해야하는 인터페이스로 정의했음

이 인터페이스를 통해 배터리에 다형성을 적용할 수 있게 됐음

그 후 건전지를 표현한 NormalBattery 클래스와 리튬 배터리를 표현한 LithiumBattery 클래스를 선언했음

NormalBattery 클래스는 건전지에 맞는 배터리 정보를 출력하도록 printBatteryInfo 메서드를 구현했음

LithiumBattery 클래스는 리튬 배터리에 맞는 배터리 정보를 출력하도록 printBatteryInfo 메서드를 구현했음

 

 

이제 각 스마트폰에서는 문자열 대신 인터페이스를 활용해 다형성이 적용된 베터리 타입을 갖고(1) 베터리 유형에 맞는 정보가 출력(2)되도록 했음

 

스마트폰을 생성하고 의존 주입을 해주는 Config 컨테이너의 코드도 상황에 맞게 바꾸자

여기서는 우선 스마트폰의 베터리를 건전지로 지정했음

package com.study.chapter03;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class Config {
	private BatteryType batteryType = new NormalBattery();
	
	@Bean
	public GalaxyS23 galaxyS23() {
		return new GalaxyS23(batteryType);
	}
	
	@Bean
	public GalaxyFlip4 galaxyFlip4() {
		return new GalaxyFlip4(batteryType);
	}
	
	@Bean
	public IPhone14 iPhone14() {
		return new IPhone14(batteryType);
	}
}

<< 코드 설명 >>

(1). 스마트폰이 의존 하고 있는 의존 객체 생성 / 이때 의존 객체로 건전지 정보를 갖고 있는 배터리를 생성했음

(2), (3), (4). 건전지 정보가 주입 된 빈을 컨테이너에 등록

 

 

다시 Ex05 소스 파일로 돌아가 프로그램을 실행시켜보자

당연히 "건전지를 교체하세요" 문구가 뜸

package com.study.chapter03;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Ex05 {

	public static void main(String[] args) {
		ApplicationContext ctx = new AnnotationConfigApplicationContext(Config.class);
		
		GalaxyS23 phone = (GalaxyS23) ctx.getBean("galaxyS23");
		
		System.out.println("<< phone >>");
		phone.printBatteryInfo();
	}

}

 

 

스마트폰의 배터리를 리튬 배터리로 교체해야한다면 잘 알고 있듯 이와 같이 배터리를 교체하면 됨

 

지금까지 @Configuration 애너테이션과 @Bean 애너테이션을 사용해 Spring Framework가 제공하는 DI, IoC를 사용했음

@Configuration 애너테이션을 사용해 컨테이너를 생성하고 @Bean 애너테이션을 사용해 프로젝트에 필요한 빈 객체를 등록했음

 

특히, 컨테이너에 의존 객체를 생성하도록 직접 코드를 썼고 의존 주입이 된 빈을 등록하도록 직접 코드를 썼음 


Spring Framework의 장점 중 하나는 직접 입력해야 할 코드를 애너테이션으로 대체할 수 있다는 점

 

현재 우리는 세 종류의 스마트폰(GalaxyS23, GalaxyFlip4, IPhone14)를 컨테이너에 빈으로 등록하고 있는데 이렇게 컨테이너에 빈으로 등록하고 싶은 클래스가 있다면 클래스 명 위에 @Component 애너테이션(1)을 붙이면 더 간편하게 빈으로 등록할 수 있음

package com.study.chapter03;

import org.springframework.stereotype.Component;

import lombok.Data;

@Component
@Data
public class GalaxyS23 {
	private BatteryType batteryType;
	
	public GalaxyS23(BatteryType batteryType) {
		this.batteryType = batteryType;
	}

	public void printBatteryInfo() {
		batteryType.printBatteryInfo();
	}
}

<< 코드 설명 >>

클래스 명 위에 @Component 애너테이션을 붙이면 컨테이너에 빈 객체를 등록할 필요가 없어짐

@Component 애너테이션은 클래스 명 위에 붙이는 것으로 클래스명 위에 만 있으면 되므로 @Data 애너테이션 위이든 아래이든 상관 없음

 

GalaxyFlip4와 IPhone14 클래스에도 @Component 애너테이션을 붙이자

 

@Component 애너테이션을 붙이면 컨테이너에 빈이 등록됨

이때 빈의 이름은 클래스 이름을 따라감

 

 

여기서 드는 의문!

"빈 객체들(GalaxyS23, GalaxyFlip4, IPhone14)은 BatteryType에 의존하고 있는데 의존 주입은 어떻게 되는거지?"

그렇다. 빈 객체에 의존 객체가 있다면 의존 객체의 클래스명 위에도 @Component 애너테이션을 붙여줘야함

 

 

이번에는 스마트폰이 의존하고 있는 의존 객체에도 @Component 애너테이션을 붙이자

 

의존 객체에 @Component 애너테이션을 사용하면 의존 주입을 하기 위해 의존 객체를 생성할 필요가 없어짐

여기서 각 스마트폰에 사용한 @Component 애너테이션과 각 베터리에 사용한 @Component 애너테이션이 다름

(1). 각 스마트폰에 사용한 @Component 애너테이션은 ( ) 가 없었음

(2). 각 베터리에 사용한 @Component 애너테이션은 ( ) 안에 문자열이 들어있음

 

 

@Component 애너테이션에 ( ) 가 없으면 해당 빈을 컨테이너에 등록할 때 클래스 이름이 빈의 이름이 됨

@Component 애너테이션의 ( ) 안에 문자열이 있으면 해당 빈을 컨테이너에 등록할 때 지정한 문자열이 빈의 이름이 됨

 

(1)의 빈 색깔과 (2)의 빈 색깔이 다른 이유는 컨테이너 입장에서는 모두 빈이지만 개발자 입장에서는 GalaxyS23, GalaxyFlip4, IPhone14는 필요한 곳에서 꺼내서 쓰기 위한 빈이고 nb, lb 빈은 GalaxyS23, GalaxyFlip4, IPhone14이 의존하고 있는 의존 객체이기 때문에 색깔을 다르게 지정했음

 

 

우선 여기까지 빈 객체들(GalaxyS23, GalaxyFlip4, IPhone14)이 주입 받아야할 의존 객체를 생성했음

빈 객체들에 의존 주입을 받으려면 빈 객체들이 갖고 있는 의존 객체에 @Autowired 애너테이션을 붙어야함

package com.study.chapter03;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

import lombok.Data;

@Component
@Data
public class GalaxyS23 {
	@Autowired
	@Qualifier("nb")
	private BatteryType batteryType;
	
	public void printBatteryInfo() {
		batteryType.printBatteryInfo();
	}
}

 

잠깐! 이 코드에는 굉장히 중요한 점이 있음!

지금까지 빈 객체들(GalaxyS23, GalaxyFlip4, IPhone14)이 생성자를 갖고 있었던 이유는 우리(개발자)가 직접 의존 주입을 하기 위해서였음

이제는 우리가 직접 의존 주입을 하지 않고 애너테이션을 통해서 Spring Framework가 알아서 의존 주입을 하도록 할 것이므로 생성자 방식 또는 setter 방식의 의존 주입 코드 는 지워야함

 

@Qualifier 애너테이션은 의존 주입 받을 빈의 이름임

따라서 GalaxyS23은 배터리가 건전지임

 

나머지 빈 객체는 리튬 배터리로 의존 주입을 하자

 

여기까지 컨테이너에 코드 한 줄 안 쓰고 @Component, @Autowired, @Qualifer 애너테이션으로 빈 객체를 등록하고 의존 주입을 했음

 

 

현재 상황에서 빈 객체를 갖고 있는 컨테이너는 누굴까?

그 컨테이너는 바로 우리가 프로젝트를 만들었을 때 자동으로 들어있던 코드 중 하나임

 

지금은 시간이 꽤 흘러 기억이 안나겠지만 우리가 처음 프로젝트를 만들었을 때부터 있던 코드가 있음

그 코드는 com.study 패키지 안에 들어있음

 

이 중에서 (프로젝트명)Application.java 인 StudyProjectApplication.java 소스 파일을 보자

 

이 소스 파일에는 굉장히 중요한 @SpringBootApplication 애너테이션이 있음

이 애너테이션은 여러 애너테이션이 하나로 합쳐진 애너테이션임

 

애너테이션 위에 마우스를 올려두면 어떤 애너테이션들이 하나로 합쳐졌는지 알 수 있음

 

이 애너테이션들은 또 다시 여러 애너테이션이 하나로 합쳐진 애너테이션임

즉, @SpringBootApplication 애너테이션은 굉장히 많은 애너테이션이 하나로 합쳐진 애너테이션임

합쳐진 애너테이션 중 @Configuration, @EnableAutoConfiguration, @ComponentScan 애너테이션이 있는데 이 애너테이션이 컨테이너를 생성하고 컨테이너에 의존 주입된 빈 객체를 등록하는 역할을 함

 

하나 하나 자세히 설명하면 또 다른 얘기로 많이 넘어가야하므로 Spring Boot 난이도에 맞춰서 대략적으로만 이해하고 있자

( 하나 하나 자세히 이해하고 싶다면 Spring Framework 를 공부해야함 )

@Configuration - Confing 컨테이너에 붙였던 애너테이션으로 컨테이너를 생성할 때 사용하는 애너테이션

@ComponentScan - 컨테이너에 등록할 빈 객체를 자동으로 찾아주는 애너테이션으로 @Component 애너테이션이 붙은 클래스를 컨테이너에 빈 객체로 등록함

@EnabledAutoConfiguration - 생성된 Config 컨테이너를 불러와 ctx에 저장해 사용했는데 이 애너테이션을 사용하면 컨테이너를 불러 올 필요 없이 컨테이너 내 빈 객체를 꺼낼 수 있음

 

 

@SpringBootApplication 애너테이션 덕분에 @Component, @Autowired, @Qualifer 애너테이션으로 빈 객체를 등록하고 의존 주입을 했다면 컨테이너를 만들 필요 없이 빈 객체를 꺼낼 수 있음

 

빈 객체를 꺼내보기 전에 빈 객체의 의존 주입 방식이 완전히 바뀌었기도 하고 Config 컨테이너는 필요 없으므로 Config 클래스는 삭제하자

(1). 컨테이너를 생성할 때 사용할 설정 클래스인 Config 클래스를 삭제했으므로 더 이상 컨테이너를 직접 생성할 수 없음

       이 코드도 삭제 해야함

(2). 빈 객체 생성과 의존 주입을 우리가 직접 한게 아니라 애너테이션을 사용해서 Spring Framework가 대신 해주도록 했으므로 더 이상 main 메서드 안에서는 빈 객체를 사용할 수 없음

main 메서드 이기 때문이 아니라 main 메서드가 static 메서드이기 때문임

 

이제 main 의 역할을 대신할 컨트롤러를 만들어야함

프로젝트 -> com.study.chapter03 -> Ex06 클래스를 추가하고 아래 코드를 추가하자

package com.study.chapter03;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class Ex06 {
	@Autowired
	private GalaxyS23 phone1;
	@Autowired
	private GalaxyFlip4 phone2;
	@Autowired
	private IPhone14 phone3;
	
	@GetMapping("/chapter03/diNioc/phone1")
	public void diAndIocTest1() {
		System.out.println("<< phone1 >>");
		phone1.printBatteryInfo();
	}
	
	@GetMapping("/chapter03/diNioc/phone2")
	public void diAndIocTest2() {
		System.out.println("<< phone2 >>");
		phone2.printBatteryInfo();
	}
	
	@GetMapping("/chapter03/diNioc/phone3")
	public void diAndIocTest3() {
		System.out.println("<< phone3 >>");
		phone3.printBatteryInfo();
	}
}

<< 코드 설명 >>

(1). @Controller 애너테이션이 붙은 클래스는 SpringFramework가 컨테이너로 만들어줌

즉, 컨테이너도 우리가 직접 생성하는게 아니라 애너테이션으로 생성한 것

앞에서는 우리가 직접 설정 클래스를 사용해서 ctx 라는 컨테이너를 생성했지만 이제는 @Controller 애너테이션 한 줄을 통해서 SpringFramework가 대신 컨테이너를 생성한 것

(2). 컨테이너에 빈 객체 등록

컨테이너에 빈 객체를 등록할 때도 @Autowired 애너테이션을 붙임

(3). GET 방식으로 http://서버주소:포트번호/chapter03/diNioc/phone1 경로에 접근했을 때는 diAndIocTest1 메서드가 호출되도록 diAndIocTest1 메서드에 GetMapping 애너테이션을 지정했음

diAndIocTest1 메서드가 호출되면 sts 내 콘솔창에는

 

<< phone1 >>

배터리는 건전지입니다.

배터리를 모두 사용했다면 건전지를 교체하세요.

 

가 출력될 것

 

이제 sts의 [ Boot Dashboard ] 에서 서버를 실행 시키고 컨트롤러를 호출해보자

 

여기서 URL에 사용한 diNioc는 DI와 IoC임


이렇게 Spring Framework는 컨트롤러가 컨테이너의 역할을 하며 사용자의 요청을 받고 사용자의 요청에 맞는 서비스 메서드를 호출함

 

서비스 메서드에는 DAO에 의존해 DB와 통신할 때 DAO를 통해 통신함

 

여기까지 Spring Framework에서 굉장히 중요한 DI, IoC 에 대해서 배웠음

DI, IoC는 굉장히 중요한 요소이므로 여러번 복습해서 꼭 내것으로 만들어야함

728x90
LIST