본문 바로가기
👨‍🏫Study/Spring Study

[Spring] 4장 의존성 주입 - 의존성 관리 / 생성자 인젝션과 Setter 인젝션 이용 / 컬렉션 객체 설정

by 코푸는 개발자 2022. 2. 6.
728x90
실습 코드 참조

 

moonhy7/SpringFramework: Spring Framework 실습 코드 정리 (github.com)

 

GitHub - moonhy7/SpringFramework: Spring Framework 실습 코드 정리

Spring Framework 실습 코드 정리. Contribute to moonhy7/SpringFramework development by creating an account on GitHub.

github.com

 

 

4.1절 의존성 관리

 

1. 스프링의 의존성 관리 방법

 

- 스프링 프레임워크의 특징 : 객체의 생성과 의존관계를 컨테이너가 자동으로 관리 (IoC의 핵심 원리)

- 스프링은 Dependency Lookup과 Dependency Injection의 2가지 형태로 IoC를 지원함

- Dependency Lookup : 컨테이너가 애플리케이션 운용에 필요한 객체를 생성하고 클라이언트는 컨테이너가 생성한 객체를 검색(Lookup )하여 사용

- 지금까지 해온 방식이 Dependency Lookup 방식임 (실제 애플리케이션 개발 과정에서 잘 사용하지 않음)

 

 

- Dependency Injection은 객체 사이의 의존 관계를 스프링 설정 파일에 등록된 정보에 따라 컨테이너가 자동으로 처리

- 의존성 설정을 바꾸고 싶을 때 코드를 수정하지 않고 스프링 설정 파일만 수정하면 됨 (유지보수성 향상)

- Dependency Injection :  컨테이너가 직접 객체들 사이에 의존관계를 처리함을 의미

-  Dependency Injection는 또다시 Setter 인젝션과 Constructor 인젝션으로 나뉨

 

2. 의존성 관계 ( _010_BoardWeb_DI )

 

1) 의존성(Dependency ) 관계란?

- 객체와 객체의 결합 관계, 즉 하나의 객체에서 다른 객체를 이용할 때 이용하려는 객체에 대한 정보가 필요

- SamsungTV는 SonySpeaker의 메소드를 이용해서 기능을 수행

- 따라서 SamsungTV는 SonySpeaker타입의 speaker 변수를 가지고 있어야함

- 또한 speaker 변수는 SonySpeaker 객체를 참조하고 있어야함

 

2) SonySpeaker.java 생성

package polymorphism;

public class SonySpeaker {
	public SonySpeaker() {
		System.out.println("====> 소니 스피커 객체 생성");
	}
	public void volumeUp() {
		System.out.println("소니 스피커---소리 울린다.");
	}
	public void volumeDown() {
		System.out.println("소니 스피커---소리 내린다.");
	}
}

 

3) SamsungTV 클래스

- SonySpeaker객체 생성 및 볼륨 조절 기능을 SonySpeaker가 사용할 수 있도록 수정

public class SamsungTV implements TV {
	//소니스피커 객체 생성
	SonySpeaker speaker;
	
	public void volumeUp() {
		speaker = new SonySpeaker();
		speaker.volumeUp();
	}
	
	public void volumeDown() {
		speaker = new SonySpeaker();
		speaker.volumeDown();
	}
}

 

4) 실행 결과

 

- 단점 1 : volumeUp down 두개에 소니스피커 객체를 두번이나 만들어줘야하는 단점

- 단점 2 : 애플스피커로 변경하려면 volumeUp()과 volumeDown() 두 개의 메소드를 모두 수정해주어야하는 단점

- 다음 절에서 의존성 주입을 사용해서 변경해볼 예정

 

 

4.2절 생성자 인젝션 이용하기

 

1. 생성자 인젝션으로 의존성 주입 테스트 ( _011_BoardWeb_DI_Constructor )

 

1) 생성자 인젝션 특징

- 컨테이너가 기본 생성자 말고 매개변수를 가지는 다른 생성자를 호출하도록 설정이 가능

- 생성자의 매개변수로 의존관계에 있는 객체의 주소 정보를 전달할 수 있음

 

2) SamsungTV 클래스에 생성자 추가

package polymorphism;

