xml
파일을 읽자마자(LOADING) 컨테이너 생성
xml
파일로 컨테이너를 조절 및 설정<beans>
<bean>
컨테이너가 fruit.red
객체를 생성
클라이언트가 컨테이너에게 id가 apple
인 객체를 요청
컨테이너는 id가 apple
인 fruit.red
객체를 return
스프링 컨테이너는(GenericXmlApplicationContext
)는 XML에 등록된 객체를 Pre-loading
스프링 컨테이너는 객체를 생성할 때 기본 생성자만 호출
스프링 컨테이너는 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 class="패키지명.클래스명"></bean>
</beans>
new
→ 서블릿 컨테이너보다 좀 더 명확new GenericXmlApplicationContext("applicationContext.xml");
자세한 건 아래 실습을 참고하자
제어의 역전 =역제어
GenericXmlApplicationContext
사용
자바 코드 수정 없이, 실행 객체를 교체할 수 있음
일반적인 Java 객체를 new
로 생성하여 개발자가 관리하는 것 ❌
객체에 대한 제어를 Spring Container에게 관리를 전부 맡김 ⭕
👉 개발자에서 프레임워크로 제어의 객체 관리 권한이 넘어감
👉 스프링의 가장 큰 특징
역제어 ↔ 순제어
객체에 대한 제어를 개발자가 자바코드로 일일히 제어
유지보수가 힘듦
전통적인 자바 프로그래밍 방식
TV tv = new SamsungTV();
IoC 가 좋은 이유 → 컨테이너가 모든 것을 해줌
xml 을 이용해서 컨테이너를 프리로딩함
xml 파일을 수정하면 실행되는 객체가 변경
👉 이때 왜 굳이 JAVA를 사용할까?
👉 IoC 를 제공하기 때문, 유지보수할 때 자바 코드를 수정하지 않음
“네임 스페이스”
에 등록된 태그만 쓸 수 있다고 지정👉 스프링 컨테이너는 약속된 태그만 사용할 수 있다
👉 컨테이너가 해당 태그에 해당하는 클래스를 찾아서 객체를 프리로딩
context.
태그가 보여짐
👉 XML 파일에 새로운 네임스페이스를 추가하는 것
👉 = 스프링 컨테이너에게 전혀 다른 종류의 작업을 지시할 수 있음을 의미한다
import
applicationContext.xml
하나만 로딩하면? 나머지 3개의 xml 파일도 같이 로딩할 수 있음<bean>
new
로 새로 생성된 객체id
는 옵션class
는 필수id
를 지정하지 않으면? 자동으로 아래처럼 id
가 부여(#
)<bean>
태그에서 id는 생략 가능하다. 하지만 class
속성은 필수❗
id
를 통해 생성된 객체를 식별할 수 있어야 하기 때문에
id
속성 값을 반드시 전체 객체에서 유일해야 한다❗
👉 id
는 유니크해야한다 → 아이디로 객체 구분
id
속성 값은 자바의 변수명 규칙을 적용// 1. 스프링 컨테이너 생성/구동
GenericXmlApplicationContext container = new GenericXmlApplicationContext("applicationContext.xml"); // 객체 생성되는 시점
// 2. 스프링 컨테이너로부터 id가 "tv"인 TV 타입의 객체를 획득(=Lockup)
TV tv = (TV) container.getBean("tv"); // 컨테이너에게 id가 "tv"인 객체 return해달라고함
tv.powerOn();
tv.volumeUp();
tv.volumeDown();
tv.powerOff();
// 3. 스프링 컨테이너를 종료
container.close();
<bean id="tv" class="polymorphism.SamsungTV" init-method="멤버변수초기화" destroy-method="자원해제"></bean>
init-method
init()
메서드와 동일destroy-method
👉 모든 컨테이너는 컨테이너가 종료되기 직전에
👉 컨테이너가 생성한 객체들을 먼저 메모리에서 제거하는 작업을 수행
init-method
속성lazy-init="true"
lazy-init="false"
scope="prototype”
getBean()
) 할 때마다 새로운 객체를 생성하여 return
scope="singleton”
의존성 주입
의존성으로부터 격리 → 코드 테스트에 용이
DI 를 통해, 불가능한 상황을 Mock와 같은 기술로 안정적인 테스트 가능
코드 확장 또는 변경 시 → 영향을 최소화 및 추상화
모듈의 결합도 감소 + 유연성 증가
순환참조 막기 가능
외부에서 내가 사용하는 객체를 주입시켜 주는 것
public class GoogleTV extends TV {
private SonySpeaker speaker;
public GoogleTV() {
System.out.println("===> GoogleTV 생성");
}
public void powerOn() {
System.out.println("GoogleTV---전원 키기");
}
public void powerOff() {
System.out.println("GoogleTV---전원 끄기");
}
public void volumeUp() {
// speaker = new SonySpeaker(); // 순제어 방식
speaker.volumeUp();
}
public void volumeDown() {
speaker.volumeDown();
}
}
public class SonySpeaker {
public SonySpeaker() {
System.out.println("===> SonySpeaker 생성");
}
public void volumeUp() {
System.out.println("SonySpeaker---소리 올리기");
}
public void volumeDown() {
System.out.println("SonySpeaker---소리 내리기");
}
}
volumeUp()
, volumeDown()
에 NullpointerException
발생순제어 방식 사용
volumeUp()
, volumeDown()
에 speaker = new SonySpeaker();
문제점
⇒ xml
파일에서 constructor-arg
로 해결(아래)
Speaker
객체를 매개변수로 받아서 → 멤버변수를 초기화할 수 있음NullpointerException
발생하지 않음! public class GoogleTV implements TV {
private Speaker speaker;
private int price;
public GoogleTV() {
System.out.println("===> GoogleTV(1) 생성");
}
public GoogleTV(Speaker speaker) {
System.out.println("===> GoogleTV(2) 생성"); // 출력
this.speaker = speaker;
}
. . .
]
<bean id="speaker" class="polymorphism.SonySpeaker"></bean>
<bean id="tv" class="polymorphism.GoogleTV">
<constructor-arg ref="speaker"></constructor-arg>
<constructor-arg value="1500000"></constructor-arg>
</bean>
constructor-arg
id
가 speaker
인 객체를 넘기는 것Alt+Shift+T
우클릭-Refator
SonySpeaker
든 AppleSpeaker
든 Speaker
인터페이스를 상속받음xml
수정만으로 사용 가능public interface Speaker {
void volumeUp();
void volumeDown();
}
public interface TV {
public void powerOn();
public void powerOff();
public void volumeUp();
public void volumeDown();
}
public class GoogleTV implements TV {
. . .
// setter 인젝션
public GoogleTV(Speaker speaker, int price) {
System.out.println("===> GoogleTV(3) 생성");
this.speaker = speaker;
this.price = price;
}
public void setSpeaker(Speaker speaker) {
System.out.println("---> setSpeaker() 호출");
this.speaker = speaker;
}
public void setPrice(int price) {
System.out.println("---> setPrice() 호출");
this.price = price;
}
. . .
}
<!-- Setter 인젝션 설정 -->
<bean id="speaker" class="polymorphism.AppleSpeaker"></bean>
<bean id="tv" class="polymorphism.GoogleTV">
<property name="speaker" ref="speaker"></property>
<property name="price" value="100000"></property>
</bean>
p
네임스페이스 사용<bean id="speaker" class="polymorphism.AppleSpeaker"></bean>
<bean id="tv" class="polymorphism.GoogleTV">
<property name="speaker" ref="speaker"></property>
<property name="price" value="100000"></property>
</bean>
<bean id="speaker" class="polymorphism.SonySpeaker"></bean>
<bean id="tv" class="polymorphism.GoogleTV" p:speaker-ref="speaker" p:price="100000"></bean>
생성자 인젝션
<constructor-arg>
태그를 이용하여 생성자를 호출
세터 인젝션
<property>
태그를 이용하여 세터 메소드를 호출
기본적인 인코딩방식 : url 인코딩 방식
아래 코드처럼 필요할 때마다 계속해서 추가하면서 사용해야함
⇒ 매우 불편한 방식, 아래 실습에서 분리시켜보자
String url = "www.naver.com/books/it?page=10&size=20&name=spring-boot";
// Base 64 encoding
Encoder encoder = new Encoder();
String result = encoder.encode(url);
System.out.println(result);
// url encoding
UrlEncoder urlEncoder = new UrlEncoder();
String urlResult = urlEncoder.encode(url);
System.out.println(urlResult);
Base64Encoder.java
, UrlEncoder.java
의 공통적인 부분을IEncoder.java
인터페이스로 추상화시켜 사용Encoder.java
내부에 의존성이 있는 객체(현. 인터페이스 IEncoder
)를 받을 변수를 만들고,main
에서 Encoder
객체를 생성하고, 필요한 인코딩 방식을 적용👉 즉 Encoder
내부를 직접적으로 변경하지 않고, 외부(현. main
)에서 주입하는 방식
👉 가독성, 편리성, 효율성 증가
public class Base64Encoder implements IEncoder{
public String encode(String message) {
return Base64.getEncoder().encodeToString(message.getBytes());
}
}
public class UrlEncoder implements IEncoder{
public String encode(String message) {
try {
return URLEncoder.encode(message, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
}
public interface IEncoder {
String encode(String message);
}
public class Encoder {
private IEncoder iEncoder**; // 의존성이 있는 객체
public Encoder(IEncoder iEncoder) { // 의존성이 있는 객체를 주입 받아서 처리(DI)
// this.iEncoder = new Base64Encoder();
// this.iEncoder = new UrlEncoder();
// DI
this.iEncoder = iEncoder;
}
public String encode(String message) {
return iEncoder.encode(message);
}
}
String url = "www.naver.com/books/it?page=10&size=20&name=spring-boot";
// 추상화
// new Encoder(외부에서 주입)
// Encoder 내부는 변경 안함
// Base 64 encoding
Encoder encoder = new Encoder(new Base64Encoder());
String result = encoder.encode(url);
System.out.println(result);
// url encoding
Encoder urlEncoder = new Encoder(new UrlEncoder());
String result2 = urlEncoder.encode(url);
System.out.println(result2);
어노테이션 개념 및 은 Spring 시리즈 3번 을 참고해주세요
@Component
를 붙여서, 제어의 역전이 일어나도록 함// Spring 에 객체로 관리해달라는 요청/권한 넘김 -> Bean으로 만들어서 관리
@Component
public class Base64Encoder implements IEncoder{
. . .
}
// Spring 에 객체로 관리해달라는 요청/권한 넘김 -> Bean으로 만들어서 관리
@Component
public class UrlEncoder implements IEncoder{
. . .
}
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Component
public class ApplicationContextProvider implements ApplicationContextAware {
private static ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
// (ApplicationContext applicationContext) 알아서 주입
context = applicationContext;
}
public static ApplicationContext getContext() {
return context;
}
}
Spring
으로 객체관리를 넘길 때, 객체를 주입 받을 수 있는 장소를 생성set
메서드로 진행해야함.
.
.
// Spring 을 주입 받을 수 있는 장소 -> 변수 생성자, setMethod
public void setIEncoder(IEncoder iEncoder) {
this.iEncoder = iEncoder;
}
SpringApplication.run(SpringIocApplication.class, args);
ApplicationContext context = ApplicationContextProvider.getContext();
Base64Encoder base64Encoder = context.getBean(Base64Encoder.class);
UrlEncoder urlEncoder = context.getBean(UrlEncoder.class);
Encoder encoder = new Encoder(base64Encoder);
String url = "www.naver.com/books/it?page=10&size=20&name=spring-boot";
String result = encoder.encode(url);
System.out.println(result);
encoder.setIEncoder(urlEncoder);
result = encoder.encode(url);
System.out.println(result);
@Component
가 위 실습에서 이미 적용되어 있는 상태에서, Encoder에도 적용하려면?@Component
가 만들어 지기 때문에@Qualifier("urlEncoder")
로 어떤 컴포넌트를 쓸지 지정해주면 됨Encoder.java
public Encoder(@Qualifier("**base74Encoder**") IEncoder iEncoder) { . . . }
Base64Encoder.java
**@Component**("**base74Encoder**")
public class Base64Encoder implements IEncoder{ . . . }
SpringApplication.run(SpringIocApplication.class, args);
ApplicationContext context = ApplicationContextProvider.getContext();
Encoder encoder = context.**getBean(Encoder.class)**;
String url = "www.naver.com/books/it?page=10&size=20&name=spring-boot";
String result = encoder.encode(url);
System.out.println(result);
d3d3Lm5hdmVyLmNvbS9ib29rcy9pdD9wYWdlPTEwJnNpemU9MjAmbmFtZT1zcHJpbmctYm9vdA==
@Component
로 등록하지않고, Bean 으로 직접 등록하는 방법// @Configuration -> 한 클래스에서 여러개 Bean 등록
@Configuration
class AppConfig{
// Base64Encoder 를 Spring 으로부터 주입받기
@Bean("base64Encode")
public Encoder encoder(Base64Encoder base64Encoder) {
return new Encoder(base64Encoder);
}
// UrlEncoder 를 Spring 으로부터 주입받기
@Bean("urlEncode")
public Encoder encoder(UrlEncoder urlEncoder) {
return new Encoder(urlEncoder);
}
}
// bean 여러개 등록된 클래스 불러오기
Encoder encoder = context.getBean("urlEncode", Encoder.class);
String url = "www.naver.com/books/it?page=10&size=20&name=spring-boot";
String result = encoder.encode(url);
System.out.println(result);
IoC
Spring 컨테이너가 관리하는 것
DI
객체를 주입받는 것
실제 프로젝트에서
객체(클래스)를 새로 만들지 않고, Spring의 context
를 가져오는 형태로 사용
생성자, setMethod
등으로 직접 객체를 받아와서 사용