public class SamsungTV implements TV {
	//소니스피커 객체 생성
	SonySpeaker speaker;
	
	
	//생성자 만들기
	public SamsungTV() {
		System.out.println("====> SamsungTV 객체(1) 생성");
	}
	
	public SamsungTV(SonySpeaker speaker) {
		System.out.println("====> SamsungTV 객체(2) 생성");
		this.speaker = speaker; // 빈 객체에 추가해주었기때문에 자동으로 소니스피커를 넣어줌
	}
	
	public void powerOn() {
		System.out.println("SamsungTV---전원 켠다.");
	}
	
	public void powerOff() {
		System.out.println("SamsungTV---전원 끈다.");
	}
	
	public void volumeUp() {
		speaker.volumeUp();
	}
	
	public void volumeDown() {
		speaker.volumeDown();
	}
}

 

3) applicationContext.xml에 <bean> 객체 추가

- 생성자 매개변수로 순서 자동 변경 (삼성티비보다 소니객체를 먼저! 생성하도록)

<bean> 객체에 등록된 순서가 아닌, 필요에 따라 객체 생성 순서를 지정해줌

<bean id="tv" class="polymorphism.SamsungTV">
		<constructor-arg ref="sony"></constructor-arg>
		<!-- 생성자 매개변수로 전달할 때 사용하는 엘리먼트임 / 자동으로 전달해줌! -->
</bean>
<bean id="sony" class="polymorphism.SonySpeaker"></bean>

 

4) 실행결과

- 스프링 컨테이너는 기본적으로 bean 등록된 순서대로 객체를 생성함

- 모든 객체는 기본 생성자 호출을 원칙으로 함

- 하지만 생성자 인젝션으로 의존성 주입될 SonySpeaker가 먼저 객체 생성됨

- SonySpeaker 객체를 매개변수로 받아들이는 생성자를 호출하여 객체를 생성함

- SamsungTV는 SonySpeaker 객체를 참조할 수 있으므로 문제없이 볼륨 조절 기능 처리 가능함

 

2. 다중 변수 매핑 ( _012_BoardWeb_DI_Constructor_MultiVariables )

 

1) SamsungTV.java

- 생성자 인젝션에서 초기화해야 할 멤버변수가 여러 개면 여러 개의 값을 한꺼번에 전달해야함

package polymorphism;

public class SamsungTV implements TV {
	//소니스피커 객체 생성
	private SonySpeaker speaker;
	private int price;
	
	//생성자 만들기
	public SamsungTV() {
		System.out.println("====> SamsungTV 객체(1) 생성");
	}
	
	public SamsungTV(SonySpeaker speaker) {
		System.out.println("====> SamsungTV 객체(2) 생성");
		this.speaker = speaker; // 빈 객체에 추가해주었기때문에 자동으로 소니스피커를 넣어줌
	}
	
	public SamsungTV(SonySpeaker speaker, int price){
		System.out.println("====> SamsungTV 객체(3) 생성");
		this.speaker = speaker; // 빈 객체에 추가해주었기때문에 자동으로 소니스피커를 넣어줌
		this.price = price; //소니스피커가 0번째, 가격이 1번째 인덱스가 됨
	}
    ...
}

 

2) applicationContext.xml

- index 속성을 이용하여 어떤 값이 몇 번째 매개변수로 매핑되는지 지정할 수 있음

- 이렇게 생성자가 여러 개 오버로딩 된 경우 어떤 생성자를 호출해야할지 알려주는 역할

<?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="tv" class="polymorphism.SamsungTV">
		<!-- 생성자 매개변수로 전달할 때 사용하는 엘리먼트임 / 자동으로 전달해줌! -->
		<constructor-arg ref="sony"></constructor-arg>
		
		<!-- bean으로 생성된 객체를 참조할 때는 ref속성을 사용하지만 고정된 값을 주입할 때는  value 사용해서 의존성 주입  -->
		<!-- index : 생성자함수의 몇 번째 인자값인지 명시할 수 있음 -->
		<constructor-arg index="1" value="2700000"></constructor-arg>
	</bean>
	
	<bean id="sony" class="polymorphism.SonySpeaker"></bean>
</beans>

 

3) 실행결과

 

3. 의존관계 변경 ( _013_BoardWeb_DI_Interface )

 

1) 인터페이스 생성하는법 

- 단축키 : alt + shift + T

 

2) Speaker 인터페이스 생성하기

- 모든 스피커의 최상위 부모로 사용할 인터페이스임

package polymorphism;

public interface Speaker {
	void volumeUp();
	void volumeDown();
}

 

3) AppleSpeaker 생성

- Speaker 인터페이스를 구현한 또 다른 스피커 클래스

- SonySpeaker 클래스도 Speaker 인터페이스를 implements 하도록 수정

package polymorphism;

public class AppleSpeaker implements Speaker {
	public AppleSpeaker() {
		System.out.println("====> 애플 스피커 객체 생성");
	}
	public void volumeUp() {
		System.out.println("애플 스피커---소리 울린다.");
	}
	public void volumeDown() {
		System.out.println("애플 스피커---소리 내린다.");
	}

}

 

4) SamsungTV 클래스 수정

- 멤버변수와 매개변수 타입을 모두 SonySpeaker에서 Speaker로 수정하기

package polymorphism;

public class SamsungTV implements TV {
	private Speaker speaker;
	private int price;
	
	//생성자 만들기
	public SamsungTV() {
		System.out.println("====> SamsungTV 객체(1) 생성");
	}
	
	public SamsungTV(Speaker speaker) {
		System.out.println("====> SamsungTV 객체(2) 생성");
		this.speaker = speaker; // 빈 객체에 추가해주었기때문에 자동으로 소니스피커를 넣어줌
	}
	
	public SamsungTV(Speaker speaker, int price){
		System.out.println("====> SamsungTV 객체(3) 생성");
		this.speaker = speaker; // 빈 객체에 추가해주었기때문에 자동으로 소니스피커를 넣어줌
		this.price = price; //소니스피커가 0번째, 가격이 1번째 인덱스가 됨
	}
	
	
	//initMethod() 초기화 작업 진행
	public void initMethod() {
		System.out.println("객체 초기화 작업 처리...");
	}
	
	//destroyMethod() 객체 삭제전 처리할 로직
		public void destroyMethod() {
			System.out.println("객체 삭제전 처리할 로직...");
		}
	
	public void powerOn() {
		System.out.println("SamsungTV---전원 켠다. (가격 : " + price + ")");
	}
	
	public void powerOff() {
		System.out.println("SamsungTV---전원 끈다.");
	}
	
	public void volumeUp() {
		speaker.volumeUp();
	}
	
	public void volumeDown() {
		speaker.volumeDown();
	}
}

 

5) applicationContext.xml

- AppleSpeake도 <bean> 등록해주기

- constructor-arg> 엘리먼트의 속성값 지정

- 속성값을 apple로 지정하면 SamsungTV가 AppleSpeaker를 이용하여 볼륨을 조절함

<bean id="tv" class="polymorphism.SamsungTV">
  <constructor-arg ref="apple"></constructor-arg>
  <constructor-arg index="1" value="2700000"></constructor-arg>
</bean>
	
<bean id="sony" class="polymorphism.SonySpeaker"></bean>
<bean id="apple" class="polymorphism.AppleSpeaker"></bean>

 

 

6) ref만 sony로 변경하면 SonySpeaker가 실행됨

- 빈 객체로 apple을 지정해주고 ref로 의존성을 주입해서 바로바로 사용가능하다는게 큰 장점!!

<constructor-arg ref="apple"></constructor-arg>

 

7) 실행 결과

- <bean>에 추가한 객체는 일단 모두 생성됨

- 요청할때만 생성해주는건 lazy 방식

 

 

4.3절 Setter 인젝션 이용하기

 

1. Setter 인젝션 기본 

 

1) Setter 인젝션 특징

- 생성자인젝션은 생성자를 이용하여 의존성을 처리했지만 Setter 인젝션은 Setter메소드를 호출하여 의존성주입을 처리

- Setter 인젝션이나 Constructor 인젝션 중에 아무거나 사용해도 상관없지만 프로젝트진행시 하나의 방식으로 통일하기

 

2) Setter 인젝션 테스트 ( _014_BoardWeb_DI_Setter )

① Setter 메소드 추가

- Setter 메소드는 스프링 컨테이너가 자동으로 호출

- <bean> 객체 생성 직'후'에 호출하게됨

- 따라서 Setter 인젝션이 동작하려면 기본 생성자도 당연히 필요함

package polymorphism;

public class SamsungTV implements TV {
	private Speaker speaker;
	private int price;	

	public SamsungTV() {
		System.out.println("====> SamsungTV 객체(1) 생성");
	}

	public void setSpeaker(Speaker speaker) {
		System.out.println("====> setSpeaker() 호출");
		this.speaker = speaker;
	}

	public void setPrice(int price) {
		System.out.println("====> setPrice() 호출");
		this.price = price;
	}

	public void initMethod() {
		System.out.println("객체 초기화 작업 처리...");
	}
	
	public void destroyMethod() {
		System.out.println("객체 삭제전 처리할 로직...");
	}
	
	public void powerOn() {
		System.out.println("SamsungTV---전원 켠다. (가격 : " + price + ")");
	}
	
	public void powerOff() {
		System.out.println("SamsungTV---전원 끈다.");
	}
	
	public void volumeUp() {
		/* speaker = new SonySpeaker(); */
		speaker.volumeUp();
//		System.out.println("SamsungTV---소리 올린다.");
	}
	
	public void volumeDown() {
		/* speaker = new SonySpeaker(); */
		/* speaker = new AppleSpeaker(); */
		speaker.volumeDown();
//		System.out.println("SamsungTV---소리 내린다.");
	}
}

 

② 스프링 설정 파일

- <property> 엘리먼트 사용

- name 속성값에 써있는 speaker에서 첫 글자를 대문자로 바꾸고 앞에 set을 붙인 후 해당하는 메소드 이름을 호출 

<bean id="tv" class="polymorphism.SamsungTV">
    <!-- property의 name속성으로  각각에 맞는 setter 메소드를 찾아서 의존성 주입 -->
    <property name="speaker" ref="apple"></property>
    <property name="price" value="2700000"></property>
</bean>

 

③ name 속성값과 Setter 메소드 이름의 관계

 

④ 실행 결과

 

2. p 네임스페이스 사용하기 ( _015_BoardWeb_DI_p_namespace )

 

1) STS 기능으로 p 네임스페이스 추가

- applicationContext.xml > nemespaces > p 태그 체크 > 저장

- p 네임스페이스를 이용하면 좀 더 효율적으로 의존성 주입 처리가 가능

 

2) applicationContext.xml

- 참조형 변수에 참조할 객체를 할당할 수 있게됨

- p:변수명-ref="참조할 객체의 이름이나 아이디"

- p:변수명="설정할 값"

<!-- p:변수명-ref = 참조할 객체 -->
<!-- p:변수명 = 설정할 값 -->
<bean id="tv" class="polymorphism.SamsungTV" p:speaker-ref="sony" p:price="2700000">
</bean>

 

3) 실행 결과

 

 

4.4절 컬렉션(Collection) 객체 설정

 

0. 컬렉션 매핑 엘리먼트 종류 ( _016_BoardWeb_DI_Collection )

 

 

1. List 타입 매핑

 

1) CollectionBean 클래스

- List 컬렉션을 멤버변수로 추가

- getter, setter 메소드 추가

package com.springbook.ioc.injection;

import java.util.List;

public class CollectionBean {
	private List<String> addressList;

	public List<String> getAddressList() {
		return addressList;
	}

	public void setAddressList(List<String> addressList) {
		this.addressList = addressList;
	}

	public Set<String> getAddressList2() {
		return addressList2;
	}
}

 

2) applicationContext.xml

- 위에서 만든 CollectionBean 클래스를 스프링 설정 파일에 <bean> 등록하기

- 세 개의 문자열 주소가 저장된 List 객체를 setAddressList() 메소드를 호출할 때 인자로 전달

- addressList 멤버변수를 초기화하는 설정임

<bean id="collectionBean" class="com.springbook.ioc.injection.CollectionBean">
  <!-- Collection(List, Set, Map, Properties) 의존성 주입 -->
  <property name="addressList">
    <list>
      <value>서울시 강남구 역삼동</value>
      <value>서울시 성동구 행당동</value>
      <value>서울시 성동구 행당동</value>
    </list>
  </property>
</bean>

 

3) 클라이언트 클래스 생성

- List 컬렉션이 정상적으로 의존성 주입되었는지 확인

package com.springbook.ioc.injection;

import java.util.List;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;

public class CollectionBeanClient {
	public static void main(String[] args) {
		AbstractApplicationContext factory = new GenericXmlApplicationContext("applicationContext.xml");
		
		CollectionBean bean = (CollectionBean)factory.getBean("collectionBean");
		List<String> addressList = bean.getAddressList();
		for(String address : addressList) {
			System.out.println(address.toString());
		}
		
		factory.close();
	}
}

 

4) 실행 결과

- 중복된 값이라도 둘다 출력됨

 

2. Set 타입 매핑

 

1) CollectionBean 클래스

- Set 컬렉션을 멤버변수로 추가 (중복 값을 허용하지 않는 집합 개념)

- getter, setter 메소드 추가

package com.springbook.ioc.injection;

import java.util.List;
import java.util.Map;

public class CollectionBean {
	private List<String> addressList;
	private Set<String> addressList2;

	public void setAddressList2(Set<String> addressList2) {
		this.addressList2 = addressList2;
	}

	public Map<String, String> getAddressList3() {
		return addressList3;
	}
}

 

2) applicationContext.xml

<!-- <bean id="sony" class="polymorphism.SonySpeaker"></bean>
	<bean id="apple" class="polymorphism.AppleSpeaker"></bean> -->
	
	<bean id="collectionBean" class="com.springbook.ioc.injection.CollectionBean">		
		<property name="addressList2">
			<set value-type="java.lang.String">
				<value>서울시 강남구 역삼동</value>
				<value>서울시 성동구 행당동</value>
				<value>서울시 성동구 행당동</value>
			</set>
		</property>
	</bean>

 

3) 실행 결과

- 같은 주소가 두 번 등록되었지만 저장할 때는 중복된 값을 하나만 저장하므로 실제 실행해보면 중복된 값 중 하나만 출력됨을 알 수 있음

 

3. Map 타입 매핑

 

1) CollectionBean 클래스

- Map컬렉션을 멤버변수로 추가 (특정 key로 데이터를 등록하고 사용)

- getter, setter 메소드 추가

package com.springbook.ioc.injection;

import java.util.Map;

public class CollectionBean {
	private Map<String, String> addressList3;

	public Map<String, String> getAddressList3() {
		return addressList3;
	}

	public void setAddressList3(Map<String, String> addressList3) {
		this.addressList3 = addressList3;
	}
}

 

2) applicationContext.xml

- setAddressList() 메소드가 호출될 때 Map 타입의 객체를 인자로 전달하는 설정

- <key> 엘리먼트는 Map 객체의 key 값을 설정할 때 사용

- <value> 엘리먼트는 Map 객체의 value를 설정할 때 사용

<property name="addressList3">
  <map>
    <entry>
      <key><value>고길동</value></key>
      <value>서울시 강남구 역삼동</value>
    </entry>
    <entry>
      <key><value>마이콜</value></key>
      <value>서울시 강서구 화곡동</value>
    </entry>
  </map>
</property>

 

3) 실행 결과

 

2. Properties 타입 매핑

 

1) CollectionBean 클래스

- Properties 컬렉션을 멤버변수로 추가 (key=value 형태의 데이터를 등록할 때 사용)

- getter, setter 메소드 추가

package com.springbook.ioc.injection;

import java.util.Properties;

public class CollectionBean {
	private Properties addressList4;

	public Properties getAddressList4() {
		return addressList4;
	}

	public void setAddressList4(Properties addressList4) {
		this.addressList4 = addressList4;
	}
}

 

2) applicationContext.xml

- key=value 형태의 데이터를 등록할 때 사용

- <props> 엘리먼트 사용해서 설정

- name 속성의 addressList4를 통해 setAddressList4() 메소드를 호출함

- 호출 시 Properties 타입의 객체를 인자로 전달하는 설정

<bean id="collectionBean" class="com.springbook.ioc.injection.CollectionBean">
  <property name="addressList4">
    <props>
      <prop key="고길동">서울시 강남구 역삼동</prop>
      <prop key="마이콜">서울시 강서구 화곡동</prop>
    </props>
  </property>
</bean>

 

3) 실행 결과

 

 

728x90

댓글